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.
This commit is contained in:
2026-06-12 22:45:10 +09:00
parent fc47c3d0f2
commit e09a8c1775
2 changed files with 35 additions and 23 deletions

View File

@@ -10716,7 +10716,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (sl_off == 0) { if (sl_off == 0) {
sl_off = localoff(c, &locals, "@asrt_spill", sl_off = localoff(c, &locals, "@asrt_spill",
slot_size, cg_frame); 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 /* #37: >32B box read — ADDRESS in AX; copy
* the whole box from memory. */ * the whole box from memory. */
cgexpr(c, s, locals); cgexpr(c, s, locals);

View File

@@ -14,24 +14,21 @@
* • `is` half = ALIGN-UP. cstage reads the tag correctly (MOVQ g(SB),AX); * • `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 → * 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). * 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 * • `as` half = #263 BOTH-WRONG, now CLOSED both stages. cstage N_TYPEASSERT
* fails: it spills cgexpr's AX (tag only) plus UNINITIALIZED DX as the * on a global ident spilled cgexpr's AX (tag only) plus UNINITIALIZED DX as
* payload (never loads g(SB)+8) → returns garbage (0). The wwstage FIX * the payload (never loaded g(SB)+8) → garbage (0). The ww half landed in
* copies the global box words from g(SB)+0/+8[/+16] into a fresh * 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 * @asrt_spill so the tag-check + payload load index off memory like a local)
* local → ww runtime-correct, while cstage stays wrong: cs≠ww residual * landed in ww-core TASK #46. Both stages now read the real payload and the
* BY DESIGN until the cstage half lands (ww-core TASK #46). The as_* rows * .s is byte-identical.
* assert ww-correct AND pin cstage's documented-wrong value so the residual
* is never misread as a regression.
* *
* Rows (built+run on cstage `ww` and wwstage `ww_ww`; each driver checked * Rows (built+run on cstage `ww` and wwstage `ww_ww`; cs==ww on every row):
* against ITS expected — want_cs / want_ww — to encode the #263 residual): * row | shape | exit | cat
* row | shape | cs | ww | cat * ----------+----------------------------------------+------+--------
* ----------+----------------------------------------+----+----+-------- * is_match | g:(i64|bool)=5; if g is i64 →0 else 12 | 0 | align-UP
* 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 | 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 | 42 | #46
* as_i64 | g:(i64|bool)=42; (g as i64):int | 0 | 42 | #263 (#46) * as_str | g:(str|i64)="hello"; len(g as str) | 5 | #46
* as_str | g:(str|i64)="hello"; len(g as str) | 0 | 5 | #263 (#46)
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -71,22 +68,22 @@ static const struct row rows[] = {
"export fn main() int = { return check(); };\n", "export fn main() int = { return check(); };\n",
0, 0 }, 0, 0 },
/* #263: cstage spills uninitialized DX as the payload (task #46) → /* #46 CLOSED both stages: cstage now copies the box from g(SB) into the
* returns 0; wwstage copies the box from g(SB) → 42. */ * @asrt_spill (was spilling uninitialized DX) → 42 == wwstage. */
{ "as_i64", { "as_i64",
"package main;\n" "package main;\n"
"let g: (i64 | bool) = 42;\n" "let g: (i64 | bool) = 42;\n"
"fn check() int = { let x: i64 = g as i64; return x: int; };\n" "fn check() int = { let x: i64 = g as i64; return x: int; };\n"
"export fn main() int = { return check(); };\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", { "as_str",
"package main;\n" "package main;\n"
"let g: (str | i64) = \"hello\";\n" "let g: (str | i64) = \"hello\";\n"
"fn check() int = { let s: str = g as str; return len(s): int; };\n" "fn check() int = { let s: str = g as str; return len(s): int; };\n"
"export fn main() int = { return check(); };\n", "export fn main() int = { return check(); };\n",
0, 5 }, 5, 5 },
}; };
/* run_build — build+run `src` via `driver`; returns the binary's exit /* run_build — build+run `src` via `driver`; returns the binary's exit