commit 757fa31e7ccda6059b00c27ab30ee4e9b56881b3
parent c9c1495aa91c311d916704f17cd3c9f0a79079af
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 5 Jun 2026 11:13:53 -0700
modularity wave 3: route frontend/driver/backend consumers through hooks
C frontend + preprocessor (P2): c_abi_va_list_type -> kit_cg_target_va_list_kind (#1, also
fixes a latent win64 va_list-type bug); __builtin___clear_cache -> backend ICACHE_COHERENT
feature (#5); kit_target_uses_lp64/long_double_is_binary128/ty_wchar -> resolved spec fields
(#33,#3,#32); pp.c Windows/_M_* predefines consolidated into one os-keyed function reading
spec.wchar_size/long_size + kit_cg_target_c_label_prefix (#20).
Driver (P3): HostedProfile (os,obj) registry replaces the if/else+switch dispatch (#19); new
driver_default_exe_name/obj_ext/needs_sysroot_libdir/default_hosted_profile helpers replace the
os==WINDOWS forks in cc.c/build.c (#34); emu arch via driver_arch_from_name single authority
(#22); dbg REPL caching via frontend caps (#8); build.c lang->name reverse maps via
kit_language_name (#26).
Backend: x64/aa64 TEB-TLS selection via obj_format_tls_model (#15); aa64 variadic FP class via
ABIFuncInfo.vararg_fp_via_int (#16); mc.c .eh_frame via spec.emits_eh_frame (#17); c_target
underscore strip via obj_format_demangle_c (#10); c_target alias/weak/TLS spellings via new
ObjFormatImpl.alias_via_thunk/weak_undef_attr + obj_format_tls_via_descriptor (#27).
Residual (tracked): parse-time -x/source-ext classifiers (#6/#7) need compiler-free resolvers;
objcopy/objdump (#35) need BFD-alias fmt names — both addressed next.
Full suite green: parse/pp/toy/cg-api/opt/isa/aa64-inline/link/elf/ar/driver-ar/debug/dwarf/
lib-deps/libc(musl+glibc)/smoke-x64/smoke-rv64.
Diffstat:
17 files changed, 516 insertions(+), 318 deletions(-)
diff --git a/driver/cmd/build.c b/driver/cmd/build.c
@@ -1014,7 +1014,7 @@ static void build_enable_hosted_for_sysroot(BuildOptions* o) {
}
static void build_apply_default_hosted_profile(BuildOptions* o) {
- if (o->target.os != KIT_OS_WINDOWS || o->target.obj != KIT_OBJ_COFF) return;
+ if (!driver_target_default_hosted_profile(o->target)) return;
if (o->no_stdlib || o->no_defaultlibs || o->wants_hosted_libc) return;
if (!o->sysroot || !o->sysroot[0]) return;
o->wants_hosted_libc = 1;
@@ -1070,7 +1070,7 @@ static int build_append_windows_lib_dirs(BuildOptions* o) {
const char* sysroot = o->sysroot;
char* joined;
size_t srlen, need_slash, bytes, off = 0;
- if (o->target.os != KIT_OS_WINDOWS) return 0;
+ if (!driver_target_needs_sysroot_libdir(o->target)) return 0;
if (!sysroot || !sysroot[0]) {
sysroot = driver_getenv("KIT_SYSROOT");
if (!sysroot || !sysroot[0]) return 0;
@@ -1251,13 +1251,9 @@ static int build_compile_source(BuildOptions* o, KitCompiler* compiler,
if (fe_n) {
if (kit_frontend_parse_options(compiler, lang, (int)fe_n, fe_argv,
&lang_extra) != KIT_OK) {
- driver_errf(
- o->tool, "unsupported -X%.*s frontend flag: %.*s",
- KIT_SLICE_ARG(kit_slice_cstr(lang == KIT_LANG_C ? "c"
- : lang == KIT_LANG_ASM ? "asm"
- : lang == KIT_LANG_TOY ? "toy"
- : "wasm")),
- KIT_SLICE_ARG(kit_slice_cstr(fe_argv[0])));
+ driver_errf(o->tool, "unsupported -X%.*s frontend flag: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(kit_language_name(compiler, lang))),
+ KIT_SLICE_ARG(kit_slice_cstr(fe_argv[0])));
goto out;
}
}
@@ -1293,7 +1289,6 @@ static void build_fill_code(const BuildOptions* o, KitCodeOptions* code) {
static char* build_default_obj_name(DriverEnv* env, const BuildOptions* o,
const char* src, size_t* out_size) {
- int win = (o->target.os == KIT_OS_WINDOWS);
const char* ext;
size_t ext_len, srclen = driver_strlen(src), dot = driver_strlen(src),
slash = 0, k;
@@ -1311,8 +1306,7 @@ static char* build_default_obj_name(DriverEnv* env, const BuildOptions* o,
ext_len = 2u;
break;
default:
- ext = win ? ".obj" : ".o";
- ext_len = win ? 4u : 2u;
+ driver_default_obj_ext(o->target, &ext, &ext_len);
break;
}
for (k = srclen; k > 0; --k) {
@@ -1414,10 +1408,7 @@ static int build_compile_all(BuildOptions* o, KitCompiler* compiler,
&lang_extras[i]) != KIT_OK) {
driver_errf(
o->tool, "unsupported -X%.*s frontend flag: %.*s",
- KIT_SLICE_ARG(kit_slice_cstr(lang == KIT_LANG_C ? "c"
- : lang == KIT_LANG_ASM ? "asm"
- : lang == KIT_LANG_TOY ? "toy"
- : "wasm")),
+ KIT_SLICE_ARG(kit_slice_cstr(kit_language_name(compiler, lang))),
KIT_SLICE_ARG(kit_slice_cstr(fe_argv[0])));
if (fe_argv) driver_free(o->env, fe_argv, fe_n * sizeof(*fe_argv));
goto out;
@@ -1923,8 +1914,7 @@ static int build_validate(BuildOptions* o) {
driver_errf(o->tool, "--emit/-S/-fsyntax-only are build-obj options");
return 1;
}
- if (!o->output_path)
- o->output_path = (o->target.os == KIT_OS_WINDOWS) ? "a.exe" : "a.out";
+ if (!o->output_path) o->output_path = driver_default_exe_name(o->target);
return 0;
}
diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c
@@ -852,7 +852,7 @@ static int cc_append_windows_lib_dirs(CcOptions* o) {
size_t need_slash;
size_t bytes;
size_t off = 0;
- if (o->target.os != KIT_OS_WINDOWS) return 0;
+ if (!driver_target_needs_sysroot_libdir(o->target)) return 0;
if (!sysroot || !sysroot[0]) {
sysroot = driver_getenv("KIT_SYSROOT");
if (!sysroot || !sysroot[0]) return 0;
@@ -905,7 +905,7 @@ static void cc_enable_hosted_for_sysroot(CcOptions* o) {
}
static void cc_apply_default_hosted_profile(CcOptions* o) {
- if (o->target.os != KIT_OS_WINDOWS || o->target.obj != KIT_OBJ_COFF) return;
+ if (!driver_target_default_hosted_profile(o->target)) return;
if (o->no_stdlib || o->no_defaultlibs || o->wants_hosted_libc) return;
if (!o->sysroot || !o->sysroot[0]) return;
if (!cc_has_link_action(o) && o->nsource_files + o->nsource_memory == 0)
@@ -1586,7 +1586,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
} else if (o->preprocess_only) {
/* stdout */
} else {
- o->output_path = (o->target.os == KIT_OS_WINDOWS) ? "a.exe" : "a.out";
+ o->output_path = driver_default_exe_name(o->target);
}
}
}
@@ -1811,11 +1811,12 @@ static char* cc_dep_default_target(DriverEnv* env, const CcOptions* o,
}
}
{
- int win = (o && o->target.os == KIT_OS_WINDOWS);
- size_t ext_len = win ? 4u : 2u;
- const char* ext = win ? ".obj" : ".o";
+ const char* ext;
+ size_t ext_len;
size_t name_len = dot - slash;
- size_t bufsz = name_len + ext_len + 1u;
+ size_t bufsz;
+ driver_default_obj_ext(o->target, &ext, &ext_len);
+ bufsz = name_len + ext_len + 1u;
buf = driver_alloc(env, bufsz);
if (!buf) return NULL;
driver_memcpy(buf, src + slash, name_len);
@@ -1829,11 +1830,9 @@ static char* cc_dep_default_target(DriverEnv* env, const CcOptions* o,
static char* cc_default_obj_path_for_name(DriverEnv* env, const CcOptions* o,
const char* src, size_t* out_size) {
- /* Windows targets default to a `.obj` suffix; everyone else `.o`.
- * Drivers accept both spellings as inputs (driver/inputs.c), but
- * tooling that scrapes default outputs expects the canonical
- * platform extension. */
- int win = (o && o->target.os == KIT_OS_WINDOWS);
+ /* -S/--emit override the object suffix; otherwise the canonical
+ * platform object extension (Windows `.obj`, else `.o`) via the shared
+ * per-target helper. */
const char* ext;
size_t ext_len;
if (o && o->emit_asm_source) {
@@ -1842,12 +1841,8 @@ static char* cc_default_obj_path_for_name(DriverEnv* env, const CcOptions* o,
} else if (o && o->emit_ir) {
ext = ".ir";
ext_len = 3u;
- } else if (win) {
- ext = ".obj";
- ext_len = 4u;
} else {
- ext = ".o";
- ext_len = 2u;
+ driver_default_obj_ext(o->target, &ext, &ext_len);
}
size_t srclen = driver_strlen(src);
size_t dot = srclen;
diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c
@@ -1966,9 +1966,18 @@ static int dbg_jit_compile_append_ex(DbgState* s, KitLanguage lang,
/* Published: make the staged declarations durable. */
kit_compile_session_commit(session);
if (generated_source_name) s->source_counter = source_id;
- if (lang == KIT_LANG_TOY && input_kind == KIT_FRONTEND_INPUT_REPL_TOPLEVEL &&
- dbg_source_cache_put(s, sin.name, src, len) != 0) {
- driver_errf(DBG_TOOL, "out of memory caching source for list");
+ {
+ /* Cache the verbatim toplevel text only for frontends that re-read
+ * earlier toplevel source on later compiles (toy today), per the
+ * frontend's static capability rather than a hard-coded language. */
+ KitFrontendCaps caps = {0};
+ int cache_source =
+ kit_frontend_caps(s->compiler, lang, &caps) == KIT_OK &&
+ caps.cache_repl_toplevel_source;
+ if (cache_source && input_kind == KIT_FRONTEND_INPUT_REPL_TOPLEVEL &&
+ dbg_source_cache_put(s, sin.name, src, len) != 0) {
+ driver_errf(DBG_TOOL, "out of memory caching source for list");
+ }
}
dbg_refresh_dwarf(s);
return 0;
diff --git a/driver/cmd/emu.c b/driver/cmd/emu.c
@@ -109,13 +109,17 @@ static int emu_alloc_arrays(EmuOptions* o, int argc) {
}
static int emu_record_arch(EmuOptions* o, const char* val) {
- if (driver_streq(val, "aarch64") || driver_streq(val, "arm64")) {
- o->guest_arch = KIT_ARCH_ARM_64;
- o->guest_arch_set = 1;
- return 0;
- }
- if (driver_streq(val, "riscv64") || driver_streq(val, "rv64")) {
- o->guest_arch = KIT_ARCH_RV64;
+ KitArchKind arch;
+ /* `rv64` is an emu-layer alias not carried by the shared arch-name table
+ * (parallel to the cc-only -x aliases); fold it onto `riscv64` first. */
+ const char* name = driver_streq(val, "rv64") ? "riscv64" : val;
+ /* The emulator only supports the aarch64 / riscv64 guests today. Recognize
+ * arch names through the shared driver_arch_from_name table, then restrict
+ * to those two so an unsupported (but otherwise valid) arch still reports
+ * the same "unsupported -arch value" error. */
+ if (driver_arch_from_name(name, &arch, NULL) == 0 &&
+ (arch == KIT_ARCH_ARM_64 || arch == KIT_ARCH_RV64)) {
+ o->guest_arch = arch;
o->guest_arch_set = 1;
return 0;
}
diff --git a/driver/driver.h b/driver/driver.h
@@ -158,6 +158,15 @@ int driver_target_from_triple(const char* triple, KitTargetSpec* out);
* nonzero when `buf` is too small. */
int driver_target_to_triple(KitTargetSpec target, char* buf, size_t cap);
+/* Map an architecture-name literal (the arch component of a triple:
+ * x86_64/amd64, i386/i486/i586/i686, aarch64/arm64, arm/armv7, riscv64,
+ * riscv32, wasm32, wasm64) to its KitArchKind and natural pointer size. The
+ * single authority for arch-name spellings; driver_target_from_triple uses
+ * the same table. Returns 0 on success, nonzero on an unrecognized name or a
+ * NULL argument. Out-pointers may be NULL. */
+int driver_arch_from_name(const char* name, KitArchKind* arch_out,
+ uint8_t* ptr_size_out);
+
typedef struct DriverTargetFeatures {
DriverEnv* env;
KitTargetFeature* features;
@@ -196,4 +205,24 @@ KitPic driver_default_pic(KitObjFmt obj, KitOSKind os);
int driver_link_pie(KitTargetSpec target, int explicit_pie, int shared,
int relocatable);
+/* Per-target driver defaults, centralizing the per-OS forks the cc/build
+ * drivers used to inline. */
+
+/* Default executable name when the user gives no -o: "a.exe" on Windows
+ * (PE/COFF), "a.out" elsewhere. */
+const char* driver_default_exe_name(KitTargetSpec target);
+
+/* Default relocatable-object extension for the target: ".obj" (len 4) on
+ * Windows, ".o" (len 2) elsewhere. NULL out-pointers are ignored. */
+void driver_default_obj_ext(KitTargetSpec target, const char** ext_out,
+ size_t* ext_len_out);
+
+/* Whether `<sysroot>/lib` should be folded into the library search path for
+ * this target (Windows mingw import-library tree). */
+int driver_target_needs_sysroot_libdir(KitTargetSpec target);
+
+/* Whether the hosted libc profile is engaged by default for this target
+ * (Windows-COFF, given a sysroot and no -nostdlib). */
+int driver_target_default_hosted_profile(KitTargetSpec target);
+
#endif
diff --git a/driver/lib/hosted.c b/driver/lib/hosted.c
@@ -568,40 +568,112 @@ static const char* hosted_linux_inc_triple_sub(KitArchKind arch) {
/* Expand an explicit --sysroot/KIT_SYSROOT into the dir list, per target-OS
* layout. Mirrors the per-host probes' shape so both producers converge on the
* same resolver contract. Dirs are added unconditionally (existence is checked
- * when the resolver consumes them); returns nonzero only on alloc/overflow. */
+ * when the resolver consumes them); returns nonzero only on alloc/overflow.
+ * One function per hosted OS; selected by the HostedProfile table below. */
+
+static int hosted_sysroot_layout_darwin(DriverHostedDirs* dirs,
+ KitTargetSpec target,
+ const char* sysroot) {
+ (void)target;
+ if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "usr/include") != 0 ||
+ driver_hosted_dirs_add_lib_join(dirs, sysroot, "usr/lib") != 0)
+ return 1;
+ return 0;
+}
+
+static int hosted_sysroot_layout_linux(DriverHostedDirs* dirs,
+ KitTargetSpec target,
+ const char* sysroot) {
+ const char* triple = hosted_linux_inc_triple_sub(target.arch);
+ if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "include") != 0) return 1;
+ if (triple && driver_hosted_dirs_add_inc_join(dirs, sysroot, triple) != 0)
+ return 1;
+ if (driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0) return 1;
+ return 0;
+}
+
+static int hosted_sysroot_layout_freebsd(DriverHostedDirs* dirs,
+ KitTargetSpec target,
+ const char* sysroot) {
+ (void)target;
+ if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "usr/include") != 0 ||
+ driver_hosted_dirs_add_lib_join(dirs, sysroot, "usr/lib") != 0 ||
+ driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0)
+ return 1;
+ return 0;
+}
+
+static int hosted_sysroot_layout_windows(DriverHostedDirs* dirs,
+ KitTargetSpec target,
+ const char* sysroot) {
+ (void)target;
+ if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "include") != 0 ||
+ driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0)
+ return 1;
+ return 0;
+}
+
+/* ---------------- hosted-profile registry ---------------- *
+ *
+ * One row per hosted (OS, object-format) pairing, mirroring the
+ * src/{arch,obj,abi}/registry.c tables. A new hosted OS is a new row: its
+ * crt/libc resolver and its --sysroot directory layout. The dispatcher and
+ * the sysroot expander both go through hosted_profile_for so the (os,obj)
+ * decision lives in exactly one place. */
+typedef int (*HostedResolveFn)(const DriverHostedRequest*,
+ const DriverHostedDirs*, DriverHostedPlan*);
+typedef int (*HostedSysrootLayoutFn)(DriverHostedDirs*, KitTargetSpec,
+ const char*);
+
+typedef struct HostedProfile {
+ KitOSKind os;
+ KitObjFmt obj;
+ HostedResolveFn resolve;
+ HostedSysrootLayoutFn sysroot_layout;
+} HostedProfile;
+
+static const HostedProfile hosted_profiles[] = {
+ {KIT_OS_MACOS, KIT_OBJ_MACHO, hosted_resolve_darwin,
+ hosted_sysroot_layout_darwin},
+ {KIT_OS_LINUX, KIT_OBJ_ELF, hosted_resolve_linux,
+ hosted_sysroot_layout_linux},
+ {KIT_OS_FREEBSD, KIT_OBJ_ELF, hosted_resolve_freebsd,
+ hosted_sysroot_layout_freebsd},
+ {KIT_OS_WINDOWS, KIT_OBJ_COFF, hosted_resolve_windows_mingw,
+ hosted_sysroot_layout_windows},
+};
+
+/* The crt/libc resolve dispatch keys on the full (os, obj) pairing -- a
+ * matching OS with a foreign object format is not a hosted profile. */
+static const HostedProfile* hosted_profile_for(KitTargetSpec target) {
+ size_t i;
+ for (i = 0; i < sizeof hosted_profiles / sizeof hosted_profiles[0]; ++i) {
+ if (hosted_profiles[i].os == target.os &&
+ hosted_profiles[i].obj == target.obj)
+ return &hosted_profiles[i];
+ }
+ return NULL;
+}
+
+/* The sysroot directory layout keys on OS alone (each hosted OS has a single
+ * canonical object format), matching the historical os-only expansion. */
+static const HostedProfile* hosted_profile_for_os(KitOSKind os) {
+ size_t i;
+ for (i = 0; i < sizeof hosted_profiles / sizeof hosted_profiles[0]; ++i) {
+ if (hosted_profiles[i].os == os) return &hosted_profiles[i];
+ }
+ return NULL;
+}
+
+/* Expand an explicit sysroot into the dir list via the target's profile.
+ * Unknown targets leave the dir list empty (the dispatcher emits the error). */
static int hosted_dirs_from_sysroot(DriverHostedDirs* dirs,
KitTargetSpec target, const char* sysroot) {
+ const HostedProfile* profile = hosted_profile_for_os(target.os);
dirs->root =
sysroot; /* a single explicit sysroot; -print-sysroot reports it */
- switch (target.os) {
- case KIT_OS_MACOS:
- if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "usr/include") != 0 ||
- driver_hosted_dirs_add_lib_join(dirs, sysroot, "usr/lib") != 0)
- return 1;
- return 0;
- case KIT_OS_LINUX: {
- const char* triple = hosted_linux_inc_triple_sub(target.arch);
- if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "include") != 0)
- return 1;
- if (triple && driver_hosted_dirs_add_inc_join(dirs, sysroot, triple) != 0)
- return 1;
- if (driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0) return 1;
- return 0;
- }
- case KIT_OS_FREEBSD:
- if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "usr/include") != 0 ||
- driver_hosted_dirs_add_lib_join(dirs, sysroot, "usr/lib") != 0 ||
- driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0)
- return 1;
- return 0;
- case KIT_OS_WINDOWS:
- if (driver_hosted_dirs_add_inc_join(dirs, sysroot, "include") != 0 ||
- driver_hosted_dirs_add_lib_join(dirs, sysroot, "lib") != 0)
- return 1;
- return 0;
- default:
- return 0; /* unsupported target; the dispatcher emits the error */
- }
+ if (!profile) return 0; /* unsupported target; the dispatcher emits the error */
+ return profile->sysroot_layout(dirs, target, sysroot);
}
/* ---------------- orchestration ---------------- */
@@ -642,6 +714,7 @@ int driver_hosted_resolve(const DriverHostedRequest* req,
DriverHostedPlan* out) {
DriverHostedPlan zero = {0};
DriverHostedDirs dirs;
+ const HostedProfile* profile;
int rc;
if (!req || !out || !req->env || !req->tool) return 1;
*out = zero;
@@ -649,16 +722,9 @@ int driver_hosted_resolve(const DriverHostedRequest* req,
driver_errf(req->tool, "out of memory");
return 1;
}
- if (req->target.os == KIT_OS_MACOS && req->target.obj == KIT_OBJ_MACHO) {
- rc = hosted_resolve_darwin(req, &dirs, out);
- } else if (req->target.os == KIT_OS_LINUX && req->target.obj == KIT_OBJ_ELF) {
- rc = hosted_resolve_linux(req, &dirs, out);
- } else if (req->target.os == KIT_OS_FREEBSD &&
- req->target.obj == KIT_OBJ_ELF) {
- rc = hosted_resolve_freebsd(req, &dirs, out);
- } else if (req->target.os == KIT_OS_WINDOWS &&
- req->target.obj == KIT_OBJ_COFF) {
- rc = hosted_resolve_windows_mingw(req, &dirs, out);
+ profile = hosted_profile_for(req->target);
+ if (profile) {
+ rc = profile->resolve(req, &dirs, out);
} else {
driver_errf(req->tool, "no hosted libc profile for target");
rc = 1;
diff --git a/driver/lib/target.c b/driver/lib/target.c
@@ -21,6 +21,53 @@ static int triple_tok_prefix(const char* s, size_t n, const char* lit) {
return n >= l && memcmp(s, lit, l) == 0;
}
+/* Recognize an architecture token, the single authority for the arch-name
+ * spellings the driver accepts. Writes arch + natural pointer size on a hit.
+ * Returns 0 on success, nonzero for an unrecognized token. Shared by the
+ * triple parser and the public driver_arch_from_name. */
+static int arch_from_tok(const char* s, size_t n, KitArchKind* arch_out,
+ uint8_t* ptr_size_out) {
+ KitArchKind arch;
+ uint8_t ptr_size;
+ if (triple_tok_eq(s, n, "x86_64") || triple_tok_eq(s, n, "amd64")) {
+ arch = KIT_ARCH_X86_64;
+ ptr_size = 8;
+ } else if (triple_tok_eq(s, n, "i386") || triple_tok_eq(s, n, "i486") ||
+ triple_tok_eq(s, n, "i586") || triple_tok_eq(s, n, "i686")) {
+ arch = KIT_ARCH_X86_32;
+ ptr_size = 4;
+ } else if (triple_tok_eq(s, n, "aarch64") || triple_tok_eq(s, n, "arm64")) {
+ arch = KIT_ARCH_ARM_64;
+ ptr_size = 8;
+ } else if (triple_tok_eq(s, n, "arm") || triple_tok_eq(s, n, "armv7")) {
+ arch = KIT_ARCH_ARM_32;
+ ptr_size = 4;
+ } else if (triple_tok_eq(s, n, "riscv64")) {
+ arch = KIT_ARCH_RV64;
+ ptr_size = 8;
+ } else if (triple_tok_eq(s, n, "riscv32")) {
+ arch = KIT_ARCH_RV32;
+ ptr_size = 4;
+ } else if (triple_tok_eq(s, n, "wasm32")) {
+ arch = KIT_ARCH_WASM;
+ ptr_size = 4;
+ } else if (triple_tok_eq(s, n, "wasm64")) {
+ arch = KIT_ARCH_WASM;
+ ptr_size = 8;
+ } else {
+ return 1;
+ }
+ if (arch_out) *arch_out = arch;
+ if (ptr_size_out) *ptr_size_out = ptr_size;
+ return 0;
+}
+
+int driver_arch_from_name(const char* name, KitArchKind* arch_out,
+ uint8_t* ptr_size_out) {
+ if (!name) return 1;
+ return arch_from_tok(name, kit_slice_cstr(name).len, arch_out, ptr_size_out);
+}
+
KitPic driver_default_pic(KitObjFmt obj, KitOSKind os) {
/* WASM has no PIC/PIE concept; freestanding targets have no dynamic
* loader to apply load-time relocations. Everything else is hosted and
@@ -37,6 +84,41 @@ int driver_link_pie(KitTargetSpec target, int explicit_pie, int shared,
return target.pic == KIT_PIC_PIE;
}
+const char* driver_default_exe_name(KitTargetSpec target) {
+ /* PE/COFF executables conventionally carry a `.exe` suffix; ELF/Mach-O
+ * default link output is the historical `a.out`. */
+ return target.os == KIT_OS_WINDOWS ? "a.exe" : "a.out";
+}
+
+void driver_default_obj_ext(KitTargetSpec target, const char** ext_out,
+ size_t* ext_len_out) {
+ /* Windows targets default to a `.obj` suffix; everyone else `.o`. Drivers
+ * accept both spellings as inputs, but tooling that scrapes default outputs
+ * expects the canonical platform extension. */
+ if (target.os == KIT_OS_WINDOWS) {
+ if (ext_out) *ext_out = ".obj";
+ if (ext_len_out) *ext_len_out = 4u;
+ } else {
+ if (ext_out) *ext_out = ".o";
+ if (ext_len_out) *ext_len_out = 2u;
+ }
+}
+
+int driver_target_needs_sysroot_libdir(KitTargetSpec target) {
+ /* Windows targets fold `<sysroot>/lib` into the library search path (the
+ * mingw import-library tree). The POSIX hosted profiles enumerate their
+ * libdirs through the hosted resolver instead. */
+ return target.os == KIT_OS_WINDOWS ? 1 : 0;
+}
+
+int driver_target_default_hosted_profile(KitTargetSpec target) {
+ /* Windows-COFF is the one target whose hosted libc profile is engaged by
+ * default (given a sysroot and no -nostdlib): the mingw/ucrt import
+ * libraries are mandatory to produce a runnable PE. Other targets stay
+ * freestanding unless the user opts in (-lc / explicit sysroot wiring). */
+ return target.os == KIT_OS_WINDOWS && target.obj == KIT_OBJ_COFF ? 1 : 0;
+}
+
static int target_features_grow(DriverTargetFeatures* tf) {
DriverEnv* env = tf->env;
uint32_t old_cap = tf->cap_features;
@@ -270,39 +352,7 @@ int driver_target_from_triple(const char* triple, KitTargetSpec* out) {
p = dash + 1;
}
- if (triple_tok_eq(parts[0], plen[0], "x86_64") ||
- triple_tok_eq(parts[0], plen[0], "amd64")) {
- t.arch = KIT_ARCH_X86_64;
- t.ptr_size = 8;
- } else if (triple_tok_eq(parts[0], plen[0], "i386") ||
- triple_tok_eq(parts[0], plen[0], "i486") ||
- triple_tok_eq(parts[0], plen[0], "i586") ||
- triple_tok_eq(parts[0], plen[0], "i686")) {
- t.arch = KIT_ARCH_X86_32;
- t.ptr_size = 4;
- } else if (triple_tok_eq(parts[0], plen[0], "aarch64") ||
- triple_tok_eq(parts[0], plen[0], "arm64")) {
- t.arch = KIT_ARCH_ARM_64;
- t.ptr_size = 8;
- } else if (triple_tok_eq(parts[0], plen[0], "arm") ||
- triple_tok_eq(parts[0], plen[0], "armv7")) {
- t.arch = KIT_ARCH_ARM_32;
- t.ptr_size = 4;
- } else if (triple_tok_eq(parts[0], plen[0], "riscv64")) {
- t.arch = KIT_ARCH_RV64;
- t.ptr_size = 8;
- } else if (triple_tok_eq(parts[0], plen[0], "riscv32")) {
- t.arch = KIT_ARCH_RV32;
- t.ptr_size = 4;
- } else if (triple_tok_eq(parts[0], plen[0], "wasm32")) {
- t.arch = KIT_ARCH_WASM;
- t.ptr_size = 4;
- } else if (triple_tok_eq(parts[0], plen[0], "wasm64")) {
- t.arch = KIT_ARCH_WASM;
- t.ptr_size = 8;
- } else {
- return 1;
- }
+ if (arch_from_tok(parts[0], plen[0], &t.arch, &t.ptr_size) != 0) return 1;
os_set = 0;
for (i = 1; i < np; ++i) {
diff --git a/lang/c/abi/c_abi.c b/lang/c/abi/c_abi.c
@@ -119,9 +119,16 @@ const Type* c_abi_uintptr_type(KitCompiler* a, Pool* p) {
}
const Type* c_abi_va_list_type(KitCompiler* a, Pool* p) {
- KitTargetSpec target = kit_compiler_target_spec(a);
- switch (target.arch) {
- case KIT_ARCH_X86_64: {
+ /* Key the frontend `__builtin_va_list` spelling on the abstract va_list
+ * shape the target ABI reports, not on a re-derived arch/os ladder. The
+ * record field definitions are unchanged; only the dispatch is abstract:
+ * SYSV_X64 -> the System V __va_list_tag register-save record;
+ * AAPCS64 -> the AArch64 __va_list register-save record;
+ * POINTER / OPAQUE -> a single pointer that walks the arg area.
+ * The old Apple-vs-AAPCS64 arm64 split disappears (Apple is POINTER), as
+ * does the x86_64/rv64 split (both follow the abstract kind). */
+ switch (kit_cg_target_va_list_kind(a)) {
+ case KIT_CG_VALIST_SYSV_X64: {
const Type* vp = type_ptr(p, type_void(p));
const Type* uit = type_prim(p, TY_UINT);
Sym name = kit_sym_intern(p->c, KIT_SLICE_LIT("__va_list_tag"));
@@ -143,36 +150,33 @@ const Type* c_abi_va_list_type(KitCompiler* a, Pool* p) {
.type = vp});
return type_record_end(p, b);
}
- case KIT_ARCH_ARM_64:
- if (target.os == KIT_OS_MACOS) {
- return type_ptr(p, type_prim(p, TY_CHAR));
- } else {
- const Type* vp = type_ptr(p, type_void(p));
- const Type* it = type_prim(p, TY_INT);
- Sym name = kit_sym_intern(p->c, KIT_SLICE_LIT("__va_list"));
- SrcLoc nl = {0, 0, 0};
- TagId tg = type_tag_new(p, TAG_STRUCT, name, nl);
- TypeRecordBuilder* b = type_record_begin(p, TY_STRUCT, tg, name);
- type_record_field(
- b, (Field){.name = kit_sym_intern(p->c, KIT_SLICE_LIT("__stack")),
- .type = vp});
- type_record_field(
- b, (Field){.name = kit_sym_intern(p->c, KIT_SLICE_LIT("__gr_top")),
- .type = vp});
- type_record_field(
- b, (Field){.name = kit_sym_intern(p->c, KIT_SLICE_LIT("__vr_top")),
- .type = vp});
- type_record_field(
- b, (Field){.name = kit_sym_intern(p->c, KIT_SLICE_LIT("__gr_offs")),
- .type = it});
- type_record_field(
- b, (Field){.name = kit_sym_intern(p->c, KIT_SLICE_LIT("__vr_offs")),
- .type = it});
- return type_record_end(p, b);
- }
- case KIT_ARCH_RV64:
- return type_ptr(p, type_void(p));
- default:
- return type_ptr(p, type_void(p));
+ case KIT_CG_VALIST_AAPCS64: {
+ const Type* vp = type_ptr(p, type_void(p));
+ const Type* it = type_prim(p, TY_INT);
+ Sym name = kit_sym_intern(p->c, KIT_SLICE_LIT("__va_list"));
+ SrcLoc nl = {0, 0, 0};
+ TagId tg = type_tag_new(p, TAG_STRUCT, name, nl);
+ TypeRecordBuilder* b = type_record_begin(p, TY_STRUCT, tg, name);
+ type_record_field(
+ b, (Field){.name = kit_sym_intern(p->c, KIT_SLICE_LIT("__stack")),
+ .type = vp});
+ type_record_field(
+ b, (Field){.name = kit_sym_intern(p->c, KIT_SLICE_LIT("__gr_top")),
+ .type = vp});
+ type_record_field(
+ b, (Field){.name = kit_sym_intern(p->c, KIT_SLICE_LIT("__vr_top")),
+ .type = vp});
+ type_record_field(
+ b, (Field){.name = kit_sym_intern(p->c, KIT_SLICE_LIT("__gr_offs")),
+ .type = it});
+ type_record_field(
+ b, (Field){.name = kit_sym_intern(p->c, KIT_SLICE_LIT("__vr_offs")),
+ .type = it});
+ return type_record_end(p, b);
+ }
+ case KIT_CG_VALIST_POINTER:
+ case KIT_CG_VALIST_OPAQUE:
+ break;
}
+ return type_ptr(p, type_void(p));
}
diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c
@@ -49,8 +49,10 @@ static const Type* ty_char16(Parser* p) {
static const Type* ty_char32(Parser* p) { return type_prim(p->pool, TY_UINT); }
static const Type* ty_wchar(Parser* p) {
+ /* sizeof(wchar_t) is a resolved data-model fact (2 on Windows, 4 else);
+ * key on the width rather than re-deriving from the OS identity. */
KitTargetSpec target = kit_compiler_target_spec(p->c);
- return target.os == KIT_OS_WINDOWS ? ty_char16(p) : ty_int(p);
+ return target.wchar_size == 2 ? ty_char16(p) : ty_int(p);
}
static int pointer_pointees_compatible(Parser* p, const Type* lhs,
@@ -1227,17 +1229,13 @@ static int parse_builtin_clear_cache_call(Parser* p, Sym name, SrcLoc loc) {
* __builtin___clear_cache is a no-op on those targets — matching GCC/Clang.
* Emitting the __clear_cache libcall there would reference an undefined
* symbol. Targets that need explicit coherency (ARM, RISC-V) keep the call.
- * The argument expressions are still evaluated for their side effects. */
- switch (kit_compiler_target_spec(p->c).arch) {
- case KIT_ARCH_WASM:
- case KIT_ARCH_X86_32:
- case KIT_ARCH_X86_64:
- pcg_drop(p);
- pcg_drop(p);
- pcg_push_int(p, 0, ty_int(p));
- return 1;
- default:
- break;
+ * The argument expressions are still evaluated for their side effects. The
+ * coherency fact is a backend capability, not an arch identity. */
+ if (kit_cg_target_backend_features(p->c) & KIT_CG_BACKEND_ICACHE_COHERENT) {
+ pcg_drop(p);
+ pcg_drop(p);
+ pcg_push_int(p, 0, ty_int(p));
+ return 1;
}
params[0] = void_ptr_ty;
diff --git a/lang/cpp/cpp_support.h b/lang/cpp/cpp_support.h
@@ -37,9 +37,11 @@ typedef struct Pool {
/* C data model for frontend-visible scalar spelling. kit currently uses LP64
* for 64-bit non-Windows targets, LLP64 for 64-bit Windows targets, and ILP32
- * for 32-bit targets. */
+ * for 32-bit targets. The distinction is exactly sizeof(long): 8 for LP64,
+ * 4 for LLP64/ILP32 — a resolved data-model fact carried on the spec, so we
+ * read it instead of re-deriving from ptr_size + OS identity. */
static inline int kit_target_uses_lp64(KitTargetSpec t) {
- return t.ptr_size == 8 && t.os != KIT_OS_WINDOWS;
+ return t.long_size == 8;
}
static inline Pool* c_pool_new(Compiler* c) {
@@ -88,16 +90,13 @@ _Noreturn static inline void compiler_panicv(Compiler* c, SrcLoc loc,
* target rather than an alias of `double`. RISC-V (LP64/LP64D) and
* non-Apple/non-Windows aarch64 follow the quad psABI; wasm32 matches
* clang/LLVM's wasm convention. x86 (80-bit x87, not modeled), Apple, and
- * Windows alias long double to double. Centralized so the preprocessor's
- * __LDBL_* / __SIZEOF_LONG_DOUBLE__ macros and the C type system's
- * long-double -> CG-builtin mapping cannot drift apart. */
+ * Windows alias long double to double. This is a resolved ABI fact
+ * (long_double_format) computed once in kit_target_new, so we read the
+ * spec field instead of re-deriving an arch/os ladder here. Centralized so
+ * the preprocessor's __LDBL_* / __SIZEOF_LONG_DOUBLE__ macros and the C type
+ * system's long-double -> CG-builtin mapping cannot drift apart. */
static inline int kit_target_long_double_is_binary128(KitTargetSpec t) {
- if (t.arch == KIT_ARCH_RV64) return 1;
- if (t.arch == KIT_ARCH_ARM_64 && t.os != KIT_OS_MACOS &&
- t.os != KIT_OS_WINDOWS)
- return 1;
- if (t.arch == KIT_ARCH_WASM) return 1;
- return 0;
+ return t.long_double_format == KIT_LDBL_BINARY128;
}
#endif
diff --git a/lang/cpp/pp/pp.c b/lang/cpp/pp/pp.c
@@ -338,6 +338,136 @@ static void pp_register_static_predefined(Pp* pp) {
pp_define(pp, "__ATOMIC_SEQ_CST", "5");
}
+/* OS-keyed predefined macros: the single place to extend per operating system.
+ * Each OS arm owns its full set of OS-specific predefines (including the
+ * arch-specific MSVC machine macros, which are an OS-flavor concern, not a
+ * data-model one). Add a new `case` to support another OS personality; the
+ * data-model / type-width macros stay in pp_register_target_predefined since
+ * they are OS-independent. */
+static void pp_register_os_predefined(Pp* pp, KitTargetSpec target) {
+ switch (target.os) {
+ case KIT_OS_WINDOWS:
+ /* Windows / mingw predefined macros. kit targets the mingw
+ * flavor (DWARF debug info, mingwex CRT) rather than MSVC, so we
+ * advertise __MINGW{32,64}__ and friends but never set _MSC_VER.
+ * Both _WIN32 and the legacy unprefixed WIN32 are defined; _WIN64
+ * is set on 64-bit targets only. The MSVC-compat machine macros
+ * (_M_X64 / _M_AMD64 / _M_ARM64) are useful for headers that gate
+ * on them but harmless to set everywhere — mingw's own headers
+ * tolerate them. */
+ pp_define(pp, "_WIN32", "1");
+ pp_define(pp, "WIN32", "1");
+ pp_define(pp, "__MINGW32__", "1");
+ if (target.ptr_size == 8) {
+ pp_define(pp, "_WIN64", "1");
+ pp_define(pp, "__MINGW64__", "1");
+ }
+ if (target.arch == KIT_ARCH_X86_64) {
+ pp_define(pp, "_M_X64", "100");
+ pp_define(pp, "_M_AMD64", "100");
+ } else if (target.arch == KIT_ARCH_ARM_64) {
+ pp_define(pp, "_M_ARM64", "1");
+ }
+ /* mingw's <vadefs.h> / many CRT headers gate __builtin_va_list /
+ * __gnuc_va_list on __GNUC__. kit implements the va_* builtins
+ * and __builtin_va_list with the GCC contract, so impersonating a
+ * conservative GCC vintage lets the mingw header tree compile.
+ * We pick 4.0 — old enough that no header expects GCC-specific
+ * extensions kit doesn't implement (e.g. transactional memory,
+ * GIMPLE plugins), but new enough to clear every __GNUC__ >= N
+ * gate we've seen in practice. */
+ pp_define(pp, "__GNUC__", "4");
+ pp_define(pp, "__GNUC_MINOR__", "0");
+ pp_define(pp, "__GNUC_PATCHLEVEL__", "0");
+ /* __has_builtin / __has_attribute / __has_include_next: clang/GCC
+ * preprocessor extensions. mingw's _mingw.h gates inline-asm
+ * intrinsic definitions on whether the compiler claims to have
+ * them as builtins (e.g. __debugbreak, __fastfail, __prefetch).
+ * kit doesn't model individual builtin lookups; claim "yes"
+ * uniformly so mingw skips its inline-asm fallbacks (which use
+ * intel/{$}-form asm syntax kit's parser doesn't accept). */
+ pp_define(pp, "__has_builtin(x)", "1");
+ pp_define(pp, "__has_feature(x)", "0");
+ pp_define(pp, "__has_attribute(x)", "0");
+ /* MSVC fixed-width integer types. mingw's corecrt.h uses these
+ * directly (e.g. `typedef unsigned __int64 size_t;`). Map to the
+ * C standard equivalents. */
+ pp_define(pp, "__int8", "char");
+ pp_define(pp, "__int16", "short");
+ pp_define(pp, "__int32", "int");
+ pp_define(pp, "__int64", "long long");
+ /* mingw's psdk_inc/intrin-impl.h emits an inline implementation
+ * for every MSVC intrinsic (_lrotl, _BitScanForward, ...) and
+ * gates them with __INTRINSIC_PROLOG, which uses ## to paste the
+ * intrinsic's name into a `defined(__INTRINSIC_DEFINED_<name>)`
+ * test. Once an intrinsic gets defined, a later re-invocation of
+ * the same gate macro hits a kit pp bug where a *defined*
+ * symbol referenced inside `defined()` gets expanded before the
+ * `defined` operator captures it. Predefining
+ * __INTRINSIC_ONLYSPECIAL flips the gate's second clause so
+ * none of the inline intrinsics are emitted (mingw expects this
+ * idiom for non-special builds; the linker pulls them from
+ * libmingwex/libmsvcrt instead). This sidesteps the pp bug
+ * entirely. */
+ pp_define(pp, "__INTRINSIC_ONLYSPECIAL", "1");
+ /* __declspec(...) is the MSVC syntax for attributes. mingw uses
+ * it in headers for dllimport/dllexport, alignment, noreturn,
+ * etc. kit's COFF linker routes externs through the IAT
+ * regardless of the dllimport hint and doesn't yet model
+ * dllexport via this attribute — so we erase it as a no-op
+ * macro. (Note: this is at the preprocessor layer; the parser
+ * still needs to handle the syntax if/when the macro is removed.)
+ */
+ pp_define(pp, "__declspec(x)", "");
+ /* GNU `__extension__` is a pedantic-quiet wrapper around
+ * non-standard constructs (statement exprs, anonymous structs).
+ * kit's parser is permissive about those already; the keyword
+ * has no effect on parsing, so we erase it. */
+ pp_define(pp, "__extension__", "");
+ /* __restrict / __restrict__: GCC-flavored alternates to the C99
+ * `restrict` keyword. kit parses `restrict` already; map the
+ * GCC spellings onto it. */
+ pp_define(pp, "__restrict", "restrict");
+ pp_define(pp, "__restrict__", "restrict");
+ pp_define(pp, "__volatile__", "volatile");
+ pp_define(pp, "__const__", "const");
+ pp_define(pp, "__signed__", "signed");
+ /* MSVC calling-convention attributes. On x86_64 they're no-ops
+ * (every function uses the Win64 ABI) and on ARM64 likewise; on
+ * i386 they actually mean something but kit doesn't target it.
+ * Defining them as empty macros lets mingw headers that say
+ * `void __cdecl foo(void)` parse correctly. Same posture mingw's
+ * own GCC takes: __MINGW_USYMBOL((__cdecl__)). */
+ /* MSVC calling-convention attributes — no-ops on Win64. kit
+ * pre-defines them empty *only when* mingw's headers don't
+ * themselves redefine them; we use the __MINGW_<x>_REDEFINE form
+ * via `#undef` first to play nicely with mingw's own
+ * redefinitions (mingw's _mingw.h does `#define __cdecl
+ * __attribute__((__cdecl__))` further down). Setting them empty
+ * here is safe because kit's parser will see the redefinition
+ * before any header uses them. */
+ pp_define(pp, "__cdecl", "");
+ pp_define(pp, "__stdcall", "");
+ pp_define(pp, "__fastcall", "");
+ pp_define(pp, "__thiscall", "");
+ pp_define(pp, "__vectorcall", "");
+ pp_define(pp, "_cdecl", "");
+ pp_define(pp, "_stdcall", "");
+ pp_define(pp, "_fastcall", "");
+ /* __forceinline / __inline / __w64: mingw's _mingw.h redefines
+ * them itself when __GNUC__ is set, so we leave them alone here
+ * to avoid a redefinition-with-different-replacement error. */
+ break;
+ case KIT_OS_LINUX:
+ case KIT_OS_MACOS:
+ case KIT_OS_FREEBSD:
+ case KIT_OS_FREESTANDING:
+ case KIT_OS_WASI:
+ /* No OS-specific predefines beyond the shared data-model set. */
+ break;
+ }
+}
+
/* Target-dependent predefined macros consumed by rt/include/stddef.h and
* rt/include/stdint.h. The set mirrors the subset of GCC/Clang's __*_TYPE__
* / __*_MAX__ namespace that those headers reference. We split only on
@@ -349,16 +479,18 @@ static void pp_register_target_predefined(Pp* pp) {
uint32_t narch_defs = kit_compiler_arch_predefines(pp->c, &arch_defs);
uint32_t i;
int ptr64 = (target.ptr_size == 8);
- int win = (target.os == KIT_OS_WINDOWS);
int lp64 = kit_target_uses_lp64(target);
- int wchar16 = win;
+ /* sizeof(wchar_t) is a resolved data-model fact carried on the spec. */
+ int wchar16 = (target.wchar_size == 2);
for (i = 0; i < narch_defs; ++i) {
pp_define(pp, arch_defs[i].name.s, arch_defs[i].body.s);
}
- pp_define(pp, "__USER_LABEL_PREFIX__",
- target.obj == KIT_OBJ_MACHO ? "_" : "");
+ /* __USER_LABEL_PREFIX__ is the C source-symbol prefix the object format
+ * prepends ("_" for Mach-O, "" else); read it from the CG target rather
+ * than re-deriving from the object-format identity. */
+ pp_define(pp, "__USER_LABEL_PREFIX__", kit_cg_target_c_label_prefix(pp->c));
/* Byte / type sizes. kit uses a single LP64 (or ILP32) model across
* every supported target: int=4, short=2, long-long=8, float=4, double=8,
@@ -381,118 +513,11 @@ static void pp_register_target_predefined(Pp* pp) {
pp_define(pp, "__SIZEOF_LONG_DOUBLE__",
kit_target_long_double_is_binary128(target) ? "16" : "8");
- /* Windows / mingw predefined macros. kit targets the mingw
- * flavor (DWARF debug info, mingwex CRT) rather than MSVC, so we
- * advertise __MINGW{32,64}__ and friends but never set _MSC_VER.
- * Both _WIN32 and the legacy unprefixed WIN32 are defined; _WIN64
- * is set on 64-bit targets only. The MSVC-compat machine macros
- * (_M_X64 / _M_AMD64 / _M_ARM64) are useful for headers that gate
- * on them but harmless to set everywhere — mingw's own headers
- * tolerate them. */
- if (target.os == KIT_OS_WINDOWS) {
- pp_define(pp, "_WIN32", "1");
- pp_define(pp, "WIN32", "1");
- pp_define(pp, "__MINGW32__", "1");
- if (target.ptr_size == 8) {
- pp_define(pp, "_WIN64", "1");
- pp_define(pp, "__MINGW64__", "1");
- }
- if (target.arch == KIT_ARCH_X86_64) {
- pp_define(pp, "_M_X64", "100");
- pp_define(pp, "_M_AMD64", "100");
- } else if (target.arch == KIT_ARCH_ARM_64) {
- pp_define(pp, "_M_ARM64", "1");
- }
- /* mingw's <vadefs.h> / many CRT headers gate __builtin_va_list /
- * __gnuc_va_list on __GNUC__. kit implements the va_* builtins
- * and __builtin_va_list with the GCC contract, so impersonating a
- * conservative GCC vintage lets the mingw header tree compile.
- * We pick 4.0 — old enough that no header expects GCC-specific
- * extensions kit doesn't implement (e.g. transactional memory,
- * GIMPLE plugins), but new enough to clear every __GNUC__ >= N
- * gate we've seen in practice. */
- pp_define(pp, "__GNUC__", "4");
- pp_define(pp, "__GNUC_MINOR__", "0");
- pp_define(pp, "__GNUC_PATCHLEVEL__", "0");
- /* __has_builtin / __has_attribute / __has_include_next: clang/GCC
- * preprocessor extensions. mingw's _mingw.h gates inline-asm
- * intrinsic definitions on whether the compiler claims to have
- * them as builtins (e.g. __debugbreak, __fastfail, __prefetch).
- * kit doesn't model individual builtin lookups; claim "yes"
- * uniformly so mingw skips its inline-asm fallbacks (which use
- * intel/{$}-form asm syntax kit's parser doesn't accept). */
- pp_define(pp, "__has_builtin(x)", "1");
- pp_define(pp, "__has_feature(x)", "0");
- pp_define(pp, "__has_attribute(x)", "0");
- /* MSVC fixed-width integer types. mingw's corecrt.h uses these
- * directly (e.g. `typedef unsigned __int64 size_t;`). Map to the
- * C standard equivalents. */
- pp_define(pp, "__int8", "char");
- pp_define(pp, "__int16", "short");
- pp_define(pp, "__int32", "int");
- pp_define(pp, "__int64", "long long");
- /* mingw's psdk_inc/intrin-impl.h emits an inline implementation
- * for every MSVC intrinsic (_lrotl, _BitScanForward, ...) and
- * gates them with __INTRINSIC_PROLOG, which uses ## to paste the
- * intrinsic's name into a `defined(__INTRINSIC_DEFINED_<name>)`
- * test. Once an intrinsic gets defined, a later re-invocation of
- * the same gate macro hits a kit pp bug where a *defined*
- * symbol referenced inside `defined()` gets expanded before the
- * `defined` operator captures it. Predefining
- * __INTRINSIC_ONLYSPECIAL flips the gate's second clause so
- * none of the inline intrinsics are emitted (mingw expects this
- * idiom for non-special builds; the linker pulls them from
- * libmingwex/libmsvcrt instead). This sidesteps the pp bug
- * entirely. */
- pp_define(pp, "__INTRINSIC_ONLYSPECIAL", "1");
- /* __declspec(...) is the MSVC syntax for attributes. mingw uses
- * it in headers for dllimport/dllexport, alignment, noreturn,
- * etc. kit's COFF linker routes externs through the IAT
- * regardless of the dllimport hint and doesn't yet model
- * dllexport via this attribute — so we erase it as a no-op
- * macro. (Note: this is at the preprocessor layer; the parser
- * still needs to handle the syntax if/when the macro is removed.)
- */
- pp_define(pp, "__declspec(x)", "");
- /* GNU `__extension__` is a pedantic-quiet wrapper around
- * non-standard constructs (statement exprs, anonymous structs).
- * kit's parser is permissive about those already; the keyword
- * has no effect on parsing, so we erase it. */
- pp_define(pp, "__extension__", "");
- /* __restrict / __restrict__: GCC-flavored alternates to the C99
- * `restrict` keyword. kit parses `restrict` already; map the
- * GCC spellings onto it. */
- pp_define(pp, "__restrict", "restrict");
- pp_define(pp, "__restrict__", "restrict");
- pp_define(pp, "__volatile__", "volatile");
- pp_define(pp, "__const__", "const");
- pp_define(pp, "__signed__", "signed");
- /* MSVC calling-convention attributes. On x86_64 they're no-ops
- * (every function uses the Win64 ABI) and on ARM64 likewise; on
- * i386 they actually mean something but kit doesn't target it.
- * Defining them as empty macros lets mingw headers that say
- * `void __cdecl foo(void)` parse correctly. Same posture mingw's
- * own GCC takes: __MINGW_USYMBOL((__cdecl__)). */
- /* MSVC calling-convention attributes — no-ops on Win64. kit
- * pre-defines them empty *only when* mingw's headers don't
- * themselves redefine them; we use the __MINGW_<x>_REDEFINE form
- * via `#undef` first to play nicely with mingw's own
- * redefinitions (mingw's _mingw.h does `#define __cdecl
- * __attribute__((__cdecl__))` further down). Setting them empty
- * here is safe because kit's parser will see the redefinition
- * before any header uses them. */
- pp_define(pp, "__cdecl", "");
- pp_define(pp, "__stdcall", "");
- pp_define(pp, "__fastcall", "");
- pp_define(pp, "__thiscall", "");
- pp_define(pp, "__vectorcall", "");
- pp_define(pp, "_cdecl", "");
- pp_define(pp, "_stdcall", "");
- pp_define(pp, "_fastcall", "");
- /* __forceinline / __inline / __w64: mingw's _mingw.h redefines
- * them itself when __GNUC__ is set, so we leave them alone here
- * to avoid a redefinition-with-different-replacement error. */
- }
+ /* OS-specific predefines (Windows/mingw + MSVC machine macros) live in one
+ * os-keyed table — pp_register_os_predefined — so adding an OS personality
+ * touches a single place. Emitted here, between the data-model size macros
+ * and the stddef.h type aliases, to preserve the predefined-macro ordering. */
+ pp_register_os_predefined(pp, target);
/* stddef.h base aliases */
if (lp64) {
diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c
@@ -2017,7 +2017,7 @@ static void aa_tls_addr_of(NativeTarget* t, NativeLoc dst, ObjSymId sym,
if (rd != 0) aa_emit32(mc, aa64_mov_reg(1, rd, 0));
return;
}
- if (t->c->target.os == KIT_OS_WINDOWS) {
+ if (obj_format_tls_model(t->c) == OBJ_TLS_WINDOWS_TEB) {
aa_tls_addr_of_win(t, dst, sym, addend);
return;
}
@@ -2589,7 +2589,7 @@ static const ABIArgInfo* aa_param_abi(NativeTarget* t, const ABIFuncInfo* abi,
* via fmov x,d. Every other ABI keeps the `...` FP args in v registers. */
((ABIArgPart*)scratch->parts)[0].cls =
(cg_type_is_float(t->c, desc->args[i].type) &&
- t->c->target.os != KIT_OS_WINDOWS)
+ !(abi && abi->vararg_fp_via_int))
? ABI_CLASS_FP
: ABI_CLASS_INT;
((ABIArgPart*)scratch->parts)[0].loc = ABI_LOC_REG;
diff --git a/src/arch/c_target/c_emit.c b/src/arch/c_target/c_emit.c
@@ -24,6 +24,7 @@
#include "core/heap.h"
#include "core/pool.h"
#include "core/slice.h"
+#include "obj/format.h"
#include "obj/obj.h"
/* Forward decls. */
@@ -911,12 +912,10 @@ const char* c_sym_name(CTarget* t, ObjSymId sym) {
Slice nm = pool_slice(t->c->global, os->name);
const char* s = nm.s;
size_t n = nm.len;
- /* Mach-O linker symbols are mangled with a leading underscore; the host
- * C compiler will re-add it on its own, so strip when re-emitting source. */
- if (t->c->target.obj == KIT_OBJ_MACHO && s && n > 0 && s[0] == '_') {
- s += 1;
- n -= 1;
- }
+ /* Linker symbols carry the active object format's C-mangle prefix (a leading
+ * underscore on Mach-O); the host C compiler will re-add it on its own, so
+ * strip when re-emitting source. */
+ obj_format_demangle_c(t->c, &s, &n);
/* Sanitize for C identifier rules: assemblers accept '.', '$', etc. in
* symbol names; C does not. Replace each illegal byte with '_' and prepend
* '_' if the first char isn't alpha/underscore. Local syms (SB_LOCAL) get
@@ -2510,7 +2509,8 @@ void c_emit_alias(CTarget* t, ObjSymId alias_sym, ObjSymId target_sym,
const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, type));
int is_func = fty && fty->kind == KIT_CG_TYPE_FUNC;
- if (t->c->target.obj != KIT_OBJ_MACHO) {
+ const ObjFormatImpl* fmt = obj_format_lookup(t->c->target.obj);
+ if (!fmt || !fmt->alias_via_thunk) {
/* Attribute form. Works for both function and object aliases on ELF
* and PE/COFF. */
c_emit_func_signature(t, &t->forwards, alias_name, type, NULL, 0);
@@ -3749,11 +3749,12 @@ static void c_emit_data_symbol(CTarget* t, ObjSymId id, const ObjSym* os) {
if (c_is_local_static_sym(t, id)) return;
if (os->kind == SK_FUNC || os->kind == SK_IFUNC) return;
if (os->kind == SK_SECTION || os->kind == SK_FILE) return;
- /* On Mach-O, obj_tls.c synthesizes `__tlv_bootstrap` as an SK_UNDEF
- * extern for the TLV descriptor's first field. The C target delegates all
- * TLS lowering to the host compiler via `_Thread_local`, so this
- * descriptor-time-only symbol has no place in the emitted source. */
- if (os->kind == SK_UNDEF && t->c->target.obj == KIT_OBJ_MACHO) {
+ /* On descriptor-model TLS targets (Mach-O), obj_tls.c synthesizes
+ * `__tlv_bootstrap` as an SK_UNDEF extern for the TLV descriptor's first
+ * field. The C target delegates all TLS lowering to the host compiler via
+ * `_Thread_local`, so this descriptor-time-only symbol has no place in the
+ * emitted source. */
+ if (os->kind == SK_UNDEF && obj_format_tls_via_descriptor(t->c)) {
const ObjBuilder* ob = t->obj;
if (id == obj_tlv_bootstrap_get(ob)) return;
}
@@ -3789,11 +3790,13 @@ static void c_emit_data_symbol(CTarget* t, ObjSymId id, const ObjSym* os) {
* undefined weak ref is `__attribute__((weak_import))`. On ELF/PE the
* existing `weak` attribute works as expected. */
if (os->bind == SB_WEAK) {
- if (t->c->target.obj == KIT_OBJ_MACHO) {
- cbuf_puts(b, "__attribute__((weak_import)) ");
- } else {
- cbuf_puts(b, "__attribute__((weak)) ");
- }
+ const ObjFormatImpl* fmt = obj_format_lookup(t->c->target.obj);
+ const char* weak_attr = (fmt && fmt->weak_undef_attr)
+ ? fmt->weak_undef_attr
+ : "weak";
+ cbuf_puts(b, "__attribute__((");
+ cbuf_puts(b, weak_attr);
+ cbuf_puts(b, ")) ");
}
if (os->vis == SV_HIDDEN) {
cbuf_puts(b, "__attribute__((visibility(\"hidden\"))) ");
@@ -3807,7 +3810,7 @@ static void c_emit_data_symbol(CTarget* t, ObjSymId id, const ObjSym* os) {
cbuf_puts(b, "[];\n");
return;
}
- if (is_tls && t->c->target.obj == KIT_OBJ_MACHO) {
+ if (is_tls && obj_format_tls_via_descriptor(t->c)) {
/* Mach-O splits TLS across two object-file symbols (see obj_tls.c): the
* user-visible sym is a 24-byte TLV descriptor in
* __DATA,__thread_vars; the actual initial bytes live in a synthesized
diff --git a/src/arch/mc.c b/src/arch/mc.c
@@ -757,12 +757,13 @@ void mc_emit_eh_frame(MCEmitter* m) {
mc->eh_frame_emitted = 1;
return;
}
- /* Freestanding (bare-metal, target.os == none): emit no .eh_frame. kit marks
- * .eh_frame SF_ALLOC so a hosted unwinder can consume it, but a bare-metal
- * link (e.g. riscv32-none-elf) has no unwinder and would otherwise have to
- * /DISCARD/ the orphaned ALLOC section. Drop CFI entirely there; hosted output
- * (linux/macos/windows/freebsd/wasi) is unaffected and byte-identical. */
- if (m->c->target.os == KIT_OS_FREESTANDING) {
+ /* Freestanding (bare-metal): emit no .eh_frame. kit marks .eh_frame
+ * SF_ALLOC so a hosted unwinder can consume it, but a bare-metal link
+ * (e.g. riscv32-none-elf) has no unwinder and would otherwise have to
+ * /DISCARD/ the orphaned ALLOC section. emits_eh_frame is 0 exactly for
+ * those freestanding targets and 1 for hosted output (linux/macos/windows/
+ * freebsd/wasi), which is unaffected and byte-identical. */
+ if (!m->c->target.emits_eh_frame) {
heap = m->c->ctx->heap;
for (i = 0; i < mc->nfdes; ++i) {
if (mc->fdes[i].directives) {
diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c
@@ -2909,7 +2909,7 @@ static void x64_tls_addr_of(NativeTarget* t, NativeLoc dst, ObjSymId sym,
u32 sec = mc->section_id;
u32 rd = loc_reg(dst);
u32 disp_pos;
- if (t->c->target.os == KIT_OS_WINDOWS) {
+ if (obj_format_tls_model(t->c) == OBJ_TLS_WINDOWS_TEB) {
x64_tls_addr_of_win64(t, dst, sym, addend);
return;
}
diff --git a/src/obj/format.h b/src/obj/format.h
@@ -177,6 +177,23 @@ typedef struct ObjFormatImpl {
* =0. Read by obj_format_supports_symbol_feature. */
u8 tls_symbol_features;
+ /* ---- C-source backend (c_target) emission spellings ----
+ *
+ * The portable C backend re-emits symbols as C source; a few constructs
+ * spell differently per object format. These fields let c_target read the
+ * spelling from the format vtable instead of branching on format identity.
+ *
+ * alias_via_thunk: how an aliased symbol is re-emitted. 0 = the format
+ * accepts `__attribute__((alias("target")))` directly (ELF / PE-COFF /
+ * Wasm). 1 = the format has no working alias attribute, so the backend
+ * emits a forwarding thunk function instead (Mach-O). */
+ u8 alias_via_thunk;
+ /* weak_undef_attr: the GCC/Clang attribute spelling that declares an
+ * *undefined* weak reference. "weak" works for ELF / PE-COFF / Wasm; on
+ * Mach-O the `weak` attribute requires a definition, so an undefined weak
+ * ref must use "weak_import". NULL is treated as "weak". */
+ const char* weak_undef_attr;
+
/* Inject a synthetic input object before symbol resolution. NULL when
* the format synthesizes nothing. */
ObjFormatSynthInputsFn synth_inputs;
diff --git a/src/obj/registry.c b/src/obj/registry.c
@@ -328,6 +328,8 @@ static const ObjFormatImpl obj_format_impl_wasm = {
.builds_own_static_got = 0,
.weak_undef_pulls_archive_member = 0,
.tls_symbol_features = 0,
+ .alias_via_thunk = 0,
+ .weak_undef_attr = "weak",
};
#endif
@@ -351,6 +353,8 @@ static const ObjFormatImpl obj_format_impl_elf = {
.builds_own_static_got = 0,
.weak_undef_pulls_archive_member = 0,
.tls_symbol_features = 1,
+ .alias_via_thunk = 0,
+ .weak_undef_attr = "weak",
.elf_arch = obj_elf_arch,
.elf_machine = obj_elf_machine,
};
@@ -374,6 +378,8 @@ static const ObjFormatImpl obj_format_impl_macho = {
.builds_own_static_got = 1,
.weak_undef_pulls_archive_member = 0,
.tls_symbol_features = 1,
+ .alias_via_thunk = 1,
+ .weak_undef_attr = "weak_import",
.macho_arch = obj_macho_arch,
.macho_cputype = obj_macho_cputype,
};
@@ -396,6 +402,8 @@ static const ObjFormatImpl obj_format_impl_coff = {
.builds_own_static_got = 0,
.weak_undef_pulls_archive_member = 1,
.tls_symbol_features = 0,
+ .alias_via_thunk = 0,
+ .weak_undef_attr = "weak",
/* synth_inputs: the COFF __CTOR_LIST__/__DTOR_LIST__ + __chkstk
* synthesizer (link_synth_coff_ctor_dtor_list) genuinely needs Linker
* internals (LinkInput append, link_arch_desc_for); it cannot be