# Checking out the latest features in OxCaml 5.4.0+ox9

*2026-11-06 — note*


This [website](https://anil.recoil.org/notes/bushel-lives) runs on [OxCaml](https://anil.recoil.org/projects/oxcaml), a set of performance-oriented
extensions to the OCaml language [developed](https://oxcaml.org) at Jane Street.
I [last upgraded](https://anil.recoil.org/notes/oxcaml-opam-guards) our monorepo back in the summer, and [David Allsopp](https://www.dra27.uk) has
just released a major [OxCaml 5.4.0-ox9](https://github.com/oxcaml/opam-repository/pull/66) release.
This upgrade shifts the base version of the fork over to a newer release of OCaml (yay!),
as well as many improvements to their extensions.

If you're new to OxCaml then you may want to start with the [ICFP tutorial](https://anil.recoil.org/notes/icfp25-oxcaml) first, and there's a [quick recap](#a-recap-of-oxcaml-tis-a-speedy-camel)
and [installation instructions](#installing-oxcaml-540-ox9-yourself). The rest of
the post goes through the changes that affected my code, grouped into
[reducing allocations](#reducing-allocations), [kinds](#kinds),
[modes](#modes-and-stack-allocation), [effect handlers](#effect-handlers),
the [C interface](#foreign-function-interface) and [performance](#performance-and-optimisations), followed by a peek at the
[emerging features](#emerging-features) behind extension flags and some
[advice](#should-you-use-oxcaml-yourself) on whether to dive in yourself.

<figure class="image-center"><img src="/images/arod-ss-ox9.webp" alt="This website's development dashboard, using the OCaml console library, running OxCaml 5.4.0+ox9" title="This website's development dashboard, using the OCaml console library, running OxCaml 5.4.0+ox9" loading="lazy" srcset="/images/arod-ss-ox9.768.webp 768w, /images/arod-ss-ox9.640.webp 640w, /images/arod-ss-ox9.480.webp 480w, /images/arod-ss-ox9.320.webp 320w, /images/arod-ss-ox9.2560.webp 2560w, /images/arod-ss-ox9.1920.webp 1920w, /images/arod-ss-ox9.1600.webp 1600w, /images/arod-ss-ox9.1440.webp 1440w, /images/arod-ss-ox9.1280.webp 1280w, /images/arod-ss-ox9.1024.webp 1024w"><figcaption>This website's development dashboard, using the OCaml console library, running OxCaml 5.4.0+ox9</figcaption></figure>

## A recap of OxCaml: tis a speedy camel

For those arriving fresh, [OxCaml](https://oxcaml.org) is a fork of the OCaml
compiler ([oxcaml/oxcaml](https://github.com/oxcaml/oxcaml)) and used in production at
Jane Street. The extensions are designed to be conservative, so normal OCaml
code mostly works, and are enabled when more control over allocation, memory layout, or
data-race-free concurrency is needed.

I like it because, unlike Rust, we can benefit from the ergonomics of having
a garbage collector by default, and selectively write high-performance code
where necessary.  The main extensions are:

- [*Layouts and kinds*](https://oxcaml.org/documentation/kinds/intro/), which let you declare
  [unboxed types](https://oxcaml.org/documentation/unboxed-types/intro/) such as `float#`,
  `int64_u` and unboxed records `r#` that are stored flat with no [pointer
  indirection](https://dev.realworldocaml.org/runtime-memory-layout.html), and polymorphic code that's explicit about which of these
  representations it accepts.
- [*Modes*](https://doi.org/10.1145/3674642), which annotate a value with dataflow properties.
  These include
  `local` for stack allocation, `unique` for values with a single mutator, and
  `portable` and `contended` for values that can cross between parallel domains
  safely.
- [*Checked performance claims*](https://oxcaml.org/documentation/miscellaneous-extensions/zero_alloc_check/), such as `[@zero_alloc]` on a function to
  guarantee no allocations, along with [SIMD vector operations](https://oxcaml.org/documentation/simd/intro/)
  and the [Flambda 2 optimiser](https://icfp23.sigplan.org/details/ocaml-2023-papers/8/Efficient-OCaml-compilation-with-Flambda-2).
- [*Parallelism*](https://oxcaml.org/documentation/parallelism/intro/), via the `parallel` and `await` libraries and the `capsule`
  abstraction, which depend on the mode system for their safety guarantees.
  We did a [tutorial at ICFP](https://anil.recoil.org/notes/icfp25-oxcaml) about these last year.

OxCaml generally tracks upstream OCaml, rebasing irregularly and tagging
numbered snapshots in between. The 5.2-based series has run for some years
now (from minus25 to minus40), and the process of rebasing to the 5.4-based series has now reached ox9.
While the compiler itself advances regularly, the supporting OxCaml libraries must also match or
else we can't really take advantage of the new features.

These support libraries like Base, Core, `ppx_jane` etc, are distributed through an
[oxcaml opam overlay](https://github.com/oxcaml/opam-repository) whose
[guard packages](https://anil.recoil.org/notes/oxcaml-opam-guards) make sure that an OxCaml opam switch
installs the right packages.  This is a complex packaging endeavour, and that's why
I'm excited that [David Allsopp](https://www.dra27.uk) has merged a usable 5.4.0-ox9 release that we can
all use in public that also includes the corresponding Jane Street libraries\!

## Installing OxCaml 5.4.0-ox9 yourself

The instructions on oxcaml.org still refer to `5.2.0+ox` and will no doubt be
updated soon, but I did the following to directly install the `5.4.0-ox9` libraries.

```sh
opam update --all
opam switch create 5.4.0-ox9 oxcaml-compiler.5.4.0-ox9 \
  --repos ox=git+https://github.com/oxcaml/opam-repository.git,default
eval $(opam env --switch 5.4.0-ox9)
opam install -y merlin ocaml-lsp-server ocamlformat utop core_unix parallel
```

The `ox` repository must be listed before `default` so that patched versions of
tools such as merlin, ocamlformat and ppxlib take priority over the upstream ones.
If you already have an older OxCaml switch, there's no upgrade path within
it as the libraries for minus39 conflict with any other compiler release. It's
easier just to create a fresh switch for the new 5.4.0 series.

I went through all of the changes while updating my [Claude OCaml Marketplace](https://anil.recoil.org/notes/aoah-2025-25) skills for
[OxCaml 5.4.0-ox9](https://github.com/avsm/ocaml-claude-marketplace/tree/main/plugins/ocaml-dev/skills/oxcaml) ([changeset](https://github.com/avsm/ocaml-claude-marketplace/commit/258d5756b0cc0af6f2bfe996daba334056a6c523)).
What follows are interesting new features released in the past three months
that I reviewed while also updating my monorepos to deploy the compiler.
I've left out everything that arrived with upstream OCaml 5.3/5.4
(effect handler syntax, `Stdlib.Iarray` and so on), so look at the
[upstream release notes](https://ocaml.org/releases/5.4.0) for those first if you're unfamiliar with the
OCaml ecosystem.

## Reducing allocations

The headline feature of OxCaml is to execute programs faster by doing
less work on housekeeping (like allocating and deallocating memory) and more on
useful compute.

### Unboxed numbers and mixed records

OxCaml has several mechanisms to reduce [boxing](https://www.janestreet.com/tech-talks/unboxed-types-for-ocaml/) of integers and floats,
which are extremely beneficial to improving data processing performance.
The `#` suffix is used for the unboxed version of primitive types, such
as `float#`, `int8#` and the unboxed version `r#` of a record `r`.

In addition to these unboxed variants of standard types, there are
also custom types for 32-bit, 64-bit and native unboxed integers and a 32-bit float
which used to also use the `int64#`, `int32#`, `nativeint#` and `float32#` syntax.
These four are now standalone types called `int64_u`, `int32_u`, `nativeint_u` and `float32_u`
([#6663](https://github.com/oxcaml/oxcaml/pull/6663)), which is a breaking
(but good) change.

```ocaml
type sample = { id : int64_u; weight : float#; tag : int8#; label : string }
let s = { id = #7L; weight = #0.5; tag = #3s; label = "a" }

type boxed = float# box                         (* equal to float *)

type m = { f : float; u : float# }              (* no [@@flatten_floats] *)
let unboxed () : m# = #{ f = 1.0; u = #2.0 }

type node = { next : #(node option * float#) }
let rec cycle = { next = #(Some cycle, #4.0) }  (* through an unboxed field *)
```

5.4.0-ox9 also makes unboxed types much more usable in general, as seen in the above
snippets:

- There was no way to name the boxed form of an unboxed type, but now the predefined
  `'a box` exists ([#5342](https://github.com/oxcaml/oxcaml/pull/5342)).
- A record mixing `float` and `float#` fields was rejected unless annotated
  `[@@flatten_floats]`. The attribute is now optional, and without it the
  record also generates an unboxed version
  ([#6106](https://github.com/oxcaml/oxcaml/pull/6106)).
- A recursive value couldn't pass through an unboxed field ("not allowed as
  right-hand side of let rec"), which made cyclic structures built from
  flat records very difficult to express (about as awkward as they are in Rust!).
  This now works in 5.4.0-ox9 ([#6847](https://github.com/oxcaml/oxcaml/pull/6847)).
- Unsigned types are now builtin, with `uint8_u` through to `uint64_u` with
  unsigned division and remainder primitives. I couldn't find literals or arithmetic
  operations yet though ([#6534](https://github.com/oxcaml/oxcaml/pull/6534)).

### Non-allocating optional types

The builtin [`'a or_null`](https://oxcaml.org/documentation/unboxed-types/or-null/) type is a
non-allocating replacement for `option`, where `This x` is represented as `x` itself and
`Null` as a special immediate value.

```ocaml
type 'a maybe = Nope | Yep of 'a [@@or_null]
type callback = Unset | Set of (unit -> unit) [@@or_null]
type flag = Off of unit# | On of string [@@or_null]

let run = function Unset -> () | Set f -> f ()
```

5.2.0-minus39 introduced `[@@or_null]` on user-defined variants, which allows
an unboxed optional type. It required the payload to have exactly
one type parameter, so only generic `option`\-like types qualified. As shown above, the payload can now
be any type in 5.4.0+ox9 ([#5830](https://github.com/oxcaml/oxcaml/pull/5830)), and the null
constructor can also carry a void payload
([#6383](https://github.com/oxcaml/oxcaml/pull/6383)), and the resulting type
has the same kind as the built-in `or_null`.

In practice this means an optional callback or an optional integer can have a descriptive type of its own without
requiring an allocation due to the boxing.  I've started using this quite a bit in my [zero allocation httpz](https://anil.recoil.org/notes/oxcaml-httpz)
library that drives this site.

### Atomic record fields

An OCaml record field can be marked `[@atomic]` (since [OCaml 5.4](https://ocaml.org/releases/5.4.0)) so that the field is accessed with atomic reads and writes.
This allows it to be safely shared between parallel domains without being wrapped in a separate `Atomic.t`.

An `[@atomic]` field couldn't previously appear in a record that also held
unboxed fields, which meant that (e.g.) a lock-free counter alongside a `float#` in
a record had to be boxed in a separately allocated `Atomic.t`.
Block indices also didn't support atomic fields either, but both now work
([#6423](https://github.com/oxcaml/oxcaml/pull/6423), [#6667](https://github.com/oxcaml/oxcaml/pull/6667)).

This means that shared mutable state can be inline in a flat record, which
allows for some tightly laid out concurrent-friendly datastructures.

One behaviour that's changed is that a functional update `{ c with … }` used to copy
all atomic fields with an ordinary read.
This is now rejected unless the atomic field is given
explicitly `{ c with the_atomic=... }` ([#6453](https://github.com/oxcaml/oxcaml/pull/6453)).

```ocaml
open Stdlib_stable

type counter = { mutable hits : int [@atomic]; rate : float# }
let hits : (counter, int) idx_atomic = (.hits)

let bump (c : counter) = Idx_atomic.incr c hits
let swap (c : counter) = Idx_atomic.compare_and_set c hits 0 1

let reset (c : counter) = { c with hits = 0 }
(* { c with rate = #1.0 } is rejected: it would read [hits] implicitly *)
```

## Kinds

[OxCaml kinds](https://oxcaml.org/documentation/kinds/intro/) describe how a type is represented in memory (e.g. a boxed pointer, or a flat
float, etc) along with whether it can cross modes.
Kinds allow polymorphic code to be more explicit about which representations work, instead of assuming everything is a pointer.

> Roughly, the kind system adds an extra level to the type system: terms are
> classified by types, and types are classified by kinds.  \[...\] Kinds capture
> properties of types along several different dimensions. For example, kinds
> can be used to identify which types have values that are passed in floating
> point registers, or are safely ignored by the garbage collector.
> <cite>\-- [OxCaml Kinds](https://oxcaml.org/documentation/kinds/intro/), Jan 2026</cite>

Kind annotations used to be accepted without any check that they were
accurate. Writing `('a : any)` on a parameter used as (e.g.) an `'a list` compiled
silently with the kind narrowed to what the body needed. The 5.4.0+ox9 compiler now
warns by default when an annotation is weaker than the inferred kind
([#5362](https://github.com/oxcaml/oxcaml/pull/5362)) or when a modifier has no
effect. This lets annotations be trusted as documentation, so some of my older
code raised warnings when upgrading (but everything was easily fixed to the more
specific kind annotation).

The remaining changes remove gaps in the compiler implementation.

- `[@@@implicit_kind]` was ignored in structures; it now applies there as it does in signatures
  ([#6114](https://github.com/oxcaml/oxcaml/pull/6114)).
- Modifiers on abstract kinds are now accepted
  ([#5874](https://github.com/oxcaml/oxcaml/pull/5874)).
- The new modifier `addressable` marks kinds whose boxed form has
  interior pointers ([#6712](https://github.com/oxcaml/oxcaml/pull/6712)).
- A constructor whose arguments are all void used to be accepted, but now
  needs `[@immediate_all_void_constructor]`
  ([#6256](https://github.com/oxcaml/oxcaml/pull/6256)). This is because the
  representation of such constructors is planned to change in a future release
  and this attribute will make the upgrade easier.

```ocaml
type ('a : any mod separable) cell = 'a array   (* [any] alone: warning 181 *)

module Buffer = struct
  [@@@implicit_kind: ('elt : bits64)]
  let singleton : 'elt -> 'elt array = fun x -> [| x |]
end

module type S = sig
  kind_ k
  val get : ('a : k separable). 'a array -> 'a
end

type handle : bits8 addressable
type t = A of unit# [@immediate_all_void_constructor] | B of int
```

## Modes and stack allocation

A mode is an annotation on a _value_ that tracks a dataflow property, like
whether the value is allocated on the stack (`local`), has a single owner
(`unique`) or can safely cross to another domain (`portable`). The OxCaml
type checker enforces that these properties are used consistently.

The change in 5.4.0-ox9 that broke my code was a bugfix for a leak
([#6396](https://github.com/oxcaml/oxcaml/pull/6396)). Previously, if the last expression
of a function was a call that returned a local value (e.g. `fst p` on a local
pair), then the enclosing function was also made local-returning as well which
never released the caller's stack frames. This is now an error unless the call
is wrapped in `exclave_`, bound to a variable, or annotated `[@nontail]`. The
fix itself was mechanical in the few places in my monorepo where the ox9
compiler rejected old code.

Implicit mode inference, by contrast, became more permissive and usable:

- A mode on a parameter used to apply to every axis; e.g.
  `(f @ stateful)` made `f` nonportable.  The implied axes are now inferred
  ([#6745](https://github.com/oxcaml/oxcaml/pull/6745)) using fancy type math.
- A partial application is now constrained only by used parameters
  ([#6966](https://github.com/oxcaml/oxcaml/pull/6966)).
- A coercion can mode-cross the result of a function
  ([#6407](https://github.com/oxcaml/oxcaml/pull/6407)).

Together these remove a number of mode annotations and eta-expansions that were
previously quite verbose and also sometimes breaking existing OCaml code. A handy new
default warning also reports modalities that have no effect ([#5532](https://github.com/oxcaml/oxcaml/pull/5532)).

```ocaml
let first (p : (int * int) @ local) = fst p [@nontail]  (* a tail call is rejected *)
let pair x y = exclave_ stack_ (x, y)

let needs_portable (f : (unit -> unit) @ portable) = ()
let h (f @ stateful) = needs_portable f     (* portability is inferred, not fixed *)

let g : int -> int = fun x -> x + 1
let g' = (g :> int -> int @ portable)       (* coercion mode-crosses the result *)

type r = { mutable n : int @@ global }      (* warning 220: redundant modality *)
```

## Effect handlers

Effect handlers let a computation suspend itself and hand control to a
handler further up the stack, allowing direct-style concurrency libraries
such as [Eio](https://github.com/ocaml-multicore/eio) to schedule lightweight fibers without resorting to monadic code.
The effect handlers in OxCaml expose a different interface to those I helped
[engineer into OCaml 5](https://anil.recoil.org/papers/2021-pldi-retroeff).

Previously, `Effect.perform` was the only way to perform an effect and was
mostly dynamically typed.
A second interface in OxCaml now threads a zero-size token
from the point where a handler is installed to each `perform`
([#6281](https://github.com/oxcaml/oxcaml/pull/6281)).

This helps with the JavaScript compilation (where, until the [wasm stack switching](https://github.com/WebAssembly/stack-switching)
extension is more widely adopted, there is a regional CPS transform to turn effects
into JavaScript).
The old effect function still works but raises an alert.

There's also a new `Stdlib.Dynamic` ([#6412](https://github.com/oxcaml/oxcaml/pull/6412)).
to allow fiber-local storage. A dynamic variable is bound for the
extent of a function call, and is inherited by child fibers, and is safe to read in
parallel.

```ocaml
type _ Effect.t += Ask : int Effect.t

let body (h : Effect.Handler.t @ local) () = Effect.Safe.perform h Ask + 1

let answer =                                (* 42 *)
  Effect.Deep.Safe.try_with body ()
    { effc = fun (type a) (e : a Effect.t) ->
        match e with
        | Ask -> Some (fun (k : (a, _) Effect.Deep.continuation) ->
                   Effect.Deep.continue k 41)
        | _ -> None }

let depth : int Dynamic.t = Dynamic.make ()
let current () = match Dynamic.get depth with Null -> 0 | This d -> d
let nested () =                             (* 1; an unbound variable reads Null *)
  let r = Dynamic.with_temporarily depth 1 ~f:current in
  r
```

I haven't ported my local OxCaml Eio fork to the new functions yet, but it
looks reasonably straightforward.

## Foreign function interface

One of the more complex areas to deal with in OxCaml is the C interface,
since the homogeneous value layouts are more flexible with modes.

- A C stub declared `external` could previously not satisfy a
  `[@@zero_alloc]` signature. This now works
  ([#6651](https://github.com/oxcaml/oxcaml/pull/6651)), so allocation-free
  interfaces no longer need a wrapper function around each stub\!
- Reading memory at a raw address now has compiler primitives to avoid Obj.magic
  ([#6350](https://github.com/oxcaml/oxcaml/pull/6350)).
- C code that called back into OxCaml had no means of releasing stack
  allocations made by that callback. They can now open and close a region
  ([#6142](https://github.com/oxcaml/oxcaml/pull/6142)), and can also donate
  memory it has laid out as OCaml blocks to the major heap
  ([#6248](https://github.com/oxcaml/oxcaml/pull/6248)). This last feature
  looks really useful for my DuckDB/Clickhouse bindings\!

```ocaml
module Trunc : sig
  val to_int : float -> int [@@zero_alloc]
end = struct
  external to_int : float -> int = "caml_int_of_float" [@@noalloc]
end

external peek : ('a : any). int64_u @ local -> 'a @ local
  = "%unsafe_get_ext_ptr" [@@layout_poly]
```

```c
#ifdef OXCAML_RUNTIME
caml_region_t region = caml_region_begin();
value v = caml_callback(f, Val_unit);      /* may return a stack-allocated value */
intnat n = Long_val(Field(v, 0));
caml_region_end(region);                   /* frees the callback's stack allocations */
#endif
```

## Performance and optimisations

The GC performance seems to have generally
had a big boost due to elite Jane Street optimisation ninjas beavering away.

The garbage collector has much better pacing now. Previously, the major collector had no idle phase but
now after sweeping it waits until allocation passes a threshold before starting
again ([#6484](https://github.com/oxcaml/oxcaml/pull/6484) and [upstream](https://github.com/ocaml/ocaml/pull/14365)).
This reduces GC activity significantly in programs with small or steady-state heaps.
Pacing also accounts better for large off-heap data, which we encounter when mapping in large IO libraries in Bigarrays.

There seem to be tons of new optimisations like continuation lifting and specialisation that are now enabled
at `-O2`/`-O3` codegen. x86-64 output also looks better through conditional moves, and immediate and address folding.
For larger binaries, frametables are about 40% smaller ([#6399](https://github.com/oxcaml/oxcaml/pull/6399)), and large links,
debug information and incremental rebuilds are faster. Things are generally snappier overall in my builds; fast compile
time is one of OCaml's best features and OxCaml is still pretty good even in release builds\!

## Emerging features

The following need an extension flag or are documented as experimental, but
I couldn't resist taking a peek to see where the language is heading. I'm still
not using most of these things in my production binaries though.

### Layout polymorphism

This is a huge quality-of-life feature to allow multiple representations without
resorting to ugly `ppx_template` extensions.  On 5.2.0+minus39, a `let poly_` binding type-checked, but
actually using it at an unboxed layout killed the compiler with an internal error.
Both now compile and run
([#5870](https://github.com/oxcaml/oxcaml/pull/5870),
[#6287](https://github.com/oxcaml/oxcaml/pull/6287))\!

```ocaml
(* -extension layout_poly_alpha *)
let poly_ id x = x
let poly_ pair x y = #(x, y)

let demo () =
  let #(s, n) = pair "a" #1L in     (* string, int64_u *)
  #(s, id n, id #42.0)              (* id at bits64 and at float64 *)
```

### Mode polymorphism

A let-bound function has so far had only one mode signature, and so different mode use needed two copies or a
(extremely ugly) ppx template. The good news is that inference can now generalise over modes
([#5483](https://github.com/oxcaml/oxcaml/pull/5483)) but the bad news is that there's no syntax yet\!

```ocaml
(* -extension mode_polymorphism_alpha *)
let id x = x                        (* 'a @ [< 'm] -> 'a @ [> 'm] *)
let keep (r : int ref @ unique) : int ref @ unique = id r
let both (r : int ref) = (id r, id r)
(* without the extension [id] has one mode and [both] is rejected *)
```

### Preemption

5.2.0+minus39 had a fledgling `Preemption` effect, but no actual
handler interface for scheduling when a fiber should be forced to yield. A fiber run under a
preemptible handler in 5.4.0+ox9 now seems to receive that effect when its tick callback asks for
one
([#5913](https://github.com/oxcaml/oxcaml/pull/5913)).

This makes time-slicing of compute-bound fibers expressible in a user-level scheduler (in native
code only). It feels a little weird to have this feature after spending so many decades working in
cooperative-style threads, but I guess preemption has its place too\!

```ocaml
let preemptions = ref 0
let run work =
  Domain.Tick.with_ ~interval_usec:1000 (fun _ ->
    Effect.Deep.Preemptible.match_with work ()
      { retc = Fun.id; exnc = raise;
        effc = (fun (type a) (e : a Effect.t) ->
          match e with
          | Effect.Preemption ->
            Some (fun (k : (a, _) Effect.Deep.continuation) ->
              incr preemptions; Effect.Deep.continue k ())
          | _ -> None);
        tickc = (fun () -> Effect.Preempt) })
```

### AVX 512 operations

In the land of [Tessera](https://anil.recoil.org/projects/tessera), we really do make heavy use of as many
vector operations as we can get our mittens on, and [Mark Elvers](https://www.tunbury.org/) has also
been heavily using [AMX](https://www.tunbury.org/2026/10/05/week-40/#tessera-inference-on-endeavour) on
Intel's Endeavour cluster.

Excitingly, OxCaml has a 512-bit vector type now behind a beta flag, with
mask register types including load and store primitives, and also via C stub support
([#6293](https://github.com/oxcaml/oxcaml/pull/6293)). There don't seem to be any intrinsics
yet, so I'm not sure how to use these without going through C, but I guess that's why it's still beta\!


```ocaml
(* -extension simd_beta *)
type state = { lanes : int64x8#; live : mask#; name : string }
```

There have also been bugfixes as vector registers were not preserved across a stack check, resulting
in silent miscompilation of SIMD code that's now been fixed ([#6334](https://github.com/oxcaml/oxcaml/pull/6334)).

## Should you use OxCaml yourself?

This is all very bleeding-edge stuff, so unless you're deep into OCaml or very willing
to deal with build issues, I'd wait a little while. If you really want to have a go, then
I recommend my [agent OxCaml skills](https://github.com/avsm/ocaml-claude-marketplace) (`ocaml-dev/oxcaml`)
as they really do make navigating all the new extensions much much easier. You can also
track [Thicket](https://thicket.dev) to track changes more regularly.

I'll document in the coming few weeks how my own OxCaml monorepo works. While upgrading
to this latest release, I also had to [port odoc](https://github.com/ocaml/odoc/pull/1520),
and lots of other compiler-specific tooling also needs more work due to the internals
changing rapidly. My monorepo also has extensive forks of existing ecosystem libraries with
their signatures altered to support the `portable` and `local` modes, as well as importing
in cool [new libraries](https://gazagnaire.org/blog/2026-11-02-console.html) and adapting those
too.

None of these were particularly bad blockers, and the [OxCaml guard packages](https://anil.recoil.org/notes/oxcaml-opam-guards) continue to do their job of keeping different
releases apart to avoid build failures from mixing and matching the wrong
ecosystems. The OxCaml compiler itself is rock solid, and the resulting
binaries are extremely fast (otherwise I wouldn't be putting all this effort
into it!). I'm very pleased overall with the output of OxCaml, and the emerging
mode/kind/layout polymorphism will hopefully result in the classic OCaml
ergonomics being restored in the early part of 2027.

## References

1. Madhavapeddy (2025). Holding an OxCaml tutorial at ICFP/SPLASH 2025. <https://doi.org/10.59350/55bc5-x4p75>
2. Madhavapeddy (2026). My (very) fast zero-allocation webserver using OxCaml. <https://doi.org/10.59350/9c6bz-kb659>
3. Sivaramakrishnan et al (2021). Retrofitting effect handlers onto OCaml. ACM. <https://doi.org/10.1145/3453483.3454039>
4. Madhavapeddy (2025). Arise Bushel, my sixth generation oxidised website. <https://doi.org/10.59350/0r62w-c8g63>
5. Lorenzen et al (2024). Oxidizing OCaml with Modal Memory Management. <https://doi.org/10.1145/3674642>

Synopsis: What changed between OxCaml 5.2.0+minus39 and 5.4.0+ox9, covering unboxed values, atomic fields, safer effects, and experimental polymorphism over layouts and modes.
Words: 3635
DOI: 10.59350/r852a-fja33

Discussion:
- Bluesky: <https://bsky.app/profile/anil.recoil.org/post/3mzqbilyjbk2f>
- Mastodon: <https://amok.recoil.org/@avsm/117576000452353845>
- Twitter: <https://x.com/avsm/status/2119097897187000471>

## Related

- [console: Making OCaml Command-Line Tools Look Good](https://gazagnaire.org/blog/2026-11-02-console.html) (feed, 2026-11-02)
  Thomas Gazagnaire. A command-line tool can print lines, which stay in the terminal's scrollback but look plain. Or it can take over the screen like a full-screen app, which looks good until it exits and takes its output with it. I wanted the looks of the second and the scrollback of the first, so I wrote console, an O…
- [Updating to the OxCaml 5.2.0-minus39 opam packaging](https://anil.recoil.org/notes/oxcaml-opam-guards) (note, 2026-08-08)
  How the OxCaml overlay's guard packages keep incompatible releases out, and how to contribute to it with your own packages.
- [My (very) fast zero-allocation webserver using OxCaml](https://anil.recoil.org/notes/oxcaml-httpz) (note, 2026-02-01)
  Building httpz, a high-performance HTTP/1.1 parser with zero heap allocation using OxCaml's unboxed types, local allocations, and mutable local variables.
- [AoAH Day 25: Claude OCaml Marketplace for all your festive coding needs](https://anil.recoil.org/notes/aoah-2025-25) (note, 2025-12-25)
  Wrapping up 25 days of agentic coding with a Claude Code OCaml plugin marketplace to share the skills and tools developed throughout the series.
- [Holding an OxCaml tutorial at ICFP/SPLASH 2025](https://anil.recoil.org/notes/icfp25-oxcaml) (note, 2025-10-06)
  Tutorial at ICFP 2025 on OxCaml extensions for performance engineering with modes and locals.
- [Arise Bushel, my sixth generation oxidised website](https://anil.recoil.org/notes/bushel-lives) (note, 2025-01-29)
  Learn about my sixth generation oxidised website built with a bleeding-edge OCaml variant.
- [OxCaml Labs](https://anil.recoil.org/projects/oxcaml) (project, 2025-01-01)
- [TESSERA, a pixelwise geospatial foundation model](https://anil.recoil.org/projects/tessera) (project, 2025-01-01)
- [Retrofitting effect handlers onto OCaml](https://anil.recoil.org/papers/2021-pldi-retroeff) (paper, 2021-06-01)
  KC Sivaramakrishnan, Stephen Dolan et al. — Proceedings of the 42nd ACM SIGPLAN International Conference on Programming Language Design and Implementation

---
Canonical: https://anil.recoil.org/notes/oxcaml-ox9-improvements
Type: note
License: CC BY 4.0 <https://creativecommons.org/licenses/by/4.0/>
Tags: ocaml, oxcaml, programming, systems
