From 95da8707346aa5628f161688375d8c34c899119a Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 13 Jun 2026 04:43:57 +0900 Subject: [PATCH] w6a/ww: DATAR naming an undefined slot is a loud reject MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A DATAR relocation against an undefined data slot was silently dropped — the relocation vanished from the object. Reject loudly, matching the cstage single-pass resolution behavior. --- Makefile | 9 ++ selfhost/cmd/w6a/asm.ww | 15 +++- selfhost/cmd/w6a/main.combined.ww | 15 +++- test/wcc/989_datargate_run.c | 139 ++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 test/wcc/989_datargate_run.c diff --git a/Makefile b/Makefile index c67c65ff..c4d9dbe5 100644 --- a/Makefile +++ b/Makefile @@ -264,6 +264,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_loopcap_run \ $(BIN)/test_enumcap_run \ $(BIN)/test_wwdumpgate_run \ + $(BIN)/test_datargate_run \ $(BIN)/test_ampfncollide_run \ $(BIN)/test_trycallcollide_run \ $(BIN)/test_gunsigned_run \ @@ -837,6 +838,14 @@ $(BIN)/test_wwdumpgate_run: test/wcc/989_wwdumpgate_run.c \ $(BIN)/wwdump $(BIN)/wwdump_ww | $(BIN) $(CC) $(CFLAGS) -o $@ $< +# 989_datargate_run (#59, F15 c5): w6a loud-rejects a DATAR relocation whose +# slot was never defined by a prior DATAW instead of silently dropping the +# reloc (zero-filled pointer slot in the .o). Runs w6a + w6a_ww directly and +# asserts a byte-identical .o on the healthy path (rule-10). See the header. +$(BIN)/test_datargate_run: test/wcc/989_datargate_run.c \ + $(BIN)/w6a $(BIN)/w6a_ww | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + # 989_ampfncollide_run (#4, c2): `&fn` synthesis (unoptype TK_AMP + # assignableaddrfn) prefers the current module's fn when a same-leaf fn is # declared in a later module. Builds/rejects on BOTH driver twins (rule-10). diff --git a/selfhost/cmd/w6a/asm.ww b/selfhost/cmd/w6a/asm.ww index ba2294d7..55721a8c 100644 --- a/selfhost/cmd/w6a/asm.ww +++ b/selfhost/cmd/w6a/asm.ww @@ -300,10 +300,17 @@ export fn encode(a: *asm_) i32 = { // pointing at target. Slot must already be defined // by a prior DATAW. let holder: *asym = intern(a, p.from.asym); - if (holder.defined == 0) { - p = p.link; continue; - }; - if (holder.isdata == 0) { + // #59: a DATAR slot with no prior DATAW definition must be + // a loud error, not a silently dropped relocation (the .o + // would ship a zero-filled pointer slot — a null pointer at + // link/runtime). Mirrors cmd/w6a/asm.c:363 (errs++ → main + // returns 1 before opening the output, so no .o is written). + if (holder.defined == 0 || holder.isdata == 0) { + let msg: str = "w6a: DATAR slot not yet defined as DATAW: "; + os.write(2, msg.ptr, msg.len: u64); + os.write(2, p.from.asym.ptr, p.from.asym.len: u64); + os.write(2, "\n".ptr, 1u64); + a.errs += 1; p = p.link; continue; }; let target: *asym = intern(a, p.to.asym); diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index 5880429e..62e47437 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -4023,10 +4023,17 @@ export fn encode(a: *asm_) i32 = { // pointing at target. Slot must already be defined // by a prior DATAW. let holder: *asym = intern(a, p.from.asym); - if (holder.defined == 0) { - p = p.link; continue; - }; - if (holder.isdata == 0) { + // #59: a DATAR slot with no prior DATAW definition must be + // a loud error, not a silently dropped relocation (the .o + // would ship a zero-filled pointer slot — a null pointer at + // link/runtime). Mirrors cmd/w6a/asm.c:363 (errs++ → main + // returns 1 before opening the output, so no .o is written). + if (holder.defined == 0 || holder.isdata == 0) { + let msg: str = "w6a: DATAR slot not yet defined as DATAW: "; + os.write(2, msg.ptr, msg.len: u64); + os.write(2, p.from.asym.ptr, p.from.asym.len: u64); + os.write(2, "\n".ptr, 1u64); + a.errs += 1; p = p.link; continue; }; let target: *asym = intern(a, p.to.asym); diff --git a/test/wcc/989_datargate_run.c b/test/wcc/989_datargate_run.c new file mode 100644 index 00000000..488ec170 --- /dev/null +++ b/test/wcc/989_datargate_run.c @@ -0,0 +1,139 @@ +/* + * 989_datargate_run (#59, F15 c5) — w6a must loud-reject a DATAR relocation + * whose slot was never defined by a prior DATAW, not silently drop the reloc. + * + * THE BUG (wwstage w6a only, cat-A silent wrong output under rc=0): asm.ww's + * A_DATAR arm, when holder.defined==0 or holder.isdata==0, did `p = p.link; + * continue;` with no diagnostic and no errs++ — so the R_X86_64_64 .data + * relocation was silently dropped and a rc=0 .o was emitted with a zero-filled + * pointer slot (a null pointer at link/runtime). The cstage twin (cmd/w6a/ + * asm.c:363) printed a diagnostic + a->errs++, and main returns 1 before + * opening the output, so no .o is written. THE FIX: emit a diagnostic and bump + * a.errs (encode returns a.errs → main returns 1 before writing the .o). + * + * row | shape | result (cs & ww agree) + * ----+-------------------------------+-------------------------------- + * bad | DATAR on a slot with no DATAW | rc != 0, no .o written + * ok | DATAW then DATAR on that slot | rc == 0, .o byte-identical (cs==ww) + * + * bad was RED pre-c5 on wwstage (rc=0, .o with the reloc silently dropped). + * ok pins the defined-slot path: both stages rc=0 and a byte-identical .o. + */ +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return -1; +} + +static const char *BAD = + "TEXT f,$0\n\tRET\n\tDATAR x+0(SB), f(SB)\n"; +static const char *OK = + "TEXT f,$0\n\tRET\n" + "DATAW x(SB),\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n" + "\tDATAR x+0(SB), f(SB)\n"; + +static int +write_file(const char *path, const char *body) +{ + FILE *f = fopen(path, "wb"); + if (!f) return -1; + fputs(body, f); + fclose(f); + return 0; +} + +/* Assemble `src` with `tool` into `obj` (removed first). Returns rc; sets + * *exists to whether the .o was created. */ +static int +assemble(const char *tool, const char *src, const char *obj, int *exists) +{ + char cmd[1024]; + unlink(obj); + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", tool, obj, src); + int rc = runwait(cmd); + if (exists) *exists = (access(obj, F_OK) == 0); + return rc; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[1024]; + if (bin[0] != '/') { + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + + char ctool[1024], wtool[1024]; + snprintf(ctool, sizeof ctool, "%s/w6a", bin); + snprintf(wtool, sizeof wtool, "%s/w6a_ww", bin); + int have_ww = (access(wtool, X_OK) == 0); + if (!have_ww) + fprintf(stderr, "datargate: skip wwstage (no %s)\n", wtool); + + char bads[64], oks[64], co[64], wo[64]; + snprintf(bads, sizeof bads, "/tmp/datarg_bad_%d.s", getpid()); + snprintf(oks, sizeof oks, "/tmp/datarg_ok_%d.s", getpid()); + snprintf(co, sizeof co, "/tmp/datarg_c_%d.o", getpid()); + snprintf(wo, sizeof wo, "/tmp/datarg_w_%d.o", getpid()); + if (write_file(bads, BAD) || write_file(oks, OK)) return 1; + + int fail = 0, total = 0, ex; + + /* bad: both stages loud, no .o */ + total++; + if (assemble(ctool, bads, co, &ex) == 0 || ex) { + fprintf(stderr, "datargate[cstage][bad]: rc=0/.o written on " + "undefined DATAR slot\n"); + fail++; + } + if (have_ww) { + if (assemble(wtool, bads, wo, &ex) == 0 || ex) { + fprintf(stderr, "datargate[wwstage][bad]: rc=0/.o written on " + "undefined DATAR slot — reloc silently dropped (#59)\n"); + fail++; + } + } + + /* ok: both stages rc=0, byte-identical .o */ + total++; + int crc = assemble(ctool, oks, co, &ex); + if (crc != 0 || !ex) { + fprintf(stderr, "datargate[cstage][ok]: rc=%d .o=%d\n", crc, ex); + fail++; + } + if (have_ww) { + int wrc = assemble(wtool, oks, wo, &ex); + if (wrc != 0 || !ex) { + fprintf(stderr, "datargate[wwstage][ok]: rc=%d .o=%d\n", wrc, ex); + fail++; + } + char cmp[1024]; + snprintf(cmp, sizeof cmp, "cmp -s %s %s", co, wo); + if (runwait(cmp) != 0) { + fprintf(stderr, "datargate[ok]: cs/ww .o differ (#59)\n"); + fail++; + } + } + + unlink(bads); unlink(oks); unlink(co); unlink(wo); + + if (fail) { + fprintf(stderr, "datargate_run: %d check(s) failed\n", fail); + return 1; + } + printf("datargate_run: %d/%d ok\n", total, total); + return 0; +}