w6c: materialize full tagged slot on N_IDENT-source return (#263) — cstage align-up to wwstage
cgreturn's passthrough predicate was TYPE-only (istagged && type-eq), with
no source-kind filter. It forwarded the source's AX/DX/CX unchanged, which
is correct ONLY when the source already materialized the full tagged slot
into registers — N_CALL / N_INDEX / N_DOT (the #261-broadened set). For a
tagged LOCAL ident, cgexpr loads only word0 (the tag) into AX, never the
payload into DX, so passthrough dropped the payload: `return v` of a
`(i32|void)=7i32` exited 0 instead of 7. wwstage was already correct — its
forwardtagged kind filter excludes N_IDENT, routing it through the
scratch-widen path. The runtime oracle (cstage 0, wwstage 7) proved cstage
is the bug; this aligns cstage UP.
Gate passthrough to {N_CALL,N_INDEX,N_DOT}; a tagged-ident return now falls
to the existing scratch-slot widen path (cg_widen_tagged_store tagged-subset
N_IDENT arm), byte-identical to wwstage's return scratch-widen. cstage-only
(no combined.ww regen — combined.ww embeds the unchanged wwstage source;
byte-id is blind here, the new 949 rows are the net).
test/949: tagged_ident_ret_i32 (7) + tagged_ident_ret_void (void tag
survives) + register-resident controls tagged_call_ret_ctrl /
tagged_dot_ret_ctrl (passthrough must still fire); INDEX control already
present. All dual-stage run + cs==ww byte-id.
This commit is contained in:
@@ -8843,7 +8843,21 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
Type *vu = (vt && vt->kind == TY_NAMED)
|
||||
? vt->under : vt;
|
||||
int istagged = vu && vu->kind == TY_TAGGED;
|
||||
int passthrough = istagged && (vu == rt ||
|
||||
/* #263: passthrough forwards the source's AX/DX/CX
|
||||
* unchanged — correct ONLY when the source already
|
||||
* materialised the FULL tagged slot into registers:
|
||||
* N_CALL / N_INDEX / N_DOT (the #261-broadened set).
|
||||
* A tagged LOCAL ident leaves only word0 (the tag)
|
||||
* in AX (cgexpr of an ident loads a single word), so
|
||||
* DX (the payload) is garbage and the passthrough
|
||||
* drops it. Route a tagged-ident return through the
|
||||
* scratch-widen path below instead. Mirrors wwstage's
|
||||
* forwardtagged kind filter, which already excludes
|
||||
* N_IDENT (selfhost cgenstmt). */
|
||||
int srcreg = n->lhs->kind == N_CALL ||
|
||||
n->lhs->kind == N_INDEX ||
|
||||
n->lhs->kind == N_DOT;
|
||||
int passthrough = istagged && srcreg && (vu == rt ||
|
||||
type_eq(vt, cg_ret_type));
|
||||
int isstruct = vu && vu->kind == TY_STRUCT;
|
||||
/* #242: a tuple variant must be PACKED into the union
|
||||
|
||||
@@ -692,6 +692,64 @@ static const struct row rows[] = {
|
||||
" case void => yield 1: i32;\n"
|
||||
" };\n"
|
||||
"};\n", 1, 1 },
|
||||
/* #263 IDENT-source tagged return. `return v` where v is a tagged
|
||||
* LOCAL ident: cstage's passthrough predicate was TYPE-only (no kind
|
||||
* filter), so it forwarded the source's AX/DX unchanged — but cgexpr
|
||||
* of a tagged ident loads only word0 (the tag) into AX, never the
|
||||
* payload into DX, so the payload was DROPPED (cstage exited 0 on the
|
||||
* i32-7 repro; wwstage exited 7 — the runtime oracle that proved
|
||||
* cstage is the bug). Fix gates passthrough to the register-resident
|
||||
* source kinds (N_CALL/N_INDEX/N_DOT) and routes a tagged-ident return
|
||||
* through the scratch-widen path, mirroring wwstage's forwardtagged
|
||||
* kind filter. byteid=1: post-fix the materialization is byte-id with
|
||||
* wwstage's return scratch-widen. The void row proves the tag survives
|
||||
* (the dropped-payload bug would still surface the wrong variant; the
|
||||
* void tag is read correctly either way, so its survival pins the tag
|
||||
* column like the #261 i32-AND-void design). */
|
||||
{ "tagged_ident_ret_i32",
|
||||
"package main;\n"
|
||||
"fn g() (i32 | void) = { let v: (i32 | void) = 7i32; return v; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return match (g()) {\n"
|
||||
" case let n: i32 => yield n;\n"
|
||||
" case void => yield 1: i32;\n"
|
||||
" };\n"
|
||||
"};\n", 7, 1 },
|
||||
{ "tagged_ident_ret_void",
|
||||
"package main;\n"
|
||||
"fn g() (i32 | void) = { let v: (i32 | void) = void; return v; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return match (g()) {\n"
|
||||
" case let n: i32 => yield 99: i32;\n"
|
||||
" case void => yield 1: i32;\n"
|
||||
" };\n"
|
||||
"};\n", 1, 1 },
|
||||
/* #263 CONTROLS: the register-resident passthrough must STILL fire
|
||||
* (don't over-gate). CALL-source (forwarding another tagged-returning
|
||||
* fn) + DOT-source (a tagged struct field). INDEX-source is already
|
||||
* covered by tagged_return_i32 above (`return x.o[1]`). */
|
||||
{ "tagged_call_ret_ctrl",
|
||||
"package main;\n"
|
||||
"fn inner() (i32 | void) = { let v: (i32 | void) = 42i32; return v; };\n"
|
||||
"fn outer() (i32 | void) = { return inner(); };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return match (outer()) {\n"
|
||||
" case let n: i32 => yield n;\n"
|
||||
" case void => yield 1: i32;\n"
|
||||
" };\n"
|
||||
"};\n", 42, 1 },
|
||||
{ "tagged_dot_ret_ctrl",
|
||||
"package main;\n"
|
||||
"type w = struct { f: (i32 | void) };\n"
|
||||
"fn dot(x: *w) (i32 | void) = { return x.f; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: w;\n"
|
||||
" x.f = 63;\n"
|
||||
" return match (dot(&x)) {\n"
|
||||
" case let n: i32 => yield n;\n"
|
||||
" case void => yield 1: i32;\n"
|
||||
" };\n"
|
||||
"};\n", 63, 1 },
|
||||
{ NULL, NULL, 0, 0 }
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user