commit 1604e6a34af780121e05fcdf883b8caf90295d8c
parent 7f8eab153bbe7861f2466909b1e0b71ad9488730
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 10 Jun 2026 07:56:22 -0700
fix(c_target): T1.3 — reject non-absolute data relocations
c_emit_data_symbol derived reloc width as (R_ABS32)?4:8 and rendered every
reloc as an absolute &sym pointer. A PC-relative / section-difference reloc
has no portable C spelling (pcrel is not a constant expression; a
sub-pointer-width address cannot be truncated into a 4-byte slot), and the
width logic mis-sized such slots. Per the design choice, reject any
non-absolute data reloc with a clear compiler_panic instead of silently
emitting a wrong slot. Only R_ABS32/R_ABS64 remain supported.
toy 96_data_relocations / 100_record_data_relocation exercise pcrel-in-data
on every backend, so they now carry a .cbackend.skip on the C path.
Documented in doc/CBACKEND.md.
test-cbackend: toy/C 159 pass/0 fail (96+100 skip as intended), parse/C
467/0. (The unrelated memory_grow_large/C failure is the pre-existing
memgrow bug, tracked separately and left parked.)
Diffstat:
5 files changed, 25 insertions(+), 13 deletions(-)
diff --git a/doc/CBACKEND.md b/doc/CBACKEND.md
@@ -223,6 +223,16 @@ and linker resolve the references natively. The relocation slots are sorted by
offset, and when any are present the struct is `__attribute__((packed))` so the
field layout matches the original byte image exactly.
+Only **absolute** data relocations (`R_ABS32` / `R_ABS64`) are emittable this
+way, because the slot holds a real address-of value. A PC-relative or
+section-difference relocation has no portable C spelling — a pcrel offset is not
+a constant expression, and a sub-pointer-width address (e.g. an `@pcrel` into a
+4-byte `i32` slot) cannot be truncated into its slot in standard C. The emitter
+therefore `compiler_panic`s on any non-absolute data relocation rather than
+silently mis-sizing the slot. Toy cases that exercise pcrel-in-data
+(`96_data_relocations`, `100_record_data_relocation`) carry a `.cbackend.skip`
+on the C path for this reason.
+
TLS delegates entirely to `_Thread_local`; the host compiler builds its own
descriptor. On Mach-O, where TLS is split into a descriptor symbol plus a
synthesized init symbol (see [OBJ.md](OBJ.md)), the emitter pulls the initial
diff --git a/doc/plan/TODO.md b/doc/plan/TODO.md
@@ -73,19 +73,6 @@ Add new deferred fixes below as they are discovered.
which programs compile vs panic, so it must be a reviewed change, NOT folded into
a dedup. Unblocks A.2 (below).
-## Design-needed (not a quick fix)
-
-- **c_target data relocations (T1.3 / D.5).** `c_emit_data_symbol`
- (`src/arch/c_target/c_emit.c:3846, 3911`) hardcodes reloc width `(kind==R_ABS32)?4:8`
- and renders every reloc as an absolute pointer; a sub-pointer-width address reloc
- (`@pcrel` into a 4-byte `i32` slot, e.g. toy tests 96/100) overflows the slot.
- Root issue is fundamental: the C backend cannot put a real `&sym` (8 bytes) into a
- 4-byte slot in portable C, and a true PC-relative/section-diff offset is not a C
- constant expression. Needs a design decision: **either** reject such relocs
- (`compiler_panic`) and mark tests 96/100 `SKIP` on `/C`, **or** emit runtime
- init-time fixups. (A naive panic-on-non-absolute was tried and reverted — it broke
- 96/100, which intentionally exercise pcrel-in-data on every backend incl. C.)
-
## Deferred dedups & abstraction cleanups ("use the shared seam")
- **A.2 — optimizer-path inline-asm dedup.** The three `aa_/x64_/rv_asm_block_native`
diff --git a/src/arch/c_target/c_emit.c b/src/arch/c_target/c_emit.c
@@ -3798,6 +3798,19 @@ static void c_emit_data_symbol(CTarget* t, ObjSymId id, const ObjSym* os) {
}
rs[k] = tmp;
}
+ /* The C backend can only render an absolute address (&sym as a pointer
+ * value) into a data slot. A PC-relative or section-difference reloc is
+ * not a C constant expression, and a sub-pointer-width address cannot be
+ * truncated into its slot in portable C — reject those rather than
+ * silently mis-size the slot (the width logic below assumes ABS32/ABS64). */
+ for (u32 i = 0; i < nrelocs; ++i) {
+ if (rs[i]->kind != R_ABS32 && rs[i]->kind != R_ABS64)
+ compiler_panic(t->c, (SrcLoc){0, 0, 0},
+ "C target: unsupported non-absolute data relocation "
+ "(kind %u) targeting '%s'; the C backend emits only "
+ "absolute (R_ABS32/R_ABS64) data relocations",
+ (unsigned)rs[i]->kind, c_sym_name(t, rs[i]->sym));
+ }
}
cbuf_puts(b, "struct ");
diff --git a/test/toy/cases/100_record_data_relocation.cbackend.skip b/test/toy/cases/100_record_data_relocation.cbackend.skip
@@ -0,0 +1 @@
+C backend rejects non-absolute (pcrel/section-diff) data relocations (T1.3): a PC-relative offset is not a C constant expression and cannot fit a sub-pointer-width slot in portable C.
diff --git a/test/toy/cases/96_data_relocations.cbackend.skip b/test/toy/cases/96_data_relocations.cbackend.skip
@@ -0,0 +1 @@
+C backend rejects non-absolute (pcrel/section-diff) data relocations (T1.3): a PC-relative offset is not a C constant expression and cannot fit a sub-pointer-width slot in portable C.