oxcaml
Working with the OxCaml extensions to OCaml. Use when the oxcaml compiler is available and you need high-performance, unboxing, stack allocation, data-race-free parallelism
SKILL.md
| Name | oxcaml |
| Description | Working with the OxCaml extensions to OCaml. Use when the oxcaml compiler is available and you need high-performance, unboxing, stack allocation, data-race-free parallelism |
name: oxcaml description: Working with the OxCaml extensions to OCaml. Use when the oxcaml compiler is available and you need high-performance, unboxing, stack allocation, data-race-free parallelism license: ISC
You are writing code for the OxCaml compiler, a performance-focused fork of OCaml with Jane Street extensions. This guide covers OxCaml-specific features. You should already know standard OCaml.
Detailed Guides
For in-depth coverage of each feature, see:
| Feature | Guide |
|---|---|
| Modes (local, unique, once, portable, contended) | SKILL-MODES.md |
| Stack Allocation (local_, stack_, exclave_) | SKILL-STACK-ALLOCATION.md |
| Unboxed Types (float#, int32#, mixed blocks) | SKILL-UNBOXED.md |
| Kinds (value, float64, bits32, kind products) | SKILL-KINDS.md |
| Uniqueness (unique/aliased, once/many) | SKILL-UNIQUENESS.md |
| Comprehensions (list/array builders) | SKILL-COMPREHENSIONS.md |
| SIMD (vector types, SSE/AVX intrinsics) | SKILL-SIMD.md |
| Templates (ppx_template, mangling) | SKILL-TEMPLATES.md |
| Zero-Alloc ([@zero_alloc] checking) | SKILL-ZERO-ALLOC.md |
| Base Library (OxCaml extensions) | SKILL-BASE.md |
| Core Library (OxCaml extensions) | SKILL-CORE.md |
Quick Reference: Syntax Cheat Sheet
(* Stack allocation *)
let f () = exclave_ stack_ (1, 2) (* allocate on stack, return local *)
let g (x @ local) = ... (* local parameter *)
(* Unboxed types *)
let x : float# = #3.14 (* unboxed float *)
let y : int32# = #42l (* unboxed int32 *)
type t = { a : int; b : float# } (* mixed block record *)
(* Modes on values *)
let f (x @ local unique once) = ... (* multiple modes *)
val g : t @ global -> t @ local (* in signatures *)
(* Kinds on types *)
type ('a : float64) t = ... (* kind annotation *)
val f : ('a : value). 'a -> 'a (* kind-polymorphic *)
(* Comprehensions *)
[ x * 2 for x = 1 to 10 when x mod 2 = 0 ]
[| y for y in arr when y > 0 |]
(* Labeled tuples *)
let pair = ~x:1, ~y:2 (* labeled tuple *)
let ~x, ~y = pair (* destructuring *)
(* Immutable arrays *)
let arr : int iarray = [: 1; 2; 3 :]
let x = arr.:(0)
(* Unboxed tuple destructuring - use #(...) pattern *)
let #(a, b) = some_unboxed_pair
let #(x, y, z) = fork_join3 par f1 f2 f3
(* Zero-alloc annotation *)
let[@zero_alloc] fast_add x y = x + y
1. Modes
Modes track runtime properties of values. Each mode axis is independent.
Mode Axes
| Axis | Values | Default | Purpose |
|---|---|---|---|
| Locality | local, global | global | Where value lives (stack vs heap) |
| Uniqueness | unique, aliased | aliased | Number of references |
| Linearity | once, many | many | How often closures can be called |
| Portability | portable, shareable, nonportable | nonportable | Cross-thread safety |
| Contention | contended, shared, uncontended | uncontended | Thread access patterns |
Syntax
(* On parameters *)
let f (x @ local) = ...
let f (x @ local unique) = ... (* multiple modes *)
(* On return types in signatures *)
val f : t @ local -> t @ global
val g : t @ unique once -> t @ aliased many
(* On expressions *)
let x = (expr : t @ local)
(* On let bindings *)
let local_ x = ... (* shorthand for local *)
let global_ x = ...
(* On record fields - modalities *)
type t = {
global_ data : int; (* always global *)
mutable x : int @@ aliased; (* aliased modality *)
}
Subtyping Rules
More restrictive modes can be used where less restrictive are expected:
local≤global(can use local where global expected? NO - reversed)global≤local(can use global where local expected)unique≤aliased(can use unique where aliased expected)many≤once(can use many where once expected)portable≤shareable≤nonportableuncontended≤shared≤contended
2. Stack Allocation (Locality)
Stack-allocated values avoid GC overhead but cannot escape their scope.
Key Constructs
(* Allocate on stack *)
let f () =
let local_ x = (1, 2) in (* stack-allocated tuple *)
...
(* Force stack allocation *)
let f () =
stack_ (1, 2) (* explicitly stack-allocate *)
(* Return local value from function *)
let f () = exclave_
stack_ (1, 2) (* return value allocated in caller's frame *)
(* Combined pattern for local returns *)
let f () = exclave_ stack_ (make_tuple ())
Rules
- Local values CANNOT escape their defining scope (no storing in globals, no returning without
exclave_) - Local values CAN reference global values
- Global values CANNOT reference local values
exclave_allocates in caller's stack frame and must be at tail position
Common Patterns
(* Process local data without allocation *)
let sum_pairs (pairs @ local) =
List.fold_left (fun acc (a, b) -> acc + a + b) 0 pairs
(* Return local from function *)
let make_pair x y = exclave_ stack_ (x, y)
(* Local references for accumulators *)
let count_positives lst =
let local_ r = ref 0 in
List.iter (fun x -> if x > 0 then r := !r + 1) lst;
!r
3. Unboxed Types
Unboxed types store values directly without heap allocation.
Built-in Unboxed Types
(* Numeric types - # suffix means unboxed *)
float# (* 64-bit float, kind float64 *)
int32# (* 32-bit int, kind bits32 *)
int64# (* 64-bit int, kind bits64 *)
nativeint# (* native int, kind word *)
float32# (* 32-bit float, kind float32 *)
int8# (* 8-bit int - untagged *)
int16# (* 16-bit int - untagged *)
int# (* native int - untagged *)
char# (* 8-bit char - untagged, same layout as int8# *)
(* Literals use # prefix *)
let x : float# = #3.14
let y : int32# = #42l
let z : int64# = #100L
let w : float32# = #1.0s
let a : int8# = #42s (* int8# literal *)
let b : int16# = #42S (* int16# literal *)
let c : char# = #'x' (* char# literal *)
(* Boxed versions (heap-allocated) *)
let a : float = 3.14 (* boxed *)
let b : float# = #3.14 (* unboxed *)
Untagged Int Arrays (New in 5.2.0minus-25)
Arrays of untagged types are packed for memory efficiency:
(* Untagged int arrays - tightly packed *)
let bytes : int8# array = [| #0s; #1s; #255s |]
let shorts : int16# array = [| #0S; #1S; #32767S |]
let ints : int# array = [| #0; #1; #42 |]
let chars : char# array = [| #'a'; #'b'; #'c' |]
(* int8# array: 1 byte per element *)
(* int16# array: 2 bytes per element *)
(* int# array: native word size per element *)
Unboxed Records
(* Unboxed record - stored inline, not heap-allocated *)
type point = #{ x : float#; y : float# }
(* Create unboxed record *)
let p : point = #{ x = #1.0; y = #2.0 }
(* Access fields *)
let get_x (p : point) = p.#x
Unboxed Tuples
(* Unboxed tuple syntax *)
type pair = #(float# * int32#)
let p : #(float# * int32#) = #(#1.0, #42l)
Mixed Blocks
Records can mix boxed and unboxed fields:
type mixed = {
name : string; (* boxed *)
value : float#; (* unboxed, stored flat *)
count : int32#; (* unboxed *)
}
or_null Type
Non-allocating option for nullable values:
type 'a or_null = Null | This of 'a
(* Use for optional unboxed values without allocation *)
let find_float arr idx : float# or_null =
if idx < Array.length arr then This arr.(idx)
else Null
4. Kinds
Kinds classify types by their runtime representation.
Kind Hierarchy
any (* any layout *)
├── value (* standard OCaml boxed values *)
├── float64 (* 64-bit floats *)
├── float32 (* 32-bit floats *)
├── bits32 (* 32-bit integers *)
├── bits64 (* 64-bit integers *)
├── word (* native word size *)
└── void (* uninhabited *)
Kind Annotations
(* On type parameters *)
type ('a : float64) container = ...
(* On type variables in signatures *)
val f : ('a : value). 'a -> 'a
val g : ('a : bits64). 'a -> 'a
(* On abstract types *)
type t : float64
(* Kind products for unboxed tuples *)
type pair : float64 & bits32 (* unboxed pair of float# and int32# *)
Kind Abbreviations
value = value_or_null mod non_null separable
immediate = value mod external_
immediate64 = value mod external64
mutable_data = value mod non_float
immutable_data = value mod non_float immutable
Mode Bounds on Kinds
Kinds can specify which modes a type crosses:
(* Type that cannot be used at mode local *)
type t : value mod global
(* Type that is always portable *)
type t : value mod portable
5. Uniqueness
Track values with exactly one reference for safe mutation/deallocation.
Modes
unique: Single reference existsaliased: Multiple references may exist
Syntax
(* Unique parameter - consumed by function *)
val free : t @ unique -> unit
(* Aliased return - may have multiple references *)
val duplicate : t -> t * t @ aliased
(* Once closures - can only be invoked once *)
val delay_free : t @ unique -> (unit -> unit) @ once
Uniqueness Rules
(* OK: match then use uniquely *)
let ok t =
match t with
| Con { field } -> free t
(* ERROR: using parts twice *)
let bad t =
match t with
| Con { field } ->
free_field field; (* uses field *)
free t (* uses t which contains field *)
(* OK: different branches *)
let ok t =
match t with
| Con { field } ->
if cond then free_field field
else free t
Aliased Modality
Store aliased values in unique containers:
type 'a aliased_box = { value : 'a @@ aliased } [@@unboxed]
(* Container is unique but contents are aliased *)
val push : 'a @ aliased -> 'a aliased_box list @ unique -> 'a aliased_box list @ unique
6. Comprehensions
Python/Haskell-style list and array builders.
List Comprehensions
(* Basic *)
[ x * 2 for x = 1 to 10 ]
(* With filter *)
[ x for x = 1 to 100 when x mod 2 = 0 ]
(* Nested iteration *)
[ (x, y) for x = 1 to 3 for y = 1 to 3 ]
(* Iterate over list *)
[ String.uppercase s for s in strings ]
(* Multiple conditions *)
[ x + y for x = 1 to 10 for y = 1 to 10 when x < y when x + y < 15 ]
(* Parallel iteration (evaluated together) *)
[ x + y for x = 1 to 3 and y = 10 to 12 ]
Array Comprehensions
(* Same syntax with [| |] *)
[| x * x for x = 1 to 10 |]
(* Iterate over array *)
[| f elem for elem in source_array |]
Immutable Array Comprehensions
[: x for x = 1 to 10 when x mod 2 = 0 :]
Key Differences: for vs and
for ... for ...: Nested (inner re-evaluated each outer iteration)for ... and ...: Parallel (both evaluated once upfront)
(* Nested: 9 elements *)
[ (x, y) for x = 1 to 3 for y = 1 to 3 ]
(* Parallel: 3 elements *)
[ (x, y) for x = 1 to 3 and y = 10 to 12 ]
(* = [(1,10); (2,11); (3,12)] *)
7. SIMD Vector Types
128-bit and 256-bit SIMD vectors for parallel numeric operations.
Types
(* 128-bit vectors *)
int8x16 int8x16# (* 16 x 8-bit ints *)
int16x8 int16x8# (* 8 x 16-bit ints *)
int32x4 int32x4# (* 4 x 32-bit ints *)
int64x2 int64x2# (* 2 x 64-bit ints *)
float32x4 float32x4# (* 4 x 32-bit floats *)
float64x2 float64x2# (* 2 x 64-bit floats *)
(* 256-bit vectors *)
int8x32 int8x32#
int32x8 int32x8#
float64x4 float64x4#
(* etc. *)
Usage
open Ocaml_simd_sse
let v = Float32x4.set 1.0 2.0 3.0 4.0
let v = Float32x4.sqrt v
let x, y, z, w = Float32x4.splat v
(* Load from arrays *)
let v = Int8x16.String.get text ~byte:0
C Stubs
external vec_op : (int8x16[@unboxed]) -> (int8x16[@unboxed]) =
"boxed_stub" "unboxed_stub"
8. Templates (ppx_template)
Generate multiple copies of code with different modes/kinds.
Mode Templates
(* Define once, get local and global versions *)
let%template[@mode m = (global, local)] id
: 'a. 'a @ m -> 'a @ m
= fun x -> x
(* Generates: id (global) and id__local *)
(* Instantiate *)
let f x = (id [@mode local]) x
Kind Templates
let%template[@kind k = (value, float64)] id
: ('a : k). 'a -> 'a
= fun x -> x
(* Generates: id (value) and id__float64 *)
Exclave Conditional
let%template[@mode m = (global, local)] make_pair x y =
(x, y) [@exclave_if_local m]
(* local version gets: exclave_ (x, y) *)
Alloc Templates
let%template rec map
: f:('a -> 'b @ m) -> 'a list -> 'b list @ m
= fun ~f list ->
match[@exclave_if_stack a] list with
| [] -> []
| hd :: tl -> f hd :: (map [@alloc a]) ~f tl
[@@alloc a @ m = (heap_global, stack_local)]
Portable Functors
(* Short form for portable/nonportable functor variants *)
module%template.portable Make (M : S) : T
Default Floating Attributes
[%%template:
[@@@mode.default m = (global, local)]
val min : t @ m -> t @ m -> t @ m
val max : t @ m -> t @ m -> t @ m]
9. Zero-Alloc Checking
Compile-time verification that functions don't allocate.
Basic Usage
(* Check function doesn't allocate *)
let[@zero_alloc] fast_add x y = x + y
(* Allow local/stack allocations *)
let[@zero_alloc] with_local_pair x y =
let p = stack_ (x, y) in
fst p + snd p
(* Only check in optimized builds *)
let[@zero_alloc opt] complex_func x = ...
(* Strict: no allocation even on error paths *)
let[@zero_alloc strict] very_strict x = ...
Assume Annotations
(* Trust this function is zero-alloc *)
let[@zero_alloc assume] external_wrapper x = external_func x
(* Assume for error paths *)
let[@cold][@zero_alloc assume error] handle_error e =
log_error e;
default_value
In Signatures
val[@zero_alloc] f : int -> int
val[@zero_alloc strict] g : t -> t
val[@zero_alloc arity 2] h : int -> int -> int
File-Level
[@@@zero_alloc all] (* All functions must be zero-alloc *)
let[@zero_alloc ignore] allowed_to_alloc x = [x] (* Opt out *)
10. Parallelism & Capsules
Safe parallel programming with thread isolation.
Contention Modes
contended: May be accessed from multiple threads concurrentlyshared: May be accessed from multiple threads (for shared state)uncontended: Single-thread access
Portability Modes
portable: Safe to move across thread boundaries, captures all values at contendedshareable: May execute in parallel, captures shared statenonportable: Thread-local only, captures uncontended mutable state
Capsules (Experimental)
Capsules isolate mutable state for safe parallelism:
(* Capsule contains thread-local mutable state *)
type 'a capsule
(* Access requires entering capsule context *)
val with_capsule : 'a capsule -> ('a @ local -> 'b) -> 'b
11. Miscellaneous Extensions
Labeled Tuples
(* Create *)
let point = ~x:10, ~y:20
(* Type *)
type point = x:int * y:int
(* Destructure *)
let ~x, ~y = point
(* Partial match (needs type annotation) *)
let get_x (p : x:int * y:int) =
let ~x, .. = p in x
(* Function returning labeled tuple *)
val dimensions : image -> width:int * height:int
Immutable Arrays
(* Syntax uses : instead of | *)
let arr : string iarray = [: "a"; "b"; "c" :]
(* Access *)
let first = arr.:(0)
(* Covariant - allows safe subtyping *)
let arr2 : obj iarray = (arr : sub_obj iarray :> obj iarray)
Include Functor
(* Instead of *)
module M = struct
module T = struct
type t = ...
[@@deriving compare, sexp]
end
include T
include Comparable.Make(T)
end
(* Write *)
module M = struct
type t = ...
[@@deriving compare, sexp]
include functor Comparable.Make
end
Let Mutable
(* Mutable local variable - no allocation *)
let triangle n =
let mutable total = 0 in
for i = 1 to n do
total <- total + i
done;
total
Restrictions: Cannot escape scope, no closure capture, single variable only.
Polymorphic Parameters
(* Function taking polymorphic argument *)
let create (f : 'a. 'a field -> 'a) =
{ a = f A; b = f B }
val create : ('a. 'a field -> 'a) -> t
Small Numbers
(* Types *)
float32 float32#
int8 int8#
int16 int16#
char#
(* Literals *)
1.0s (* float32 *)
#1.0s (* float32# *)
42s (* int8 *)
#42s (* int8# *)
42S (* int16 *)
#42S (* int16# *)
#'a' (* char# *)
(* Arrays - now supported and packed! *)
int8 array int8# array (* 1 byte per element *)
int16 array int16# array (* 2 bytes per element *)
char# array (* 1 byte per element *)
(* Pattern matching with char# ranges *)
match c with
| #'a'..#'z' -> `lowercase
| #'A'..#'Z' -> `uppercase
| _ -> `other
Module Strengthening
(* Instead of *)
sig type t = M.t end
(* Write *)
S with M
Common Patterns
Zero-Alloc Hot Path
let[@zero_alloc] process_batch (data @ local) =
let local_ acc = ref 0 in
for i = 0 to Array.length data - 1 do
acc := !acc + process_item data.(i)
done;
!acc
Local Allocation in Loop
let process_all items =
List.iter (fun item ->
let local_ temp = compute item in
use temp
) items
Unique Resource Management
type handle
val open_handle : unit -> handle @ unique
val use_handle : handle @ unique -> result * handle @ unique
val close_handle : handle @ unique -> unit
let with_handle f =
let h = open_handle () in
let result, h = use_handle h in
close_handle h;
result
Mode-Polymorphic Function
let%template[@mode m = (global, local)] map_pair f (a, b) =
((f a, f b) [@exclave_if_local m])
Kind-Polymorphic Container
type%template ('a : k) box = { contents : 'a }
[@@kind k = (value, float64, bits64)]
Debugging Tips
- Mode errors: Check if you're trying to return local data globally
- Kind errors: Ensure type parameters have correct layout annotations
- Zero-alloc failures: Use
-zero-alloc-checker-details-cutoff -1for full details - Template issues: Check mangled names with
__suffixpattern
Library Dependencies
Core Libraries
stdlib_stable: Immutable arrays (Iarray),Float32,Int8,Int16,Char_ubase: Jane Street's standard library with comprehensive OxCaml mode support- IMPORTANT: Consult SKILL-BASE.md for OxCaml-friendly functions!
- Contains 116 modules with extensive local/exclave, mode, and unboxed type support
- Key modules:
Modes(modal wrappers),Iarray(immutable arrays with local ops),Container_with_local, and__localvariants of most collection functions
core: Extended library with I/O, async, and system features- See SKILL-CORE.md for Iobuf, Time_ns, Bigstring extensions
PPX Libraries
ppx_template: Mode/kind polymorphism via code generation- See SKILL-TEMPLATES.md for mangling details
ppx_simd: SIMD shuffle/blend mask generation
SIMD Libraries
ocaml_simd: Base SIMD typesocaml_simd_sse: SSE intrinsics (128-bit)ocaml_simd_avx: AVX/AVX2 intrinsics (256-bit)- See SKILL-SIMD.md for usage details