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:
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,