commit 39ee5e11fb5963cf7b89f2f43f7abf2c163899c6
parent d0785fcb8283dadb957a8afb00dd246b4f59e461
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 25 May 2026 04:00:40 -0700
Add ABI registry gating
Diffstat:
10 files changed, 118 insertions(+), 74 deletions(-)
diff --git a/Makefile b/Makefile
@@ -27,8 +27,8 @@ DRIVER_CFLAGS = $(CFLAGS_COMMON) -Iinclude -Ilang
include mk/config.mk
-# Non-arch lib sources: everything under src/ except per-arch backend
-# directories. Shared arch infrastructure (src/arch/*.c) stays unconditional.
+# Non-arch lib sources: everything under src/ except gated component
+# directories/files. Shared arch/obj/abi infrastructure stays unconditional.
LIB_SRCS_NONARCH = $(shell find src -name '*.c' \
-not -path 'src/arch/aa64/*' \
-not -path 'src/arch/x64/*' \
@@ -36,7 +36,8 @@ LIB_SRCS_NONARCH = $(shell find src -name '*.c' \
-not -path 'src/arch/c_target/*' \
-not -path 'src/obj/elf/*' \
-not -path 'src/obj/macho/*' \
- -not -path 'src/obj/coff/*')
+ -not -path 'src/obj/coff/*' \
+ -not -path 'src/abi/abi_*.c')
LIB_SRCS_ARCH_AA64 = $(shell find src/arch/aa64 -name '*.c' 2>/dev/null)
LIB_SRCS_ARCH_X64 = $(shell find src/arch/x64 -name '*.c' 2>/dev/null)
@@ -47,6 +48,14 @@ LIB_SRCS_OBJ_ELF = $(shell find src/obj/elf -name '*.c' 2>/dev/null)
LIB_SRCS_OBJ_MACHO = $(shell find src/obj/macho -name '*.c' 2>/dev/null)
LIB_SRCS_OBJ_COFF = $(shell find src/obj/coff -name '*.c' 2>/dev/null)
+LIB_SRC_ABI_AAPCS64 = src/abi/abi_aapcs64.c
+LIB_SRC_ABI_APPLE_ARM64 = src/abi/abi_apple_arm64.c
+LIB_SRC_ABI_AAPCS64_WINDOWS = src/abi/abi_aapcs64_windows.c
+LIB_SRC_ABI_SYSV_X64 = src/abi/abi_sysv_x64.c
+LIB_SRC_ABI_APPLE_X64 = src/abi/abi_apple_x64.c
+LIB_SRC_ABI_WIN64_X64 = src/abi/abi_win64_x64.c
+LIB_SRC_ABI_RV64 = src/abi/abi_rv64.c
+
LIB_SRCS = $(LIB_SRCS_NONARCH)
ifeq ($(CFREE_ARCH_AA64_ENABLED),1)
LIB_SRCS += $(LIB_SRCS_ARCH_AA64)
@@ -69,6 +78,33 @@ endif
ifeq ($(CFREE_OBJ_COFF_ENABLED),1)
LIB_SRCS += $(LIB_SRCS_OBJ_COFF)
endif
+ifeq ($(CFREE_ARCH_AA64_ENABLED),1)
+ifneq ($(filter 1,$(CFREE_OBJ_ELF_ENABLED) $(CFREE_OBJ_MACHO_ENABLED) $(CFREE_OBJ_COFF_ENABLED)),)
+LIB_SRCS += $(LIB_SRC_ABI_AAPCS64)
+endif
+ifeq ($(CFREE_OBJ_MACHO_ENABLED),1)
+LIB_SRCS += $(LIB_SRC_ABI_APPLE_ARM64)
+endif
+ifeq ($(CFREE_OBJ_COFF_ENABLED),1)
+LIB_SRCS += $(LIB_SRC_ABI_AAPCS64_WINDOWS)
+endif
+endif
+ifeq ($(CFREE_ARCH_X64_ENABLED),1)
+ifneq ($(filter 1,$(CFREE_OBJ_ELF_ENABLED) $(CFREE_OBJ_MACHO_ENABLED)),)
+LIB_SRCS += $(LIB_SRC_ABI_SYSV_X64)
+endif
+ifeq ($(CFREE_OBJ_MACHO_ENABLED),1)
+LIB_SRCS += $(LIB_SRC_ABI_APPLE_X64)
+endif
+ifeq ($(CFREE_OBJ_COFF_ENABLED),1)
+LIB_SRCS += $(LIB_SRC_ABI_WIN64_X64)
+endif
+endif
+ifeq ($(CFREE_ARCH_RV64_ENABLED),1)
+ifeq ($(CFREE_OBJ_ELF_ENABLED),1)
+LIB_SRCS += $(LIB_SRC_ABI_RV64)
+endif
+endif
# Per-frontend source sets. Each is gated by its CFREE_LANG_*_ENABLED flag
# from mk/config.mk so the matching `#if` in src/api/lang_registry.c and
diff --git a/doc/REGISTRY.md b/doc/REGISTRY.md
@@ -61,8 +61,8 @@ one arch and at least one obj format are enabled.
**Status: vtable, registry, and source gating are done.**
- **Vtable**: `ArchImpl` (`src/arch/arch.h`). Carries `dwarf`/`dbg`
- hooks, the `link` arch descriptor, the per-OS `abi_vtable`
- dispatcher, and the codegen/assembler/disassembler factories.
+ hooks, the `link` arch descriptor, and the codegen/assembler/
+ disassembler factories. ABI selection lives under `src/abi/`.
- **Registry**: `src/arch/registry.c` already holds a static
`arch_impls[]` array and exposes `arch_lookup`.
- **Build**: each `src/arch/<name>/` source group is gated by the
@@ -153,21 +153,22 @@ back only when the matching `CFREE_OBJ_*_ENABLED` flag is enabled.
## 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`).
+**Status: vtable, registry, and source gating are done.**
+
+- **Vtable**: `ABIVtable` (`src/abi/abi_internal.h`). It carries the
+ function classifier and ABI-owned `va_list` layout facts.
+- **Registry**: `src/abi/registry.c` maps `(CfreeArchKind,
+ CfreeObjFmt)` to the active `ABIVtable`. `abi_init()` now performs a
+ single `abi_vtable_lookup(target.arch, target.obj)` instead of asking
+ the arch implementation to dispatch by OS.
+- **Gating**: registry entries are gated by the combined arch + object
+ format flags. ELF selects SysV/AAPCS-style ABIs, Mach-O selects Apple
+ variants, and COFF selects Windows variants.
+- **Build**: per-ABI `.c` files in `src/abi/` are excluded from the
+ broad lib source glob and re-added only for enabled arch/format cells.
+ A few ABI variants share a base classifier implementation, so the
+ Makefile also keeps those base classifier TUs when a derived variant
+ delegates to them.
| Arch | ELF (SysV-ish) | Mach-O (Apple) | PE/COFF (Windows) |
|----------|-------------------|-----------------|----------------------|
@@ -214,7 +215,7 @@ back only when the matching `CFREE_OBJ_*_ENABLED` flag is enabled.
|------------|-----------------------|---------------------------|-------------------------------------------------|
| Arch | `ArchImpl` | `src/arch/registry.c` | none for registry/source gating |
| Obj format | `ObjFormatImpl` | `src/obj/registry.c` | move small policy checks into vtable as needed |
-| ABI | `ABIVtable` | per-arch `abi_dispatch` | gate per-OS dispatch entries by obj-format flag |
+| ABI | `ABIVtable` | `src/abi/registry.c` | none for registry/source gating |
| Frontend | `CfreeFrontendVTable` | `src/api/lang_registry.c` | none for registry/source gating |
## Implementation order
@@ -224,7 +225,8 @@ back only when the matching `CFREE_OBJ_*_ENABLED` flag is enabled.
2. Done: gate arch registry entries and arch Makefile sources.
3. Done: extract `ObjFormatImpl`, move format code under
`src/obj/{elf,macho,coff}/`, and gate those directories.
-4. Remaining: gate per-ABI sources and per-OS dispatch entries.
+4. Done: add `src/abi/registry.c`, move ABI selection out of `ArchImpl`,
+ and gate per-ABI sources.
5. Done: add `src/api/lang_registry.c`, expose
`cfree_<lang>_frontend_vtable` externs, and fold frontends into
`libcfree.a`.
diff --git a/src/abi/abi.c b/src/abi/abi.c
@@ -5,16 +5,15 @@
* before calling into this layer.
*
* Per-ABI bits (function classification, __va_list shape) live in
- * abi_aapcs64.c, abi_sysv_x64.c, ... The active arch descriptor selects the
- * ABI vtable for (target.arch, target.os). The C-standard-driven scalar
- * profile and record layout stay here so all ABIs share one impl. */
+ * abi_aapcs64.c, abi_sysv_x64.c, ... The ABI registry selects the vtable
+ * for (target.arch, target.obj). The C-standard-driven scalar profile and
+ * record layout stay here so all ABIs share one impl. */
#include "abi/abi.h"
#include <string.h>
#include "abi/abi_internal.h"
-#include "arch/arch.h"
#include "cg/type.h"
#include "core/arena.h"
#include "core/core.h"
@@ -174,14 +173,12 @@ ABITypeInfo abi_va_list_info(TargetABI* a) { return a->vt->va_list_info; }
/* ---- lifecycle ---- */
static const ABIVtable* select_vtable(Compiler* c) {
- const ArchImpl* arch = arch_for_compiler(c);
- if (arch && arch->abi_vtable) {
- return arch->abi_vtable(c, c->target.os);
- }
+ const ABIVtable* vt = abi_vtable_lookup(c->target.arch, c->target.obj);
+ if (vt) return vt;
{
SrcLoc loc = {0, 0, 0};
- compiler_panic(c, loc, "abi_init: unsupported target arch %d",
- (int)c->target.arch);
+ compiler_panic(c, loc, "abi_init: unsupported target arch/obj %d/%d",
+ (int)c->target.arch, (int)c->target.obj);
}
}
diff --git a/src/abi/abi_internal.h b/src/abi/abi_internal.h
@@ -28,6 +28,8 @@ extern const ABIVtable apple_x64_vtable;
extern const ABIVtable win64_x64_vtable;
extern const ABIVtable aapcs64_windows_vtable;
+const ABIVtable* abi_vtable_lookup(CfreeArchKind arch, CfreeObjFmt obj);
+
/* Shared TargetABI internals. The struct definition is here so each ABI
* TU can reach into the per-TU caches via TargetABI*. abi.c owns the
* cache plumbing; the per-ABI TUs only allocate ABIFuncInfo / record
diff --git a/src/abi/registry.c b/src/abi/registry.c
@@ -0,0 +1,45 @@
+#include <cfree/config.h>
+
+#include "abi/abi_internal.h"
+
+/* Derived ABI registry. ABIs are not user-configurable; an entry exists only
+ * when both the machine arch and the object/OS-family format are enabled. */
+typedef struct ABIImpl {
+ CfreeArchKind arch;
+ CfreeObjFmt obj;
+ const ABIVtable* vt;
+} ABIImpl;
+
+static const ABIImpl abi_impls[] = {
+#if CFREE_ARCH_AA64_ENABLED && CFREE_OBJ_ELF_ENABLED
+ {CFREE_ARCH_ARM_64, CFREE_OBJ_ELF, &aapcs64_vtable},
+#endif
+#if CFREE_ARCH_AA64_ENABLED && CFREE_OBJ_MACHO_ENABLED
+ {CFREE_ARCH_ARM_64, CFREE_OBJ_MACHO, &apple_arm64_vtable},
+#endif
+#if CFREE_ARCH_AA64_ENABLED && CFREE_OBJ_COFF_ENABLED
+ {CFREE_ARCH_ARM_64, CFREE_OBJ_COFF, &aapcs64_windows_vtable},
+#endif
+#if CFREE_ARCH_X64_ENABLED && CFREE_OBJ_ELF_ENABLED
+ {CFREE_ARCH_X86_64, CFREE_OBJ_ELF, &sysv_x64_vtable},
+#endif
+#if CFREE_ARCH_X64_ENABLED && CFREE_OBJ_MACHO_ENABLED
+ {CFREE_ARCH_X86_64, CFREE_OBJ_MACHO, &apple_x64_vtable},
+#endif
+#if CFREE_ARCH_X64_ENABLED && CFREE_OBJ_COFF_ENABLED
+ {CFREE_ARCH_X86_64, CFREE_OBJ_COFF, &win64_x64_vtable},
+#endif
+#if CFREE_ARCH_RV64_ENABLED && CFREE_OBJ_ELF_ENABLED
+ {CFREE_ARCH_RV64, CFREE_OBJ_ELF, &rv64_vtable},
+#endif
+ {CFREE_ARCH_WASM, CFREE_OBJ_WASM, NULL},
+};
+
+const ABIVtable* abi_vtable_lookup(CfreeArchKind arch, CfreeObjFmt obj) {
+ u32 i;
+ for (i = 0; i < (u32)(sizeof abi_impls / sizeof abi_impls[0]); ++i) {
+ if (abi_impls[i].arch == arch && abi_impls[i].obj == obj)
+ return abi_impls[i].vt;
+ }
+ return NULL;
+}
diff --git a/src/arch/aa64/arch.c b/src/arch/aa64/arch.c
@@ -1,6 +1,5 @@
#include "arch/arch.h"
-#include "abi/abi_internal.h"
#include "arch/aa64/aa64.h"
#include "arch/aa64/asm.h"
#include "arch/aa64/disasm.h"
@@ -13,18 +12,6 @@
extern const LinkArchDesc link_arch_aa64;
extern const ArchDbgOps aa64_dbg_ops;
-static const ABIVtable* aa64_abi_vtable(Compiler* c, CfreeOSKind os) {
- (void)c;
- switch (os) {
- case CFREE_OS_MACOS:
- return &apple_arm64_vtable;
- case CFREE_OS_WINDOWS:
- return &aapcs64_windows_vtable;
- default:
- return &aapcs64_vtable;
- }
-}
-
static int aa64_register_at_public(uint32_t idx, CfreeArchReg* out) {
const char* nm = NULL;
int rc;
@@ -153,7 +140,6 @@ const ArchImpl arch_impl_aa64 = {
.backend = {.name = "aa64", .make = aa64_backend_make},
.kind = CFREE_ARCH_ARM_64,
.name = "aa64",
- .abi_vtable = aa64_abi_vtable,
.cgtarget_new = aa64_cgtarget_new,
.asm_new = aa64_arch_asm_new,
.disasm_new = aa64_disasm_new,
diff --git a/src/arch/arch.h b/src/arch/arch.h
@@ -1045,7 +1045,6 @@ struct ArchDisasm {
};
typedef struct LinkArchDesc LinkArchDesc;
-typedef struct ABIVtable ABIVtable;
typedef struct ArchDwarfOps {
/* DWARF .debug_line minimum instruction length and maximum operations per
@@ -1101,7 +1100,6 @@ typedef struct ArchImpl {
CfreeArchKind kind;
const char* name;
- const ABIVtable* (*abi_vtable)(Compiler*, CfreeOSKind os);
/* Low-level CGTarget constructor: caller supplies the MCEmitter. Tests use
* this directly via the cgtarget_new() wrapper; the arch's `backend.make`
* also calls it after creating an MCEmitter internally. */
diff --git a/src/arch/registry.c b/src/arch/registry.c
@@ -3,8 +3,8 @@
* This file is the *only* place in the codebase that checks
* CFREE_ARCH_*_ENABLED. Everything downstream operates on the registry's
* outputs — `const CGBackend*` for session-level code emission, and
- * `const ArchImpl*` for arch-specific metadata (ABI selection, DWARF,
- * debugger hooks, register file, etc.).
+ * `const ArchImpl*` for arch-specific metadata (DWARF, debugger hooks,
+ * register file, etc.).
*
* Conceptually:
* - A CGBackend is "something that can build a CGTarget from a Compiler +
@@ -32,8 +32,8 @@ extern const CGBackend cg_backend_c_target;
#endif
/* Arch-metadata roster. The arch_lookup_* helpers iterate this list when a
- * caller needs ABI/debug/etc. metadata — answers only come from backends
- * that have an ArchImpl, so c_target is intentionally absent.
+ * caller needs machine-arch metadata — answers only come from backends that
+ * have an ArchImpl, so c_target is intentionally absent.
* cg_backend_for_session() picks the CGBackend (which is &impl->backend or
* &cg_backend_c_target) without consulting this list. */
static const ArchImpl* const arch_impls[] = {
diff --git a/src/arch/rv64/arch.c b/src/arch/rv64/arch.c
@@ -1,6 +1,5 @@
#include "arch/arch.h"
-#include "abi/abi_internal.h"
#include "arch/rv64/asm.h"
#include "arch/rv64/disasm.h"
#include "arch/rv64/regs.h"
@@ -12,12 +11,6 @@
extern const LinkArchDesc link_arch_rv64;
extern const ArchDbgOps rv64_dbg_ops;
-static const ABIVtable* rv64_abi_vtable(Compiler* c, CfreeOSKind os) {
- (void)c;
- (void)os;
- return &rv64_vtable;
-}
-
static const ArchDwarfOps rv64_dwarf_ops = {
.min_inst_len = 4u,
.max_ops_per_inst = 1u,
@@ -147,7 +140,6 @@ const ArchImpl arch_impl_rv64 = {
.backend = {.name = "rv64", .make = rv64_backend_make},
.kind = CFREE_ARCH_RV64,
.name = "rv64",
- .abi_vtable = rv64_abi_vtable,
.cgtarget_new = rv64_cgtarget_new,
.asm_new = rv64_arch_asm_new,
.disasm_new = rv64_disasm_new,
diff --git a/src/arch/x64/arch.c b/src/arch/x64/arch.c
@@ -1,6 +1,5 @@
#include "arch/arch.h"
-#include "abi/abi_internal.h"
#include "arch/x64/asm.h"
#include "arch/x64/disasm.h"
#include "arch/x64/regs.h"
@@ -12,18 +11,6 @@
extern const LinkArchDesc link_arch_x64;
extern const ArchDbgOps x64_dbg_ops;
-static const ABIVtable* x64_abi_vtable(Compiler* c, CfreeOSKind os) {
- (void)c;
- switch (os) {
- case CFREE_OS_MACOS:
- return &apple_x64_vtable;
- case CFREE_OS_WINDOWS:
- return &win64_x64_vtable;
- default:
- return &sysv_x64_vtable;
- }
-}
-
static const ArchDwarfOps x64_dwarf_ops = {
.min_inst_len = 1u,
.max_ops_per_inst = 1u,
@@ -78,7 +65,6 @@ const ArchImpl arch_impl_x64 = {
.backend = {.name = "x64", .make = x64_backend_make},
.kind = CFREE_ARCH_X86_64,
.name = "x64",
- .abi_vtable = x64_abi_vtable,
.cgtarget_new = x64_cgtarget_new,
.asm_new = x64_arch_asm_new,
.disasm_new = x64_disasm_new,