> Cinder 0.1.0 documentation: ADR-0005: Generating the Mojo bindings
> Canonical page: https://cinder.ash10.dev/docs/adr/0005-bindings-generation/ · Source: `docs/adr/0005-bindings-generation.md` in the engine repository (engine commit 47c9352, imported 2026-11-09)
> Index of every page for agents: https://cinder.ash10.dev/llms.txt

# ADR-0005: Generating the Mojo bindings

Status: Accepted, 2026-11-08 (decided by spike 9; supersedes the "Proposed" version of the same day)

## Context

The mission names **mojo-bindgen** (libclang based) as the bindings generator and requires the output
to be reviewed and committed, with a C program and a generated Mojo test cross-checking every struct
layout and enum value. It also forbids Mojo nightlies.

## Evidence gathered in spike 9 (2026-11-08, pinned toolchain)

mojo-bindgen `0.3.4` from PyPI (MIT) was installed in an isolated pixi environment (`pixi run -e
bindgen`). Findings:

1. It requires Python 3.14 and has **no Mojo dependency** at all, so the "generator pins a nightly"
   concern from the README's unreleased 0.4.0 notes does not apply to the released package.
2. Run on `native/include/cinder_native.h` (which includes `webgpu.h` and `wgpu.h`) it produced 10,616
   lines that **do not parse under Mojo 1.2.0**: a macro body (`} )`) is emitted as code, and the output
   targets nightly idioms that 1.2.0 rejects or has removed: `InlineArray` (removed in 1.1.0),
   `UnsafePointer` (372 uses), `StringSlice` (618 uses), `ImmutOpaquePointer`, private stdlib
   internals (`_DLHandle`, `_Global`, `_find_dylib`), 25 bare `pass` bodies.
3. Its layout sidecar embeds libclang's numbers as constants computed on the generating machine, so it
   cannot detect a difference between the Linux and macOS C ABIs at test time.

## Decision

Cinder uses its own generator, `tools/bindgen/cinder_bindgen.py` (about 600 lines of Python over
libclang, run from the isolated `bindgen` pixi environment), which emits from **one parse**:

- `cinder/ffi/<lib>.mojo`: Mojo 1.2 idioms only (`Pointer`, `Array`, `comptime`, `__deinit__`-free
  plain-data structs); one `struct <Lib>` that owns the `OwnedDLHandle` and holds every resolved
  function pointer as a field, so pointers cannot outlive the library (see the ASAP-destruction note in
  `docs/MOJO_NOTES.md`); every C pointer as `Optional[Pointer[T, MutUntrackedOrigin]]` (8 bytes,
  verified); nullable callback fields as `Optional[Pointer[NoneType, ...]]` because
  `Optional[def(...) thin abi("C")]` is 16 bytes; unions, bitfield records and anonymous members as
  aligned byte blobs; variadic functions listed in a comment, never stubbed.
- `native/layout/<lib>_layout.c`: prints `sizeof`, `alignof`, `offsetof` and every enum and
  `static const` flag value with the **platform's** C compiler; CMake runs it into
  `native/build/<type>/layout/<lib>.txt`.
- `tests/ffi/test_layout_<lib>.mojo`: a TestSuite that reads that file and asserts the same numbers
  from `size_of`, `align_of`, `reflect[T].field_offset` and the generated comptime constants.

mojo-bindgen stays available in the `bindgen` environment for comparison, but nothing in the build
depends on it.

## Consequences

- A generator we control, small enough to review, that fails loudly on anything it cannot bind.
- The layout test is platform-correct by construction: Linux and macOS each compile and run their own
  printer.
- Constness of C pointers is not expressed in the raw layer; the safe layer (`cinder/gfx` and friends)
  owns it. This is deliberate: `Optional[Pointer]` has no implicit mutable-to-immutable conversion.
- Deviation from the mission's named tool, recorded here with the evidence above.

## Amendment (2026-11-08): compile-time cost

The first generator output kept the symbol names in a `comptime Array[StaticString, N]`. With the
umbrella header (4,463 functions) the Mojo 1.2.0 compiler exhausted 13 GB on any program that
instantiated the library struct; a 2,200-entry array failed the same way. The names are now one
newline-joined `StringLiteral` split at load time, after which the full module compiles in about
7 s with 0.3 GB peak. Reproducer and numbers: `bugs/comptime_array_literal_oom/`.
