cgen: ww str for-range loop-var narrows to MOVZBQ -- Phase 2 step-3 Fold 1 (align-up)

Ranging a str (for (let b .. = s)) and reading the loop var back emitted MOVZBQ on cstage (correct u8 zero-extend) but MOVQ on wwstage (the missed case, #14). Align wwstage UP. ww cgforrange derived the element-type node only for slice/array; for a str scrutinee it left elemt=nil, so the loop var registered with no type and localloadop short-circuited to MOVQ. Fix: for a str scrutinee, synthesize a u8 element node (type_ = str.sub = u8, from F1) as elemt, so localadd hands the loop var a u8 tnode and the GENERIC narrow-load fires (MOVZBQ) -- consuming str.sub as F1 intended, mirroring how []u8 supplies its element node. NOT an if-str special-case. cstage already correct, untouched (ww-only). str's own type stays nominal.

GATE is the ASM SHAPE byte-id (cstage==wwstage at the loop-var read), NOT a runtime probe: the divergence is runtime-benign (MOVQ and MOVZBQ read the same zero-extended byte) so a runtime test passes both ways and cannot distinguish -- it was a byte-id-INVISIBLE divergence (990-997 green despite cstage!=ww, since no bootstrap input exercises a narrow-read str loop var). Verified fail-pre (the cstage-MOVZBQ vs wwstage-MOVQ 1-line diff) / pass-post (.s byte-identical). []u8/slice/array for-range emission unchanged. test/wcc/940 carries the fixture (runtime corpus coverage, both drivers).

main.combined.ww regenerated via the canonical make path.
This commit is contained in:
2026-05-24 17:29:20 +09:00
parent 80527f3868
commit 3d7c707bd2
5 changed files with 239 additions and 0 deletions

View File

@@ -21356,6 +21356,27 @@ fn cgforrange(c: *cgen, n: *node) void = {
let sk: nkind = slctn.kind;
if (sk == nkind.N_TSLICE) { elemt = slctn.lhs; };
if (sk == nkind.N_TARRAY) { elemt = slctn.lhs; };
// str IS []u8 (F1: tystr.sub = tyu8). []u8 hands cgen a real
// u8 element node (slctn.lhs); a str scrutinee has none, so the
// loop var would register tnode=nil and read back as a wide
// MOVQ. Synthesise the u8 element off str.sub so the loop-var
// registration carries a u8 tnode and localloadop narrows the
// read-back to MOVZBQ on its own — aligning wwstage up to
// cstage, whose checker stamps the binding u8. Kind-gated so
// str's own type stays nominal.
if (sk == nkind.N_TNAME) {
if (streq(slctn.str, "str")) {
let sti: *tinfo = slctn.type_: *tinfo;
if (sti != nil) {
if (sti.sub != nil) {
let u8n: *node = newnode(nkind.N_TNAME, slctn.file, slctn.line, slctn.col);
u8n.str = "u8";
u8n.type_ = sti.sub: *void;
elemt = u8n;
};
};
};
};
};
// esz: raw elem byte size. For tuple-element slices `[](T0, T1)`,
// C cgen reads the resolved tuple's size (sum of raw param sizes,