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