kit

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

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:
Mdriver/cmd/build.c | 26++++++++------------------
Mdriver/cmd/cc.c | 29++++++++++++-----------------
Mdriver/cmd/dbg.c | 15++++++++++++---
Mdriver/cmd/emu.c | 18+++++++++++-------
Mdriver/driver.h | 29+++++++++++++++++++++++++++++
Mdriver/lib/hosted.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mdriver/lib/target.c | 116++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mlang/c/abi/c_abi.c | 72++++++++++++++++++++++++++++++++++++++----------------------------------
Mlang/c/parse/parse_expr.c | 22++++++++++------------
Mlang/cpp/cpp_support.h | 21++++++++++-----------
Mlang/cpp/pp/pp.c | 257+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/arch/aa64/native.c | 4++--
Msrc/arch/c_target/c_emit.c | 39+++++++++++++++++++++------------------
Msrc/arch/mc.c | 13+++++++------
Msrc/arch/x64/native.c | 2+-
Msrc/obj/format.h | 17+++++++++++++++++
Msrc/obj/registry.c | 8++++++++
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