From 5f23fe9b675d227034ccfefe712d06474e662dab Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 14:48:31 +0900 Subject: [PATCH] test: port the assembler input gates to ww asmgate_test.ww absorbs 530_w6a_parsenum and 989_datargate_run: the strtoll-semantics identity rows plus the bare-minus reject against both assemblers with cross-stage .o byte-identity, and the undefined DATAR-slot loud reject (no .o written) with the defined-slot byte-identical ok path. The w6a_ww skip gates drop: the Make target declares both assemblers. --- Makefile | 2 +- test/object/asmgate_test.ww | 156 ++++++++++++++++++++++++++++++++++ test/wcc/530_w6a_parsenum.c | 159 ----------------------------------- test/wcc/989_datargate_run.c | 139 ------------------------------ 4 files changed, 157 insertions(+), 299 deletions(-) create mode 100644 test/object/asmgate_test.ww delete mode 100644 test/wcc/530_w6a_parsenum.c delete mode 100644 test/wcc/989_datargate_run.c diff --git a/Makefile b/Makefile index ed2c3cf8..486461be 100644 --- a/Makefile +++ b/Makefile @@ -396,7 +396,7 @@ ASM_WW_TARGETS = $(ASM_WW_TESTS:%=wwtest/%) # linker flag capacity, assembler input gates). Compiler/driver gates # like test/sep: they run under test-compiler. OBJECT_WW_TESTS = test/object/elfobj_test.ww test/object/link_test.ww \ - test/object/archive_test.ww + test/object/archive_test.ww test/object/asmgate_test.ww OBJECT_WW_TARGETS = $(OBJECT_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ diff --git a/test/object/asmgate_test.ww b/test/object/asmgate_test.ww new file mode 100644 index 00000000..4cfa1c2e --- /dev/null +++ b/test/object/asmgate_test.ww @@ -0,0 +1,156 @@ +package asmgate_test; + +// Assembler input gates, w6a vs w6a_ww on hand-written asm the +// compiler never emits. Ports of the retired native carriers +// test/wcc/530_w6a_parsenum.c and 989_datargate_run.c; every +// assertion preserved. +// +// parsenum (530, F14 #62) — w6a_ww's parsenum must match the C +// twin's strtoll(s,end,0) (cmd/w6a/lex.c:30): `$ 5` skips the +// whitespace (→5), `$08` stops at the octal-invalid '8' (→0), and a +// bare `-(BP)` is a loud reject on BOTH stages. Identity rows +// (including the canonical control shapes w6c does emit) must +// produce byte-identical .o across the two assemblers. +// +// datargate (989, #59 F15 c5) — a DATAR against a slot never defined +// by DATAW is a loud reject on both stages with NO .o written (the +// pre-c5 wwstage silently dropped the R_X86_64_64 reloc under rc=0); +// the DATAW-then-DATAR shape assembles on both stages to a +// byte-identical .o (rule 10). +// +// Dropped C machinery, not assertions: the w6a_ww absent-tool skip +// gates (the Make target declares both assemblers) and per-file +// unlink accounting (testenv.clean). + +import os; +import os.exec; +import strings; +import testenv; +import time; + +fn fail(label: str, why: str) void = { + let m: str = strings.concat("asmgate FAIL: ", label, " -- ", why, + "\n"); + os.write(2, m.ptr, m.len: u64); + assert(false); +}; + +fn tmo() time.duration = { + return (240i64 * (time.second: i64)): time.duration; +}; + +// -1 encodes an abnormal (non-EXIT) termination, never a valid code. +fn runcode(dir: str, name: str, argv: []str) i32 = { + let co: testenv.commandout; + testenv.runcommand(dir, dir, name, argv, tmo(), &co); + if (co.termination != exec.termination.EXIT) { return -1; }; + return co.code; +}; + +fn assemble(td: str, tool: str, tag: str, src: str, obj: str) i32 = { + let av: []str = [testenv.driver(tool), "-o", obj, src]; + return runcode(td, tag, av); +}; + +// ---- parsenum (530) ---------------------------------------------------- + +@test fn parsenum() void = { + let td: str = testenv.fresh(); + let labels: []str = ["ws_after_dollar", "leading_zero", + "plain_imm", "plain_imm_eight", "hex_imm", "zero_imm", + "neg_indir", "pos_indir"]; + // rows 0-1 are the strtoll edge shapes; the rest are the + // canonical controls w6c actually emits (no regression) + let texts: []str = [ + "TEXT main,$16\n\tMOVQ\t$ 5, AX\n\tRET\n", + "TEXT main,$16\n\tMOVQ\t$08, AX\n\tRET\n", + "TEXT main,$16\n\tMOVQ\t$5, AX\n\tRET\n", + "TEXT main,$16\n\tMOVQ\t$8, AX\n\tRET\n", + "TEXT main,$16\n\tMOVQ\t$0x1f, AX\n\tRET\n", + "TEXT main,$16\n\tMOVQ\t$0, AX\n\tRET\n", + "TEXT main,$16\n\tMOVQ\t-8(BP), AX\n\tRET\n", + "TEXT main,$16\n\tMOVQ\t8(BP), AX\n\tRET\n", + ]; + let i: i32 = 0; + for (i < 8) { + let src: str = strings.concat(td, "/", labels[i], ".s"); + let co: str = strings.concat(td, "/", labels[i], "_c.o"); + let wo: str = strings.concat(td, "/", labels[i], "_w.o"); + testenv.writefile(src, texts[i]); + if (assemble(td, "w6a", strings.concat("c_", labels[i]), + src, co) != 0) { + fail("parsenum", strings.concat(labels[i], + ": cstage assemble failed")); + }; + if (assemble(td, "w6a_ww", strings.concat("w_", labels[i]), + src, wo) != 0) { + fail("parsenum", strings.concat(labels[i], + ": wwstage assemble failed")); + }; + if (!testenv.same(testenv.readfile(co), testenv.readfile(wo))) { + fail("parsenum", strings.concat(labels[i], + ": .o differ across assemblers")); + }; + i += 1; + }; + + // reject row: no digit after the sign — loud on both stages + let rsrc: str = strings.concat(td, "/bare_minus_indir.s"); + testenv.writefile(rsrc, "TEXT main,$16\n\tMOVQ\t-(BP), AX\n\tRET\n"); + if (assemble(td, "w6a", "c_bare_minus", + rsrc, strings.concat(td, "/bare_minus_c.o")) == 0) { + fail("parsenum", "bare_minus_indir: cstage accepted, want reject"); + }; + if (assemble(td, "w6a_ww", "w_bare_minus", + rsrc, strings.concat(td, "/bare_minus_w.o")) == 0) { + fail("parsenum", "bare_minus_indir: wwstage accepted, want reject"); + }; + testenv.clean(td); +}; + +// ---- datargate (989) --------------------------------------------------- + +@test fn datargate() void = { + let td: str = testenv.fresh(); + let bads: str = strings.concat(td, "/bad.s"); + let oks: str = strings.concat(td, "/ok.s"); + testenv.writefile(bads, + "TEXT f,$0\n\tRET\n\tDATAR x+0(SB), f(SB)\n"); + testenv.writefile(oks, strings.concat( + "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")); + + // bad: both stages loud, and the reject must not write a .o + let tools: []str = ["w6a", "w6a_ww"]; + let t: i32 = 0; + for (t < 2) { + let obj: str = strings.concat(td, "/bad_", tools[t], ".o"); + if (assemble(td, tools[t], strings.concat("bad_", tools[t]), + bads, obj) == 0) { + fail("datargate", strings.concat(tools[t], + ": rc=0 on an undefined DATAR slot (reloc dropped?)")); + }; + if (testenv.exists(obj)) { + fail("datargate", strings.concat(tools[t], + ": .o written on the reject path")); + }; + t += 1; + }; + + // ok: both stages rc=0, .o present, byte-identical (rule 10) + let cobj: str = strings.concat(td, "/ok_c.o"); + let wobj: str = strings.concat(td, "/ok_w.o"); + if (assemble(td, "w6a", "ok_c", oks, cobj) != 0 + || !testenv.exists(cobj)) { + fail("datargate", "cstage failed the defined-slot path"); + }; + if (assemble(td, "w6a_ww", "ok_w", oks, wobj) != 0 + || !testenv.exists(wobj)) { + fail("datargate", "wwstage failed the defined-slot path"); + }; + if (!testenv.same(testenv.readfile(cobj), testenv.readfile(wobj))) { + fail("datargate", "cs/ww .o differ on the defined-slot path"); + }; + testenv.clean(td); +}; diff --git a/test/wcc/530_w6a_parsenum.c b/test/wcc/530_w6a_parsenum.c deleted file mode 100644 index 76c1bc75..00000000 --- a/test/wcc/530_w6a_parsenum.c +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 530_w6a_parsenum — F14 #62: w6a's parsenum diverged from the C twin's - * strtoll(s,end,0) (cmd/w6a/lex.c:30) on three hand-written-asm edge - * shapes, all gate-blind (w6c emits the canonical $5 / $8 / -8(BP), never - * these). The fix aligns wwstage's assembler to strtoll semantics: - * (a) `$ 5` — leading whitespace after $: strtoll skips it (→5); ww - * had no skip and silently encoded imm 0. - * (b) `$08` — strtoll base-0 reads a leading 0 as octal and STOPS at - * '8' (→0); ww parsed it as decimal 8. - * (c) `-(BP)` — strtoll/cstage require a digit after the sign, so a bare - * `-(` is `unrecognised operand` (loud reject); ww silently - * accepted it as 0(BP). - * - * (a)/(b): both assemblers must emit a byte-identical .o. - * (c): both assemblers must FAIL (loud reject). - * Controls: the canonical $5 / $8 / -8(BP) shapes that w6c DOES emit must - * stay byte-identical (no regression). - * - * Drives w6a (cstage) and w6a_ww (wwstage) directly; gated on w6a_ww. - */ -#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; -} - -/* assemble `asm` with `tool` into `obj`; returns the build rc. */ -static int -assemble(const char *tool, const char *asmtext, const char *obj, int i) -{ - char src[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/w6apn_%d_%d.s", getpid(), i); - FILE *f = fopen(src, "wb"); - if (!f) return -2; - fputs(asmtext, f); - fclose(f); - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", tool, obj, src); - int rc = runwait(cmd); - unlink(src); - return rc; -} - -static int -files_equal(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - int eq = (fa && fb); - if (fa && fb) { - for (;;) { - int ca = fgetc(fa), cb = fgetc(fb); - if (ca != cb) { eq = 0; break; } - if (ca == EOF) break; - } - } - if (fa) fclose(fa); - if (fb) fclose(fb); - return eq; -} - -struct idrow { const char *label; const char *asmtext; }; - -/* shapes that must assemble byte-identically across the two assemblers. */ -static const struct idrow idrows[] = { - { "ws_after_dollar", "TEXT main,$16\n\tMOVQ\t$ 5, AX\n\tRET\n" }, - { "leading_zero", "TEXT main,$16\n\tMOVQ\t$08, AX\n\tRET\n" }, - /* controls — the canonical shapes w6c actually emits. */ - { "plain_imm", "TEXT main,$16\n\tMOVQ\t$5, AX\n\tRET\n" }, - { "plain_imm_eight", "TEXT main,$16\n\tMOVQ\t$8, AX\n\tRET\n" }, - { "hex_imm", "TEXT main,$16\n\tMOVQ\t$0x1f, AX\n\tRET\n" }, - { "zero_imm", "TEXT main,$16\n\tMOVQ\t$0, AX\n\tRET\n" }, - { "neg_indir", "TEXT main,$16\n\tMOVQ\t-8(BP), AX\n\tRET\n" }, - { "pos_indir", "TEXT main,$16\n\tMOVQ\t8(BP), AX\n\tRET\n" }, -}; - -/* shapes both assemblers must reject. */ -static const struct idrow rejrows[] = { - { "bare_minus_indir", "TEXT main,$16\n\tMOVQ\t-(BP), AX\n\tRET\n" }, -}; - -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); - - if (access(wtool, X_OK) != 0) { - fprintf(stderr, "w6a_parsenum: skip (no %s)\n", wtool); - printf("w6a_parsenum: skipped\n"); - return 77; - } - - int nid = (int)(sizeof idrows / sizeof idrows[0]); - int nrej = (int)(sizeof rejrows / sizeof rejrows[0]); - int fail = 0; - - for (int i = 0; i < nid; i++) { - char co[64], wo[64]; - snprintf(co, sizeof co, "/tmp/w6apn_%d_%d_c.o", getpid(), i); - snprintf(wo, sizeof wo, "/tmp/w6apn_%d_%d_w.o", getpid(), i); - int cr = assemble(ctool, idrows[i].asmtext, co, i); - int wr = assemble(wtool, idrows[i].asmtext, wo, 1000 + i); - if (cr != 0 || wr != 0) { - fprintf(stderr, "w6a_parsenum[%s]: assemble failed " - "(c=%d w=%d)\n", idrows[i].label, cr, wr); - fail++; - } else if (!files_equal(co, wo)) { - fprintf(stderr, "w6a_parsenum[%s]: .o differ across " - "assemblers\n", idrows[i].label); - fail++; - } - unlink(co); unlink(wo); - } - - for (int i = 0; i < nrej; i++) { - char co[64], wo[64]; - snprintf(co, sizeof co, "/tmp/w6apn_%d_r%d_c.o", getpid(), i); - snprintf(wo, sizeof wo, "/tmp/w6apn_%d_r%d_w.o", getpid(), i); - int cr = assemble(ctool, rejrows[i].asmtext, co, 2000 + i); - int wr = assemble(wtool, rejrows[i].asmtext, wo, 3000 + i); - if (cr == 0) { - fprintf(stderr, "w6a_parsenum[%s]: cstage accepted, " - "expected reject\n", rejrows[i].label); - fail++; - } - if (wr == 0) { - fprintf(stderr, "w6a_parsenum[%s]: wwstage accepted, " - "expected reject\n", rejrows[i].label); - fail++; - } - unlink(co); unlink(wo); - } - - if (fail) { - fprintf(stderr, "w6a_parsenum: %d fixtures failed\n", fail); - return 1; - } - printf("w6a_parsenum: %d/%d ok\n", nid + nrej, nid + nrej); - return 0; -} diff --git a/test/wcc/989_datargate_run.c b/test/wcc/989_datargate_run.c deleted file mode 100644 index 488ec170..00000000 --- a/test/wcc/989_datargate_run.c +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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; -}