plugins/ocaml-dev/skills/oxcaml/SKILL.md
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
npx skillsauth add avsm/ocaml-claude-marketplace oxcamlInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
Current target: OxCaml 5.2.0minus-31. The OCaml runtime reports
itself as 5.2.0+ox (unchanged from 5.2.0minus-25 — the runtime base
has not moved). What did change in this window is the bootstrap
toolchain requirement: building OxCaml now needs upstream OCaml
5.4.0 on PATH (previously 4.14.1), via the oxcaml-dev opam
package. A handful of 5.4.0 stdlib features (notably Format_doc, the
caml_array_make symbol rename) were backported.
See CHANGES-25-31.md for what changed since
5.2.0minus-25, and CHANGES-23-25.md for the prior
window. The upgrade guide at the bottom of CHANGES-25-31.md is the
right starting point when bumping a project between these versions.
When diagnosing build failures after a version bump, check these first:
5.4.0
on PATH (was 4.14.1). The OxCaml runtime itself is still 5.2.0+ox
— this is only the dev-time requirement. Use the oxcaml-dev opam
package per HACKING.md.borrow_, poly_, repr_, kind_, plus literals
#true, #false, #() are now reserved. Identifiers collide → rename.kind_abbrev_ → kind_ — the keyword was renamed. Mechanical fix.mixed_block_layout_v4 gone; use
mixed_block_layout_v5. C macro renames to match.Effect low-level primitives refactored — public ('a, 'b) continuation is unchanged; internal cont gained a termination type
parameter and low-level primitives were renamed. Migrate
caml_alloc_stack/%runstack → %with_stack, and update %resume
call sites.Array.make / Array.create_float now
bind to caml_array_make / caml_array_create_float. Re-promote any
zero-alloc-checker expect tests. The old C symbols remain as shims,
so C externals linking against them still work.value base kinds imply mod external_ — if you need the unmoded
form, use _internal (e.g. bits64_internal).Obj.raw_field / Obj.set_raw_field no longer external — they're
now regular vals.See CHANGES-25-31.md § Breaking Changes and Upgrade Guide for the full list and an ordered checklist.
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 |
(* 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 *)
let b : bool# = #true (* unboxed bool (5.2.0minus-31+) *)
let u : unit# = #() (* unboxed unit (5.2.0minus-31+) *)
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
(* Borrowing (5.2.0minus-31+) - prefix form cooperating with uniqueness *)
let r = ref 0 in f (borrow_ r) (* typical: function argument *)
(* Implicit kinds in signatures (5.2.0minus-31+) *)
module type S = sig
[@@@implicit_kind: ('elt : word)]
type 'elt collection (* 'elt defaults to kind word *)
end
Modes track runtime properties of values. Each mode axis is independent.
| 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 |
(* 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 *)
}
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 ≤ contendedStack-allocated values avoid GC overhead but cannot escape their scope.
(* 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 ())
exclave_)exclave_ allocates in caller's stack frame and must be at tail position(* 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
Unboxed types store values directly without heap allocation.
(* 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, kind bits8 *)
int16# (* 16-bit int - untagged, kind bits16 *)
int# (* native int - untagged *)
char# (* 8-bit char - untagged, same layout as int8# *)
bool# (* 8-bit bool, kind bits8 (5.2.0minus-31+) *)
unit# (* zero-size, kind void (5.2.0minus-31+) *)
(* 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 *)
let d : bool# = #true (* bool# literal - also #false *)
let e : unit# = #() (* unit# literal *)
(* Boxed versions (heap-allocated) *)
let a : float = 3.14 (* boxed *)
let b : float# = #3.14 (* unboxed *)
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 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 tuple syntax *)
type pair = #(float# * int32#)
let p : #(float# * int32#) = #(#1.0, #42l)
Records can mix boxed and unboxed fields:
type mixed = {
name : string; (* boxed *)
value : float#; (* unboxed, stored flat *)
count : int32#; (* unboxed *)
}
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
Kinds classify types by their runtime representation.
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 *)
(* 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# *)
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
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
Track values with exactly one reference for safe mutation/deallocation.
unique: Single reference existsaliased: Multiple references may exist(* 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
(* 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
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
Python/Haskell-style list and array builders.
(* 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 ]
(* Same syntax with [| |] *)
[| x * x for x = 1 to 10 |]
(* Iterate over array *)
[| f elem for elem in source_array |]
[: x for x = 1 to 10 when x mod 2 = 0 :]
for vs andfor ... 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)] *)
128-bit and 256-bit SIMD vectors for parallel numeric operations.
(* 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. *)
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
external vec_op : (int8x16[@unboxed]) -> (int8x16[@unboxed]) =
"boxed_stub" "unboxed_stub"
Generate multiple copies of code with different modes/kinds.
(* 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
let%template[@kind k = (value, float64)] id
: ('a : k). 'a -> 'a
= fun x -> x
(* Generates: id (value) and id__float64 *)
let%template[@mode m = (global, local)] make_pair x y =
(x, y) [@exclave_if_local m]
(* local version gets: exclave_ (x, y) *)
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)]
(* Short form for portable/nonportable functor variants *)
module%template.portable Make (M : S) : T
[%%template:
[@@@mode.default m = (global, local)]
val min : t @ m -> t @ m -> t @ m
val max : t @ m -> t @ m -> t @ m]
Compile-time verification that functions don't allocate.
(* 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 = ...
(* 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
val[@zero_alloc] f : int -> int
val[@zero_alloc strict] g : t -> t
val[@zero_alloc arity 2] h : int -> int -> int
[@@@zero_alloc all] (* All functions must be zero-alloc *)
let[@zero_alloc ignore] allowed_to_alloc x = [x] (* Opt out *)
Safe parallel programming with thread isolation.
contended: May be accessed from multiple threads concurrentlyshared: May be accessed from multiple threads (for shared state)uncontended: Single-thread accessportable: Safe to move across thread boundaries, captures all values at contendedshareable: May execute in parallel, captures shared statenonportable: Thread-local only, captures uncontended mutable stateCapsules 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
The borrow_ keyword is a prefix expression form (borrow_ e) that
cooperates with the uniqueness analysis. It parses anywhere an
expression is valid — misuse produces mode errors, not parse
errors. Typical positions where you'll actually want it:
let update_if_positive r =
if !r > 0 then f (borrow_ r) (* common: function argument *)
let y = borrow_ r in ... (* let-binding RHS *)
match borrow_ r with _ -> ... (* match scrutinee *)
Diagnostics:
use-during-borrowing — "Use of a value during an
active borrow." Raised when a value is used while being borrowed.Unique_use_during_borrowing — the uniqueness analysis
detected a conflict between a borrow and a unique use.Use borrow_ where a callee only reads/writes through a unique value
transiently and you want to keep the unique reference after the call.
A floating [@@@implicit_kind: ...] attribute at the top of a signature
declares that specific type-variable names default to a chosen kind. It
saves repetitive per-declaration annotations:
module type Word_collection = sig
[@@@implicit_kind: ('elt : word)]
type 'elt collection
val singleton : 'elt -> 'elt collection
val length : 'elt collection -> int
end
Multiple names at once:
[@@@implicit_kind: ('a : immediate) * ('b : immediate)]
val swap : 'a * 'b -> 'b * 'a
Rules: implicit kinds can't be overridden in nested signatures, don't
propagate through include, apply inside constraint clauses, and are
not legal in structures. See SKILL-KINDS.md for details.
(* 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
(* 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)
(* 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
(* 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.
(* Function taking polymorphic argument *)
let create (f : 'a. 'a field -> 'a) =
{ a = f A; b = f B }
val create : ('a. 'a field -> 'a) -> t
(* 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
(* Instead of *)
sig type t = M.t end
(* Write *)
S with M
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
let process_all items =
List.iter (fun item ->
let local_ temp = compute item in
use temp
) items
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
let%template[@mode m = (global, local)] map_pair f (a, b) =
((f a, f b) [@exclave_if_local m])
type%template ('a : k) box = { contents : 'a }
[@@kind k = (value, float64, bits64)]
-zero-alloc-checker-details-cutoff -1 for full details__suffix patternstdlib_stable: Immutable arrays (Iarray), Float32, Int8, Int16, Char_ubase: Jane Street's standard library with comprehensive OxCaml mode support
Modes (modal wrappers), Iarray (immutable arrays with local ops),
Container_with_local, and __local variants of most collection functionscore: Extended library with I/O, async, and system features
ppx_template: Mode/kind polymorphism via code generation
ppx_simd: SIMD shuffle/blend mask generationocaml_simd: Base SIMD typesocaml_simd_sse: SSE intrinsics (128-bit)ocaml_simd_avx: AVX/AVX2 intrinsics (256-bit)development
Creating OCaml library tutorials using .mld documentation format with MDX executable examples. Use when discussing tutorials, documentation, .mld files, MDX, or interactive documentation.
development
Testing strategies for OCaml libraries. Use when discussing tests, alcotest, eio mocks, test structure, or test-driven development in OCaml projects.
development
Security hardening for OCaml libraries through systematic vulnerability research. Use when Claude needs to: (1) Research CVEs in similar implementations (C, Rust, Go, Python) and add regression tests, (2) Add fuzz tests for parsers and encoders, (3) Audit integer handling and buffer operations, (4) Test boundary conditions and malformed input, (5) Review cryptographic usage, (6) Add defensive checks against common vulnerability classes
testing
Working with IETF RFCs in OCaml projects. Use when mentioning RFC numbers, implementing internet standards, adding specification documentation, or discussing protocol compliance.