OCaml Internals

See OCaml Setup for the toolchain. This page covers the main runtime concepts; implementation details depend on the compiler version and build.

Value representation

In the standard runtime representation, an OCaml value is a tagged word: either an immediate value or a pointer to a heap block. Integers use the low bit as a tag, leaving 63 value bits on a 64-bit platform.

Blocks have headers describing their size and tag. Some blocks contain values the collector follows; others contain data it does not scan. Float arrays use a specialised representation.

Unsafe operations such as Obj.magic can violate these assumptions and corrupt memory.

The collector

New objects usually begin in the minor heap. Minor collection copies surviving objects to the major heap. Updating mutable fields must preserve the collector's bookkeeping, including references from older objects to younger ones.

The Gc module provides statistics and tuning controls. Measure allocation and survival before changing settings.

The pipeline

The compiler parses and type-checks the program, then lowers it through intermediate representations. Native compilation eventually produces machine code.

-dlambda shows Lambda output; -dcmm shows lower-level operations; -S keeps assembly. Optimisation differs between compiler builds, including those using Flambda.

Modules and the type system

Functors parameterise modules. Applicative functors can preserve type identity across applications to the same module path; generative functors create fresh types.

First-class modules package modules as values. GADTs refine type information within a match. The value restriction limits generalisation to keep mutable values sound.

OCaml 5: domains and effects

Domains enable parallel execution and have separate minor heaps with a shared major heap. Shared mutation still needs appropriate synchronisation.

Effect handlers capture a continuation that can be resumed once. They support control-flow patterns such as cooperative scheduling; unhandled effects fail at runtime.

The FFI

C code must register OCaml values that need to remain live across allocations. Use the documented root macros and field-update functions so collection and write barriers work correctly.

Allocation and sharing

Immutable values can be shared without copying their contents. A list operation may construct a new prefix while reusing the remaining tail. Mutability changes the reasoning: two references to the same mutable value observe changes to that value.

Physical equality checks identity; structural equality compares contents for supported values. Do not substitute one for the other merely because it appears faster. Comparing functional values and cyclic structures needs particular care.

Tail calls

A tail call has no remaining work in its caller after the call returns. Tail-call optimisation can keep stack use bounded for a chain of such calls. Accumulator-based code can change allocation and traversal order, so check both correctness and performance.

Not every recursive function is tail-recursive. A recursive call inside an argument to another operation usually leaves work to do afterwards.

Runtime measurements

Separate allocation rate from retained live data. A large minor allocation count need not mean the major heap is growing. Compare collection counters over a defined interval and use a representative workload.

Before reading assembly, identify the source operation you are investigating: allocation, bounds checks, dispatch or a loop. Compiler output is easier to understand when you have a specific question.

Reading