w6c+selfhost+lib: zero-init multi-word no-rhs lets
`let x: T;` for str/slice/tuple/struct/tagged previously left the slot holding stack garbage — only 8B-primitive slots were zeroed. This bit `expectbindname` in lib/ww/parse: `let empty: str; *into = empty;` was copying stack bytes (often a recently-vacated str descriptor) into the caller's `id`, so wwstage emitted `_` discard nodes carrying random text instead of "". Both stages now zero the full slot on no-rhs lets; `[N]T` arrays keep the per-index-write contract. Also tightens the two known buggy sites: parse.ww `expectbindname` writes `*into = ""` directly, expr.ww `_` primary returns the bare newnode (amalloc already zeroes).
This commit is contained in:
@@ -4158,8 +4158,31 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
}
|
}
|
||||||
} else if (sz == 8) {
|
} else if (sz == 8) {
|
||||||
ins2(c, A_MOVQ, aimm(0), amem(D_BP, off));
|
ins2(c, A_MOVQ, aimm(0), amem(D_BP, off));
|
||||||
|
} else if (!n->rhs && sz > 8 && lu && lu->kind != TY_ARRAY) {
|
||||||
|
/* `let x: T;` with no rhs for a multi-word composite
|
||||||
|
* (str/slice/tuple/struct/tagged). Zero the slot so
|
||||||
|
* reads after the bare let see {0...} rather than
|
||||||
|
* whatever the stack already held. Arrays keep the
|
||||||
|
* per-index-write contract — leave them uninit. */
|
||||||
|
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||||
|
int zi = 0;
|
||||||
|
while (zi + 8 <= sz) {
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, off + zi));
|
||||||
|
zi += 8;
|
||||||
|
}
|
||||||
|
while (zi + 4 <= sz) {
|
||||||
|
ins2(c, A_MOVL, areg(D_AX),
|
||||||
|
amem(D_BP, off + zi));
|
||||||
|
zi += 4;
|
||||||
|
}
|
||||||
|
while (zi < sz) {
|
||||||
|
ins2(c, A_MOVB, areg(D_AX),
|
||||||
|
amem(D_BP, off + zi));
|
||||||
|
zi += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/* arrays/slices left uninitialised — caller writes via index */
|
/* arrays left uninitialised — caller writes via index */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case N_RETURN:
|
case N_RETURN:
|
||||||
|
|||||||
@@ -68,14 +68,12 @@ fn parseprimary(p: *parser) *node = {
|
|||||||
return newnode(p.a, nkind.N_VOIDLIT, pf, pl, pc);
|
return newnode(p.a, nkind.N_VOIDLIT, pf, pl, pc);
|
||||||
};
|
};
|
||||||
if (p.curkind == tkind.TK_UNDER) {
|
if (p.curkind == tkind.TK_UNDER) {
|
||||||
// Bare `_` — valid only as a discard lvalue. Emit an nkind.N_IDENT
|
// Bare `_` — valid only as a discard lvalue. Emit an N_IDENT
|
||||||
// with empty str; the checker rejects it outside lvalue
|
// with empty str (newnode zeroes the node, so str.len is
|
||||||
|
// already 0); the checker rejects it outside lvalue
|
||||||
// positions.
|
// positions.
|
||||||
advance(p);
|
advance(p);
|
||||||
let n: *node = newnode(p.a, nkind.N_IDENT, pf, pl, pc);
|
return newnode(p.a, nkind.N_IDENT, pf, pl, pc);
|
||||||
let empty: str;
|
|
||||||
n.str = empty;
|
|
||||||
return n;
|
|
||||||
};
|
};
|
||||||
if (p.curkind == tkind.TK_LBRACK) {
|
if (p.curkind == tkind.TK_LBRACK) {
|
||||||
// Array literal `[a, b, c]` or `[v, w...]` (repeat suffix).
|
// Array literal `[a, b, c]` or `[v, w...]` (repeat suffix).
|
||||||
|
|||||||
@@ -92,8 +92,7 @@ fn expectident(p: *parser, into: *str) bool = {
|
|||||||
// scope_define for the binding.
|
// scope_define for the binding.
|
||||||
fn expectbindname(p: *parser, into: *str) bool = {
|
fn expectbindname(p: *parser, into: *str) bool = {
|
||||||
if (p.curkind == tkind.TK_UNDER) {
|
if (p.curkind == tkind.TK_UNDER) {
|
||||||
let empty: str;
|
*into = "";
|
||||||
*into = empty;
|
|
||||||
advance(p);
|
advance(p);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -64,5 +64,13 @@ Fixed (no workaround needed):
|
|||||||
selfhost/cmd/wcc/cgenutil.ww slotsize N_TARRAY follows
|
selfhost/cmd/wcc/cgenutil.ww slotsize N_TARRAY follows
|
||||||
`aliaslookup` on a TNAME element, and `aliaslookup` strips a
|
`aliaslookup` on a TNAME element, and `aliaslookup` strips a
|
||||||
`pkg.` prefix so cross-module references resolve.
|
`pkg.` prefix so cross-module references resolve.
|
||||||
|
- Bare `let x: T;` (no rhs) of a multi-word composite — str (16B),
|
||||||
|
slice (24B), tuple, struct, tagged — now zero-inits the slot.
|
||||||
|
Previously only 8B-primitive slots were zeroed; larger slots
|
||||||
|
read whatever the stack held, so `let empty: str; *into = empty;`
|
||||||
|
copied stack garbage into the caller's slot. `[N]T` arrays still
|
||||||
|
follow the per-index-write contract and stay uninit. See
|
||||||
|
cmd/w6c/cgen.c N_LET (else branch, sz > 8 && !TY_ARRAY) and
|
||||||
|
selfhost/cmd/wcc/cgenstmt.ww cglet no-rhs branch.
|
||||||
|
|
||||||
If a port "should work" but the binary is wrong, suspect these first.
|
If a port "should work" but the binary is wrong, suspect these first.
|
||||||
|
|||||||
@@ -2730,14 +2730,12 @@ fn parseprimary(p: *parser) *node = {
|
|||||||
return newnode(p.a, nkind.N_VOIDLIT, pf, pl, pc);
|
return newnode(p.a, nkind.N_VOIDLIT, pf, pl, pc);
|
||||||
};
|
};
|
||||||
if (p.curkind == tkind.TK_UNDER) {
|
if (p.curkind == tkind.TK_UNDER) {
|
||||||
// Bare `_` — valid only as a discard lvalue. Emit an nkind.N_IDENT
|
// Bare `_` — valid only as a discard lvalue. Emit an N_IDENT
|
||||||
// with empty str; the checker rejects it outside lvalue
|
// with empty str (newnode zeroes the node, so str.len is
|
||||||
|
// already 0); the checker rejects it outside lvalue
|
||||||
// positions.
|
// positions.
|
||||||
advance(p);
|
advance(p);
|
||||||
let n: *node = newnode(p.a, nkind.N_IDENT, pf, pl, pc);
|
return newnode(p.a, nkind.N_IDENT, pf, pl, pc);
|
||||||
let empty: str;
|
|
||||||
n.str = empty;
|
|
||||||
return n;
|
|
||||||
};
|
};
|
||||||
if (p.curkind == tkind.TK_LBRACK) {
|
if (p.curkind == tkind.TK_LBRACK) {
|
||||||
// Array literal `[a, b, c]` or `[v, w...]` (repeat suffix).
|
// Array literal `[a, b, c]` or `[v, w...]` (repeat suffix).
|
||||||
@@ -3799,8 +3797,7 @@ fn expectident(p: *parser, into: *str) bool = {
|
|||||||
// scope_define for the binding.
|
// scope_define for the binding.
|
||||||
fn expectbindname(p: *parser, into: *str) bool = {
|
fn expectbindname(p: *parser, into: *str) bool = {
|
||||||
if (p.curkind == tkind.TK_UNDER) {
|
if (p.curkind == tkind.TK_UNDER) {
|
||||||
let empty: str;
|
*into = "";
|
||||||
*into = empty;
|
|
||||||
advance(p);
|
advance(p);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -11788,16 +11785,46 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Bare `let x: T;` with no initializer. C cgen
|
// Bare `let x: T;` with no initializer. C cgen
|
||||||
// (cmd/w6c/cgen.c:3317) zero-inits whenever the raw type size
|
// (cmd/w6c/cgen.c N_LET no-rhs branch) zero-inits in two
|
||||||
// is 8: scalar primitives, pointers, fn/chan handles, plus 8B
|
// shapes:
|
||||||
// composites like `[8]bool`, `[2]i32`, `[4]i16`, `[1]i64`.
|
// - 8B primitives (scalar/ptr/fn/chan/`[8]bool` etc.):
|
||||||
// Larger composites and `[N]T` with size != 8 are left for
|
// single `MOVQ $0, off(BP)`.
|
||||||
// per-field writes.
|
// - multi-word composites (str/slice/tuple/struct/tagged):
|
||||||
|
// `XORQ AX,AX` + a run of `MOVQ AX, ...` over the slot
|
||||||
|
// so reads after the bare let see {0...} rather than
|
||||||
|
// stack garbage.
|
||||||
|
// `[N]T` arrays of size != 8 keep the per-index-write
|
||||||
|
// contract — they're left uninit.
|
||||||
|
let isarr: bool = false;
|
||||||
|
if (n.lhs != nil) {
|
||||||
|
if (n.lhs.kind == nkind.N_TARRAY) { isarr = true; };
|
||||||
|
};
|
||||||
if (typeis8byteprimitive(c, n.lhs)) {
|
if (typeis8byteprimitive(c, n.lhs)) {
|
||||||
emitline("\tMOVQ\t$0, ");
|
emitline("\tMOVQ\t$0, ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
} else { if (!isarr) { if (sz > 8) {
|
||||||
|
emitline("\tXORQ\tAX, AX\n");
|
||||||
|
let zi: i32 = 0;
|
||||||
|
for (zi + 8 <= sz) {
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitoff((off + zi): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
zi += 8;
|
||||||
|
};
|
||||||
|
for (zi + 4 <= sz) {
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitoff((off + zi): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
zi += 4;
|
||||||
|
};
|
||||||
|
for (zi < sz) {
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitoff((off + zi): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
zi += 1;
|
||||||
|
};
|
||||||
|
}; }; };
|
||||||
};
|
};
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -544,16 +544,46 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Bare `let x: T;` with no initializer. C cgen
|
// Bare `let x: T;` with no initializer. C cgen
|
||||||
// (cmd/w6c/cgen.c:3317) zero-inits whenever the raw type size
|
// (cmd/w6c/cgen.c N_LET no-rhs branch) zero-inits in two
|
||||||
// is 8: scalar primitives, pointers, fn/chan handles, plus 8B
|
// shapes:
|
||||||
// composites like `[8]bool`, `[2]i32`, `[4]i16`, `[1]i64`.
|
// - 8B primitives (scalar/ptr/fn/chan/`[8]bool` etc.):
|
||||||
// Larger composites and `[N]T` with size != 8 are left for
|
// single `MOVQ $0, off(BP)`.
|
||||||
// per-field writes.
|
// - multi-word composites (str/slice/tuple/struct/tagged):
|
||||||
|
// `XORQ AX,AX` + a run of `MOVQ AX, ...` over the slot
|
||||||
|
// so reads after the bare let see {0...} rather than
|
||||||
|
// stack garbage.
|
||||||
|
// `[N]T` arrays of size != 8 keep the per-index-write
|
||||||
|
// contract — they're left uninit.
|
||||||
|
let isarr: bool = false;
|
||||||
|
if (n.lhs != nil) {
|
||||||
|
if (n.lhs.kind == nkind.N_TARRAY) { isarr = true; };
|
||||||
|
};
|
||||||
if (typeis8byteprimitive(c, n.lhs)) {
|
if (typeis8byteprimitive(c, n.lhs)) {
|
||||||
emitline("\tMOVQ\t$0, ");
|
emitline("\tMOVQ\t$0, ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
} else { if (!isarr) { if (sz > 8) {
|
||||||
|
emitline("\tXORQ\tAX, AX\n");
|
||||||
|
let zi: i32 = 0;
|
||||||
|
for (zi + 8 <= sz) {
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitoff((off + zi): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
zi += 8;
|
||||||
|
};
|
||||||
|
for (zi + 4 <= sz) {
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitoff((off + zi): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
zi += 4;
|
||||||
|
};
|
||||||
|
for (zi < sz) {
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitoff((off + zi): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
zi += 1;
|
||||||
|
};
|
||||||
|
}; }; };
|
||||||
};
|
};
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2730,14 +2730,12 @@ fn parseprimary(p: *parser) *node = {
|
|||||||
return newnode(p.a, nkind.N_VOIDLIT, pf, pl, pc);
|
return newnode(p.a, nkind.N_VOIDLIT, pf, pl, pc);
|
||||||
};
|
};
|
||||||
if (p.curkind == tkind.TK_UNDER) {
|
if (p.curkind == tkind.TK_UNDER) {
|
||||||
// Bare `_` — valid only as a discard lvalue. Emit an nkind.N_IDENT
|
// Bare `_` — valid only as a discard lvalue. Emit an N_IDENT
|
||||||
// with empty str; the checker rejects it outside lvalue
|
// with empty str (newnode zeroes the node, so str.len is
|
||||||
|
// already 0); the checker rejects it outside lvalue
|
||||||
// positions.
|
// positions.
|
||||||
advance(p);
|
advance(p);
|
||||||
let n: *node = newnode(p.a, nkind.N_IDENT, pf, pl, pc);
|
return newnode(p.a, nkind.N_IDENT, pf, pl, pc);
|
||||||
let empty: str;
|
|
||||||
n.str = empty;
|
|
||||||
return n;
|
|
||||||
};
|
};
|
||||||
if (p.curkind == tkind.TK_LBRACK) {
|
if (p.curkind == tkind.TK_LBRACK) {
|
||||||
// Array literal `[a, b, c]` or `[v, w...]` (repeat suffix).
|
// Array literal `[a, b, c]` or `[v, w...]` (repeat suffix).
|
||||||
@@ -3799,8 +3797,7 @@ fn expectident(p: *parser, into: *str) bool = {
|
|||||||
// scope_define for the binding.
|
// scope_define for the binding.
|
||||||
fn expectbindname(p: *parser, into: *str) bool = {
|
fn expectbindname(p: *parser, into: *str) bool = {
|
||||||
if (p.curkind == tkind.TK_UNDER) {
|
if (p.curkind == tkind.TK_UNDER) {
|
||||||
let empty: str;
|
*into = "";
|
||||||
*into = empty;
|
|
||||||
advance(p);
|
advance(p);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -11788,16 +11785,46 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Bare `let x: T;` with no initializer. C cgen
|
// Bare `let x: T;` with no initializer. C cgen
|
||||||
// (cmd/w6c/cgen.c:3317) zero-inits whenever the raw type size
|
// (cmd/w6c/cgen.c N_LET no-rhs branch) zero-inits in two
|
||||||
// is 8: scalar primitives, pointers, fn/chan handles, plus 8B
|
// shapes:
|
||||||
// composites like `[8]bool`, `[2]i32`, `[4]i16`, `[1]i64`.
|
// - 8B primitives (scalar/ptr/fn/chan/`[8]bool` etc.):
|
||||||
// Larger composites and `[N]T` with size != 8 are left for
|
// single `MOVQ $0, off(BP)`.
|
||||||
// per-field writes.
|
// - multi-word composites (str/slice/tuple/struct/tagged):
|
||||||
|
// `XORQ AX,AX` + a run of `MOVQ AX, ...` over the slot
|
||||||
|
// so reads after the bare let see {0...} rather than
|
||||||
|
// stack garbage.
|
||||||
|
// `[N]T` arrays of size != 8 keep the per-index-write
|
||||||
|
// contract — they're left uninit.
|
||||||
|
let isarr: bool = false;
|
||||||
|
if (n.lhs != nil) {
|
||||||
|
if (n.lhs.kind == nkind.N_TARRAY) { isarr = true; };
|
||||||
|
};
|
||||||
if (typeis8byteprimitive(c, n.lhs)) {
|
if (typeis8byteprimitive(c, n.lhs)) {
|
||||||
emitline("\tMOVQ\t$0, ");
|
emitline("\tMOVQ\t$0, ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
} else { if (!isarr) { if (sz > 8) {
|
||||||
|
emitline("\tXORQ\tAX, AX\n");
|
||||||
|
let zi: i32 = 0;
|
||||||
|
for (zi + 8 <= sz) {
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitoff((off + zi): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
zi += 8;
|
||||||
|
};
|
||||||
|
for (zi + 4 <= sz) {
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitoff((off + zi): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
zi += 4;
|
||||||
|
};
|
||||||
|
for (zi < sz) {
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitoff((off + zi): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
zi += 1;
|
||||||
|
};
|
||||||
|
}; }; };
|
||||||
};
|
};
|
||||||
c.lastwasreturn = 0;
|
c.lastwasreturn = 0;
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user