kit

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

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:
Msrc/arch/arch.h | 9+++++++++
Msrc/arch/arm32/asm.c | 4++++
Msrc/asm/asm.c | 20+++++++++++++-------
Msrc/obj/elf/elf.h | 3+++
Msrc/obj/elf/emit.c | 12++++++++----
Msrc/obj/format.h | 7+++++++
Msrc/obj/registry.c | 3+++
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 && \