selfhost: fix 4B array load/store width + 8B uninit zero-init

This commit is contained in:
2026-05-12 16:30:24 +09:00
parent e087c843e9
commit 67eaa9796a
6 changed files with 279 additions and 141 deletions

View File

@@ -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;
};

View File

@@ -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;
};
};

View File

@@ -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;
};

View File

@@ -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; };

View File

@@ -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;
};

View File

@@ -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