kit

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

commit a30a842d4d00917c292efc5ae5c1c37cc7ed28cd
parent 45214fd7bc2683b9318bb9e6164251be7d74b11a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 23 May 2026 10:43:31 -0700

cg: introduce CGBackend; gate arch sources via config.h

Land the first axis of the build-time component registry in
doc/REGISTRY.md, plus the supporting refactor that lets the rest of
the codebase stay generic over a CGBackend vtable rather than
branching on emit_c_source / arch identity.

  - include/cfree/config.h holds the per-component _ENABLED flags
    (suffix is necessary to avoid colliding with the CfreeArchKind /
    CfreeObjFormat / CfreeFrontendKind enum constants).
  - mk/config.mk parses that header so the Makefile drops the same
    sources that #if would drop from the compile.
  - src/core/config_assert.c _Static_asserts at-least-one arch and
    obj format are on.
  - src/arch/registry.c is the only file that consults the _ENABLED
    flags. It exports cg_backend_for_session() returning a CGBackend*
    plus the existing ArchImpl* lookups (ELF machine, Mach-O cputype,
    COFF machine).
  - ArchImpl embeds CGBackend as its first field, so every machine-
    code arch IS a CGBackend by composition; c_target is a standalone
    CGBackend with no ArchImpl, replacing the old free function
    arch_c_target_new.
  - cg_mc_debug_new() helper centralizes MCEmitter + Debug setup so
    aa64/x64/rv64 backend factories don't duplicate it.
  - src/cg/session.c collapses to generic backend dispatch — no
    MCEmitter creation, no emit_c_source branching, no #if anywhere.

Diffstat:
MMakefile | 30+++++++++++++++++++++++++++++-
Ainclude/cfree/config.h | 41+++++++++++++++++++++++++++++++++++++++++
Amk/config.mk | 19+++++++++++++++++++
Msrc/arch/aa64/arch.c | 13+++++++++++++
Msrc/arch/arch.h | 40++++++++++++++++++++++++++++++++++++++++
Msrc/arch/c_target/target.c | 16++++++++++++++++
Msrc/arch/cgtarget.c | 23+++++++++++++++++++++++
Msrc/arch/registry.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/arch/rv64/arch.c | 13+++++++++++++
Msrc/arch/x64/arch.c | 13+++++++++++++
Msrc/cg/session.c | 54++++++++----------------------------------------------
Asrc/core/config_assert.c | 15+++++++++++++++
12 files changed, 291 insertions(+), 51 deletions(-)

diff --git a/Makefile b/Makefile @@ -20,7 +20,35 @@ LIB_CFLAGS = $(CFLAGS_COMMON) -ffreestanding -Iinclude -Isrc DRIVER_CFLAGS = $(CFLAGS_COMMON) -Iinclude -I. LANG_CFLAGS = $(CFLAGS_COMMON) -Iinclude -LIB_SRCS = $(shell find src -name '*.c') +include mk/config.mk + +# Non-arch lib sources: everything under src/ except per-arch backend +# directories. Shared arch infrastructure (src/arch/*.c) stays unconditional. +LIB_SRCS_NONARCH = $(shell find src -name '*.c' \ + -not -path 'src/arch/aa64/*' \ + -not -path 'src/arch/x64/*' \ + -not -path 'src/arch/rv64/*' \ + -not -path 'src/arch/c_target/*') + +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) +LIB_SRCS_ARCH_RV64 = $(shell find src/arch/rv64 -name '*.c' 2>/dev/null) +LIB_SRCS_ARCH_C_TARGET = $(shell find src/arch/c_target -name '*.c' 2>/dev/null) + +LIB_SRCS = $(LIB_SRCS_NONARCH) +ifeq ($(CFREE_ARCH_AA64_ENABLED),1) +LIB_SRCS += $(LIB_SRCS_ARCH_AA64) +endif +ifeq ($(CFREE_ARCH_X64_ENABLED),1) +LIB_SRCS += $(LIB_SRCS_ARCH_X64) +endif +ifeq ($(CFREE_ARCH_RV64_ENABLED),1) +LIB_SRCS += $(LIB_SRCS_ARCH_RV64) +endif +ifeq ($(CFREE_ARCH_C_TARGET_ENABLED),1) +LIB_SRCS += $(LIB_SRCS_ARCH_C_TARGET) +endif + LANG_C_SRCS = $(shell find lang/c -name '*.c' 2>/dev/null) LANG_WASM_SRCS = $(shell find lang/wasm -name '*.c' 2>/dev/null) LIB_ASMS = $(shell find src -name '*.S') diff --git a/include/cfree/config.h b/include/cfree/config.h @@ -0,0 +1,41 @@ +#ifndef CFREE_CONFIG_H +#define CFREE_CONFIG_H + +/* Build-time component configuration for libcfree and the cfree driver. + * + * Three independent axes are gated here: + * - Backend architectures + * - Object/image formats + * - Language frontends + * + * ABIs are derived from (arch x obj-format) and are pulled in automatically + * when both sides are enabled (see src/abi/). + * + * Flag names use the `_ENABLED` suffix to avoid colliding with the + * `CfreeArchKind` / `CfreeObjFormat` / `CfreeFrontendKind` enum constants + * declared in the public headers (e.g. `CFREE_ARCH_RV64`, `CFREE_OBJ_ELF`, + * `CFREE_LANG_C` are enum values, not macros). + * + * All flags must expand to a constant expression usable by `#if` AND + * by `_Static_assert` (i.e. a literal 0 or 1). + */ + +/* Backend architectures. */ +#define CFREE_ARCH_AA64_ENABLED 1 +#define CFREE_ARCH_X64_ENABLED 1 +#define CFREE_ARCH_RV64_ENABLED 1 +#define CFREE_ARCH_C_TARGET_ENABLED 1 + +/* Object/image formats. Each gates emit + read + link-image paths and + * the matching arch-side reloc tables. */ +#define CFREE_OBJ_ELF_ENABLED 1 +#define CFREE_OBJ_MACHO_ENABLED 1 +#define CFREE_OBJ_COFF_ENABLED 1 + +/* Language frontends. The assembler frontend is unconditional: it 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 /* CFREE_CONFIG_H */ diff --git a/mk/config.mk b/mk/config.mk @@ -0,0 +1,19 @@ +# Build-time component flags, mirrored from include/cfree/config.h so the +# Makefile drops the same sources that #if drops from the compile. +# +# Single source of truth is the header; this file just parses it. +CFG_HDR := include/cfree/config.h +cfg_flag = $(shell awk '/^\#define $(1)[ \t]/{print $$3; exit}' $(CFG_HDR)) + +CFREE_ARCH_AA64_ENABLED := $(call cfg_flag,CFREE_ARCH_AA64_ENABLED) +CFREE_ARCH_X64_ENABLED := $(call cfg_flag,CFREE_ARCH_X64_ENABLED) +CFREE_ARCH_RV64_ENABLED := $(call cfg_flag,CFREE_ARCH_RV64_ENABLED) +CFREE_ARCH_C_TARGET_ENABLED := $(call cfg_flag,CFREE_ARCH_C_TARGET_ENABLED) + +CFREE_OBJ_ELF_ENABLED := $(call cfg_flag,CFREE_OBJ_ELF_ENABLED) +CFREE_OBJ_MACHO_ENABLED := $(call cfg_flag,CFREE_OBJ_MACHO_ENABLED) +CFREE_OBJ_COFF_ENABLED := $(call cfg_flag,CFREE_OBJ_COFF_ENABLED) + +CFREE_LANG_C_ENABLED := $(call cfg_flag,CFREE_LANG_C_ENABLED) +CFREE_LANG_TOY_ENABLED := $(call cfg_flag,CFREE_LANG_TOY_ENABLED) +CFREE_LANG_WASM_ENABLED := $(call cfg_flag,CFREE_LANG_WASM_ENABLED) diff --git a/src/arch/aa64/arch.c b/src/arch/aa64/arch.c @@ -145,6 +145,18 @@ static int aa64_apply_label_fixup(Compiler* c, const ArchLabelFixup* fx) { return 0; } +static CGTarget* aa64_backend_make(Compiler* c, ObjBuilder* o, + const CfreeCodeOptions* opts) { + MCEmitter* mc = NULL; + Debug* debug = NULL; + CGTarget* t; + if (cg_mc_debug_new(c, o, opts, &mc, &debug) != CFREE_OK) return NULL; + t = aa64_cgtarget_new(c, o, mc); + if (!t) return NULL; + t->debug = debug; + return t; +} + static const CfreePredefinedMacro aa64_predefined_macros[] = { {"__aarch64__", "1"}, {"__AARCH64EL__", "1"}, @@ -158,6 +170,7 @@ static const CfreePredefinedMacro aa64_predefined_macros[] = { }; const ArchImpl arch_impl_aa64 = { + .backend = {.name = "aa64", .make = aa64_backend_make}, .kind = CFREE_ARCH_ARM_64, .name = "aa64", .abi_vtable = aa64_abi_vtable, diff --git a/src/arch/arch.h b/src/arch/arch.h @@ -1010,6 +1010,15 @@ CGTarget* cgtarget_new(Compiler*, ObjBuilder*, MCEmitter*); void cgtarget_finalize(CGTarget*); void cgtarget_free(CGTarget*); +/* Construct the MCEmitter + (optionally) Debug pair that a machine-code + * CGBackend's `make` typically needs. On success, sets *out_mc to a fresh + * MCEmitter; sets *out_debug to a Debug producer (and wires mc->debug) when + * opts->debug_info is true, else NULL. On allocation failure returns + * CFREE_NOMEM with both outputs left NULL and any partial state cleaned up. + * c_target's backend ignores this and does not create either. */ +CfreeStatus cg_mc_debug_new(Compiler*, ObjBuilder*, const CfreeCodeOptions*, + MCEmitter** out_mc, Debug** out_debug); + /* Helper for backends without a native indexed addressing mode. If addr has * an index (addr.v.ind.index != REG_NONE), materializes * base + (index << log2_scale) into `scratch` and returns a plain @@ -1095,11 +1104,30 @@ typedef struct ArchDbgOps { int (*is_call)(const ArchDbgInsn* insn); } ArchDbgOps; +/* A CGBackend is the unit the registry hands out: "give me a CGTarget for + * this Compiler + ObjBuilder + emit options." Machine-code archs expose one + * (which internally creates MCEmitter + optional Debug then builds the + * arch-specific CGTarget); c_target exposes one (which reads + * opts->c_source_writer). Callers never branch on backend kind — they + * just call backend->make(...). */ +typedef struct CGBackend { + const char* name; + CGTarget* (*make)(Compiler*, ObjBuilder*, const CfreeCodeOptions*); +} CGBackend; + typedef struct ArchImpl { + /* First field, so `(const CGBackend*)&arch_impl_x` is the arch's backend + * view. Every machine-code arch is a CGBackend by composition; c_target + * is a standalone CGBackend with no ArchImpl. */ + CGBackend backend; + 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. */ CGTarget* (*cgtarget_new)(Compiler*, ObjBuilder*, MCEmitter*); ArchAsm* (*asm_new)(Compiler*); ArchDisasm* (*disasm_new)(Compiler*); @@ -1137,6 +1165,18 @@ const ArchImpl* arch_lookup_elf_machine(u32 e_machine); const ArchImpl* arch_lookup_macho_cputype(u32 cputype); const ArchImpl* arch_lookup_coff_machine(u16 machine); +/* Pick the right CGBackend for a session given the compiler's target arch + * and the per-emit CodeOptions. Returns &arch_for_compiler(c)->backend for + * normal machine-code emission, or &cg_backend_c_target when opts requests + * C-source emission. Returns NULL when no backend in this build can serve + * the request — callers should treat that the same as any other + * unsupported-target outcome (CFREE_UNSUPPORTED). + * + * This is the only entry point session-level emission code needs; the + * #if CFREE_ARCH_*_ENABLED gating lives entirely inside the registry. */ +const CGBackend* cg_backend_for_session(const Compiler*, + const CfreeCodeOptions*); + ArchDisasm* arch_disasm_new(Compiler*); u32 arch_disasm_decode(ArchDisasm*, const u8* bytes, size_t len, u64 vaddr, CfreeInsn* out); diff --git a/src/arch/c_target/target.c b/src/arch/c_target/target.c @@ -286,3 +286,19 @@ CGTarget* c_cgtarget_new(Compiler* c, ObjBuilder* o, CfreeWriter* w) { compiler_defer(c, cgt_cleanup, t); return t; } + +static CGTarget* c_target_backend_make(Compiler* c, ObjBuilder* o, + const CfreeCodeOptions* opts) { + /* c_target ignores opt_level, debug_info, and MCEmitter entirely — see + * doc/CBACKEND.md §"Sequencing with opt". It only needs the writer. */ + if (!opts || !opts->c_source_writer) { + compiler_panic(c, (SrcLoc){0, 0, 0}, + "c_target backend: emit_c_source requires c_source_writer"); + } + return c_cgtarget_new(c, o, opts->c_source_writer); +} + +const CGBackend cg_backend_c_target = { + .name = "c_target", + .make = c_target_backend_make, +}; diff --git a/src/arch/cgtarget.c b/src/arch/cgtarget.c @@ -7,6 +7,7 @@ #include "arch/arch.h" #include "cg/type.h" +#include "debug/debug.h" CGTarget* cgtarget_new(Compiler* c, ObjBuilder* o, MCEmitter* m) { const ArchImpl* arch = arch_for_compiler(c); @@ -29,6 +30,28 @@ void cgtarget_free(CGTarget* t) { /* Arena-backed; nothing to free. */ } +CfreeStatus cg_mc_debug_new(Compiler* c, ObjBuilder* o, + const CfreeCodeOptions* opts, + MCEmitter** out_mc, Debug** out_debug) { + MCEmitter* mc; + Debug* debug = NULL; + *out_mc = NULL; + *out_debug = NULL; + mc = mc_new(c, o); + if (!mc) return CFREE_NOMEM; + if (opts && opts->debug_info) { + debug = debug_new(c, o); + if (!debug) { + mc_free(mc); + return CFREE_NOMEM; + } + mc->debug = debug; + } + *out_mc = mc; + *out_debug = debug; + return CFREE_OK; +} + /* Default fold for backends without a native indexed addressing mode. * * If `addr` carries an index register (addr.v.ind.index != REG_NONE), diff --git a/src/arch/registry.c b/src/arch/registry.c @@ -1,17 +1,59 @@ +/* CGBackend / ArchImpl registry. + * + * 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 (ELF/Mach-O/COFF reloc + * tables, ABI selection, DWARF, debugger hooks, register file, etc.). + * + * Conceptually: + * - A CGBackend is "something that can build a CGTarget from a Compiler + + * ObjBuilder + CfreeCodeOptions". Machine-code arches and c_target both + * qualify. + * - An ArchImpl is a CGBackend plus the machine-code metadata (its first + * field is a CGBackend, so `(const CGBackend*)&arch_impl_x` is valid). + * - c_target has no ArchImpl — it is a CGBackend and nothing more. + */ + #include "arch/arch.h" +#include "cfree/config.h" +#if CFREE_ARCH_AA64_ENABLED extern const ArchImpl arch_impl_aa64; +#endif +#if CFREE_ARCH_RV64_ENABLED extern const ArchImpl arch_impl_rv64; +#endif +#if CFREE_ARCH_X64_ENABLED extern const ArchImpl arch_impl_x64; +#endif +#if CFREE_ARCH_C_TARGET_ENABLED +extern const CGBackend cg_backend_c_target; +#endif +/* Arch-metadata roster. The arch_lookup_* helpers iterate this list when a + * caller needs ELF/Mach-O/COFF/ABI/etc. 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[] = { +#if CFREE_ARCH_AA64_ENABLED &arch_impl_aa64, +#endif +#if CFREE_ARCH_X64_ENABLED &arch_impl_x64, +#endif +#if CFREE_ARCH_RV64_ENABLED &arch_impl_rv64, +#endif }; +static u32 arch_impls_count(void) { + return (u32)(sizeof arch_impls / sizeof arch_impls[0]); +} + const ArchImpl* arch_lookup(CfreeArchKind arch) { - for (u32 i = 0; i < (u32)(sizeof arch_impls / sizeof arch_impls[0]); ++i) { + for (u32 i = 0; i < arch_impls_count(); ++i) { if (arch_impls[i]->kind == arch) return arch_impls[i]; } return NULL; @@ -23,7 +65,7 @@ const ArchImpl* arch_for_compiler(const Compiler* c) { } const ArchImpl* arch_lookup_elf_machine(u32 e_machine) { - for (u32 i = 0; i < (u32)(sizeof arch_impls / sizeof arch_impls[0]); ++i) { + for (u32 i = 0; i < arch_impls_count(); ++i) { const ArchImpl* impl = arch_impls[i]; if (impl->elf && impl->elf->e_machine == e_machine) return impl; } @@ -31,7 +73,7 @@ const ArchImpl* arch_lookup_elf_machine(u32 e_machine) { } const ArchImpl* arch_lookup_macho_cputype(u32 cputype) { - for (u32 i = 0; i < (u32)(sizeof arch_impls / sizeof arch_impls[0]); ++i) { + for (u32 i = 0; i < arch_impls_count(); ++i) { const ArchImpl* impl = arch_impls[i]; if (impl->macho && impl->macho->cputype == cputype) return impl; } @@ -44,9 +86,24 @@ const ArchImpl* arch_lookup_coff_machine(u16 machine) { * linker treats both as a single image's worth of code on Windows * targets. */ if (machine == 0xA641u) machine = 0xAA64u; - for (u32 i = 0; i < (u32)(sizeof arch_impls / sizeof arch_impls[0]); ++i) { + for (u32 i = 0; i < arch_impls_count(); ++i) { const ArchImpl* impl = arch_impls[i]; if (impl->coff && impl->coff->machine == machine) return impl; } return NULL; } + +const CGBackend* cg_backend_for_session(const Compiler* c, + const CfreeCodeOptions* opts) { + if (opts && opts->emit_c_source) { +#if CFREE_ARCH_C_TARGET_ENABLED + return &cg_backend_c_target; +#else + return NULL; +#endif + } + { + const ArchImpl* impl = arch_for_compiler(c); + return impl ? &impl->backend : NULL; + } +} diff --git a/src/arch/rv64/arch.c b/src/arch/rv64/arch.c @@ -134,7 +134,20 @@ static const CfreePredefinedMacro rv64_predefined_macros[] = { {"__LITTLE_ENDIAN__", "1"}, }; +static CGTarget* rv64_backend_make(Compiler* c, ObjBuilder* o, + const CfreeCodeOptions* opts) { + MCEmitter* mc = NULL; + Debug* debug = NULL; + CGTarget* t; + if (cg_mc_debug_new(c, o, opts, &mc, &debug) != CFREE_OK) return NULL; + t = rv64_cgtarget_new(c, o, mc); + if (!t) return NULL; + t->debug = debug; + return t; +} + const ArchImpl arch_impl_rv64 = { + .backend = {.name = "rv64", .make = rv64_backend_make}, .kind = CFREE_ARCH_RV64, .name = "rv64", .abi_vtable = rv64_abi_vtable, diff --git a/src/arch/x64/arch.c b/src/arch/x64/arch.c @@ -82,7 +82,20 @@ static int x64_register_at_public(uint32_t idx, CfreeArchReg* out) { return x64_register_iter_get(idx, &out->dwarf_idx, &out->name); } +static CGTarget* x64_backend_make(Compiler* c, ObjBuilder* o, + const CfreeCodeOptions* opts) { + MCEmitter* mc = NULL; + Debug* debug = NULL; + CGTarget* t; + if (cg_mc_debug_new(c, o, opts, &mc, &debug) != CFREE_OK) return NULL; + t = x64_cgtarget_new(c, o, mc); + if (!t) return NULL; + t->debug = debug; + return t; +} + const ArchImpl arch_impl_x64 = { + .backend = {.name = "x64", .make = x64_backend_make}, .kind = CFREE_ARCH_X86_64, .name = "x64", .abi_vtable = x64_abi_vtable, diff --git a/src/cg/session.c b/src/cg/session.c @@ -1,11 +1,5 @@ #include "cg/internal.h" -/* C-source CGTarget constructor lives in src/arch/c_target/target.c. Declared - * here rather than in arch/arch.h because it's the only consumer of the - * CfreeWriter passed via CodeOptions — every other CGTarget is constructed - * via the per-arch ArchImpl. */ -CGTarget* c_cgtarget_new(Compiler* c, ObjBuilder* o, CfreeWriter* w); - static void cg_free_obj_state(CfreeCg* g) { Heap* h; u32 i; @@ -90,11 +84,9 @@ CfreeStatus cfree_cg_new(CfreeCompiler* c, CfreeCg** cg_out) { CfreeStatus cfree_cg_begin_obj(CfreeCg* g, CfreeObjBuilder* out, const CfreeCodeOptions* opts) { CfreeCompiler* c; - MCEmitter* mc = NULL; CGTarget* target; - Debug* debug = NULL; + const CGBackend* backend; int opt_level = opts ? opts->opt_level : 0; - int emit_c_source = opts ? (int)opts->emit_c_source : 0; if (!g || !g->c || !out) return CFREE_INVALID; if (g->obj || g->target || g->mc || g->debug) return CFREE_INVALID; c = (CfreeCompiler*)g->c; @@ -102,48 +94,18 @@ CfreeStatus cfree_cg_begin_obj(CfreeCg* g, CfreeObjBuilder* out, compiler_panic((Compiler*)c, api_no_loc(), "CfreeCg: unsupported opt_level %d", opt_level); } - if (emit_c_source) { - /* See doc/CBACKEND.md §"Sequencing with opt": opt churns IR we'd - * immediately re-flatten to C source. Force opt_level=0 and bypass the - * MCEmitter / Debug producers entirely. */ - if (!opts->c_source_writer) { - compiler_panic((Compiler*)c, api_no_loc(), - "CfreeCg: emit_c_source requires c_source_writer"); - } - opt_level = 0; - } - if (!emit_c_source) { - mc = mc_new((Compiler*)c, (ObjBuilder*)out); - if (!mc) return CFREE_NOMEM; - if (opts && opts->debug_info) { - debug = debug_new((Compiler*)c, (ObjBuilder*)out); - if (!debug) { - mc_free(mc); - return CFREE_NOMEM; - } - mc->debug = debug; - } - } - if (emit_c_source) { - target = - c_cgtarget_new((Compiler*)c, (ObjBuilder*)out, opts->c_source_writer); - } else { - target = cgtarget_new((Compiler*)c, (ObjBuilder*)out, mc); - } - if (!target) { - if (debug) debug_free(debug); - if (mc) mc_free(mc); - return CFREE_UNSUPPORTED; - } - target->debug = debug; + backend = cg_backend_for_session((Compiler*)c, opts); + if (!backend) return CFREE_UNSUPPORTED; + target = backend->make((Compiler*)c, (ObjBuilder*)out, opts); + if (!target) return CFREE_UNSUPPORTED; if (opt_level > 0) { target = opt_cgtarget_new((Compiler*)c, target, opt_level); - if (target) target->debug = debug; + if (!target) return CFREE_UNSUPPORTED; } g->obj = (ObjBuilder*)out; g->target = target; - g->mc = mc; - g->debug = debug; + g->mc = target->mc; + g->debug = target->debug; g->opt_level = opt_level; return CFREE_OK; } diff --git a/src/core/config_assert.c b/src/core/config_assert.c @@ -0,0 +1,15 @@ +/* Build-time invariants on the component configuration in + * include/cfree/config.h. Kept in its own TU so the asserts always + * compile, regardless of which other sources are gated out. */ +#include "cfree/config.h" + +_Static_assert(CFREE_ARCH_AA64_ENABLED + CFREE_ARCH_X64_ENABLED + + CFREE_ARCH_RV64_ENABLED + + CFREE_ARCH_C_TARGET_ENABLED >= + 1, + "at least one backend architecture must be enabled"); + +_Static_assert(CFREE_OBJ_ELF_ENABLED + CFREE_OBJ_MACHO_ENABLED + + CFREE_OBJ_COFF_ENABLED >= + 1, + "at least one object/image format must be enabled");