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:
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");