From 67eaa9796acc2d234c8a688a6eed7c893517a619 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 16:30:24 +0900 Subject: [PATCH] selfhost: fix 4B array load/store width + 8B uninit zero-init --- selfhost/cmd/w6c/main.combined.ww | 134 +++++++++++++++++---------- selfhost/cmd/wcc/cgenexpr.ww | 33 +++++-- selfhost/cmd/wcc/cgenstmt.ww | 66 ++++++------- selfhost/cmd/wcc/cgenutil.ww | 35 ++++++- selfhost/cmd/wwdump/main.combined.ww | 134 +++++++++++++++++---------- test/wcc/990_selfhost.c | 18 ++++ 6 files changed, 279 insertions(+), 141 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index cc7db2d9..a7588e18 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -5281,7 +5281,24 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { if (k == nkind.N_TFN) { return true; }; if (k == nkind.N_TCHAN) { return true; }; if (k == nkind.N_TSLICE) { return false; }; - if (k == nkind.N_TARRAY) { return false; }; + if (k == nkind.N_TARRAY) { + // C cgen (cmd/w6c/cgen.c:3317) zero-inits TY_ARRAY whenever + // its raw byte size is 8 — e.g. `[8]bool`, `[2]i32`, `[4]i16`, + // `[1]i64`. Mirror that here so the wwstage matches. + let lenn: *node = t.rhs; + let elemn: *node = t.lhs; + if (lenn == nil) { return false; }; + if (lenn.kind != nkind.N_INTLIT) { return false; }; + let elen: i64 = lenn.uval: i64; + let esz: i32 = 8; + if (elemn != nil) { + if (elemn.kind == nkind.N_TNAME) { + let ps: i32 = primsize(elemn.str); + if (ps > 0) { esz = ps; }; + }; + }; + return (esz: i64 * elen) == 8i64; + }; if (k == nkind.N_TTUPLE) { return false; }; if (k == nkind.N_TTAGGED){ return false; }; if (k == nkind.N_TNAME) { @@ -5297,6 +5314,22 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { return false; }; +// elemissigned — given an indexable type (`*T`, `[]T`, `[N]T`), is +// its element a signed narrow primitive (i8/i16/i32/rune)? Used by +// cgindex to pick MOVSXD vs MOVL at esz=4. Mirrors C cgen's +// `signed_elem` check. +fn elemissigned(t: *node) bool = { + if (t == nil) { return false; }; + let elem: *node = nil; + let k: nkind = t.kind; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return false; }; + if (elem.kind != nkind.N_TNAME) { return false; }; + return typenameissigned(elem.str); +}; + // typenameissigned — true for i8/i16/i32/i64/int/rune. fn typenameissigned(nm: str) bool = { if (streq(nm, "i8")) { return true; }; @@ -6644,18 +6677,22 @@ fn cgident(c: *cgen, n: *node) void = { }; fn cgindex(c: *cgen, n: *node) void = { - // Element-size-aware load: u8-element bases use MOVZBQ, - // everything else MOVQ. Fast path when the base is a bare - // ident (mem.ww shape). + // Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL, + // str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast + // path when the base is a bare ident (mem.ww shape). let base: *node = n.lhs; let idx: *node = n.rhs; let esz: i32 = 8; + let signed_elem: bool = false; let baselocal: *local = nil; if (base != nil) { if (base.kind == nkind.N_IDENT) { let bn: str = base.str; baselocal = localfindnode(c, bn); - if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); }; + if (baselocal != nil) { + esz = elemsizeof(baselocal.tnode); + signed_elem = elemissigned(baselocal.tnode); + }; } else { if (base.kind == nkind.N_DOT) { esz = indexbaseesz(c, base); };}; @@ -6689,8 +6726,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, BX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { emitline("\tMOVQ\t(BX), AX\n"); }; + if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } + else { if (esz == 4) { + if (signed_elem) { emitline("\tMOVSXD\t(BX), AX\n"); } + else { emitline("\tMOVL\t(BX), AX\n"); }; + } + else { emitline("\tMOVQ\t(BX), AX\n"); };}; return; }; // Generic fallback when base isn't a plain ident. @@ -6703,8 +6744,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\t(AX), AX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } - else { emitline("\tMOVQ\t(AX), AX\n"); }; + if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } + else { if (esz == 4) { + if (signed_elem) { emitline("\tMOVSXD\t(AX), AX\n"); } + else { emitline("\tMOVL\t(AX), AX\n"); }; + } + else { emitline("\tMOVQ\t(AX), AX\n"); };}; return; }; @@ -7950,8 +7995,9 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, 8(BX)\n"); return; }; - if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } - else { emitline("\tMOVQ\tAX, (BX)\n"); }; + if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } + else { if (esz == 4) { emitline("\tMOVL\tAX, (BX)\n"); } + else { emitline("\tMOVQ\tAX, (BX)\n"); };}; return; }; }; @@ -8976,13 +9022,11 @@ fn cglet(c: *cgen, n: *node) void = { }; } else { // Bare `let x: T;` with no initializer. C cgen - // (cmd/w6c/cgen.c:2181-2183) zero-inits only when - // the underlying type's natural size is 8 — pointers, - // i64/u64, function pointers, ints. Structs/arrays/ - // slices/strings/tagged/tuples are left for per-field - // writes. ww's slotsize pads struct slots up to 8, - // so we can't just check sz == 8: walk the type AST - // directly to make the same call. + // (cmd/w6c/cgen.c:3317) zero-inits whenever the raw type size + // is 8: scalar primitives, pointers, fn/chan handles, plus 8B + // composites like `[8]bool`, `[2]i32`, `[4]i16`, `[1]i64`. + // Larger composites and `[N]T` with size != 8 are left for + // per-field writes. if (typeis8byteprimitive(c, n.lhs)) { emitline("\tMOVQ\t$0, "); emitoff(off: i64); @@ -9271,15 +9315,12 @@ fn cgforrange(c: *cgen, n: *node) void = { let ioff: i32 = localalloc(c, iname, 8, nil); let loff: i32 = localalloc(c, lname, 8, nil); - // Per-binding (up to 8 — matches the C array). Parallel i64 arrays - // keep every elem at 8B so the indexed-store hits the working MOVQ - // path (selfhost cgen doesn't yet emit MOVL for i32-array writes, - // and doesn't zero-init `[8]bool` uninit slots — both byte-diverge - // from C w6c on the wwstage rebuild). - let bind_off: [8]i64; - let bind_sz: [8]i64; - let bind_foff: [8]i64; - let bind_signed: [8]i64; // 0 / 1 + // Per-binding (up to 8 — matches the C array). Parallel arrays so + // we don't depend on local-struct cgen. + let bind_off: [8]i32; + let bind_sz: [8]i32; + let bind_foff: [8]i32; + let bind_signed: [8]bool; let nbinds: i32 = 0; if (destruct) { @@ -9300,15 +9341,14 @@ fn cgforrange(c: *cgen, n: *node) void = { }; let slot_sz: i32 = fsz; if (slot_sz < 8) { slot_sz = 8; }; - bind_sz[nbinds] = fsz: i64; - bind_foff[nbinds] = field_off: i64; - if (signf) { bind_signed[nbinds] = 1i64; } - else { bind_signed[nbinds] = 0i64; }; + bind_sz[nbinds] = fsz; + bind_foff[nbinds] = field_off; + bind_signed[nbinds] = signf; let bnm: str = m.str; if (bnm.len > 0) { - bind_off[nbinds] = localadd(c, bnm, slot_sz, nil): i64; + bind_off[nbinds] = localadd(c, bnm, slot_sz, nil); } else { - bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64; + bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil); }; field_off += fsz; nbinds += 1; @@ -9319,18 +9359,18 @@ fn cgforrange(c: *cgen, n: *node) void = { } else { let slot_sz: i32 = esz; if (slot_sz < 8) { slot_sz = 8; }; - bind_sz[0] = esz: i64; - bind_foff[0] = 0i64; + bind_sz[0] = esz; + bind_foff[0] = 0; // Single-binding signed-narrow detection: mirror C which // reads `u->sub->kind` for the elem type. - let signf0: bool = false; - if (elemt != nil) { signf0 = paramissigned(elemt); }; - if (signf0) { bind_signed[0] = 1i64; } - else { bind_signed[0] = 0i64; }; + bind_signed[0] = false; + if (elemt != nil) { + bind_signed[0] = paramissigned(elemt); + }; if (n.str.len > 0) { - bind_off[0] = localadd(c, n.str, slot_sz, nil): i64; + bind_off[0] = localadd(c, n.str, slot_sz, nil); } else { - bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64; + bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil); }; nbinds = 1; }; @@ -9424,18 +9464,18 @@ fn cgforrange(c: *cgen, n: *node) void = { let b: i32 = 0; for (b < nbinds) { let op: str = "MOVQ"; - if (bind_sz[b] == 1i64) { op = "MOVZBQ"; } - else { if (bind_sz[b] == 4i64) { - if (bind_signed[b] != 0i64) { op = "MOVSXD"; } - else { op = "MOVL"; }; + if (bind_sz[b] == 1) { op = "MOVZBQ"; } + else { if (bind_sz[b] == 4) { + if (bind_signed[b]) { op = "MOVSXD"; } + else { op = "MOVL"; }; };}; emitline("\t"); emitline(op); emitline("\t"); - emitoff(bind_foff[b]); + emitoff(bind_foff[b]: i64); emitline("(BX), AX\n"); emitline("\tMOVQ\tAX, "); - emitoff(bind_off[b]); + emitoff(bind_off[b]: i64); emitline("(BP)\n"); b += 1; }; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 29e3a238..9b2622fb 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -516,18 +516,22 @@ fn cgident(c: *cgen, n: *node) void = { }; fn cgindex(c: *cgen, n: *node) void = { - // Element-size-aware load: u8-element bases use MOVZBQ, - // everything else MOVQ. Fast path when the base is a bare - // ident (mem.ww shape). + // Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL, + // str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast + // path when the base is a bare ident (mem.ww shape). let base: *node = n.lhs; let idx: *node = n.rhs; let esz: i32 = 8; + let signed_elem: bool = false; let baselocal: *local = nil; if (base != nil) { if (base.kind == nkind.N_IDENT) { let bn: str = base.str; baselocal = localfindnode(c, bn); - if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); }; + if (baselocal != nil) { + esz = elemsizeof(baselocal.tnode); + signed_elem = elemissigned(baselocal.tnode); + }; } else { if (base.kind == nkind.N_DOT) { esz = indexbaseesz(c, base); };}; @@ -561,8 +565,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, BX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { emitline("\tMOVQ\t(BX), AX\n"); }; + if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } + else { if (esz == 4) { + if (signed_elem) { emitline("\tMOVSXD\t(BX), AX\n"); } + else { emitline("\tMOVL\t(BX), AX\n"); }; + } + else { emitline("\tMOVQ\t(BX), AX\n"); };}; return; }; // Generic fallback when base isn't a plain ident. @@ -575,8 +583,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\t(AX), AX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } - else { emitline("\tMOVQ\t(AX), AX\n"); }; + if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } + else { if (esz == 4) { + if (signed_elem) { emitline("\tMOVSXD\t(AX), AX\n"); } + else { emitline("\tMOVL\t(AX), AX\n"); }; + } + else { emitline("\tMOVQ\t(AX), AX\n"); };}; return; }; @@ -1822,8 +1834,9 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, 8(BX)\n"); return; }; - if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } - else { emitline("\tMOVQ\tAX, (BX)\n"); }; + if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } + else { if (esz == 4) { emitline("\tMOVL\tAX, (BX)\n"); } + else { emitline("\tMOVQ\tAX, (BX)\n"); };}; return; }; }; diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 9e0c0ec3..34198b81 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -504,13 +504,11 @@ fn cglet(c: *cgen, n: *node) void = { }; } else { // Bare `let x: T;` with no initializer. C cgen - // (cmd/w6c/cgen.c:2181-2183) zero-inits only when - // the underlying type's natural size is 8 — pointers, - // i64/u64, function pointers, ints. Structs/arrays/ - // slices/strings/tagged/tuples are left for per-field - // writes. ww's slotsize pads struct slots up to 8, - // so we can't just check sz == 8: walk the type AST - // directly to make the same call. + // (cmd/w6c/cgen.c:3317) zero-inits whenever the raw type size + // is 8: scalar primitives, pointers, fn/chan handles, plus 8B + // composites like `[8]bool`, `[2]i32`, `[4]i16`, `[1]i64`. + // Larger composites and `[N]T` with size != 8 are left for + // per-field writes. if (typeis8byteprimitive(c, n.lhs)) { emitline("\tMOVQ\t$0, "); emitoff(off: i64); @@ -799,15 +797,12 @@ fn cgforrange(c: *cgen, n: *node) void = { let ioff: i32 = localalloc(c, iname, 8, nil); let loff: i32 = localalloc(c, lname, 8, nil); - // Per-binding (up to 8 — matches the C array). Parallel i64 arrays - // keep every elem at 8B so the indexed-store hits the working MOVQ - // path (selfhost cgen doesn't yet emit MOVL for i32-array writes, - // and doesn't zero-init `[8]bool` uninit slots — both byte-diverge - // from C w6c on the wwstage rebuild). - let bind_off: [8]i64; - let bind_sz: [8]i64; - let bind_foff: [8]i64; - let bind_signed: [8]i64; // 0 / 1 + // Per-binding (up to 8 — matches the C array). Parallel arrays so + // we don't depend on local-struct cgen. + let bind_off: [8]i32; + let bind_sz: [8]i32; + let bind_foff: [8]i32; + let bind_signed: [8]bool; let nbinds: i32 = 0; if (destruct) { @@ -828,15 +823,14 @@ fn cgforrange(c: *cgen, n: *node) void = { }; let slot_sz: i32 = fsz; if (slot_sz < 8) { slot_sz = 8; }; - bind_sz[nbinds] = fsz: i64; - bind_foff[nbinds] = field_off: i64; - if (signf) { bind_signed[nbinds] = 1i64; } - else { bind_signed[nbinds] = 0i64; }; + bind_sz[nbinds] = fsz; + bind_foff[nbinds] = field_off; + bind_signed[nbinds] = signf; let bnm: str = m.str; if (bnm.len > 0) { - bind_off[nbinds] = localadd(c, bnm, slot_sz, nil): i64; + bind_off[nbinds] = localadd(c, bnm, slot_sz, nil); } else { - bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64; + bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil); }; field_off += fsz; nbinds += 1; @@ -847,18 +841,18 @@ fn cgforrange(c: *cgen, n: *node) void = { } else { let slot_sz: i32 = esz; if (slot_sz < 8) { slot_sz = 8; }; - bind_sz[0] = esz: i64; - bind_foff[0] = 0i64; + bind_sz[0] = esz; + bind_foff[0] = 0; // Single-binding signed-narrow detection: mirror C which // reads `u->sub->kind` for the elem type. - let signf0: bool = false; - if (elemt != nil) { signf0 = paramissigned(elemt); }; - if (signf0) { bind_signed[0] = 1i64; } - else { bind_signed[0] = 0i64; }; + bind_signed[0] = false; + if (elemt != nil) { + bind_signed[0] = paramissigned(elemt); + }; if (n.str.len > 0) { - bind_off[0] = localadd(c, n.str, slot_sz, nil): i64; + bind_off[0] = localadd(c, n.str, slot_sz, nil); } else { - bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64; + bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil); }; nbinds = 1; }; @@ -952,18 +946,18 @@ fn cgforrange(c: *cgen, n: *node) void = { let b: i32 = 0; for (b < nbinds) { let op: str = "MOVQ"; - if (bind_sz[b] == 1i64) { op = "MOVZBQ"; } - else { if (bind_sz[b] == 4i64) { - if (bind_signed[b] != 0i64) { op = "MOVSXD"; } - else { op = "MOVL"; }; + if (bind_sz[b] == 1) { op = "MOVZBQ"; } + else { if (bind_sz[b] == 4) { + if (bind_signed[b]) { op = "MOVSXD"; } + else { op = "MOVL"; }; };}; emitline("\t"); emitline(op); emitline("\t"); - emitoff(bind_foff[b]); + emitoff(bind_foff[b]: i64); emitline("(BX), AX\n"); emitline("\tMOVQ\tAX, "); - emitoff(bind_off[b]); + emitoff(bind_off[b]: i64); emitline("(BP)\n"); b += 1; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index b48463f4..3904f19c 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -299,7 +299,24 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { if (k == nkind.N_TFN) { return true; }; if (k == nkind.N_TCHAN) { return true; }; if (k == nkind.N_TSLICE) { return false; }; - if (k == nkind.N_TARRAY) { return false; }; + if (k == nkind.N_TARRAY) { + // C cgen (cmd/w6c/cgen.c:3317) zero-inits TY_ARRAY whenever + // its raw byte size is 8 — e.g. `[8]bool`, `[2]i32`, `[4]i16`, + // `[1]i64`. Mirror that here so the wwstage matches. + let lenn: *node = t.rhs; + let elemn: *node = t.lhs; + if (lenn == nil) { return false; }; + if (lenn.kind != nkind.N_INTLIT) { return false; }; + let elen: i64 = lenn.uval: i64; + let esz: i32 = 8; + if (elemn != nil) { + if (elemn.kind == nkind.N_TNAME) { + let ps: i32 = primsize(elemn.str); + if (ps > 0) { esz = ps; }; + }; + }; + return (esz: i64 * elen) == 8i64; + }; if (k == nkind.N_TTUPLE) { return false; }; if (k == nkind.N_TTAGGED){ return false; }; if (k == nkind.N_TNAME) { @@ -315,6 +332,22 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { return false; }; +// elemissigned — given an indexable type (`*T`, `[]T`, `[N]T`), is +// its element a signed narrow primitive (i8/i16/i32/rune)? Used by +// cgindex to pick MOVSXD vs MOVL at esz=4. Mirrors C cgen's +// `signed_elem` check. +fn elemissigned(t: *node) bool = { + if (t == nil) { return false; }; + let elem: *node = nil; + let k: nkind = t.kind; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return false; }; + if (elem.kind != nkind.N_TNAME) { return false; }; + return typenameissigned(elem.str); +}; + // typenameissigned — true for i8/i16/i32/i64/int/rune. fn typenameissigned(nm: str) bool = { if (streq(nm, "i8")) { return true; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 87c8706e..efc7ec43 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -5281,7 +5281,24 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { if (k == nkind.N_TFN) { return true; }; if (k == nkind.N_TCHAN) { return true; }; if (k == nkind.N_TSLICE) { return false; }; - if (k == nkind.N_TARRAY) { return false; }; + if (k == nkind.N_TARRAY) { + // C cgen (cmd/w6c/cgen.c:3317) zero-inits TY_ARRAY whenever + // its raw byte size is 8 — e.g. `[8]bool`, `[2]i32`, `[4]i16`, + // `[1]i64`. Mirror that here so the wwstage matches. + let lenn: *node = t.rhs; + let elemn: *node = t.lhs; + if (lenn == nil) { return false; }; + if (lenn.kind != nkind.N_INTLIT) { return false; }; + let elen: i64 = lenn.uval: i64; + let esz: i32 = 8; + if (elemn != nil) { + if (elemn.kind == nkind.N_TNAME) { + let ps: i32 = primsize(elemn.str); + if (ps > 0) { esz = ps; }; + }; + }; + return (esz: i64 * elen) == 8i64; + }; if (k == nkind.N_TTUPLE) { return false; }; if (k == nkind.N_TTAGGED){ return false; }; if (k == nkind.N_TNAME) { @@ -5297,6 +5314,22 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { return false; }; +// elemissigned — given an indexable type (`*T`, `[]T`, `[N]T`), is +// its element a signed narrow primitive (i8/i16/i32/rune)? Used by +// cgindex to pick MOVSXD vs MOVL at esz=4. Mirrors C cgen's +// `signed_elem` check. +fn elemissigned(t: *node) bool = { + if (t == nil) { return false; }; + let elem: *node = nil; + let k: nkind = t.kind; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return false; }; + if (elem.kind != nkind.N_TNAME) { return false; }; + return typenameissigned(elem.str); +}; + // typenameissigned — true for i8/i16/i32/i64/int/rune. fn typenameissigned(nm: str) bool = { if (streq(nm, "i8")) { return true; }; @@ -6644,18 +6677,22 @@ fn cgident(c: *cgen, n: *node) void = { }; fn cgindex(c: *cgen, n: *node) void = { - // Element-size-aware load: u8-element bases use MOVZBQ, - // everything else MOVQ. Fast path when the base is a bare - // ident (mem.ww shape). + // Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL, + // str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast + // path when the base is a bare ident (mem.ww shape). let base: *node = n.lhs; let idx: *node = n.rhs; let esz: i32 = 8; + let signed_elem: bool = false; let baselocal: *local = nil; if (base != nil) { if (base.kind == nkind.N_IDENT) { let bn: str = base.str; baselocal = localfindnode(c, bn); - if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); }; + if (baselocal != nil) { + esz = elemsizeof(baselocal.tnode); + signed_elem = elemissigned(baselocal.tnode); + }; } else { if (base.kind == nkind.N_DOT) { esz = indexbaseesz(c, base); };}; @@ -6689,8 +6726,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, BX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { emitline("\tMOVQ\t(BX), AX\n"); }; + if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } + else { if (esz == 4) { + if (signed_elem) { emitline("\tMOVSXD\t(BX), AX\n"); } + else { emitline("\tMOVL\t(BX), AX\n"); }; + } + else { emitline("\tMOVQ\t(BX), AX\n"); };}; return; }; // Generic fallback when base isn't a plain ident. @@ -6703,8 +6744,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("\tMOVQ\t(AX), AX\n"); return; }; - if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } - else { emitline("\tMOVQ\t(AX), AX\n"); }; + if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } + else { if (esz == 4) { + if (signed_elem) { emitline("\tMOVSXD\t(AX), AX\n"); } + else { emitline("\tMOVL\t(AX), AX\n"); }; + } + else { emitline("\tMOVQ\t(AX), AX\n"); };}; return; }; @@ -7950,8 +7995,9 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\tMOVQ\tCX, 8(BX)\n"); return; }; - if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } - else { emitline("\tMOVQ\tAX, (BX)\n"); }; + if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } + else { if (esz == 4) { emitline("\tMOVL\tAX, (BX)\n"); } + else { emitline("\tMOVQ\tAX, (BX)\n"); };}; return; }; }; @@ -8976,13 +9022,11 @@ fn cglet(c: *cgen, n: *node) void = { }; } else { // Bare `let x: T;` with no initializer. C cgen - // (cmd/w6c/cgen.c:2181-2183) zero-inits only when - // the underlying type's natural size is 8 — pointers, - // i64/u64, function pointers, ints. Structs/arrays/ - // slices/strings/tagged/tuples are left for per-field - // writes. ww's slotsize pads struct slots up to 8, - // so we can't just check sz == 8: walk the type AST - // directly to make the same call. + // (cmd/w6c/cgen.c:3317) zero-inits whenever the raw type size + // is 8: scalar primitives, pointers, fn/chan handles, plus 8B + // composites like `[8]bool`, `[2]i32`, `[4]i16`, `[1]i64`. + // Larger composites and `[N]T` with size != 8 are left for + // per-field writes. if (typeis8byteprimitive(c, n.lhs)) { emitline("\tMOVQ\t$0, "); emitoff(off: i64); @@ -9271,15 +9315,12 @@ fn cgforrange(c: *cgen, n: *node) void = { let ioff: i32 = localalloc(c, iname, 8, nil); let loff: i32 = localalloc(c, lname, 8, nil); - // Per-binding (up to 8 — matches the C array). Parallel i64 arrays - // keep every elem at 8B so the indexed-store hits the working MOVQ - // path (selfhost cgen doesn't yet emit MOVL for i32-array writes, - // and doesn't zero-init `[8]bool` uninit slots — both byte-diverge - // from C w6c on the wwstage rebuild). - let bind_off: [8]i64; - let bind_sz: [8]i64; - let bind_foff: [8]i64; - let bind_signed: [8]i64; // 0 / 1 + // Per-binding (up to 8 — matches the C array). Parallel arrays so + // we don't depend on local-struct cgen. + let bind_off: [8]i32; + let bind_sz: [8]i32; + let bind_foff: [8]i32; + let bind_signed: [8]bool; let nbinds: i32 = 0; if (destruct) { @@ -9300,15 +9341,14 @@ fn cgforrange(c: *cgen, n: *node) void = { }; let slot_sz: i32 = fsz; if (slot_sz < 8) { slot_sz = 8; }; - bind_sz[nbinds] = fsz: i64; - bind_foff[nbinds] = field_off: i64; - if (signf) { bind_signed[nbinds] = 1i64; } - else { bind_signed[nbinds] = 0i64; }; + bind_sz[nbinds] = fsz; + bind_foff[nbinds] = field_off; + bind_signed[nbinds] = signf; let bnm: str = m.str; if (bnm.len > 0) { - bind_off[nbinds] = localadd(c, bnm, slot_sz, nil): i64; + bind_off[nbinds] = localadd(c, bnm, slot_sz, nil); } else { - bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64; + bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil); }; field_off += fsz; nbinds += 1; @@ -9319,18 +9359,18 @@ fn cgforrange(c: *cgen, n: *node) void = { } else { let slot_sz: i32 = esz; if (slot_sz < 8) { slot_sz = 8; }; - bind_sz[0] = esz: i64; - bind_foff[0] = 0i64; + bind_sz[0] = esz; + bind_foff[0] = 0; // Single-binding signed-narrow detection: mirror C which // reads `u->sub->kind` for the elem type. - let signf0: bool = false; - if (elemt != nil) { signf0 = paramissigned(elemt); }; - if (signf0) { bind_signed[0] = 1i64; } - else { bind_signed[0] = 0i64; }; + bind_signed[0] = false; + if (elemt != nil) { + bind_signed[0] = paramissigned(elemt); + }; if (n.str.len > 0) { - bind_off[0] = localadd(c, n.str, slot_sz, nil): i64; + bind_off[0] = localadd(c, n.str, slot_sz, nil); } else { - bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64; + bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil); }; nbinds = 1; }; @@ -9424,18 +9464,18 @@ fn cgforrange(c: *cgen, n: *node) void = { let b: i32 = 0; for (b < nbinds) { let op: str = "MOVQ"; - if (bind_sz[b] == 1i64) { op = "MOVZBQ"; } - else { if (bind_sz[b] == 4i64) { - if (bind_signed[b] != 0i64) { op = "MOVSXD"; } - else { op = "MOVL"; }; + if (bind_sz[b] == 1) { op = "MOVZBQ"; } + else { if (bind_sz[b] == 4) { + if (bind_signed[b]) { op = "MOVSXD"; } + else { op = "MOVL"; }; };}; emitline("\t"); emitline(op); emitline("\t"); - emitoff(bind_foff[b]); + emitoff(bind_foff[b]: i64); emitline("(BX), AX\n"); emitline("\tMOVQ\tAX, "); - emitoff(bind_off[b]); + emitoff(bind_off[b]: i64); emitline("(BP)\n"); b += 1; }; diff --git a/test/wcc/990_selfhost.c b/test/wcc/990_selfhost.c index c65b39a1..54e6bf1a 100644 --- a/test/wcc/990_selfhost.c +++ b/test/wcc/990_selfhost.c @@ -345,6 +345,24 @@ probe_ww_compile(const char *bin) " dec(&c); dec(&c); dec(&c);\n" " return c.x;\n" "};", 2 }, + /* Uninit `[8]u8` local — raw size 8B, C cgen zero-inits via + * `MOVQ $0, off(BP)`. Pre-fix the wwstage left the slot + * holding stack junk and `buf[0]` returned non-zero. */ + { "fn main() i32 = {\n" + " let buf: [8]u8;\n" + " return buf[0]: i32;\n" + "};", 0 }, + /* [N]i32 indexed write + read: MOVL store, MOVSXD load. + * Pre-fix the wwstage emitted MOVQ in both directions — + * overwrites adjacent slots and loads the wrong width. + * arr[0..3] = -7, 3, 11, sum = 7. */ + { "fn main() i32 = {\n" + " let arr: [3]i32;\n" + " arr[0] = 0 - 7;\n" + " arr[1] = 3;\n" + " arr[2] = 11;\n" + " return arr[0] + arr[1] + arr[2];\n" + "};", 7 }, /* Hare-style for-range over a slice: `for (let b .. s)`. * Allocates `.rgi`/`.rgl` scratch slots, walks i=0..s.len * loading s.ptr[i] into the binding. esz=1 here so the