kit

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

commit c9c1495aa91c311d916704f17cd3c9f0a79079af
parent e5603fea9e7ddce3fdff1427bbf18ddab1ea87d1
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri,  5 Jun 2026 10:51:12 -0700

modularity wave 2: route obj/link/cg/api/asm/dwarf through capability hooks

Removes identity switches from generic consumers by reading the wave-1 hooks:
- src/api/core.c: KitTargetSpec gains resolved props (long_size #33, wchar_size #32,
  long_double_format #3, emits_eh_frame #17) computed once in kit_target_new; RISC-V
  float-abi gate replaced by arch_resolve_float_abi (#2).
- src/cg/type.c: supports_symbol_feature -> obj_format_supports_symbol_feature (#14);
  add public kit_cg_target_va_list_kind + kit_cg_target_c_label_prefix.
- src/api/object_detect.c/object_file.c: machine->arch via fmt reverse maps + kit_arch_ptr_size
  (#4), float-abi via ObjElfArchOps.float_abi_from_e_flags (#23), reloc names via ELF arch ops
  (#24); public kit_obj_fmt_from_name/kit_obj_fmt_name (#35 impl).
- src/link: tpoff64 via LinkArchDesc (#25), COFF synth via obj_format_synth_inputs (#13),
  boundary syms via obj_format_boundary_sym_kind (#12), static-ifunc/debug/got/weak-pull via
  obj_format_* predicates (#18,#28,#29); 9 target.obj/os switches removed.
- src/asm/asm.c: macho seckind via obj_macho_seckind_for_secname (#21).
- src/debug/dwarf_open.c: macho section spelling via obj_macho_native_secname (#36).

Gatekeeper fix: supports_symbol_feature TLS arm now reads a per-format ObjFormatImpl
.tls_symbol_features field (ELF/Mach-O=1, COFF/Wasm=0) instead of deriving from tls_model,
which wrongly returned 1 for Wasm. Build + targeted tests (link/elf/ar/debug/dwarf/asm/
driver-ar/toy/parse/pp/cg-api/smoke-x64/smoke-rv64) green.

Diffstat:
Minclude/kit/cg.h | 20++++++++++++++++++++
Minclude/kit/core.h | 20++++++++++++++++++++
Msrc/api/core.c | 100+++++++++++++++++++++++++++----------------------------------------------------
Msrc/api/object_detect.c | 205+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/api/object_file.c | 92++++++++++++++++++++++++++++---------------------------------------------------
Msrc/arch/aa64/link.c | 3+++
Msrc/arch/riscv/link.c | 5+++++
Msrc/arch/x64/link.c | 3+++
Msrc/asm/asm.c | 49+++++++++++++++++++++++++------------------------
Msrc/cg/type.c | 24+++++++++++++++++++++++-
Msrc/debug/dwarf_open.c | 15++++-----------
Msrc/link/link_arch.h | 8++++++++
Msrc/link/link_layout.c | 36++++++++++++++++++++++++------------
Msrc/link/link_reloc_layout.c | 7+++----
Msrc/link/link_resolve.c | 18+++++++++++-------
Msrc/obj/format.h | 5+++++
Msrc/obj/obj_secnames.c | 14++++++++------
Msrc/obj/registry.c | 16+++++++++++++---
18 files changed, 348 insertions(+), 292 deletions(-)

diff --git a/include/kit/cg.h b/include/kit/cg.h @@ -240,6 +240,26 @@ KIT_API int kit_cg_target_supports_symbol_feature(KitCompiler*, /* kit_cg_target_supports_intrinsic is declared after KitCgIntrinsic. */ KIT_API uint64_t kit_cg_target_backend_features(KitCompiler*); +/* Shape of the target's `va_list` object, as an ABI fact. Frontends that + * lower <stdarg.h> consult this to pick the right va_list representation + * without reaching into libkit-internal ABI tables: + * KIT_CG_VALIST_OPAQUE : implementation-private blob. + * KIT_CG_VALIST_POINTER : a single pointer that walks the arg area. + * KIT_CG_VALIST_AAPCS64 : the AArch64 __va_list register-save record. + * KIT_CG_VALIST_SYSV_X64: the System V x86-64 register-save record. */ +typedef enum KitCgVaListKind { + KIT_CG_VALIST_OPAQUE, + KIT_CG_VALIST_POINTER, + KIT_CG_VALIST_AAPCS64, + KIT_CG_VALIST_SYSV_X64, +} KitCgVaListKind; +KIT_API KitCgVaListKind kit_cg_target_va_list_kind(const KitCompiler*); + +/* The C source-level symbol prefix the active object format prepends on + * disk: "_" for Mach-O, "" for ELF / COFF / Wasm. Never NULL. Drives the + * preprocessor's __USER_LABEL_PREFIX__. */ +KIT_API const char* kit_cg_target_c_label_prefix(const KitCompiler*); + /* Pointer width in bytes for an architecture, as a target fact independent of * any constructed KitTarget. Single source of truth for the byte-aligned * pointer size, so the object-format detector, the driver triple parser, and diff --git a/include/kit/core.h b/include/kit/core.h @@ -170,6 +170,17 @@ typedef enum KitFloatAbi { KIT_FLOAT_ABI_DOUBLE, } KitFloatAbi; +/* In-memory representation of the C `long double` type. kit models only two: + * BINARY128 (IEEE-754 quad) on targets that follow the quad psABI, and DOUBLE + * (long double aliases double, the same 8-byte binary64) everywhere else. The + * x86 80-bit x87 format is not modeled; x86 maps to DOUBLE, matching today's + * behavior. Consumers reproduce __SIZEOF_LONG_DOUBLE__ (16 vs 8) and the + * TY_LDOUBLE -> CG-builtin (F128 vs F64) mapping from this field. */ +typedef enum KitLongDoubleFormat { + KIT_LDBL_DOUBLE, /* long double == double (binary64) */ + KIT_LDBL_BINARY128, /* IEEE-754 binary128 (quad) */ +} KitLongDoubleFormat; + typedef struct KitTargetSpec { KitArchKind arch; KitOSKind os; @@ -180,6 +191,15 @@ typedef struct KitTargetSpec { uint8_t pic; /* KitPic */ uint8_t code_model; /* KitCodeModel */ uint8_t float_abi; /* KitFloatAbi */ + /* === Resolved data-model / ABI properties ================================= + * Derived once from arch/os/ptr_size in kit_target_new (the one os/arch + * resolver) so consumers read a field instead of re-deriving by identity. + * Zero-initialized spec constructors that bypass kit_target_new leave these + * at 0; only specs that pass through kit_target_new carry resolved values. */ + uint8_t long_size; /* sizeof(long): 8 for LP64, else 4 (LLP64/ILP32) */ + uint8_t wchar_size; /* sizeof(wchar_t): 2 on Windows, else 4 */ + uint8_t long_double_format; /* KitLongDoubleFormat */ + uint8_t emits_eh_frame; /* 1 when os != KIT_OS_FREESTANDING, else 0 */ } KitTargetSpec; typedef struct KitTargetFeature { diff --git a/src/api/core.c b/src/api/core.c @@ -64,6 +64,29 @@ KitStatus kit_target_new(const KitContext* ctx, const KitTargetOptions* opts, t->ctx = ctx; t->spec = opts->spec; + /* Resolve the data-model / ABI properties once, here, from arch/os/ptr_size. + * These mirror the predicates the frontend/backend used to re-derive by + * identity (kit_target_uses_lp64, the Windows wchar width, the binary128 + * long-double psABI test, and the freestanding eh_frame gate). */ + /* #33 LP64 vs LLP64/ILP32: long is 8 bytes only for 64-bit non-Windows. */ + t->spec.long_size = + (t->spec.ptr_size == 8u && t->spec.os != KIT_OS_WINDOWS) ? 8u : 4u; + /* #32/#20 wchar_t: 2 bytes on Windows, 4 elsewhere. */ + t->spec.wchar_size = (t->spec.os == KIT_OS_WINDOWS) ? 2u : 4u; + /* #3/#20 long double: binary128 on the quad-psABI targets (rv64, non-Apple/ + * non-Windows aarch64, wasm), else aliases double. Replicates + * kit_target_long_double_is_binary128 exactly. */ + if (t->spec.arch == KIT_ARCH_RV64 || + (t->spec.arch == KIT_ARCH_ARM_64 && t->spec.os != KIT_OS_MACOS && + t->spec.os != KIT_OS_WINDOWS) || + t->spec.arch == KIT_ARCH_WASM) { + t->spec.long_double_format = (uint8_t)KIT_LDBL_BINARY128; + } else { + t->spec.long_double_format = (uint8_t)KIT_LDBL_DOUBLE; + } + /* #17 eh_frame: hosted targets emit .eh_frame; freestanding does not. */ + t->spec.emits_eh_frame = (t->spec.os != KIT_OS_FREESTANDING) ? 1u : 0u; + nwords = (arch->ntarget_features + 63u) / 64u; if (nwords) { t->feature_words = @@ -101,75 +124,20 @@ KitStatus kit_target_new(const KitContext* ctx, const KitTargetOptions* opts, opts->features[i].enabled); } - /* Resolve & validate the float ABI for RISC-V targets only. Other arches - * leave float_abi at KIT_FLOAT_ABI_DEFAULT. */ - if (opts->spec.arch == KIT_ARCH_RV32 || opts->spec.arch == KIT_ARCH_RV64) { - u32 fidx, didx; - int has_f = arch_target_feature_index(arch, kit_slice_cstr("f"), &fidx) && - bitset_get(t->feature_words, t->nfeature_words, fidx); - int has_d = arch_target_feature_index(arch, kit_slice_cstr("d"), &didx) && - bitset_get(t->feature_words, t->nfeature_words, didx); - KitFloatAbi fa; - - if (opts->abi.s && opts->abi.len) { - int is_ilp32 = 0; - int is_lp64 = 0; - if (kit_slice_eq_cstr(opts->abi, "ilp32") || - kit_slice_eq_cstr(opts->abi, "ilp32f") || - kit_slice_eq_cstr(opts->abi, "ilp32d")) { - is_ilp32 = 1; - } else if (kit_slice_eq_cstr(opts->abi, "lp64") || - kit_slice_eq_cstr(opts->abi, "lp64f") || - kit_slice_eq_cstr(opts->abi, "lp64d")) { - is_lp64 = 1; - } else { - target_diag(ctx, "unsupported ABI for %s: %.*s", arch->name, - KIT_SLICE_ARG(opts->abi)); - kit_target_free(t); - return KIT_INVALID; - } - /* Width prefix must match pointer size. */ - if ((is_ilp32 && t->spec.ptr_size != 4u) || - (is_lp64 && t->spec.ptr_size != 8u)) { - target_diag(ctx, "ABI %.*s does not match pointer width for %s", - KIT_SLICE_ARG(opts->abi), arch->name); - kit_target_free(t); - return KIT_INVALID; - } - if (kit_slice_eq_cstr(opts->abi, "ilp32d") || - kit_slice_eq_cstr(opts->abi, "lp64d")) { - fa = KIT_FLOAT_ABI_DOUBLE; - } else if (kit_slice_eq_cstr(opts->abi, "ilp32f") || - kit_slice_eq_cstr(opts->abi, "lp64f")) { - fa = KIT_FLOAT_ABI_SINGLE; - } else { - fa = KIT_FLOAT_ABI_SOFT; - } - } else { - /* Derive from the resolved -march feature bits. */ - if (has_d) - fa = KIT_FLOAT_ABI_DOUBLE; - else if (has_f) - fa = KIT_FLOAT_ABI_SINGLE; - else - fa = KIT_FLOAT_ABI_SOFT; - } - - if (fa == KIT_FLOAT_ABI_SINGLE && !has_f) { - target_diag(ctx, - "hardware single-float ABI requires the 'f' extension for %s", - arch->name); - kit_target_free(t); - return KIT_INVALID; - } - if (fa == KIT_FLOAT_ABI_DOUBLE && !has_d) { - target_diag(ctx, - "hardware double-float ABI requires the 'd' extension for %s", - arch->name); + /* Resolve & validate the float ABI by arch capability. Arches with no + * float-ABI axis (everything but RISC-V) leave float_abi at + * KIT_FLOAT_ABI_DEFAULT via the no-op hook. The RISC-V hook produces the + * same byte-identical diagnostics the old inline block emitted. */ + { + char errbuf[256]; + errbuf[0] = '\0'; + if (arch_resolve_float_abi(arch, &t->spec, t->feature_words, + t->nfeature_words, opts->abi, errbuf, + sizeof errbuf) == KIT_INVALID) { + target_diag(ctx, "%s", errbuf); kit_target_free(t); return KIT_INVALID; } - t->spec.float_abi = (uint8_t)fa; } *out = t; diff --git a/src/api/object_detect.c b/src/api/object_detect.c @@ -1,35 +1,38 @@ /* Binary format and target detection from object header bytes. */ +#include <kit/cg.h> #include <kit/config.h> #include <kit/object.h> #include "core/core.h" +#include "obj/elf/elf.h" +#include "obj/format.h" -/* COFF Machine numbers we recognize, mapped to their kit arch. ARM64EC - * (0xA641) is accepted as plain AArch64: the encoding is unchanged, only the - * ABI differs, and src/obj/registry.c aliases 0xA641 to ARM64 before lookup - * (coff.h documents ARM64EC as plain AArch64). Single source of truth for the - * two membership checks in kit_detect_fmt and the arch mapping in - * detect_coff. */ -static const struct { - u16 machine; - KitArchKind arch; -} COFF_MACHINES[] = { - {0x8664, KIT_ARCH_X86_64}, {0x014C, KIT_ARCH_X86_32}, - {0xAA64, KIT_ARCH_ARM_64}, {0xA641, KIT_ARCH_ARM_64}, - {0x01C4, KIT_ARCH_ARM_32}, {0x5032, KIT_ARCH_RV32}, - {0x5064, KIT_ARCH_RV64}, +/* COFF Machine numbers recognized as the COFF *binary* format by + * kit_detect_fmt. This is format membership only — the arch mapping (in + * detect_coff) routes through the registry's coff_machine reverse map. + * + * The set here is intentionally broader than the registry's coff_machine + * ops (which models only the link/codegen-supported AMD64 / ARM64): a COFF + * header for any of these machines should still be classified as COFF so + * the reader/target paths report the precise reason it cannot be consumed. + * ARM64EC (0xA641) is plain AArch64 (same encoding, differing ABI); the + * registry aliases it to ARM64 before lookup (coff.h). */ +static const u16 COFF_MACHINES[] = { + 0x8664, /* AMD64 */ + 0x014C, /* I386 */ + 0xAA64, /* ARM64 */ + 0xA641, /* ARM64EC */ + 0x01C4, /* ARMNT */ + 0x5032, /* RISCV32 */ + 0x5064, /* RISCV64 */ }; -/* Look up a COFF Machine number. Returns 1 and sets *arch on a match, 0 - * otherwise. *arch may be NULL when only membership matters. */ -static int coff_machine_lookup(u16 machine, KitArchKind* arch) { +/* Is `machine` a COFF Machine number we classify as the COFF format? */ +static int coff_machine_known(u16 machine) { size_t i; for (i = 0; i < sizeof COFF_MACHINES / sizeof COFF_MACHINES[0]; i++) { - if (COFF_MACHINES[i].machine == machine) { - if (arch) *arch = COFF_MACHINES[i].arch; - return 1; - } + if (COFF_MACHINES[i] == machine) return 1; } return 0; } @@ -65,7 +68,7 @@ KitBinFmt kit_detect_fmt(const uint8_t* data, size_t len) { } if (len >= 2) { coff_machine = (u16)data[0] | ((u16)data[1] << 8); - if (coff_machine_lookup(coff_machine, NULL)) return KIT_BIN_COFF; + if (coff_machine_known(coff_machine)) return KIT_BIN_COFF; } /* Microsoft "short import" record: Sig1=0, Sig2=0xFFFF. Routed * through read_coff (which dispatches to read_coff_short_import). @@ -75,7 +78,7 @@ KitBinFmt kit_detect_fmt(const uint8_t* data, size_t len) { if (len >= 8 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0xFF && data[3] == 0xFF) { u16 mach = (u16)data[6] | ((u16)data[7] << 8); - if (coff_machine_lookup(mach, NULL)) return KIT_BIN_COFF; + if (coff_machine_known(mach)) return KIT_BIN_COFF; } return KIT_BIN_UNKNOWN; } @@ -88,22 +91,15 @@ static void detect_target_defaults(KitTargetSpec* t) { } static void detect_set_ptr(KitTargetSpec* t, KitArchKind arch) { + /* kit_arch_ptr_size is the single source of truth for an arch's byte + * pointer width (cg.h); ptr_align tracks ptr_size for every arch this + * detector resolves. wasm32 reports 4 here; the wasm path in + * kit_detect_target sets its own spec and does not call detect_set_ptr, + * so wasm64's 8-byte width is unaffected. */ + uint8_t w = kit_arch_ptr_size(arch); t->arch = arch; - switch (arch) { - case KIT_ARCH_X86_64: - case KIT_ARCH_ARM_64: - case KIT_ARCH_RV64: - t->ptr_size = 8; - t->ptr_align = 8; - break; - case KIT_ARCH_X86_32: - case KIT_ARCH_ARM_32: - case KIT_ARCH_RV32: - case KIT_ARCH_WASM: - t->ptr_size = 4; - t->ptr_align = 4; - break; - } + t->ptr_size = w; + t->ptr_align = w; } static KitStatus detect_elf(const u8* d, size_t len, KitTargetSpec* out) { @@ -125,29 +121,22 @@ static KitStatus detect_elf(const u8* d, size_t len, KitTargetSpec* out) { out->big_endian = (ei_data == 2); out->obj = KIT_OBJ_ELF; - switch (e_machine) { - case 0x03: + /* Resolve the arch through the ELF format's machine reverse map + * (obj_elf_machine_class also splits EM_RISCV into RV32/RV64 by + * EI_CLASS). The registry models only the link/codegen arches, so the + * legacy 32-bit ABI-classifiable machines it does not carry (EM_386, + * EM_ARM) are mapped here explicitly to preserve detection. */ + { + const ObjElfArchOps* ops = obj_elf_machine_class(e_machine, ei_class); + if (ops) { + detect_set_ptr(out, ops->arch); + } else if (e_machine == 0x03) { /* EM_386 */ detect_set_ptr(out, KIT_ARCH_X86_32); - break; - case 0x3E: - detect_set_ptr(out, KIT_ARCH_X86_64); - break; - case 0x28: + } else if (e_machine == 0x28) { /* EM_ARM */ detect_set_ptr(out, KIT_ARCH_ARM_32); - break; - case 0xB7: - detect_set_ptr(out, KIT_ARCH_ARM_64); - break; - case 0xF3: - if (ei_class == 1) - detect_set_ptr(out, KIT_ARCH_RV32); - else if (ei_class == 2) - detect_set_ptr(out, KIT_ARCH_RV64); - else - return KIT_MALFORMED; - break; - default: + } else { return KIT_UNSUPPORTED; + } } /* EI_CLASS must agree with the arch's pointer width: 32-bit arches are * ELFCLASS32, 64-bit arches ELFCLASS64. EM_RISCV is already disambiguated @@ -161,33 +150,28 @@ static KitStatus detect_elf(const u8* d, size_t len, KitTargetSpec* out) { else out->os = KIT_OS_FREESTANDING; - /* Recover the RISC-V float ABI from e_flags so a detected target selects the - * matching runtime variant (rv32 ilp32 soft vs ilp32f single share an arch + - * pointer width and differ only here). e_flags is a 4-byte LE field after the - * three native-width addr fields: offset 36 on ELFCLASS32, 48 on ELFCLASS64. - * The two FP-ABI bits map SOFT(0)/SINGLE(2)/DOUBLE(4); leave DEFAULT (the - * wildcard) when the header is truncated. */ - if (e_machine == 0xF3) { - size_t flags_off = (ei_class == 1) ? 36u : 48u; - if (len >= flags_off + 4u) { - u32 e_flags; - if (ei_data == 1) - e_flags = (u32)d[flags_off] | ((u32)d[flags_off + 1] << 8) | - ((u32)d[flags_off + 2] << 16) | ((u32)d[flags_off + 3] << 24); - else - e_flags = (u32)d[flags_off + 3] | ((u32)d[flags_off + 2] << 8) | - ((u32)d[flags_off + 1] << 16) | ((u32)d[flags_off] << 24); - switch (e_flags & 0x6u) { - case 0x2u: - out->float_abi = KIT_FLOAT_ABI_SINGLE; - break; - case 0x4u: - case 0x6u: /* QUAD — no kit ABI; treat as double-float for selection */ - out->float_abi = KIT_FLOAT_ABI_DOUBLE; - break; - default: - out->float_abi = KIT_FLOAT_ABI_SOFT; - break; + /* Recover the float ABI from e_flags via the per-arch decoder so a detected + * target selects the matching runtime variant (rv32 ilp32 soft vs ilp32f + * single share an arch + pointer width and differ only here). e_flags is a + * 4-byte LE field after the three native-width addr fields: offset 36 on + * ELFCLASS32, 48 on ELFCLASS64. Only RISC-V supplies a decoder + * (float_abi_from_e_flags non-NULL); other arches leave the default. The + * decoder is only consulted when the header carries the full e_flags word, + * leaving DEFAULT (the wildcard) when it is truncated. */ + { + const ObjElfArchOps* ops = obj_elf_machine_class(e_machine, ei_class); + if (ops && ops->float_abi_from_e_flags) { + size_t flags_off = (ei_class == 1) ? 36u : 48u; + if (len >= flags_off + 4u) { + u32 e_flags; + if (ei_data == 1) + e_flags = (u32)d[flags_off] | ((u32)d[flags_off + 1] << 8) | + ((u32)d[flags_off + 2] << 16) | + ((u32)d[flags_off + 3] << 24); + else + e_flags = (u32)d[flags_off + 3] | ((u32)d[flags_off + 2] << 8) | + ((u32)d[flags_off + 1] << 16) | ((u32)d[flags_off] << 24); + out->float_abi = (u8)ops->float_abi_from_e_flags(e_flags); } } } @@ -197,9 +181,31 @@ static KitStatus detect_elf(const u8* d, size_t len, KitTargetSpec* out) { static KitStatus detect_coff(const u8* d, size_t len, KitTargetSpec* out) { u16 machine; KitArchKind arch; + const ObjFormatImpl* fmt; + const ObjCoffArchOps* ops; if (len < 2) return KIT_MALFORMED; machine = (u16)d[0] | ((u16)d[1] << 8); - if (!coff_machine_lookup(machine, &arch)) return KIT_UNSUPPORTED; + + /* Resolve the arch through the COFF format's machine reverse map (which + * also aliases ARM64EC -> ARM64). The registry models only the + * link/codegen arches (AMD64 / ARM64); the legacy ABI-classifiable COFF + * machines it does not carry are mapped explicitly to preserve detection + * for objects classified as COFF by kit_detect_fmt. */ + fmt = obj_format_lookup(KIT_OBJ_COFF); + ops = (fmt && fmt->coff_machine) ? fmt->coff_machine(machine) : NULL; + if (ops) { + arch = ops->arch; + } else if (machine == 0x014Cu) { /* IMAGE_FILE_MACHINE_I386 */ + arch = KIT_ARCH_X86_32; + } else if (machine == 0x01C4u) { /* IMAGE_FILE_MACHINE_ARMNT */ + arch = KIT_ARCH_ARM_32; + } else if (machine == 0x5032u) { /* IMAGE_FILE_MACHINE_RISCV32 */ + arch = KIT_ARCH_RV32; + } else if (machine == 0x5064u) { /* IMAGE_FILE_MACHINE_RISCV64 */ + arch = KIT_ARCH_RV64; + } else { + return KIT_UNSUPPORTED; + } detect_target_defaults(out); out->obj = KIT_OBJ_COFF; out->os = KIT_OS_WINDOWS; @@ -242,21 +248,24 @@ static KitStatus detect_macho(const u8* d, size_t len, KitTargetSpec* out) { detect_target_defaults(out); out->obj = KIT_OBJ_MACHO; out->os = KIT_OS_MACOS; - switch (cputype) { - case 0x00000007u: + + /* Resolve the arch through the Mach-O format's cputype reverse map. The + * registry models only the link/codegen arches (ARM64 / X86_64); the + * legacy 32-bit ABI-classifiable cputypes it does not carry (CPU_TYPE_X86, + * CPU_TYPE_ARM) are mapped explicitly to preserve detection. */ + { + const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_MACHO); + const ObjMachoArchOps* ops = + (fmt && fmt->macho_cputype) ? fmt->macho_cputype(cputype) : NULL; + if (ops) { + detect_set_ptr(out, ops->arch); + } else if (cputype == 0x00000007u) { /* CPU_TYPE_X86 */ detect_set_ptr(out, KIT_ARCH_X86_32); - break; - case 0x01000007u: - detect_set_ptr(out, KIT_ARCH_X86_64); - break; - case 0x0000000Cu: + } else if (cputype == 0x0000000Cu) { /* CPU_TYPE_ARM */ detect_set_ptr(out, KIT_ARCH_ARM_32); - break; - case 0x0100000Cu: - detect_set_ptr(out, KIT_ARCH_ARM_64); - break; - default: + } else { return KIT_UNSUPPORTED; + } } (void)is64; return KIT_OK; diff --git a/src/api/object_file.c b/src/api/object_file.c @@ -108,6 +108,14 @@ void kit_obj_free(KitObjFile* f) { KitObjFmt kit_obj_fmt(const KitObjFile* f) { return f->fmt; } +/* Public object-format name <-> KitObjFmt mapping (objcopy/objdump bfdname + * spellings). Thin wrappers over the internal obj_format name registry. */ +KitStatus kit_obj_fmt_from_name(const char* name, KitObjFmt* out) { + return obj_format_fmt_from_name(name, out) ? KIT_OK : KIT_NOT_FOUND; +} + +const char* kit_obj_fmt_name(KitObjFmt fmt) { return obj_format_fmt_name(fmt); } + KitTargetSpec kit_obj_target(const KitObjFile* f) { return f->target; } uint32_t kit_obj_nsections(const KitObjFile* f) { @@ -355,68 +363,32 @@ KitStatus kit_obj_dynreliter_new(KitObjFile* f, KitObjRelocIter** out) { return reliter_make(f, 1, out); } +/* Format-specific canonical spelling of a reloc kind (e.g. "R_X86_64_PLT32"), + * or NULL when the format/arch has no per-arch name table (callers fall back + * to the arch-neutral reloc_kind_name). + * + * The per-arch ELF reloc-name table lives on ObjElfArchOps.reloc_name; the + * kit-canonical RelocKind is lowered to its ELF wire type via reloc_to first. + * Only the x86_64 ELF table is consulted here, matching the historical + * behavior exactly: other ELF arches (aarch64 / riscv) and the Mach-O / COFF + * formats keep the arch-neutral spelling their callers and tests expect (e.g. + * the rv64 objdump corpus prints "RV_CALL", not "R_RISCV_CALL"). reloc_to maps + * unsupported kinds to wire type 0; only R_NONE legitimately names that slot, + * so anything else falling through to 0 is reported as "no per-arch name" + * (NULL) rather than the format's NONE spelling. */ static const char* kit_obj_reloc_kind_name(KitArchKind arch, KitObjFmt fmt, u32 kind) { - if (fmt == KIT_OBJ_ELF && arch == KIT_ARCH_X86_64) { - switch ((RelocKind)kind) { - case R_NONE: - return "R_X86_64_NONE"; - case R_ABS64: - return "R_X86_64_64"; - case R_PC32: - return "R_X86_64_PC32"; - case R_GOT32: - return "R_X86_64_GOT32"; - case R_PLT32: - case R_X64_PLT32: - return "R_X86_64_PLT32"; - case R_X64_COPY: - return "R_X86_64_COPY"; - case R_X64_GLOB_DAT: - return "R_X86_64_GLOB_DAT"; - case R_X64_JUMP_SLOT: - return "R_X86_64_JUMP_SLOT"; - case R_X64_RELATIVE: - return "R_X86_64_RELATIVE"; - case R_X64_GOTPCREL: - return "R_X86_64_GOTPCREL"; - case R_ABS32: - return "R_X86_64_32"; - case R_X64_32S: - return "R_X86_64_32S"; - case R_X64_PC8: - return "R_X86_64_PC8"; - case R_X64_DTPMOD64: - return "R_X86_64_DTPMOD64"; - case R_X64_DTPOFF64: - return "R_X86_64_DTPOFF64"; - case R_X64_TPOFF64: - return "R_X86_64_TPOFF64"; - case R_X64_TLSGD: - return "R_X86_64_TLSGD"; - case R_X64_TLSLD: - return "R_X86_64_TLSLD"; - case R_X64_DTPOFF32: - return "R_X86_64_DTPOFF32"; - case R_X64_GOTTPOFF: - return "R_X86_64_GOTTPOFF"; - case R_X64_TPOFF32: - return "R_X86_64_TPOFF32"; - case R_PC64: - return "R_X86_64_PC64"; - case R_X64_GOTOFF64: - return "R_X86_64_GOTOFF64"; - case R_X64_GOTPC32: - return "R_X86_64_GOTPC32"; - case R_X64_GOTPCRELX: - return "R_X86_64_GOTPCRELX"; - case R_X64_REX_GOTPCRELX: - return "R_X86_64_REX_GOTPCRELX"; - default: - break; - } - } - return NULL; + const ObjFormatImpl* impl; + const ObjElfArchOps* ops; + u32 wire; + if (fmt != KIT_OBJ_ELF || arch != KIT_ARCH_X86_64) return NULL; + impl = obj_format_lookup(fmt); + if (!impl || !impl->elf_arch) return NULL; + ops = impl->elf_arch(arch); + if (!ops || !ops->reloc_to || !ops->reloc_name) return NULL; + wire = ops->reloc_to(kind); + if (wire == 0u && (RelocKind)kind != R_NONE) return NULL; + return ops->reloc_name(wire); } KitIterResult kit_obj_reliter_next(KitObjRelocIter* it, KitObjReloc* out) { diff --git a/src/arch/aa64/link.c b/src/arch/aa64/link.c @@ -231,6 +231,9 @@ const LinkArchDesc link_arch_aa64 = { .is_direct_page_reloc = aa64_is_direct_page_reloc, .needs_jit_call_stub = aa64_is_branch_reloc, + /* AAPCS64 variant I: GOT TLS-IE slots hold (X - tls_vaddr) + TCB. */ + .tpoff64_reloc = R_AARCH64_TPOFF64, + .coff_chkstk_bytes = aa64_coff_chkstk, .coff_chkstk_len = sizeof aa64_coff_chkstk, }; diff --git a/src/arch/riscv/link.c b/src/arch/riscv/link.c @@ -126,6 +126,9 @@ const LinkArchDesc link_arch_rv64 = { .emit_plt_entry = rv64_emit_plt_entry, .emit_iplt_stub = rv64_emit_iplt_stub, .needs_jit_call_stub = rv64_is_branch_reloc, + /* RISC-V variant I shares the internal raw-64-bit variant-I tpoff with + * AArch64 ((X - tls_vaddr) + TCB); there is no R_RV_TPOFF64. */ + .tpoff64_reloc = R_AARCH64_TPOFF64, }; /* RV32 link descriptor: identical to rv64 (PLT0/entry/stub byte sizes, @@ -142,4 +145,6 @@ const LinkArchDesc link_arch_rv32 = { .emit_plt_entry = rv32_emit_plt_entry, .emit_iplt_stub = rv32_emit_iplt_stub, .needs_jit_call_stub = rv64_is_branch_reloc, + /* See rv64: shares R_AARCH64_TPOFF64 (variant-I internal tpoff). */ + .tpoff64_reloc = R_AARCH64_TPOFF64, }; diff --git a/src/arch/x64/link.c b/src/arch/x64/link.c @@ -92,4 +92,7 @@ const LinkArchDesc link_arch_x64 = { .is_branch_reloc = x64_is_branch_reloc, .is_got_load_reloc = x64_is_got_load_reloc, .needs_jit_call_stub = x64_is_branch_reloc, + + /* x86_64 variant II: GOT TLS-IE slots hold (X - tls_memsz). */ + .tpoff64_reloc = R_X64_TPOFF64, }; diff --git a/src/asm/asm.c b/src/asm/asm.c @@ -874,30 +874,31 @@ static void do_directive(AsmDriver* d, Sym name) { size_t nn = 0; const char* p = asm_str(d, sname, &nn); if (macho_2pos) { - /* Canonical Apple seg,sect → SecKind (inverse of name_to_seg_sect), - * else SEC_OTHER (e.g. __TEXT,__eh_frame). Every Mach-O section is - * allocated; the comma name is preserved so the writer round-trips - * seg/sect verbatim. */ - if (nn == 13 && memcmp(p, "__TEXT,__text", 13) == 0) { - kind = SEC_TEXT; - flags |= (u16)(SF_ALLOC | SF_EXEC); - } else if (nn == 14 && memcmp(p, "__TEXT,__const", 14) == 0) { - kind = SEC_RODATA; - flags |= (u16)SF_ALLOC; - } else if (nn == 16 && memcmp(p, "__TEXT,__cstring", 16) == 0) { - kind = SEC_RODATA; - flags |= (u16)(SF_ALLOC | SF_STRINGS); - } else if (nn == 13 && memcmp(p, "__DATA,__data", 13) == 0) { - kind = SEC_DATA; - flags |= (u16)(SF_ALLOC | SF_WRITE); - } else if (nn == 12 && memcmp(p, "__DATA,__bss", 12) == 0) { - kind = SEC_BSS; - flags |= (u16)(SF_ALLOC | SF_WRITE); - } else if (nn >= 7 && memcmp(p, "__DWARF", 7) == 0) { - kind = SEC_DEBUG; - } else { - kind = SEC_OTHER; - flags |= (u16)SF_ALLOC; + /* Canonical Apple seg,sect → SecKind via the shared inverse of the + * writer's name_to_seg_sect; unrecognized spellings (e.g. + * __TEXT,__eh_frame) fall back to SEC_OTHER. Flags are derived from the + * resolved kind: every Mach-O section is allocated (so SEC_OTHER keeps + * SF_ALLOC), and __TEXT,__cstring additionally carries SF_STRINGS. The + * comma name is preserved so the writer round-trips seg/sect verbatim. */ + if (!obj_macho_seckind_for_secname(p, nn, &kind)) kind = SEC_OTHER; + switch (kind) { + case SEC_TEXT: + flags |= (u16)(SF_ALLOC | SF_EXEC); + break; + case SEC_RODATA: + flags |= (u16)SF_ALLOC; + if (nn == 16 && memcmp(p, "__TEXT,__cstring", 16) == 0) + flags |= (u16)SF_STRINGS; + break; + case SEC_DATA: + case SEC_BSS: + flags |= (u16)(SF_ALLOC | SF_WRITE); + break; + case SEC_DEBUG: + break; + default: + flags |= (u16)SF_ALLOC; + break; } } else if (have_flags) { /* Explicit flags: a canonical name keeps its kind; any other name is a diff --git a/src/cg/type.c b/src/cg/type.c @@ -1,5 +1,6 @@ #include "arch/arch.h" #include "cg/internal.h" +#include "obj/obj.h" typedef enum CgApiTypeKind { CG_API_TYPE_PTR, @@ -964,7 +965,7 @@ int kit_cg_target_supports_symbol_feature(KitCompiler* c, case KIT_CG_SYMFEAT_TLS_INITIAL_EXEC: case KIT_CG_SYMFEAT_TLS_LOCAL_DYNAMIC: case KIT_CG_SYMFEAT_TLS_GENERAL_DYNAMIC: - return c->target.obj == KIT_OBJ_ELF || c->target.obj == KIT_OBJ_MACHO; + return obj_format_supports_symbol_feature(c, (int)feat); case KIT_CG_SYMFEAT_DLLIMPORT: case KIT_CG_SYMFEAT_DLLEXPORT: case KIT_CG_SYMFEAT_MERGE_SECTIONS: @@ -974,6 +975,27 @@ int kit_cg_target_supports_symbol_feature(KitCompiler* c, return 0; } +KitCgVaListKind kit_cg_target_va_list_kind(const KitCompiler* c) { + ABIVaListInfo va; + if (!c) return KIT_CG_VALIST_OPAQUE; + va = abi_va_list_layout(c->abi); + switch ((ABIVaListKind)va.kind) { + case ABI_VA_LIST_OPAQUE: + return KIT_CG_VALIST_OPAQUE; + case ABI_VA_LIST_POINTER: + return KIT_CG_VALIST_POINTER; + case ABI_VA_LIST_AAPCS64: + return KIT_CG_VALIST_AAPCS64; + case ABI_VA_LIST_SYSV_X64: + return KIT_CG_VALIST_SYSV_X64; + } + return KIT_CG_VALIST_OPAQUE; +} + +const char* kit_cg_target_c_label_prefix(const KitCompiler* c) { + return obj_format_c_label_prefix(c); +} + int kit_cg_target_supports_intrinsic(KitCompiler* c, KitCgIntrinsic intrin) { const ArchImpl* a; if (!c) return 0; diff --git a/src/debug/dwarf_open.c b/src/debug/dwarf_open.c @@ -27,24 +27,17 @@ void dw_find_section(KitDebugInfo* d, const char* name, DwSection* out) { /* On Mach-O the obj layer reports DWARF sections as "__DWARF,__debug_*" * (16-char-truncated) and .eh_frame as "__TEXT,__eh_frame", not the * ELF ".debug_*"/".eh_frame" spelling. Precompute the Mach-O candidate - * for the requested section so one lookup spans both formats. */ - char macho_full[8 + 17]; + * for the requested section (via the shared name translator) so one lookup + * spans both formats. */ + char macho_full[40]; int have_macho = 0; out->data = NULL; out->size = 0; out->sec_idx = UINT32_MAX; if (!d->obj) return; { - char sect[17]; size_t nl = (size_t)slice_from_cstr(name).len; - if (obj_macho_debug_sectname(name, nl, sect)) { - memcpy(macho_full, "__DWARF,", 8); - memcpy(macho_full + 8, sect, slice_from_cstr(sect).len + 1); - have_macho = 1; - } else if (nl == 9 && memcmp(name, ".eh_frame", 9) == 0) { - memcpy(macho_full, "__TEXT,__eh_frame", sizeof("__TEXT,__eh_frame")); - have_macho = 1; - } + have_macho = obj_macho_native_secname(name, nl, macho_full); } n = kit_obj_nsections(d->obj); for (i = 0; i < n; ++i) { diff --git a/src/link/link_arch.h b/src/link/link_arch.h @@ -82,6 +82,14 @@ typedef struct LinkArchDesc { int (*is_direct_page_reloc)(RelocKind); int (*needs_jit_call_stub)(RelocKind); + /* ---- TLS Initial-Exec GOT slot fill ---- + * The internal raw-64-bit local-exec tpoff written into a TLS GOT slot + * (link_emit_internal_tpoff64): x86_64 uses R_X64_TPOFF64 (variant II, + * X - tls_memsz); AArch64 and RISC-V share R_AARCH64_TPOFF64 (variant I, + * (X - tls_vaddr) + TCB). Per-arch so the GOT pass picks the right + * tpoff coordinate system without branching on target.arch. */ + RelocKind tpoff64_reloc; + /* ---- Optional COFF __chkstk stub ---- * Arches that cannot emit inline stack probes (aarch64) carry the bytes of a * __chkstk function that link_synth_coff_ctor_dtor_list emits into a retained diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -606,12 +606,13 @@ void link_emit_boundary_sym(Linker* l, LinkImage* img, const char* name, LinkSymId id = symhash_get(&img->globals, sym); LinkSymbol rec; u8 kind = SK_OBJ; + int fmt_kind; u32 i, n; - if (l->c->target.obj == KIT_OBJ_COFF && - (slice_eq_cstr(slice_from_cstr(name), "__ImageBase") || - slice_eq_cstr(slice_from_cstr(name), "_tls_used"))) { - kind = SK_ABS; - } + /* Some formats own specific boundary symbols with a fixed SymKind + * (PE/COFF `__ImageBase` / `_tls_used` are SK_ABS). Ask the format + * instead of matching names here. */ + if (obj_format_boundary_sym_kind(l->c, slice_from_cstr(name), &fmt_kind)) + kind = (u8)fmt_kind; memset(&rec, 0, sizeof(rec)); rec.name = sym; rec.kind = kind; @@ -1209,7 +1210,10 @@ LinkImage* link_resolve(Linker* l) { Heap* h; metrics_scope_begin(l->c, "link.resolve.total"); - link_synth_coff_ctor_dtor_list(l); + /* Inject any format-owned synthetic input before symbol resolution. + * Only the COFF format registers a hook (link_synth_coff_ctor_dtor_list); + * the dispatcher is a no-op for every other format. */ + obj_format_synth_inputs(l->c, l); metrics_scope_begin(l->c, "link.ingest_archives"); link_ingest_archives(l); metrics_scope_end(l->c, "link.ingest_archives"); @@ -1258,7 +1262,7 @@ LinkImage* link_resolve(Linker* l) { * handle file-only sections. */ metrics_scope_begin(l->c, "link.layout_debug"); if (!l->strip_debug && !l->jit_mode && - (l->c->target.obj == KIT_OBJ_ELF || l->c->target.obj == KIT_OBJ_MACHO)) + obj_format_carries_file_only_debug(l->c)) link_layout_debug(l, img); metrics_scope_end(l->c, "link.layout_debug"); metrics_scope_begin(l->c, "link.assign_vaddrs"); @@ -1281,10 +1285,16 @@ LinkImage* link_resolve(Linker* l) { /* PE/COFF: mingw CRT references `__ImageBase` for ASLR-relative * addressing and base-relocation bookkeeping. The PE emitter * writes LINK_PE_IMAGE_BASE into the optional header; expose the - * same value as a linker-defined symbol so input objects resolve. */ - if (l->c->target.obj == KIT_OBJ_COFF) { - link_emit_boundary_sym(l, img, "__ImageBase", LINK_PE_IMAGE_BASE); - if (img->tls_memsz) link_emit_boundary_sym(l, img, "_tls_used", 0); + * same value as a linker-defined symbol so input objects resolve. + * Driven by the format claiming `__ImageBase` (the same hook that + * fixes its SymKind) rather than a target.obj switch. */ + { + int fmt_kind; + if (obj_format_boundary_sym_kind(l->c, SLICE_LIT("__ImageBase"), + &fmt_kind)) { + link_emit_boundary_sym(l, img, "__ImageBase", LINK_PE_IMAGE_BASE); + if (img->tls_memsz) link_emit_boundary_sym(l, img, "_tls_used", 0); + } } { const LinkArchDesc* arch = link_arch_desc_for(l->c); @@ -1319,7 +1329,9 @@ LinkImage* link_resolve(Linker* l) { link_layout_jit_stubs(l, img, map_size, &stub_map); metrics_scope_end(l->c, "link.layout_jit_stubs"); metrics_scope_begin(l->c, "link.layout_got"); - if (l->c->target.obj != KIT_OBJ_MACHO || !l->emit_static_exe) + /* Skip the link-time static GOT only for formats that build their own + * static GOT / non-lazy pointer table (Mach-O) in a static image. */ + if (!obj_format_builds_own_static_got(l->c) || !l->emit_static_exe) link_layout_got(l, img, map_size, &got_map); metrics_scope_end(l->c, "link.layout_got"); metrics_scope_begin(l->c, "link.emit_relocations"); diff --git a/src/link/link_reloc_layout.c b/src/link/link_reloc_layout.c @@ -695,8 +695,7 @@ static void link_emit_internal_tpoff64(LinkImage* img, Linker* l, rrec.width = 8; rrec.write_vaddr = write_vaddr; rrec.write_file_offset = write_vaddr; - rrec.kind = (l->c->target.arch == KIT_ARCH_X86_64) ? R_X64_TPOFF64 - : R_AARCH64_TPOFF64; + rrec.kind = link_arch_desc_for(l->c)->tpoff64_reloc; rrec.target = target; rrec.addend = 0; *link_append_reloc_slot(img) = rrec; @@ -830,8 +829,8 @@ void link_layout_iplt(Linker* l, LinkImage* img) { * ctor-based __kit_ifunc_init path for hosted FreeBSD static links. Other * targets (musl/freestanding) keep the ctor, whose crt does not walk * __rela_iplt. */ - int use_rela_iplt = l->emit_static_exe && l->c->target.os == KIT_OS_FREEBSD && - l->c->target.obj == KIT_OBJ_ELF; + int use_rela_iplt = + l->emit_static_exe && obj_format_static_ifunc_via_rela_iplt(l->c); int emit_init_array = l->emit_static_exe && !use_rela_iplt; LinkSectionId rela_iplt_sec_id = 0; u64 rela_iplt_vaddr = 0, rela_iplt_size = 0; diff --git a/src/link/link_resolve.c b/src/link/link_resolve.c @@ -932,7 +932,7 @@ static int inputs_have_defined_ifunc_before(Linker* l, u32 max_order) { } static int member_satisfies(LinkArchiveMember* mem, const SymHash* defined, - const SymHash* wanted, int coff_target) { + const SymHash* wanted, int weak_undef_pulls) { ObjSymIter* it; ObjSymEntry e; int hit = 0; @@ -945,10 +945,11 @@ static int member_satisfies(LinkArchiveMember* mem, const SymHash* defined, * coff_read.c step "WEAK_EXTERNAL primary"). The archive's symbol * map still lists the member as the canonical provider of that * name, so treat such weak undefs as defining for the archive-pull - * decision. The actual alias-to-target resolution happens later in - * link_resolve_undefs. */ + * decision (formats whose COMDAT semantics pull on a weak undef — + * obj_format_weak_undef_pulls_archive_member). The actual + * alias-to-target resolution happens later in link_resolve_undefs. */ if (s->kind == SK_UNDEF) { - if (!(coff_target && s->bind == SB_WEAK)) continue; + if (!(weak_undef_pulls && s->bind == SB_WEAK)) continue; } if (s->bind != SB_GLOBAL && s->bind != SB_WEAK) continue; if (symhash_get(wanted, s->name) == LINK_SYM_NONE) continue; @@ -976,13 +977,15 @@ static int member_satisfies(LinkArchiveMember* mem, const SymHash* defined, * For programs that emit real ctor/dtor sections this synth would * need to coordinate with .ctors/.dtors layout; v1 covers the empty * case (hello-world through mingw CRT). */ +/* Registered as the COFF format's synth_inputs hook (src/obj/registry.c), so + * it is only ever invoked for COFF targets — no obj==COFF guard needed. */ void link_synth_coff_ctor_dtor_list(Linker* l) { ObjBuilder* ob; ObjSecId sid; static const u8 kZeros[16] = {0}; LinkInput* in; u32 idx; - if (!l || l->c->target.obj != KIT_OBJ_COFF) return; + if (!l) return; ob = obj_new(l->c); if (!ob) return; sid = obj_section_ex( @@ -1060,12 +1063,13 @@ void link_ingest_archives(Linker* l) { symhash_get(&defined, want_ifunc_init) == LINK_SYM_NONE) symhash_set(&undefs, want_ifunc_init, 1u); - int coff_target = (l->c->target.obj == KIT_OBJ_COFF); + int weak_undef_pulls = obj_format_weak_undef_pulls_archive_member(l->c); for (m = 0; m < ar->nmembers; ++m) { LinkArchiveMember* mem = &ar->members[m]; if (mem->included) continue; if (!mem->obj) continue; /* long-form skip (head/trailer) */ - if (!member_satisfies(mem, &defined, &undefs, coff_target)) continue; + if (!member_satisfies(mem, &defined, &undefs, weak_undef_pulls)) + continue; include_archive_member(l, ar, mem); changed = 1; } diff --git a/src/obj/format.h b/src/obj/format.h @@ -171,6 +171,11 @@ typedef struct ObjFormatImpl { * (binutils/PE COMDAT semantics); ELF/Mach-O only pull for strong * undefs. COFF=1, else 0. */ u8 weak_undef_pulls_archive_member; + /* Can represent the ELF/Mach-O TLS-access symbol features the CG layer + * mints (local-exec/initial-exec/local-dynamic/general-dynamic): ELF=1, + * Mach-O=1; COFF (Windows TEB model) and Wasm have no such representation, + * =0. Read by obj_format_supports_symbol_feature. */ + u8 tls_symbol_features; /* Inject a synthetic input object before symbol resolution. NULL when * the format synthesizes nothing. */ diff --git a/src/obj/obj_secnames.c b/src/obj/obj_secnames.c @@ -346,16 +346,18 @@ int obj_format_weak_undef_pulls_archive_member(const Compiler* c) { } int obj_format_supports_symbol_feature(const Compiler* c, int symfeat) { - /* The only format-divergent feature axis today is TLS access: COFF's - * Windows TEB model can't represent the ELF/Mach-O TLS-access features - * the CG layer mints. Every other (non-TLS) feature is representable - * by every format. */ + /* The only format-divergent feature axis today is TLS access: only ELF and + * Mach-O can represent the ELF/Mach-O TLS-access features the CG layer mints. + * COFF (Windows TEB model) and Wasm cannot. Every other (non-TLS) feature is + * representable by every format. The per-format answer lives on the vtable. */ switch (symfeat) { case KIT_CG_SYMFEAT_TLS_LOCAL_EXEC: case KIT_CG_SYMFEAT_TLS_INITIAL_EXEC: case KIT_CG_SYMFEAT_TLS_LOCAL_DYNAMIC: - case KIT_CG_SYMFEAT_TLS_GENERAL_DYNAMIC: - return obj_format_tls_model(c) != OBJ_TLS_WINDOWS_TEB; + case KIT_CG_SYMFEAT_TLS_GENERAL_DYNAMIC: { + const ObjFormatImpl* fmt = c ? obj_format_lookup(c->target.obj) : NULL; + return fmt && fmt->tls_symbol_features; + } default: return 1; } diff --git a/src/obj/registry.c b/src/obj/registry.c @@ -54,6 +54,11 @@ ObjFormatArchiveAction obj_format_archive_member_disabled( void aa64_emit_macho_stub(u8* dst, u64 stub_vaddr, u64 got_slot_vaddr); void aa64_emit_coff_iat_stub(u8* dst, u64 stub_vaddr, u64 iat_slot_vaddr); void x64_emit_coff_iat_stub(u8* dst, u64 stub_vaddr, u64 iat_slot_vaddr); +/* COFF synthetic-input hook body. Needs Linker internals (LinkInput append, + * link_arch_desc_for for the __chkstk stub), so it lives in src/link; wired + * here as the only registry row that registers synth_inputs. */ +void link_synth_coff_ctor_dtor_list(Linker*); +#define OBJ_COFF_SYNTH_INPUTS link_synth_coff_ctor_dtor_list #else void obj_format_macho_stub_disabled(u8* dst, u64 stub_vaddr, u64 got_slot_vaddr); @@ -61,6 +66,7 @@ void obj_format_coff_stub_disabled(u8* dst, u64 stub_vaddr, u64 iat_slot_vaddr); #define aa64_emit_macho_stub obj_format_macho_stub_disabled #define aa64_emit_coff_iat_stub obj_format_coff_stub_disabled #define x64_emit_coff_iat_stub obj_format_coff_stub_disabled +#define OBJ_COFF_SYNTH_INPUTS NULL #endif #if KIT_OBJ_ELF_ENABLED @@ -321,6 +327,7 @@ static const ObjFormatImpl obj_format_impl_wasm = { .carries_file_only_debug = 0, .builds_own_static_got = 0, .weak_undef_pulls_archive_member = 0, + .tls_symbol_features = 0, }; #endif @@ -343,6 +350,7 @@ static const ObjFormatImpl obj_format_impl_elf = { .carries_file_only_debug = 1, .builds_own_static_got = 0, .weak_undef_pulls_archive_member = 0, + .tls_symbol_features = 1, .elf_arch = obj_elf_arch, .elf_machine = obj_elf_machine, }; @@ -365,6 +373,7 @@ static const ObjFormatImpl obj_format_impl_macho = { .carries_file_only_debug = 1, .builds_own_static_got = 1, .weak_undef_pulls_archive_member = 0, + .tls_symbol_features = 1, .macho_arch = obj_macho_arch, .macho_cputype = obj_macho_cputype, }; @@ -386,12 +395,13 @@ static const ObjFormatImpl obj_format_impl_coff = { .carries_file_only_debug = 0, .builds_own_static_got = 0, .weak_undef_pulls_archive_member = 1, + .tls_symbol_features = 0, /* 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 - * expressed via ObjBuilder/Compiler/obj APIs alone. Left NULL here — - * T-LINK wires the existing function through this field. */ - .synth_inputs = NULL, + * expressed via ObjBuilder/Compiler/obj APIs alone. Wired to the + * src/link body (NULL when the linker subsystem is compiled out). */ + .synth_inputs = OBJ_COFF_SYNTH_INPUTS, .coff_arch = obj_coff_arch, .coff_machine = obj_coff_machine, .classify_obj_input = coff_classify_obj_input,