From d617a698b06233a8432ebc3328d60f3ce801761c Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 20 May 2026 17:41:41 +0900 Subject: [PATCH] selfhost/cmd/wcc: route cgalloc field-store foff through emitdispreg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four ad-hoc emit sites in cgalloc's N_STRUCTLIT field-store loop (cgenexpr.ww:2770-2802) wrote the displacement via emitint(foff: i64); emitline("(REG)\n"), producing 0(REG) for foff=0. cstage's txt.c:130-134 omits the zero displacement, so ww2.s (cstage compiling wwstage) and ww3.s (wwstage compiling wwstage) would diverge the moment any selfhost site migrates to alloc(T{...})!. Dormant today only because selfhost source has no alloc(T{...})! yet. Route the four sites through emitdispreg (cgen.ww:786), the existing SSoT that already omits zero displacement. Extends test/wcc/758_cgalloc_str_field.c with 4 table-driven asm_disp_rows pinning the displacement text for {str/int/f64 at offset 0, str at offset 8}. Internal subtest count: 16 → 20. The 3 foff=0 rows fail without the fix. --- selfhost/cmd/w6c/main.combined.ww | 16 ++-- selfhost/cmd/wcc/cgenexpr.ww | 16 ++-- selfhost/cmd/wwdump/main.combined.ww | 16 ++-- test/wcc/758_cgalloc_str_field.c | 120 +++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 24 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index bb90fcab..1b55e310 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -15983,8 +15983,8 @@ fn cgalloc(c: *cgen, n: *node) void = { emitline("\t"); emitline(mov); emitline("\tX0, "); - emitint(fi.foff: i64); - emitline("(BX)\n"); + emitdispreg(fi.foff: i64, "BX"); + emitline("\n"); fi = nil; } else { if (isstrtype(c, fi.tnode)) { // alloc(T{ fval = s }) for str field: cgexpr @@ -15993,11 +15993,11 @@ fn cgalloc(c: *cgen, n: *node) void = { // cmd/w6c/cgen.c:4184-4190. emitline("\tMOVQ\t(SP), CX\n"); emitline("\tMOVQ\tAX, "); - emitint(fi.foff: i64); - emitline("(CX)\n"); + emitdispreg(fi.foff: i64, "CX"); + emitline("\n"); emitline("\tMOVQ\tBX, "); - emitint((fi.foff + 8): i64); - emitline("(CX)\n"); + emitdispreg((fi.foff + 8): i64, "CX"); + emitline("\n"); fi = nil; } else { emitline("\tMOVQ\t(SP), BX\n"); @@ -16005,8 +16005,8 @@ fn cgalloc(c: *cgen, n: *node) void = { emitline("\t"); emitline(sop); emitline("\tAX, "); - emitint(fi.foff: i64); - emitline("(BX)\n"); + emitdispreg(fi.foff: i64, "BX"); + emitline("\n"); fi = nil; };}; } else { diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 130baf1e..e6a91ecd 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -2774,8 +2774,8 @@ fn cgalloc(c: *cgen, n: *node) void = { emitline("\t"); emitline(mov); emitline("\tX0, "); - emitint(fi.foff: i64); - emitline("(BX)\n"); + emitdispreg(fi.foff: i64, "BX"); + emitline("\n"); fi = nil; } else { if (isstrtype(c, fi.tnode)) { // alloc(T{ fval = s }) for str field: cgexpr @@ -2784,11 +2784,11 @@ fn cgalloc(c: *cgen, n: *node) void = { // cmd/w6c/cgen.c:4184-4190. emitline("\tMOVQ\t(SP), CX\n"); emitline("\tMOVQ\tAX, "); - emitint(fi.foff: i64); - emitline("(CX)\n"); + emitdispreg(fi.foff: i64, "CX"); + emitline("\n"); emitline("\tMOVQ\tBX, "); - emitint((fi.foff + 8): i64); - emitline("(CX)\n"); + emitdispreg((fi.foff + 8): i64, "CX"); + emitline("\n"); fi = nil; } else { emitline("\tMOVQ\t(SP), BX\n"); @@ -2796,8 +2796,8 @@ fn cgalloc(c: *cgen, n: *node) void = { emitline("\t"); emitline(sop); emitline("\tAX, "); - emitint(fi.foff: i64); - emitline("(BX)\n"); + emitdispreg(fi.foff: i64, "BX"); + emitline("\n"); fi = nil; };}; } else { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 1d014716..e83c0d98 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -15983,8 +15983,8 @@ fn cgalloc(c: *cgen, n: *node) void = { emitline("\t"); emitline(mov); emitline("\tX0, "); - emitint(fi.foff: i64); - emitline("(BX)\n"); + emitdispreg(fi.foff: i64, "BX"); + emitline("\n"); fi = nil; } else { if (isstrtype(c, fi.tnode)) { // alloc(T{ fval = s }) for str field: cgexpr @@ -15993,11 +15993,11 @@ fn cgalloc(c: *cgen, n: *node) void = { // cmd/w6c/cgen.c:4184-4190. emitline("\tMOVQ\t(SP), CX\n"); emitline("\tMOVQ\tAX, "); - emitint(fi.foff: i64); - emitline("(CX)\n"); + emitdispreg(fi.foff: i64, "CX"); + emitline("\n"); emitline("\tMOVQ\tBX, "); - emitint((fi.foff + 8): i64); - emitline("(CX)\n"); + emitdispreg((fi.foff + 8): i64, "CX"); + emitline("\n"); fi = nil; } else { emitline("\tMOVQ\t(SP), BX\n"); @@ -16005,8 +16005,8 @@ fn cgalloc(c: *cgen, n: *node) void = { emitline("\t"); emitline(sop); emitline("\tAX, "); - emitint(fi.foff: i64); - emitline("(BX)\n"); + emitdispreg(fi.foff: i64, "BX"); + emitline("\n"); fi = nil; };}; } else { diff --git a/test/wcc/758_cgalloc_str_field.c b/test/wcc/758_cgalloc_str_field.c index e11c101f..1077869f 100644 --- a/test/wcc/758_cgalloc_str_field.c +++ b/test/wcc/758_cgalloc_str_field.c @@ -221,6 +221,52 @@ static const struct asm_row asm_rows[] = { "rt_alloc" }, }; +/* asm_disp_row — pins the foff=0 displacement formatting (#24 part B). + * cstage's txt.c prints `(REG)` for zero displacement (cmd/w6c/txt.c + * prAdr D_INDIR); pre-fix wwstage emitted `0(REG)` via an unconditional + * `emitint(fi.foff)`. Each row supplies a `want_line` that MUST appear + * verbatim in BOTH stages' .s output. With the fix (4 emit sites routed + * through `emitdispreg` SSoT) wwstage matches cstage; without it the + * wwstage half fails because `0(REG)` is a different line. */ +struct asm_disp_row { const char *label; const char *src; const char *want_line; }; + +static const struct asm_disp_row asm_disp_rows[] = { + /* str at foff=0. Covers the str-field branch's first store + * (cgenexpr.ww cgalloc, MOVQ AX, (CX)). The companion +8 store + * always has a non-zero displacement so it's not the gate; the + * .ptr store is. */ + { "alloc_str_at_offset0", + "package main;\n" + "type holder = struct { s: str };\n" + "fn dummy() *holder = { return alloc(holder { s = \"x\" })!; };\n", + "\tMOVQ\tAX, (CX)\n" }, + /* str at non-zero foff. Pins that the displacement IS emitted + * (`8(CX)`) when foff != 0 — emitdispreg must not suppress + * non-zero offsets too. Pre-fix and post-fix both pass this; it + * guards against a future over-correction. */ + { "alloc_str_at_nonzero_offset", + "package main;\n" + "type holder = struct { pad: i64, s: str };\n" + "fn dummy() *holder = {\n" + " return alloc(holder { pad = 0, s = \"x\" })!;\n" + "};\n", + "\tMOVQ\tAX, 8(CX)\n" }, + /* Generic 8-byte field at foff=0 (non-str, non-float path). Covers + * the `else` branch's `MOVQ AX, (BX)` store via fieldstoreop. */ + { "alloc_int_at_offset0", + "package main;\n" + "type holder = struct { n: i64 };\n" + "fn dummy() *holder = { return alloc(holder { n = 42 })!; };\n", + "\tMOVQ\tAX, (BX)\n" }, + /* f64 at foff=0. Covers the float branch's `MOVSD X0, (BX)` + * store; mirrors the int row but routes through the MOVSD emit. */ + { "alloc_f64_at_offset0", + "package main;\n" + "type holder = struct { f: f64 };\n" + "fn dummy() *holder = { return alloc(holder { f = 1.0f64 })!; };\n", + "\tMOVSD\tX0, (BX)\n" }, +}; + /* asm_call_check — compile via direct w6c / w6c_ww (no `ww build`), * extract the `CALL\t(SB)` line referencing alloc/rt_alloc from * each .s file, and verify (a) both stages emit the same line and @@ -310,6 +356,74 @@ asm_call_check(const char *bin, const struct asm_row *r, int i) return rc; } +/* file_contains — true iff `path`'s contents contain `needle` as a + * substring. Tab/newline-bearing needles match the literal byte sequence + * the .s file holds, so `(CX)\n` does not collide with `0(CX)\n`. */ +static int +file_contains(const char *path, const char *needle) +{ + FILE *f = fopen(path, "rb"); + if (!f) return 0; + fseek(f, 0, SEEK_END); + long sz = ftell(f); + if (sz < 0 || sz > (1<<20)) { fclose(f); return 0; } + fseek(f, 0, SEEK_SET); + char *buf = (char*)malloc((size_t)sz + 1); + if (!buf) { fclose(f); return 0; } + size_t got = fread(buf, 1, (size_t)sz, f); + buf[got] = '\0'; + fclose(f); + int hit = strstr(buf, needle) != NULL; + free(buf); + return hit; +} + +/* asm_disp_check — compile via direct w6c / w6c_ww and verify that the + * literal `r->want_line` appears in BOTH stages' .s output. Pre-fix + * wwstage substituted `0(REG)` for `(REG)` so the foff=0 rows fail on + * the wwstage half; post-fix both halves carry the same text. */ +static int +asm_disp_check(const char *bin, const struct asm_disp_row *r, int i) +{ + char src[64], cs[64], ws[64], cmd[1024]; + snprintf(src, sizeof src, "/tmp/wcas_disp_%d_%d.ww", getpid(), i); + snprintf(cs, sizeof cs, "/tmp/wcas_disp_%d_%d_c.s", getpid(), i); + snprintf(ws, sizeof ws, "/tmp/wcas_disp_%d_%d_w.s", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + int rc = 0; + snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "disp[%s]: w6c errored\n", r->label); + unlink(src); + return -1; + } + snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", + bin, ws, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "disp[%s]: w6c_ww errored\n", r->label); + unlink(src); unlink(cs); + return -1; + } + + int chit = file_contains(cs, r->want_line); + int whit = file_contains(ws, r->want_line); + if (!chit || !whit) { + fprintf(stderr, + "disp[%s]: want_line missing (cstage=%d wwstage=%d) " + "want=<%s>\n", + r->label, chit, whit, r->want_line); + rc = -1; + } + + unlink(src); unlink(cs); unlink(ws); + return rc; +} + int main(void) { @@ -370,6 +484,12 @@ main(void) if (asm_call_check(bin, &asm_rows[i], i) != 0) fail++; } + int dn = (int)(sizeof asm_disp_rows / sizeof asm_disp_rows[0]); + for (int i = 0; i < dn; i++) { + total++; + if (asm_disp_check(bin, &asm_disp_rows[i], i) != 0) + fail++; + } } else { fprintf(stderr, "cgalloc_str_field: skip asm rows (no %s)\n", w6c_ww);