commit 6b30ebfeab40ba6eed5023a5e8e09d8fcd4fd7b0
parent ecb49dcb57f8a6b04c57e726b7ced60ac5a5c78a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 11:13:08 -0700
Tier 3: arm32 asm/ELF behavior -> ArchAsmOps + ObjElfArchOps hooks
Diffstat:
7 files changed, 47 insertions(+), 11 deletions(-)
diff --git a/src/arch/arch.h b/src/arch/arch.h
@@ -258,6 +258,15 @@ typedef struct ArchAsmOps {
* pair fusion for the arch. */
int (*reloc_call_pair)(u16 reloc_kind, KitSlice pair_mnemonic,
KitSlice pair_ops, const char** mnemonic_out);
+ /* 1 if this arch tags function symbols with a low-bit (LSB) ISA-state
+ * marker, the way the 32-bit ARM EABI distinguishes Thumb entry points: a
+ * defined function symbol carries value|1, the `.thumb_func` directive marks
+ * the next/named label as such, and `.size SYM, . - SYM` masks that bit back
+ * off before differencing. The standalone assembler keys all of this Thumb
+ * bookkeeping off this flag instead of switching on arch identity. 0 for
+ * every arch with no ISA-state symbol bit (aarch64, x86-64, RISC-V, …),
+ * which yields the plain "value = offset, no LSB" behavior. */
+ unsigned thumb_function_symbols : 1;
} ArchAsmOps;
typedef struct ArchImpl {
diff --git a/src/arch/arm32/asm.c b/src/arch/arm32/asm.c
@@ -1165,6 +1165,10 @@ static int arm32_is_local_branch(KitSlice m) {
const ArchAsmOps arm32_asm_ops = {
.reloc_operand = arm32_reloc_operand,
.is_local_branch = arm32_is_local_branch,
+ /* ARM EABI: function symbols carry a Thumb (LSB) ISA-state bit; the
+ * standalone assembler's `.thumb_func` / `.size . - SYM` / label-define
+ * bookkeeping keys off this flag (see ArchAsmOps). */
+ .thumb_function_symbols = 1,
};
ArchAsm* arm32_arch_asm_new(Compiler* c) {
diff --git a/src/asm/asm.c b/src/asm/asm.c
@@ -214,8 +214,13 @@ static ObjSym* sym_mut(AsmDriver* d, ObjSymId id) {
return (ObjSym*)obj_symbol_get(d->ob, id);
}
-static int asm_is_arm32(const AsmDriver* d) {
- return d->c && d->c->target.arch == KIT_ARCH_ARM_32;
+/* 1 if the active arch tags function symbols with a Thumb (LSB) ISA-state
+ * bit. Replaces the former `target.arch == KIT_ARCH_ARM_32` identity check:
+ * the behavior now hangs off the arch's ArchAsmOps vtable (set only on arm32),
+ * so generic assembler code stops switching on arch identity. */
+static int asm_thumb_func_syms(const AsmDriver* d) {
+ const ArchImpl* a = d->c ? arch_for_compiler(d->c) : NULL;
+ return a && a->asm_ops && a->asm_ops->thumb_function_symbols;
}
static int asm_sym_is_thumb_func(AsmDriver* d, Sym name) {
@@ -226,7 +231,7 @@ static int asm_sym_is_thumb_func(AsmDriver* d, Sym name) {
static void mark_thumb_func(AsmDriver* d, Sym name) {
ObjSymId id;
ObjSym* s;
- if (!asm_is_arm32(d)) return;
+ if (!asm_thumb_func_syms(d)) return;
(void)SymU8Map_set(&d->thumb_func_map, name, 1u);
id = intern_sym(d, name);
s = sym_mut(d, id);
@@ -1117,9 +1122,9 @@ static void do_directive(AsmDriver* d, Sym name) {
return;
}
if (sym_eq(d, name, "thumb_func")) {
- if (asm_is_arm32(d) && !d_is_eol(d)) {
+ if (asm_thumb_func_syms(d) && !d_is_eol(d)) {
mark_thumb_func(d, expect_ident(d, ".thumb_func"));
- } else if (asm_is_arm32(d)) {
+ } else if (asm_thumb_func_syms(d)) {
d->pending_thumb_func = 1;
}
d_skip_to_eol(d);
@@ -1142,7 +1147,8 @@ static void do_directive(AsmDriver* d, Sym name) {
const ObjSym* os = obj_symbol_get(d->ob, id);
if (os && os->section_id == d->cur_sec) {
u64 start = os->value;
- if (asm_is_arm32(d) && os->kind == SK_FUNC) start &= ~(u64)1;
+ if (asm_thumb_func_syms(d) && os->kind == SK_FUNC)
+ start &= ~(u64)1;
sz = (i64)mc_pos(d->mc) - (i64)start;
}
}
@@ -1520,7 +1526,7 @@ static void process_label(AsmDriver* d, Sym name) {
const ObjSym* os = obj_symbol_get(d->ob, id);
if (os && os->section_id != OBJ_SEC_NONE)
d_panicf(d, "asm: symbol defined twice");
- if (asm_is_arm32(d)) {
+ if (asm_thumb_func_syms(d)) {
is_thumb_func = d->pending_thumb_func || asm_sym_is_thumb_func(d, name);
d->pending_thumb_func = 0;
}
diff --git a/src/obj/elf/elf.h b/src/obj/elf/elf.h
@@ -509,6 +509,9 @@ KitFloatAbi elf_arm_float_abi_from_e_flags(u32 e_flags);
u32 elf_arm_float_abi_to_e_flags(KitFloatAbi abi);
/* REL addend reconstruction (ObjElfArchOps.reloc_field_addend). */
i64 elf_arm_reloc_field_addend(u32 kind, const u8* field, u32 width);
+/* ARM EABI `.ARM.attributes` (SHT_ARM_ATTRIBUTES) payload builder, wired into
+ * the arm32 row of obj_elf_arch_ops[] as ObjElfArchOps.build_attributes. */
+const u8* elf_arm_build_attributes(Compiler* c, u32* size_out);
/* ---- little-endian byte writers (Writer-based) ----
* Writes go through the shared writer_u*_le helpers (core/bytes.h); the
diff --git a/src/obj/elf/emit.c b/src/obj/elf/emit.c
@@ -145,7 +145,7 @@ static int obj_has_section_name(Compiler* c, ObjBuilder* ob, const char* name) {
return 0;
}
-static u8* arm32_build_attributes(Compiler* c, u32* size_out) {
+const u8* elf_arm_build_attributes(Compiler* c, u32* size_out) {
static const u8 kArm32Attrs[] = {
'A',
0x42, 0x00, 0x00, 0x00, /* vendor subsection length */
@@ -242,8 +242,12 @@ void emit_elf(Compiler* c, ObjBuilder* ob, Writer* w) {
u32 nobjsec = obj_section_count(ob);
u32 nobjgrp = obj_group_count(ob);
- int synth_arm_attrs =
- (e_machine == EM_ARM) && !obj_has_section_name(c, ob, ".ARM.attributes");
+ /* Synthesize the arch's ELF vendor build-attributes section (ARM's
+ * `.ARM.attributes`) when the arch provides a builder and the object doesn't
+ * already carry one. The gate hangs off the per-arch ObjElfArchOps vtable
+ * (build_attributes is non-NULL only for arm32) rather than e_machine. */
+ int synth_arm_attrs = elf->build_attributes &&
+ !obj_has_section_name(c, ob, ".ARM.attributes");
/* Upper bound on ELF section count:
* 1 (SHN_UNDEF)
* + nobjsec - 1 (one ELF entry per real obj section)
@@ -296,7 +300,7 @@ void emit_elf(Compiler* c, ObjBuilder* ob, Writer* w) {
if (synth_arm_attrs) {
u32 attr_size;
- u8* attr = arm32_build_attributes(c, &attr_size);
+ const u8* attr = elf->build_attributes(c, &attr_size);
ElfSec* es = &secs[nsecs];
memset(es, 0, sizeof *es);
es->name = ".ARM.attributes";
diff --git a/src/obj/format.h b/src/obj/format.h
@@ -91,6 +91,13 @@ typedef struct ObjElfArchOps {
* linker reads the same addend a field-reading linker (ld.lld) would.
* NULL or a NOBITS target -> addend 0. */
i64 (*reloc_field_addend)(u32 kind, const u8* field, u32 width);
+ /* Build this arch's ELF vendor build-attributes section payload (e.g. ARM's
+ * `.ARM.attributes`, SHT_*_ATTRIBUTES). Returns a freshly allocated buffer
+ * (in `c->scratch`) of `*size_out` bytes; the emitter synthesizes the
+ * section only when this is non-NULL. NULL for arches that emit no vendor
+ * attributes (aarch64, x86-64, RISC-V, …) — replaces the former
+ * `e_machine == EM_ARM` identity gate in the ELF emitter. */
+ const u8* (*build_attributes)(Compiler* c, u32* size_out);
} ObjElfArchOps;
/* Structural role of a Mach-O relocation entry. Mach-O's r_type is a 4-bit
diff --git a/src/obj/registry.c b/src/obj/registry.c
@@ -178,6 +178,9 @@ static const ObjElfArchOps obj_elf_arch_ops[] = {
.reloc_name = elf_arm_reloc_name,
.float_abi_from_e_flags = elf_arm_float_abi_from_e_flags,
.reloc_field_addend = elf_arm_reloc_field_addend,
+ /* ARM EABI emits a `.ARM.attributes` vendor section; the emitter
+ * synthesizes it via this hook instead of an e_machine == EM_ARM gate. */
+ .build_attributes = elf_arm_build_attributes,
},
#endif
#if !KIT_ARCH_AA64_ENABLED && !KIT_ARCH_X64_ENABLED && \