selfhost/cmd/wcc: peel NAMED in elemissignedc + refresh #63 comments (#65)

#64 flowed per-decl TY_NAMED wrappers, falsifying two #63-era assumptions
surfaced in the flip review.

elemissignedc read ti.sub for element signedness without peeling TY_NAMED;
a NAMED-of-indexable would read NAMED.sub (nil) instead of the underlying's.
Add the transitive peel ahead of the .sub read, mirroring cstage idx_eff's
type_unwrap (cmd/w6c/cgen.c:790) before eff->sub (:3518-3520). Byte-id-neutral:
every aliased indexable in-tree has a u8 element (typeissigned=false either
way). Independent .sub/.under inventory confirms elemissignedc was the sole
structural reader missing a peel (castsrcprim + TK_AMP already NAMED-guarded;
typeis* handle element NAMED via .under recursion; typeeq is nominal by
design and must not peel).

Refresh the 5 #63 peel-site comments (slotsize, fieldsize, nullableptrtag,
tupleelemslot, fieldslotsize): the peel now actively fires (#64 builds NAMED)
rather than being a no-op; byte-id holds because NAMED collapses to the
alias-invariant underlying.

make test 133/133 (byte-id 990-997 green).
This commit is contained in:
2026-05-23 19:54:00 +09:00
parent 3866ea24f5
commit 0fde4beeb4
4 changed files with 87 additions and 45 deletions

View File

@@ -8139,9 +8139,10 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
// fallback arm).
fn tupleelemslot(pt: *tinfo) u64 = {
if (pt == nil) { return 8u64; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
let t: *tinfo = pt;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };
@@ -8170,9 +8171,10 @@ fn tupleelemslot(pt: *tinfo) u64 = {
// pad-to-8); arrays use their slot-padded element-stride * elen.
fn fieldslotsize(ft: *tinfo) u64 = {
if (ft == nil) { return 8u64; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
let t: *tinfo = ft;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };
@@ -11271,6 +11273,15 @@ fn elemissignedc(c: *cgen, t: *node) bool = {
if (t == nil) { return false; };
let ti: *tinfo = t.type_: *tinfo;
if (ti == nil) { return false; };
// #65 Phase-N step-2 cleanup: peel TY_NAMED before the .sub read.
// #64 now flows per-decl NAMED wrappers, so a NAMED-of-(`*T`/`[]T`/
// `[N]T`) reaching here would read NAMED.sub (nil) instead of the
// element. Mirrors cstage idx_eff's type_unwrap (cmd/w6c/cgen.c:790)
// before the eff->sub read (:3518-3520). Byte-id-neutral: every
// aliased indexable in-tree has a u8 element (typeissigned=false
// either way). Transitive peel matches the #63 idiom.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return false; };
return typeissigned(ti.sub);
};
@@ -12195,9 +12206,10 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
if (typn == nil) { return 8; };
let ti: *tinfo = typn.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 8; };
let kk: tykind = ti.kind;
@@ -12232,9 +12244,10 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let ti: *tinfo = tnode.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 8; };
let k: tykind = ti.kind;
@@ -12581,9 +12594,10 @@ export fn nullableptrtag(t: *node) i32 = {
if (t == nil) { return 0; };
let ti: *tinfo = t.type_: *tinfo;
if (ti == nil) { return 0; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 0; };
if (ti.kind != tykind.TY_TAGGED) { return 0; };

View File

@@ -747,6 +747,15 @@ fn elemissignedc(c: *cgen, t: *node) bool = {
if (t == nil) { return false; };
let ti: *tinfo = t.type_: *tinfo;
if (ti == nil) { return false; };
// #65 Phase-N step-2 cleanup: peel TY_NAMED before the .sub read.
// #64 now flows per-decl NAMED wrappers, so a NAMED-of-(`*T`/`[]T`/
// `[N]T`) reaching here would read NAMED.sub (nil) instead of the
// element. Mirrors cstage idx_eff's type_unwrap (cmd/w6c/cgen.c:790)
// before the eff->sub read (:3518-3520). Byte-id-neutral: every
// aliased indexable in-tree has a u8 element (typeissigned=false
// either way). Transitive peel matches the #63 idiom.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return false; };
return typeissigned(ti.sub);
};
@@ -1671,9 +1680,10 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
if (typn == nil) { return 8; };
let ti: *tinfo = typn.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 8; };
let kk: tykind = ti.kind;
@@ -1708,9 +1718,10 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let ti: *tinfo = tnode.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 8; };
let k: tykind = ti.kind;
@@ -2057,9 +2068,10 @@ export fn nullableptrtag(t: *node) i32 = {
if (t == nil) { return 0; };
let ti: *tinfo = t.type_: *tinfo;
if (ti == nil) { return 0; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 0; };
if (ti.kind != tykind.TY_TAGGED) { return 0; };

View File

@@ -1042,9 +1042,10 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
// fallback arm).
fn tupleelemslot(pt: *tinfo) u64 = {
if (pt == nil) { return 8u64; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
let t: *tinfo = pt;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };
@@ -1073,9 +1074,10 @@ fn tupleelemslot(pt: *tinfo) u64 = {
// pad-to-8); arrays use their slot-padded element-stride * elen.
fn fieldslotsize(ft: *tinfo) u64 = {
if (ft == nil) { return 8u64; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
let t: *tinfo = ft;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };

View File

@@ -8139,9 +8139,10 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
// fallback arm).
fn tupleelemslot(pt: *tinfo) u64 = {
if (pt == nil) { return 8u64; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
let t: *tinfo = pt;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };
@@ -8170,9 +8171,10 @@ fn tupleelemslot(pt: *tinfo) u64 = {
// pad-to-8); arrays use their slot-padded element-stride * elen.
fn fieldslotsize(ft: *tinfo) u64 = {
if (ft == nil) { return 8u64; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
let t: *tinfo = ft;
for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; };
if (t == nil) { return 8u64; };
@@ -11271,6 +11273,15 @@ fn elemissignedc(c: *cgen, t: *node) bool = {
if (t == nil) { return false; };
let ti: *tinfo = t.type_: *tinfo;
if (ti == nil) { return false; };
// #65 Phase-N step-2 cleanup: peel TY_NAMED before the .sub read.
// #64 now flows per-decl NAMED wrappers, so a NAMED-of-(`*T`/`[]T`/
// `[N]T`) reaching here would read NAMED.sub (nil) instead of the
// element. Mirrors cstage idx_eff's type_unwrap (cmd/w6c/cgen.c:790)
// before the eff->sub read (:3518-3520). Byte-id-neutral: every
// aliased indexable in-tree has a u8 element (typeissigned=false
// either way). Transitive peel matches the #63 idiom.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return false; };
return typeissigned(ti.sub);
};
@@ -12195,9 +12206,10 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
if (typn == nil) { return 8; };
let ti: *tinfo = typn.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 8; };
let kk: tykind = ti.kind;
@@ -12232,9 +12244,10 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let ti: *tinfo = tnode.type_: *tinfo;
if (ti == nil) { return 8; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 8; };
let k: tykind = ti.kind;
@@ -12581,9 +12594,10 @@ export fn nullableptrtag(t: *node) i32 = {
if (t == nil) { return 0; };
let ti: *tinfo = t.type_: *tinfo;
if (ti == nil) { return 0; };
// #63 Phase-N step 1: peel TY_NAMED before this structural query;
// wrappers are built in step 2 (#64). No-op today (tinfofornode
// still collapses aliases → no TY_NAMED), so byte-id is unchanged.
// #63 Phase-N step 1: peel TY_NAMED before this structural query.
// #64 builds per-decl NAMED wrappers (tinfofornode), so the peel
// now fires on aliased operands; byte-id holds because it collapses
// NAMED to the alias-invariant underlying this read consumes.
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
if (ti == nil) { return 0; };
if (ti.kind != tykind.TY_TAGGED) { return 0; };