From e09a8c17757824344f47b87ce5c19a7fea39e15b Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 12 Jun 2026 22:45:10 +0900 Subject: [PATCH] w6c: 'as' on a module-global tagged ident loads the full box cstage's N_TYPEASSERT assumed cgexpr filled the registers and spilled an uninitialized payload (cs=0 for any stored value); mirror the landed wwstage emission (tag/payload/cap from g(SB)). Flips the residual row to cs==ww==correct. Task #46. --- cmd/w6c/cgen.c | 17 +++++++++++++- test/wcc/989_globtagisas_run.c | 41 ++++++++++++++++------------------ 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 4220182c..f168d876 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -10716,7 +10716,22 @@ cgexpr(Cg *c, Node *n, Local *locals) if (sl_off == 0) { sl_off = localoff(c, &locals, "@asrt_spill", slot_size, cg_frame); - if (cg_tagged_memread(s)) { + if (s && s->kind == N_IDENT && let_islet(s->str)) { + /* #46 (#263): a module-global tagged ident — + * cgexpr loads only the tag word (MOVQ g(SB),AX), + * so the DX/CX spill below would write + * uninitialized payload. Copy the whole box from + * g(SB)+0/+8[/+16] into the spill so the + * tag-check + payload load index off memory like + * a local. ww half landed F8-c3. */ + ins2(c, A_LEAQ, masym(c, s->str), areg(D_AX)); + for (int k = 0; k < slot_size; k += 8) { + ins2(c, A_MOVQ, amem(D_AX, k), + areg(D_DX)); + ins2(c, A_MOVQ, areg(D_DX), + amem(D_BP, sl_off + k)); + } + } else if (cg_tagged_memread(s)) { /* #37: >32B box read — ADDRESS in AX; copy * the whole box from memory. */ cgexpr(c, s, locals); diff --git a/test/wcc/989_globtagisas_run.c b/test/wcc/989_globtagisas_run.c index 7ff5f886..be45aa85 100644 --- a/test/wcc/989_globtagisas_run.c +++ b/test/wcc/989_globtagisas_run.c @@ -14,24 +14,21 @@ * • `is` half = ALIGN-UP. cstage reads the tag correctly (MOVQ g(SB),AX); * wwstage read 0(BP). FIX routes the global ident to MOVQ g(SB),AX → * cs==ww + runtime-correct (the is_* rows assert want on BOTH drivers). - * • `as` half = #263 BOTH-WRONG. cstage N_TYPEASSERT on a global ident also - * fails: it spills cgexpr's AX (tag only) plus UNINITIALIZED DX as the - * payload (never loads g(SB)+8) → returns garbage (0). The wwstage FIX - * copies the global box words from g(SB)+0/+8[/+16] into a fresh - * @asrt_spill so the tag-check + payload load index off memory like a - * local → ww runtime-correct, while cstage stays wrong: cs≠ww residual - * BY DESIGN until the cstage half lands (ww-core TASK #46). The as_* rows - * assert ww-correct AND pin cstage's documented-wrong value so the residual - * is never misread as a regression. + * • `as` half = #263 BOTH-WRONG, now CLOSED both stages. cstage N_TYPEASSERT + * on a global ident spilled cgexpr's AX (tag only) plus UNINITIALIZED DX as + * the payload (never loaded g(SB)+8) → garbage (0). The ww half landed in + * F8-c3; the cstage half (copy the box words from g(SB)+0/+8[/+16] into the + * @asrt_spill so the tag-check + payload load index off memory like a local) + * landed in ww-core TASK #46. Both stages now read the real payload and the + * .s is byte-identical. * - * Rows (built+run on cstage `ww` and wwstage `ww_ww`; each driver checked - * against ITS expected — want_cs / want_ww — to encode the #263 residual): - * row | shape | cs | ww | cat - * ----------+----------------------------------------+----+----+-------- - * is_match | g:(i64|bool)=5; if g is i64 →0 else 12 | 0 | 0 | align-UP - * is_nomatch| g:(i64|bool)=5; if g is bool →13 else 0 | 0 | 0 | align-UP - * as_i64 | g:(i64|bool)=42; (g as i64):int | 0 | 42 | #263 (#46) - * as_str | g:(str|i64)="hello"; len(g as str) | 0 | 5 | #263 (#46) + * Rows (built+run on cstage `ww` and wwstage `ww_ww`; cs==ww on every row): + * row | shape | exit | cat + * ----------+----------------------------------------+------+-------- + * is_match | g:(i64|bool)=5; if g is i64 →0 else 12 | 0 | align-UP + * is_nomatch| g:(i64|bool)=5; if g is bool →13 else 0 | 0 | align-UP + * as_i64 | g:(i64|bool)=42; (g as i64):int | 42 | #46 + * as_str | g:(str|i64)="hello"; len(g as str) | 5 | #46 */ #include #include @@ -71,22 +68,22 @@ static const struct row rows[] = { "export fn main() int = { return check(); };\n", 0, 0 }, - /* #263: cstage spills uninitialized DX as the payload (task #46) → - * returns 0; wwstage copies the box from g(SB) → 42. */ + /* #46 CLOSED both stages: cstage now copies the box from g(SB) into the + * @asrt_spill (was spilling uninitialized DX) → 42 == wwstage. */ { "as_i64", "package main;\n" "let g: (i64 | bool) = 42;\n" "fn check() int = { let x: i64 = g as i64; return x: int; };\n" "export fn main() int = { return check(); };\n", - 0, 42 }, + 42, 42 }, - /* #263: str variant — wwstage copies the +16 cap word too (task #46). */ + /* #46 str variant — both stages copy the +16 cap word too. */ { "as_str", "package main;\n" "let g: (str | i64) = \"hello\";\n" "fn check() int = { let s: str = g as str; return len(s): int; };\n" "export fn main() int = { return check(); };\n", - 0, 5 }, + 5, 5 }, }; /* run_build — build+run `src` via `driver`; returns the binary's exit