Small Numbers
The small numbers extension adds the types float32, int16, and int8 to
OxCaml.
Float32
When small numbers are enabled, the following float32 types are available:
float32
float32#
float32 array
float32# array
Literals use the s suffix:
1.0s : float32
#1.0s : float32#
Pattern matching on float32s is not supported.
Operations
Operations on 32-bit floats are available via the Stdlib_stable.Float32 and
Stdlib_stable.Float32_u libraries, which provide Base-like APIs.
Representation
The boxed float32 type is encoded as a custom block with similar semantics to
int32. Similarly, float32 array is a typical OxCaml array containing boxed
elements.
The float32# type is unboxed:
-
Function arguments and returns of type
float32#are passed using floating-point registers. -
Record fields of type
float32#are not boxed, but each take up one word of space. Using float32 records requires the mixed blocks extension, which is also enabled by default. -
Arrays of type
float32# arraycontain tightly packed unboxed float32 elements. The array itself is a custom block with similar semantics toint32# array.
Like floats, compiler optimizations allow boxed float32s to remain unboxed while being manipulated within the scope of a function.
C ABI
Both boxed and unboxed float32s may be passed to C stubs. The OxCaml runtime provides helper functions for working with float32s.
external float32_stub : (float32[@unboxed]) -> (float32[@unboxed]) =
"boxed_float32_stub" "unboxed_float32_stub"
external float32_hash_stub : float32# -> float32# =
"boxed_float32_stub" "unboxed_float32_stub"
(* ... *)
#include <caml/float32.h>
float unboxed_float32_stub(float v) {
return v;
}
value boxed_float32_stub(value v) {
return caml_copy_float32(unboxed_float32_stub(Float32_val(v)));
}
Int8 / Int16
When small numbers are enabled, the following types are available:
int8
int8#
int16
int16#
int8 array
int8# array
int16 array
int16# array
Pattern matching is supported for all of these types.
Literals use s for int8 and S for int16:
42s : int8
#42s : int8#
42S : int16
#42S : int16#
The range of these literals is -128s to 128s for int8 and -32768S to
32768S for int16. Note that 128s overflows to -128s since the max
value of an int8 is 127. Similarly, 32768S overflows to -32768S. This
behavior is consistent with the literals for larger integer types.
Operations
Operations on small integers are available via the Stdlib_stable.Int8,
Stdlib_stable.Int8_u, Stdlib_stable.Int16, and Stdlib_stable.Int16_u
libraries.
Representation
The types int8 and int16 are encoded as tagged immediates, similar to
regular OCaml ints. They are sign-extended to the full width of a tagged
immediate, so polymorphic hash and compare work as expected. int8 array and
int16 array are not packed.
The types int8# and int16# are passed around using general purpose
registers, but do not have a tag bit. They are sign-extended to the width of a
nativeint#. The ints in int8# arrays and int16# arrays are packed, but
they are not packed in any other context. For example, an int8# array of
length 30 takes up 4 words of memory, plus the header word, but a
#(int8# * int8#) takes up 2 words of memory and requires 2 registers to pass
around.
Codegen
In general, the compiler only emits 64-bit instructions, even for 32/16/8-bit operations, and results are sign-extended after every operation. The peephole optimizer can remove some unnecessary sign-extensions, but there is still a noticeable performance decrease compared to C.
C ABI
Both tagged and untagged int8s and int16s may be passed to C stubs.
external int16_stub : (int16[@unboxed]) -> (int16[@unboxed]) =
"tagged_int16_stub" "untagged_int16_stub"
external int16_hash_stub : int16# -> int16# =
"tagged_int16_stub" "untagged_int16_stub"
external int8_stub : (int8[@unboxed]) -> (int8[@unboxed]) =
"tagged_int8_stub" "untagged_int8_stub"
external int8_hash_stub : int8# -> int8# =
"tagged_int8_stub" "untagged_int8_stub"
The following is also valid, but discouraged:
external int16_stub_untagged : (int16[@untagged]) -> (int16[@untagged]) =
"tagged_int16_stub" "untagged_int16_stub"
[@untagged] can be applied to any immediate type, and it doesn’t sign
extend the stub return value, which is usually zero-extended. On the other
hand, the behavior of [@unboxed] depends on the particular type,
so the proper sign extensions are applied to the return value.
For example, the following implementation makes int16_stub and
int16_hash_stub valid, but int16_stub_untagged invalid:
signed short untagged_int16_stub (short x) { return -2; }
Untagged Char
When small numbers are enabled, the types char# and char# array
are available.
Literals are prefixed with #:
#'a' : char#
#'\123' : char#
#'\o123' : char#
#'\xff' : char#
Like regular char literals, untagged char literals can be used in patterns and in ranges:
match x with
| #'a' -> f ()
| #'a'..#'z' -> g ()
Operations
Operations on untagged chars are available via the Stdlib_stable.Char_u
library.
Representation
Untagged chars have the same layout as int8#, and char# arrays are packed
like int8# arrays.
C ABI
Untagged chars may be passed to C stubs:
external char_hash_stub : char# -> char# =
"tagged_char_stub" "untagged_char_stub"
char[@unboxed] is not allowed in external declarations. As a reminder, you may
use upstream OCaml’s char[@untagged] in correspondence with intnat in C.