selfhost+test: zero-init !void / void-alias let-decl slots (#22)

Wwstage's cglet skipped MOVQ $0 for sz=8 slots that cstage
zero-inits unconditionally — !void error types (utf8.invalid)
and void-alias variant tags (utf8.done / utf8.more) drifted
byte-id post-utf8 + lib/strings; promotes STATUS-3 #22 from
latent to bootstrap-blocking. Cstage emits MOVQ $0, -K(BP) in
the prologue for any sz=8 let-decl slot via the natural
type-fallthrough; wwstage's `typeis8byteprimitive` helper
returned false on N_TBANG and on N_TNAME pointing to an alias
that resolves to void, so the gate never fired and the slot
stayed uninitialised.

Polarity catalog: wwstage UNDER — `typeis8byteprimitive`
classifier too narrow at N_TBANG and void-alias N_TNAME.
Convergence wwstage → cstage's natural sz=8 fallthrough (rule
10). N_TBANG arm recurses on inner type (cmd/wcc/check.c:290
resolve_type copies T's kind, only sets iserror — so !T is
8B iff T is 8B); void-alias N_TNAME resolves through alias-
recursion the same way.

Tests:
  - 724_letdecl_zeroinit pins MOVQ $0, -K(BP) presence between
    function prologue and body on canonical !void and void-
    alias rows, plus cmp -s byte-id between stages per row.

Filed follow-up (NOT in scope here): #25 wwstage 8B struct
without rhs still under-emits (structlookup != nil short-
circuits the classifier). Same family as STATUS-4 #36 primsize
composite-aware sizing. No in-tree consumer.

96/96 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id).
This commit is contained in:
2026-05-18 02:49:06 +09:00
parent 0e2c6cd893
commit c893c4bc37
5 changed files with 267 additions and 0 deletions

View File

@@ -854,14 +854,36 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = {
};
if (k == nkind.N_TTUPLE) { return false; };
if (k == nkind.N_TTAGGED){ return false; };
// STATUS-3 #22: `!T` carries the error flag on T's underlying
// shape (cmd/wcc/check.c:290 resolve_type N_TBANG copies T's
// kind, just sets iserror). cstage's N_LET sizes off lu->kind,
// so `!void`/`!i32` land in the sz=8 default and `!str`/`!slice`
// keep their composite slot. Defer to the inner type so
// `let e: !void;` mirrors cstage's MOVQ $0 while `!str` falls
// through to the multi-word fill.
if (k == nkind.N_TBANG) { return typeis8byteprimitive(c, t.lhs); };
if (k == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return false; };
// Plain `void` slot: cstage sz=8 default → MOVQ $0. The let-
// decl is a phantom (a tagged-union variant tag carrier), but
// the slot is still 8B and zero-inits like any other prim.
if (streq(nm, "void")) { return true; };
// Struct alias: not a primitive even if the slot is 8B.
if (structlookup(c, nm) != nil) { return false; };
// Primitive (i8/u8/.../i64/u64/bool/rune/f32/f64/int/...).
// All of these get slot-padded to 8 and zero-init in C.
if (primsize(nm) > 0) { return true; };
// STATUS-3 #22: alias to `!T` or to `void` (Hare-style error
// type / phantom variant). cstage resolves the alias and
// lands on sz=8 default. Follow through aliaslookup so
// `type invalid = !void;` and `type done = void;` zero-init.
if (c != nil) {
let aliased: *node = aliaslookup(c, nm);
if (aliased != nil) {
return typeis8byteprimitive(c, aliased);
};
};
return false;
};
return false;