kit

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

commit f86498e87167a077fa87b390ad814e52e43b4f21
parent 8ca44ed8b3b7f904787a3477b1961cc5a665bd2c
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 10 May 2026 11:28:55 -0700

obj: ELF rv64 — full reloc coverage, e_flags round-trip

Adds RISC-V reloc kinds clang emits at -O0/-O2 (PCREL_HI20/LO12_I/S,
GOT_HI20, TPREL_HI20/LO12/ADD, ADD/SUB/SET 8/16/32/64, RELAX, ALIGN,
RVC_BRANCH/JUMP, 32_PCREL) and round-trips ELF e_flags so cfree
preserves the input ABI bits (RV64 default: RVC | FLOAT_ABI_DOUBLE).

Test harness gains a `<case>.arches` allowlist so aa64-only cases
(18_bti_note) are filtered out cleanly on rv64/x64 instead of
counted as skips. CFREE_TEST_ARCH=rv64 test/elf now passes 36/36.

Diffstat:
Msrc/api/pipeline.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/obj/elf.h | 36++++++++++++++++++++++++++++++++++++
Msrc/obj/elf_emit.c | 16+++++++++++++++-
Msrc/obj/elf_read.c | 2++
Msrc/obj/elf_reloc_riscv64.c | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/obj/obj.c | 18++++++++++++++++++
Msrc/obj/obj.h | 33+++++++++++++++++++++++++++++++++
Atest/elf/cases/18_bti_note.arches | 1+
Mtest/elf/run.sh | 17+++++++++++++++++
9 files changed, 278 insertions(+), 3 deletions(-)

diff --git a/src/api/pipeline.c b/src/api/pipeline.c @@ -1066,6 +1066,56 @@ static const char* reloc_kind_name(u16 kind) { return "R_RISCV_JAL"; case R_RV_CALL: return "R_RISCV_CALL"; + case R_RV_PCREL_HI20: + return "R_RISCV_PCREL_HI20"; + case R_RV_PCREL_LO12_I: + return "R_RISCV_PCREL_LO12_I"; + case R_RV_PCREL_LO12_S: + return "R_RISCV_PCREL_LO12_S"; + case R_RV_GOT_HI20: + return "R_RISCV_GOT_HI20"; + case R_RV_TPREL_HI20: + return "R_RISCV_TPREL_HI20"; + case R_RV_TPREL_LO12_I: + return "R_RISCV_TPREL_LO12_I"; + case R_RV_TPREL_LO12_S: + return "R_RISCV_TPREL_LO12_S"; + case R_RV_TPREL_ADD: + return "R_RISCV_TPREL_ADD"; + case R_RV_ADD8: + return "R_RISCV_ADD8"; + case R_RV_ADD16: + return "R_RISCV_ADD16"; + case R_RV_ADD32: + return "R_RISCV_ADD32"; + case R_RV_ADD64: + return "R_RISCV_ADD64"; + case R_RV_SUB8: + return "R_RISCV_SUB8"; + case R_RV_SUB16: + return "R_RISCV_SUB16"; + case R_RV_SUB32: + return "R_RISCV_SUB32"; + case R_RV_SUB64: + return "R_RISCV_SUB64"; + case R_RV_ALIGN: + return "R_RISCV_ALIGN"; + case R_RV_RVC_BRANCH: + return "R_RISCV_RVC_BRANCH"; + case R_RV_RVC_JUMP: + return "R_RISCV_RVC_JUMP"; + case R_RV_RELAX: + return "R_RISCV_RELAX"; + case R_RV_SUB6: + return "R_RISCV_SUB6"; + case R_RV_SET6: + return "R_RISCV_SET6"; + case R_RV_SET8: + return "R_RISCV_SET8"; + case R_RV_SET16: + return "R_RISCV_SET16"; + case R_RV_SET32: + return "R_RISCV_SET32"; case R_WASM_FUNCIDX: return "R_WASM_FUNCTION_INDEX_LEB"; case R_WASM_TABLEIDX: diff --git a/src/obj/elf.h b/src/obj/elf.h @@ -131,6 +131,16 @@ #define PF_W 0x2u #define PF_R 0x4u +/* ---- e_flags (RISC-V ABI bits, EM_RISCV) ---- */ +#define EF_RISCV_RVC 0x0001u +#define EF_RISCV_FLOAT_ABI_SOFT 0x0000u +#define EF_RISCV_FLOAT_ABI_SINGLE 0x0002u +#define EF_RISCV_FLOAT_ABI_DOUBLE 0x0004u +#define EF_RISCV_FLOAT_ABI_QUAD 0x0006u +#define EF_RISCV_FLOAT_ABI_MASK 0x0006u +#define EF_RISCV_RVE 0x0008u +#define EF_RISCV_TSO 0x0010u + /* ---- dynamic-table tags (PT_DYNAMIC body) ---- */ #define DT_NULL 0 #define DT_NEEDED 1 @@ -280,9 +290,35 @@ u32 elf_x86_64_reloc_from(u32 elf_type); #define ELF_R_RISCV_JAL 17 #define ELF_R_RISCV_CALL 18 #define ELF_R_RISCV_CALL_PLT 19 +#define ELF_R_RISCV_GOT_HI20 20 +#define ELF_R_RISCV_PCREL_HI20 23 +#define ELF_R_RISCV_PCREL_LO12_I 24 +#define ELF_R_RISCV_PCREL_LO12_S 25 #define ELF_R_RISCV_HI20 26 #define ELF_R_RISCV_LO12_I 27 #define ELF_R_RISCV_LO12_S 28 +#define ELF_R_RISCV_TPREL_HI20 29 +#define ELF_R_RISCV_TPREL_LO12_I 30 +#define ELF_R_RISCV_TPREL_LO12_S 31 +#define ELF_R_RISCV_TPREL_ADD 32 +#define ELF_R_RISCV_ADD8 33 +#define ELF_R_RISCV_ADD16 34 +#define ELF_R_RISCV_ADD32 35 +#define ELF_R_RISCV_ADD64 36 +#define ELF_R_RISCV_SUB8 37 +#define ELF_R_RISCV_SUB16 38 +#define ELF_R_RISCV_SUB32 39 +#define ELF_R_RISCV_SUB64 40 +#define ELF_R_RISCV_ALIGN 43 +#define ELF_R_RISCV_RVC_BRANCH 44 +#define ELF_R_RISCV_RVC_JUMP 45 +#define ELF_R_RISCV_RELAX 51 +#define ELF_R_RISCV_SUB6 52 +#define ELF_R_RISCV_SET6 53 +#define ELF_R_RISCV_SET8 54 +#define ELF_R_RISCV_SET16 55 +#define ELF_R_RISCV_SET32 56 +#define ELF_R_RISCV_32_PCREL 57 u32 elf_riscv64_reloc_to(u32 kind /* RelocKind */); u32 elf_riscv64_reloc_from(u32 elf_type); diff --git a/src/obj/elf_emit.c b/src/obj/elf_emit.c @@ -687,6 +687,20 @@ void emit_elf(Compiler* c, ObjBuilder* ob, Writer* w) { } } } + /* e_flags: prefer the value preserved from a prior read (round-trip); + * else synthesize a sensible per-arch default. RV64 cfree targets the + * Linux psABI's lp64d soft-relax convention (RVC + double-float ABI). */ + u32 e_flags; + if (!obj_get_elf_e_flags(ob, &e_flags)) { + switch (e_machine) { + case EM_RISCV: + e_flags = EF_RISCV_RVC | EF_RISCV_FLOAT_ABI_DOUBLE; + break; + default: + e_flags = 0; + } + } + cfree_writer_seek(w, 0); cfree_writer_write(w, ident, EI_NIDENT); elf_wr_u16(w, ET_REL); @@ -695,7 +709,7 @@ void emit_elf(Compiler* c, ObjBuilder* ob, Writer* w) { elf_wr_u64(w, 0); /* e_entry */ elf_wr_u64(w, 0); /* e_phoff */ elf_wr_u64(w, e_shoff); /* e_shoff */ - elf_wr_u32(w, 0); /* e_flags */ + elf_wr_u32(w, e_flags); /* e_flags */ elf_wr_u16(w, ELF64_EHDR_SIZE); /* e_ehsize */ elf_wr_u16(w, 0); /* e_phentsize */ elf_wr_u16(w, 0); /* e_phnum */ diff --git a/src/obj/elf_read.c b/src/obj/elf_read.c @@ -239,6 +239,7 @@ ObjBuilder* read_elf(Compiler* c, const char* name, const u8* data, } u64 e_shoff = elf_rd_u64(data + 40); + u32 e_flags = elf_rd_u32(data + 48); u16 e_shentsize = elf_rd_u16(data + 58); u16 e_shnum = elf_rd_u16(data + 60); u16 e_shstrndx = elf_rd_u16(data + 62); @@ -266,6 +267,7 @@ ObjBuilder* read_elf(Compiler* c, const char* name, const u8* data, /* Build the ObjBuilder. */ ObjBuilder* ob = obj_new(c); if (!ob) compiler_panic(c, no_loc(), "read_elf: obj_new failed"); + obj_set_elf_e_flags(ob, e_flags); /* elf_to_obj[shndx] -> ObjSecId, OBJ_SEC_NONE for skipped sections. */ u32* elf_to_obj = arena_zarray(c->scratch, u32, e_shnum); diff --git a/src/obj/elf_reloc_riscv64.c b/src/obj/elf_reloc_riscv64.c @@ -3,8 +3,8 @@ * Mirror of elf_reloc_x86_64.c for the RISC-V LP64 ABI. The arch- * agnostic R_ABS / R_PC RelocKind entries fan out to the native * RISC-V codes; the RISC-V-specific encodings (HI20/LO12, BRANCH, - * JAL, CALL, dynamic-only entries) live in the lower band as - * R_RV_*. + * JAL, CALL, PCREL_*, TPREL_*, ADD/SUB/SET, RELAX, ALIGN, RVC_*) + * live in the lower band as R_RV_*. * * Returning ELF_R_RISCV_NONE for an unsupported kind is the signal * to the caller to either panic (emit) or panic (read with diagnostic). */ @@ -19,6 +19,8 @@ u32 elf_riscv64_reloc_to(u32 kind /* RelocKind */) { return ELF_R_RISCV_64; case R_ABS32: return ELF_R_RISCV_32; + case R_PC32: + return ELF_R_RISCV_32_PCREL; case R_RV_HI20: return ELF_R_RISCV_HI20; case R_RV_LO12_I: @@ -33,6 +35,56 @@ u32 elf_riscv64_reloc_to(u32 kind /* RelocKind */) { return ELF_R_RISCV_CALL; case R_PLT32: return ELF_R_RISCV_CALL_PLT; + case R_RV_PCREL_HI20: + return ELF_R_RISCV_PCREL_HI20; + case R_RV_PCREL_LO12_I: + return ELF_R_RISCV_PCREL_LO12_I; + case R_RV_PCREL_LO12_S: + return ELF_R_RISCV_PCREL_LO12_S; + case R_RV_GOT_HI20: + return ELF_R_RISCV_GOT_HI20; + case R_RV_TPREL_HI20: + return ELF_R_RISCV_TPREL_HI20; + case R_RV_TPREL_LO12_I: + return ELF_R_RISCV_TPREL_LO12_I; + case R_RV_TPREL_LO12_S: + return ELF_R_RISCV_TPREL_LO12_S; + case R_RV_TPREL_ADD: + return ELF_R_RISCV_TPREL_ADD; + case R_RV_ADD8: + return ELF_R_RISCV_ADD8; + case R_RV_ADD16: + return ELF_R_RISCV_ADD16; + case R_RV_ADD32: + return ELF_R_RISCV_ADD32; + case R_RV_ADD64: + return ELF_R_RISCV_ADD64; + case R_RV_SUB8: + return ELF_R_RISCV_SUB8; + case R_RV_SUB16: + return ELF_R_RISCV_SUB16; + case R_RV_SUB32: + return ELF_R_RISCV_SUB32; + case R_RV_SUB64: + return ELF_R_RISCV_SUB64; + case R_RV_ALIGN: + return ELF_R_RISCV_ALIGN; + case R_RV_RVC_BRANCH: + return ELF_R_RISCV_RVC_BRANCH; + case R_RV_RVC_JUMP: + return ELF_R_RISCV_RVC_JUMP; + case R_RV_RELAX: + return ELF_R_RISCV_RELAX; + case R_RV_SUB6: + return ELF_R_RISCV_SUB6; + case R_RV_SET6: + return ELF_R_RISCV_SET6; + case R_RV_SET8: + return ELF_R_RISCV_SET8; + case R_RV_SET16: + return ELF_R_RISCV_SET16; + case R_RV_SET32: + return ELF_R_RISCV_SET32; default: return ELF_R_RISCV_NONE; } @@ -46,6 +98,8 @@ u32 elf_riscv64_reloc_from(u32 elf_type) { return R_ABS64; case ELF_R_RISCV_32: return R_ABS32; + case ELF_R_RISCV_32_PCREL: + return R_PC32; case ELF_R_RISCV_HI20: return R_RV_HI20; case ELF_R_RISCV_LO12_I: @@ -60,6 +114,56 @@ u32 elf_riscv64_reloc_from(u32 elf_type) { return R_RV_CALL; case ELF_R_RISCV_CALL_PLT: return R_PLT32; + case ELF_R_RISCV_PCREL_HI20: + return R_RV_PCREL_HI20; + case ELF_R_RISCV_PCREL_LO12_I: + return R_RV_PCREL_LO12_I; + case ELF_R_RISCV_PCREL_LO12_S: + return R_RV_PCREL_LO12_S; + case ELF_R_RISCV_GOT_HI20: + return R_RV_GOT_HI20; + case ELF_R_RISCV_TPREL_HI20: + return R_RV_TPREL_HI20; + case ELF_R_RISCV_TPREL_LO12_I: + return R_RV_TPREL_LO12_I; + case ELF_R_RISCV_TPREL_LO12_S: + return R_RV_TPREL_LO12_S; + case ELF_R_RISCV_TPREL_ADD: + return R_RV_TPREL_ADD; + case ELF_R_RISCV_ADD8: + return R_RV_ADD8; + case ELF_R_RISCV_ADD16: + return R_RV_ADD16; + case ELF_R_RISCV_ADD32: + return R_RV_ADD32; + case ELF_R_RISCV_ADD64: + return R_RV_ADD64; + case ELF_R_RISCV_SUB8: + return R_RV_SUB8; + case ELF_R_RISCV_SUB16: + return R_RV_SUB16; + case ELF_R_RISCV_SUB32: + return R_RV_SUB32; + case ELF_R_RISCV_SUB64: + return R_RV_SUB64; + case ELF_R_RISCV_ALIGN: + return R_RV_ALIGN; + case ELF_R_RISCV_RVC_BRANCH: + return R_RV_RVC_BRANCH; + case ELF_R_RISCV_RVC_JUMP: + return R_RV_RVC_JUMP; + case ELF_R_RISCV_RELAX: + return R_RV_RELAX; + case ELF_R_RISCV_SUB6: + return R_RV_SUB6; + case ELF_R_RISCV_SET6: + return R_RV_SET6; + case ELF_R_RISCV_SET8: + return R_RV_SET8; + case ELF_R_RISCV_SET16: + return R_RV_SET16; + case ELF_R_RISCV_SET32: + return R_RV_SET32; default: return (u32)-1; /* sentinel */ } diff --git a/src/obj/obj.c b/src/obj/obj.c @@ -27,6 +27,12 @@ struct CfreeObjBuilder { Symbols symbols; /* index 0 reserved as "none" */ Relocs relocs; /* flat across all sections; filtered on read */ Groups groups; /* index 0 reserved as "none" */ + /* Format-specific ELF e_flags. Set by read_elf to the input's + * e_flags (e.g. on RISC-V, EF_RISCV_RVC | EF_RISCV_FLOAT_ABI_DOUBLE); + * consumed by emit_elf to round-trip. Zero when not set — emit_elf + * derives a sensible default by arch. */ + u32 elf_e_flags; + u8 elf_e_flags_set; }; struct ObjSymIter { @@ -80,6 +86,18 @@ void obj_free(ObjBuilder* ob) { ob->heap->free(ob->heap, ob, sizeof(*ob)); } +void obj_set_elf_e_flags(ObjBuilder* ob, u32 e_flags) { + if (!ob) return; + ob->elf_e_flags = e_flags; + ob->elf_e_flags_set = 1; +} + +int obj_get_elf_e_flags(const ObjBuilder* ob, u32* out) { + if (!ob || !ob->elf_e_flags_set) return 0; + if (out) *out = ob->elf_e_flags; + return 1; +} + /* ---- write side ---- */ ObjSecId obj_section(ObjBuilder* ob, Sym name, SecKind kind, u16 flags, diff --git a/src/obj/obj.h b/src/obj/obj.h @@ -163,6 +163,31 @@ typedef enum RelocKind { R_RV_BRANCH, R_RV_JAL, R_RV_CALL, + R_RV_PCREL_HI20, + R_RV_PCREL_LO12_I, + R_RV_PCREL_LO12_S, + R_RV_GOT_HI20, + R_RV_TPREL_HI20, + R_RV_TPREL_LO12_I, + R_RV_TPREL_LO12_S, + R_RV_TPREL_ADD, + R_RV_ADD8, + R_RV_ADD16, + R_RV_ADD32, + R_RV_ADD64, + R_RV_SUB8, + R_RV_SUB16, + R_RV_SUB32, + R_RV_SUB64, + R_RV_ALIGN, + R_RV_RVC_BRANCH, + R_RV_RVC_JUMP, + R_RV_RELAX, + R_RV_SUB6, + R_RV_SET6, + R_RV_SET8, + R_RV_SET16, + R_RV_SET32, R_WASM_FUNCIDX, R_WASM_TABLEIDX, R_WASM_MEMOFS, @@ -289,6 +314,14 @@ void obj_group_add_section(ObjBuilder*, ObjGroupId group_id, void obj_finalize(ObjBuilder*); +/* Format-specific ELF e_flags (per-arch ABI bits, e.g. EF_RISCV_RVC | + * EF_RISCV_FLOAT_ABI_DOUBLE on RV64). Set by read_elf during input + * parsing; consumed by emit_elf for round-trip. The setter records + * a presence bit so emit_elf can distinguish "preserve from input" + * from "no input — synthesize per-arch default". */ +void obj_set_elf_e_flags(ObjBuilder*, u32 e_flags); +int obj_get_elf_e_flags(const ObjBuilder*, u32* out); + /* ---- read side (linker, file emitters, objdump) ---- */ u32 obj_section_count(const ObjBuilder*); const Section* obj_section_get(const ObjBuilder*, ObjSecId id); diff --git a/test/elf/cases/18_bti_note.arches b/test/elf/cases/18_bti_note.arches @@ -0,0 +1 @@ +aa64 diff --git a/test/elf/run.sh b/test/elf/run.sh @@ -130,6 +130,23 @@ if [ ${#case_srcs[@]} -eq 0 ]; then else for src in "${case_srcs[@]}"; do name="cases/$(basename "$src" .c)" + # Per-case arch applicability: a NN_name.arches file lists the + # CFREE_TEST_ARCH values the case applies to (one per line, or + # whitespace-separated). When the current arch isn't listed the + # case is silently filtered out — not a skip — because it + # exercises arch-specific features with no equivalent elsewhere + # (e.g. AArch64 BTI/PAC notes have no RISC-V analogue). + arches_file="${src%.c}.arches" + if [ -f "$arches_file" ]; then + applicable=0 + for a in $(cat "$arches_file"); do + [ "$a" = "${CFREE_TEST_ARCH:-aa64}" ] && applicable=1 + done + if [ $applicable -eq 0 ]; then + printf ' %s %s — N/A on %s\n' "$(color_yel SKIP-NA)" "$name" "${CFREE_TEST_ARCH:-aa64}" + continue + fi + fi # Per-case skip reasons: if [ $roundtrip_ok -ne 1 ]; then note_skip "$name" "cfree-roundtrip not built"; continue; fi if [ $have_clang -ne 1 ]; then note_skip "$name" "clang missing"; continue; fi