cgen: str N_DOT field read -> 3-word {ptr,len,cap} -- Phase 2 C4.6 (both stages)

Reading a str-typed struct field loaded only 2 words (ptr,len), dropping the cap word. Fold the str-field read onto the adjacent proven slice-field arm by widening its kind-gate to include str (type_isstr/isstrtype, never size==24). Sites: S1 direct struct field (local BP + global CX base) and S2 field through a *struct local (pst.f). cstage==wwstage byte-identical; the slice-field arms stay unchanged for slices.

C4.6 bundles the S1 local-field fold with a FORCED global-field lift -- the rule-11 reason they cannot split: cstage reads a field with ONE unified base_reg arm, so folding str covers local AND global together. For byte-id, ww's global field path must then lift in the SAME commit -- but ww splits local/global and its global arm has no slice sibling, so it is authored as ww's own local slice-field arm retargeted to the CX base (cap->CX last, base survives). The underlying cstage-unifies / ww-splits field-arm divergence is a separate filed structural follow-up, not resolved here.

test/wcc/933: table-driven runtime .cap-survives over local/global/*struct field reads, both drivers; verified fail-before/pass-after. The local-field row interposes a CX-clobbering call so a 2-word read cannot coincidentally pass on stale CX (the field store otherwise leaves the cap word lingering in CX).

main.combined.ww regenerated via the canonical make path (md5-stable), per the 1140a59 precedent.
This commit is contained in:
2026-05-24 13:31:58 +09:00
parent 97707155ae
commit 90deeb3821
6 changed files with 301 additions and 131 deletions

View File

@@ -15195,26 +15195,18 @@ fn cgdot(c: *cgen, n: *node) void = {
fi.foff, tsz);
return;
};
// str field via *struct: load len into a
// scratch first (so loading ptr into AX
// last leaves (AX=ptr, BX=len)).
// str IS []u8 — same 3-word {ptr,len,cap}
// as a slice field via *struct: load
// (ptr, len, cap) into (AX, BX, CX). BX
// holds the *struct pointer, so load .len
// LAST so the earlier reads still index
// off the base. str folds onto the slice
// arm (#1/Phase 3 collapse; cite cstage
// cgen.c N_DOT *struct S2).
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "BX");
emitline(", CX\n");
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\tCX, BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field via *struct: load
// (ptr, len, cap) into (AX, BX, CX).
// BX holds the *struct pointer, so
// load .len LAST so the earlier
// reads still index off the base.
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
@@ -15244,7 +15236,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
}; }; };
}; };
return;
};
fi = fi.finext;
@@ -15279,19 +15271,13 @@ fn cgdot(c: *cgen, n: *node) void = {
lc.off + fi.foff, tsz);
return;
};
// str field: load both halves so chained
// `.ptr` / `.len` see (AX=ptr, BX=len).
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP), BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field: load (ptr, len, cap)
// into (AX, BX, CX). Base is BP so
// no aliasing — order doesn't matter.
// str IS []u8 — same 3-word {ptr,len,cap}
// as a slice field: load (ptr, len, cap)
// into (AX, BX, CX). Base is BP so no
// aliasing — order doesn't matter. str
// folds onto the slice arm (#1/Phase 3
// collapse; cite cstage cgen.c N_DOT S1).
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
@@ -15317,7 +15303,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
}; }; };
}; };
return;
};
fi = fi.finext;
@@ -15509,6 +15495,12 @@ fn cgdot(c: *cgen, n: *node) void = {
cgloadtaggedfield(c, "CX", fi.foff, tsz);
return;
};
// str IS []u8 — 3-word {ptr,len,cap}, the local
// slice-field arm (BP) retargeted to the CX global
// base. cap→CX LAST: CX is the base, so .ptr/.len
// must read first. cstage folds local+global in one
// base_reg arm; ww splits them, so this global arm
// carries its own lift (filed divergence task).
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "CX");
@@ -15516,6 +15508,9 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "CX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 16): i64, "CX");
emitline(", CX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 global field: route through X0.
let mov: str = "MOVSD";

View File

@@ -1367,26 +1367,18 @@ fn cgdot(c: *cgen, n: *node) void = {
fi.foff, tsz);
return;
};
// str field via *struct: load len into a
// scratch first (so loading ptr into AX
// last leaves (AX=ptr, BX=len)).
// str IS []u8 — same 3-word {ptr,len,cap}
// as a slice field via *struct: load
// (ptr, len, cap) into (AX, BX, CX). BX
// holds the *struct pointer, so load .len
// LAST so the earlier reads still index
// off the base. str folds onto the slice
// arm (#1/Phase 3 collapse; cite cstage
// cgen.c N_DOT *struct S2).
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "BX");
emitline(", CX\n");
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\tCX, BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field via *struct: load
// (ptr, len, cap) into (AX, BX, CX).
// BX holds the *struct pointer, so
// load .len LAST so the earlier
// reads still index off the base.
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
@@ -1416,7 +1408,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
}; }; };
}; };
return;
};
fi = fi.finext;
@@ -1451,19 +1443,13 @@ fn cgdot(c: *cgen, n: *node) void = {
lc.off + fi.foff, tsz);
return;
};
// str field: load both halves so chained
// `.ptr` / `.len` see (AX=ptr, BX=len).
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP), BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field: load (ptr, len, cap)
// into (AX, BX, CX). Base is BP so
// no aliasing — order doesn't matter.
// str IS []u8 — same 3-word {ptr,len,cap}
// as a slice field: load (ptr, len, cap)
// into (AX, BX, CX). Base is BP so no
// aliasing — order doesn't matter. str
// folds onto the slice arm (#1/Phase 3
// collapse; cite cstage cgen.c N_DOT S1).
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
@@ -1489,7 +1475,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
}; }; };
}; };
return;
};
fi = fi.finext;
@@ -1681,6 +1667,12 @@ fn cgdot(c: *cgen, n: *node) void = {
cgloadtaggedfield(c, "CX", fi.foff, tsz);
return;
};
// str IS []u8 — 3-word {ptr,len,cap}, the local
// slice-field arm (BP) retargeted to the CX global
// base. cap→CX LAST: CX is the base, so .ptr/.len
// must read first. cstage folds local+global in one
// base_reg arm; ww splits them, so this global arm
// carries its own lift (filed divergence task).
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "CX");
@@ -1688,6 +1680,9 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "CX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 16): i64, "CX");
emitline(", CX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 global field: route through X0.
let mov: str = "MOVSD";

View File

@@ -15195,26 +15195,18 @@ fn cgdot(c: *cgen, n: *node) void = {
fi.foff, tsz);
return;
};
// str field via *struct: load len into a
// scratch first (so loading ptr into AX
// last leaves (AX=ptr, BX=len)).
// str IS []u8 — same 3-word {ptr,len,cap}
// as a slice field via *struct: load
// (ptr, len, cap) into (AX, BX, CX). BX
// holds the *struct pointer, so load .len
// LAST so the earlier reads still index
// off the base. str folds onto the slice
// arm (#1/Phase 3 collapse; cite cstage
// cgen.c N_DOT *struct S2).
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "BX");
emitline(", CX\n");
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\tCX, BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field via *struct: load
// (ptr, len, cap) into (AX, BX, CX).
// BX holds the *struct pointer, so
// load .len LAST so the earlier
// reads still index off the base.
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
@@ -15244,7 +15236,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
}; }; };
}; };
return;
};
fi = fi.finext;
@@ -15279,19 +15271,13 @@ fn cgdot(c: *cgen, n: *node) void = {
lc.off + fi.foff, tsz);
return;
};
// str field: load both halves so chained
// `.ptr` / `.len` see (AX=ptr, BX=len).
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP), BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field: load (ptr, len, cap)
// into (AX, BX, CX). Base is BP so
// no aliasing — order doesn't matter.
// str IS []u8 — same 3-word {ptr,len,cap}
// as a slice field: load (ptr, len, cap)
// into (AX, BX, CX). Base is BP so no
// aliasing — order doesn't matter. str
// folds onto the slice arm (#1/Phase 3
// collapse; cite cstage cgen.c N_DOT S1).
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
@@ -15317,7 +15303,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
}; }; };
}; };
return;
};
fi = fi.finext;
@@ -15509,6 +15495,12 @@ fn cgdot(c: *cgen, n: *node) void = {
cgloadtaggedfield(c, "CX", fi.foff, tsz);
return;
};
// str IS []u8 — 3-word {ptr,len,cap}, the local
// slice-field arm (BP) retargeted to the CX global
// base. cap→CX LAST: CX is the base, so .ptr/.len
// must read first. cstage folds local+global in one
// base_reg arm; ww splits them, so this global arm
// carries its own lift (filed divergence task).
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "CX");
@@ -15516,6 +15508,9 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "CX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 16): i64, "CX");
emitline(", CX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 global field: route through X0.
let mov: str = "MOVSD";