kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 45214fd7bc2683b9318bb9e6164251be7d74b11a
parent 35868a8761e3923ce2a866dc5bd07d79a44996d2
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 23 May 2026 10:06:05 -0700

doc: add REGISTRY.md — build-time component configuration plan

Plan for how cfree's optional components (backend arches, object
formats, language frontends) are gated via include/cfree/config.h and
registered at runtime through vtable + registry pairs, collapsing
dispatch sites to a single lookup.

Diffstat:
Adoc/REGISTRY.md | 219+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 219 insertions(+), 0 deletions(-)

diff --git a/doc/REGISTRY.md b/doc/REGISTRY.md @@ -0,0 +1,219 @@ +# Build-time configuration and component registries + +This document specifies how cfree's optional components — backend +architectures, object/image formats, and language frontends — are gated +at build time and registered at runtime. The goal is a single +`config.h` that selects which components are compiled into `libcfree.a` +(and the `cfree` driver), with each axis flowing through a vtable + +registry pair so dispatch sites collapse to a single lookup. + +## Configuration axes + +Three independent axes, plus one derived axis: + +1. **Backend architectures** — `aa64`, `rv64`, `x64`, `c_target` +2. **Object/image formats** — `ELF`, `Mach-O`, `PE/COFF` +3. **Language frontends** — `C`, `Toy`, `WASM` (`asm` is unconditional) +4. **ABIs (derived)** — the (arch × format/OS) cross-product. Not user + configurable; pulled in automatically when both sides are enabled. + +The ABI table is the reason we don't expose ABIs as a fourth knob: every +valid ABI maps 1:1 to an (arch, OS-family) pair, where OS family is +implied by the object format (ELF→SysV-style, Mach-O→Apple, +PE/COFF→Windows). Exposing ABIs separately would create mostly-invalid +combinations and offer no real flexibility. + +## `include/cfree/config.h` + +A single header consumed by both `libcfree.a` sources and the driver. +Hand-edited today; a future `configure` script can generate it. + +```c +#ifndef CFREE_CONFIG_H +#define CFREE_CONFIG_H + +/* Backend architectures. */ +#define CFREE_ARCH_AA64 1 +#define CFREE_ARCH_X64 1 +#define CFREE_ARCH_RV64 1 +#define CFREE_ARCH_C_TARGET 0 + +/* Object/image formats. Each gates emit + read + link-image paths and + * the matching arch-side reloc tables. */ +#define CFREE_OBJ_ELF 1 +#define CFREE_OBJ_MACHO 1 +#define CFREE_OBJ_COFF 1 + +/* Language frontends. CFREE_LANG_ASM is unconditional: the assembler + * lives inside libcfree as part of the codegen substrate. */ +#define CFREE_LANG_C_ENABLED 1 +#define CFREE_LANG_TOY_ENABLED 1 +#define CFREE_LANG_WASM_ENABLED 1 + +#endif +``` + +A `config.c` adds `_Static_assert` checks that at least one arch and at +least one obj format are enabled. + +## Axis 1: Backend architectures + +**Status: vtable and registry already exist; only gating is new.** + +- **Vtable**: `ArchImpl` (`src/arch/arch.h:1098`). Carries sub-pointers + for the format-specific reloc tables (`elf`, `macho`, `coff`), + `dwarf`/`dbg` hooks, the `link` arch descriptor, the per-OS + `abi_vtable` dispatcher, and the codegen/assembler/disassembler + factories. +- **Registry**: `src/arch/registry.c` already holds a static + `arch_impls[]` array and exposes `arch_lookup`, + `arch_lookup_elf_machine`, `arch_lookup_macho_cputype`, + `arch_lookup_coff_machine`. +- **Change**: wrap each entry (and its `extern const ArchImpl` + declaration) with `#if CFREE_ARCH_<NAME>`. The `Makefile` switches + from globbed arch sources to per-arch object groups gated by the same + flags, so disabled archs are neither compiled nor linked. + +No new vtable or registry code; this axis is the cheapest of the four. + +## Axis 2: Object/image formats + +**Status: no vtable exists today; switches in multiple call sites. +Largest piece of new work.** + +Today `emit_elf` / `emit_macho` / `emit_coff` are bare functions +(`src/obj/{elf,macho,coff}_emit.c`), the read paths are likewise bare +(`*_read.c`), and link-image emission dispatches via a switch in +`src/link/link.c:958` calling `link_emit_elf` / `link_emit_macho` / +`link_emit_coff`. DSO header readers (`*_read_dso.c`) follow the same +shape. + +**New vtable** (`src/obj/format.h`): + +```c +typedef struct ObjFormatImpl { + CfreeObjFormat kind; + const char* name; + + /* Relocatable object emit + read. */ + void (*emit)(Compiler*, ObjBuilder*, Writer*); + CfreeStatus (*read)(Compiler*, const u8* data, size_t len, + ObjBuilder* out); + + /* Link-image emit (executable / shared object). */ + void (*link_emit)(LinkImage*, Writer*); + + /* DSO header reader for `-lfoo` resolution against .so/.dylib/.dll. */ + CfreeStatus (*read_dso)(Compiler*, const u8*, size_t, /* ... */); +} ObjFormatImpl; + +const ObjFormatImpl* obj_format_lookup(CfreeObjFormat); +``` + +**New registry** (`src/obj/registry.c`), gated the same way as the arch +registry: + +```c +extern const ObjFormatImpl obj_format_impl_elf; +extern const ObjFormatImpl obj_format_impl_macho; +extern const ObjFormatImpl obj_format_impl_coff; + +static const ObjFormatImpl* const obj_format_impls[] = { +#if CFREE_OBJ_ELF + &obj_format_impl_elf, +#endif +#if CFREE_OBJ_MACHO + &obj_format_impl_macho, +#endif +#if CFREE_OBJ_COFF + &obj_format_impl_coff, +#endif +}; +``` + +**Call-site changes**: the switch in `src/link/link.c` and the obj +emit/read entry points collapse to +`obj_format_lookup(target.obj_format)->fn(...)`. + +The arch-side format reloc tables (`ArchElfOps`, `ArchMachoOps`, +`ArchCoffOps` on `ArchImpl`) stay where they are — they're the +(arch × format) intersection and naturally drop out when either side +isn't compiled in. + +## Axis 3: ABIs (derived) + +**Status: vtable and dispatch already exist; only gating is new.** + +- **Vtable**: `ABIVtable`, selected by `arch->abi_vtable(c, os)` + (`src/abi/abi.c:177`). +- **Selection**: each arch's dispatch function maps `CfreeOSKind` to one + of its supported `ABIVtable`s. Today every (arch × OS) cell that + cfree supports is wired in unconditionally. +- **Change**: gate per-OS entries inside the dispatch by the + corresponding format flag — `#if CFREE_OBJ_COFF` drops the + `win64_x64` / `aapcs64_windows` entries; `#if CFREE_OBJ_MACHO` drops + `apple_arm64` / `apple_x64`; etc. The format flag is used as a proxy + for "the OS family that uses this format." +- **Build**: per-ABI `.c` files in `src/abi/` opt in via the same + combined flags (e.g. `abi_win64_x64.c` only when + `CFREE_ARCH_X64 && CFREE_OBJ_COFF`). + +| Arch | ELF (SysV-ish) | Mach-O (Apple) | PE/COFF (Windows) | +|----------|-------------------|-----------------|----------------------| +| aa64 | aapcs64 | apple_arm64 | aapcs64_windows | +| x64 | sysv_x64 | apple_x64 | win64_x64 | +| rv64 | rv64 | — | — | +| c_target | n/a (source-level emission, no machine ABI) | + +## Axis 4: Language frontends + +**Status: vtable already exists publicly; registration is host-side. +Smallest change.** + +- **Vtable**: `CfreeFrontendVTable` (`include/cfree/compile.h`), public + API. +- **Registration today**: `driver/env.c:172-175` explicitly calls + `cfree_c_register(c)`, `cfree_register_frontend(c, TOY, ...)`, + `cfree_wasm_register(c)`. The assembler frontend is built into + libcfree and registered by `cfree_compiler_new`. Other frontends are + library consumers of libcfree, not part of it. + +**Two options considered:** + +- **A (chosen)**: keep host registration. Gate the three calls in + `driver/env.c` with `#if CFREE_LANG_<NAME>_ENABLED` and gate the + matching `lang/<name>/*.c` sources in the Makefile. +- **B (rejected)**: introduce `lang/registry.c` with auto-registration + from inside `cfree_compiler_new`. Pro: symmetric with arch/obj. Con: + pulls `lang/c` (~30 files) into `libcfree.a`, breaks the current + "libcfree is the substrate, lang/ is built on top" boundary, and + prevents embedders from swapping in a custom C frontend. + +The asymmetry is justified: arch and obj are codegen-internal — libcfree +cannot function without picking one — while frontends are user-of-libcfree +code that the public API was explicitly designed to let callers swap. + +## Summary + +| Axis | Vtable | Registry | Net new work | +|-------------|-------------------------------------------------|----------------------------------------------|-----------------------------------------------------------| +| Arch | `ArchImpl` (exists) | `src/arch/registry.c` (exists) | `#if CFREE_ARCH_*` gates | +| Obj format | `ObjFormatImpl` (new) | `src/obj/registry.c` (new) | Extract `emit_*` / `read_*` / `link_emit_*` behind vtable | +| ABI | `ABIVtable` (exists) | per-arch `abi_dispatch` (exists) | Gate per-OS dispatch entries by obj-format flag | +| Frontend | `CfreeFrontendVTable` (exists) | none — host-side (`driver/env.c`) | `#if CFREE_LANG_*_ENABLED` around three driver calls | + +## Implementation order + +1. Land `include/cfree/config.h` with all flags set to `1` (no behavior + change). Add `_Static_assert` minimums in `config.c`. +2. Gate arch registry entries and arch Makefile sources. Verify with a + build that flips one arch off. +3. Extract `ObjFormatImpl` and the obj registry. Replace the switches + in `link.c` and the obj entry points. Verify a build with one obj + format off. +4. Gate per-ABI sources and per-OS dispatch entries. +5. Gate frontend registration in `driver/env.c` and frontend Makefile + sources. + +Each step is independently testable and leaves the build green with the +default all-on configuration.