parse/cgen: tuple-destructure assign + extra type-inference holes
Closes the gaps that kept ww_ww from rebuilding the wwstage byte- for-byte. The pre-existing parser silently produced a broken AST on `a, b = fn();` (a no-op exprstmt + a single assign that lost the second tuple slot); the cgen leaned on a handful of cases that the surface type-walker didn't yet cover, so 6a/6l/ww built through wwstage drifted by a handful of bytes per file. Parser: - Multi-assign in parsestmt mirrors cmd/wwc/parse.c:1015-1031. If parseexpr is followed by `,`, switch into N_MASSIGN: collect the chained lvalues with parsebin(parseunary, 1) so they don't eat the trailing `=`, then absorb `= rhs;` and emit the node. Cgen: - N_MASSIGN handler stores AX into l0's slot, pops DX into l1's slot. Same shape as cmd/6c/cgen.c:2424-2440. Lvalues beyond two are dropped (C drops them too). - N_INDEX added to node_isunsigned: `p[i]` where p is *u8/[]u8/ [N]u8 now flags unsigned, so `p[i] >= 48u8` emits JAE instead of JGE. Bit 6a's parsenum. - node_isstr's N_DOT branch now handles chained dots via dot_inner_struct_ptr, so `p.to.asym` (Adr.asym is str) flags as str and push_args_rev pushes both halves. - index_base_esz returns 1 for str-typed struct fields. Was defaulting to 8, so `node.s[i]` scaled by 8 and used MOVQ instead of MOVZBQ. Bit ww/visit_seen. - N_LET with no initializer zero-inits the slot when the underlying type is an 8-byte primitive (pointer, fn-ptr, i64/u64, ...) — matches cmd/6c/cgen.c:2181. Structs, strings, slices, etc. are left for per-field writes, even when their slot rounds up to 8 in scan_locals. New helper type_is_8byte_primitive walks the type AST to make the same call C's checker would. Tests stay 17/17; new self-rebuild test (995) lands in the next commit and gates on these fixes.
This commit is contained in:
@@ -804,15 +804,14 @@ fn node_isstr(c: *cgen, n: *node) bool = {
|
||||
if (streq(fld, "len")) { return false; };
|
||||
if (streq(fld, "cap")) { return false; };
|
||||
if (base != nil) {
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
if (base.kind == N_IDENT) {
|
||||
let bn: str = base.str;
|
||||
let lc: *local = local_find_node(c, bn);
|
||||
let lc: *local = local_find_node(c, base.str);
|
||||
if (lc != nil) {
|
||||
let tn: *node = lc.tnode;
|
||||
let lkind: i32 = -1;
|
||||
if (tn != nil) { lkind = tn.kind; };
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
if (lkind == N_TNAME) { sname = tn.str; };
|
||||
if (lkind == N_TPTR) {
|
||||
let inner: *node = tn.lhs;
|
||||
@@ -820,18 +819,27 @@ fn node_isstr(c: *cgen, n: *node) bool = {
|
||||
if (inner.kind == N_TNAME) { sname = inner.str; };
|
||||
};
|
||||
};
|
||||
if (sname.len > 0) {
|
||||
let si: *struct_info = struct_lookup(c, sname);
|
||||
if (si != nil) {
|
||||
let fi: *field_info = si.fields;
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (streq(fn_, fld)) {
|
||||
return is_str_type(c, fi.tnode);
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
// Chained dot (`p.foo.bar`): use dot_inner_struct_ptr
|
||||
// to resolve the inner chain to the *struct it lands
|
||||
// on, then look up `fld` in that struct.
|
||||
if (base.kind == N_DOT) {
|
||||
let inner_t: *node = dot_inner_struct_ptr(c, base);
|
||||
if (inner_t != nil) {
|
||||
if (inner_t.kind == N_TNAME) { sname = inner_t.str; };
|
||||
};
|
||||
};
|
||||
if (sname.len > 0) {
|
||||
let si: *struct_info = struct_lookup(c, sname);
|
||||
if (si != nil) {
|
||||
let fi: *field_info = si.fields;
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (streq(fn_, fld)) {
|
||||
return is_str_type(c, fi.tnode);
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -862,6 +870,36 @@ fn type_node_isunsigned(t: *node) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// type_is_8byte_primitive — does this type take exactly one 8-byte
|
||||
// slot (pointer / fn-ptr / 64-bit int / chan / scalar primitive
|
||||
// padded up to 8) rather than a wider aggregate? Used by N_LET
|
||||
// zero-init to mirror C cgen's "only zero if sz == 8 at the type
|
||||
// level" rule. Strings (16), slices (24), tagged unions (>=16),
|
||||
// tuples (16), structs (varies), arrays — all fall through to
|
||||
// false here even when their *slot* rounds up to 8.
|
||||
fn type_is_8byte_primitive(c: *cgen, t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
let k: i32 = t.kind;
|
||||
if (k == N_TPTR) { return true; };
|
||||
if (k == N_TFN) { return true; };
|
||||
if (k == N_TCHAN) { return true; };
|
||||
if (k == N_TSLICE) { return false; };
|
||||
if (k == N_TARRAY) { return false; };
|
||||
if (k == N_TTUPLE) { return false; };
|
||||
if (k == N_TTAGGED){ return false; };
|
||||
if (k == N_TNAME) {
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "str")) { return false; };
|
||||
// Struct alias: not a primitive even if the slot is 8B.
|
||||
if (struct_lookup(c, nm) != nil) { return false; };
|
||||
// Primitive (i8/u8/.../i64/u64/bool/rune/f32/f64/int/...).
|
||||
// All of these get slot-padded to 8 and zero-init in C.
|
||||
if (prim_size(nm) > 0) { return true; };
|
||||
return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// type_name_issigned — true for i8/i16/i32/i64/int/rune.
|
||||
fn type_name_issigned(nm: str) bool = {
|
||||
if (streq(nm, "i8")) { return true; };
|
||||
@@ -964,6 +1002,12 @@ fn index_base_esz(c: *cgen, base: *node) i32 = {
|
||||
return 8;
|
||||
};
|
||||
if (ft.kind == N_TSLICE) { return elem_size_of(ft); };
|
||||
// str-typed field: indexing yields one byte
|
||||
// (`n.s[i]` where .s is str — matches C cgen's
|
||||
// MOVZBQ for byte indexing).
|
||||
if (ft.kind == N_TNAME) {
|
||||
if (streq(ft.str, "str")) { return 1; };
|
||||
};
|
||||
return 8;
|
||||
};
|
||||
fi = fi.finext;
|
||||
@@ -1113,6 +1157,32 @@ fn node_isunsigned(c: *cgen, n: *node) bool = {
|
||||
return node_isunsigned(c, n.rhs);
|
||||
};
|
||||
if (k == N_UN) { return node_isunsigned(c, n.lhs); };
|
||||
// N_INDEX: `p[i]` is unsigned iff p's element type is unsigned.
|
||||
// Walks the base local's declared type and pulls the element
|
||||
// out — *u8 → u8, [N]u32 → u32, []u64 → u64. Without this the
|
||||
// compare-codegen for `p[i] >= 48u8` falls back to signed JGE
|
||||
// instead of JAE, diverging from C 6c on byte indexing.
|
||||
if (k == N_INDEX) {
|
||||
let base: *node = n.lhs;
|
||||
if (base != nil) {
|
||||
if (base.kind == N_IDENT) {
|
||||
let lc: *local = local_find_node(c, base.str);
|
||||
if (lc != nil) {
|
||||
let tn: *node = lc.tnode;
|
||||
if (tn != nil) {
|
||||
let elem: *node = nil;
|
||||
if (tn.kind == N_TPTR) { elem = tn.lhs; };
|
||||
if (tn.kind == N_TARRAY) { elem = tn.lhs; };
|
||||
if (tn.kind == N_TSLICE) { elem = tn.lhs; };
|
||||
if (elem != nil) {
|
||||
return type_node_isunsigned(elem);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -2623,6 +2693,20 @@ fn cgstmt(c: *cgen, n: *node) void = {
|
||||
emit_off((off + 16): i64);
|
||||
emit_line("(BP)\n");
|
||||
};
|
||||
} else {
|
||||
// Bare `let x: T;` with no initializer. C cgen
|
||||
// (cmd/6c/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 slot_size pads struct slots up to 8,
|
||||
// so we can't just check sz == 8: walk the type AST
|
||||
// directly to make the same call.
|
||||
if (type_is_8byte_primitive(c, n.lhs)) {
|
||||
emit_line("\tMOVQ\t$0, ");
|
||||
emit_off(off: i64);
|
||||
emit_line("(BP)\n");
|
||||
};
|
||||
};
|
||||
c.last_was_return = 0;
|
||||
return;
|
||||
@@ -2679,6 +2763,42 @@ fn cgstmt(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// Tuple-destructure assign: `a, b = call();`. The call's tuple
|
||||
// return lands in (AX, DX); push DX to free it, store AX into
|
||||
// the first lvalue, then pop DX into the second. Mirrors
|
||||
// cmd/6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
|
||||
// as C — no fixture uses >2 today).
|
||||
if (k == N_MASSIGN) {
|
||||
if (n.rhs != nil) { cgexpr(c, n.rhs); };
|
||||
emit_line("\tPUSHQ\tDX\n");
|
||||
let l0: *node = n.list;
|
||||
let l1: *node = nil;
|
||||
if (l0 != nil) { l1 = l0.next; };
|
||||
if (l0 != nil) {
|
||||
if (l0.kind == N_IDENT) {
|
||||
let off: i32 = local_find(c, l0.str);
|
||||
if (off != 0) {
|
||||
emit_line("\tMOVQ\tAX, ");
|
||||
emit_off(off: i64);
|
||||
emit_line("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
emit_line("\tPOPQ\tDX\n");
|
||||
if (l1 != nil) {
|
||||
if (l1.kind == N_IDENT) {
|
||||
let off: i32 = local_find(c, l1.str);
|
||||
if (off != 0) {
|
||||
emit_line("\tMOVQ\tDX, ");
|
||||
emit_off(off: i64);
|
||||
emit_line("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
c.last_was_return = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
if (k == N_BREAK) {
|
||||
if (c.loop_top > 0) {
|
||||
let lbl: str = c.loop_end_buf[c.loop_top - 1];
|
||||
|
||||
@@ -744,9 +744,31 @@ fn parsestmt(p: *parser) *node = {
|
||||
expect_tok(p, TK_SEMI, "expected ';' after continue");
|
||||
return newnode(p.a, N_CONTINUE, pf, pl, pc);
|
||||
};
|
||||
// expression statement
|
||||
// expression statement, or tuple-destructure multi-assign:
|
||||
// a, b = expr;
|
||||
// Mirrors cmd/wwc/parse.c:1015-1031. We parse the first lvalue
|
||||
// with parseexpr (matches the C side); subsequent lvalues go
|
||||
// through parsebin(parseunary, 1) so the `=` stays for us to
|
||||
// consume — parseexpr would absorb it.
|
||||
let e: *node = parseexpr(p);
|
||||
if (p.cur_kind == TK_COMMA) {
|
||||
let m: *node = newnode(p.a, N_MASSIGN, pf, pl, pc);
|
||||
let head: *node = e;
|
||||
let tail: *node = e;
|
||||
for (p.cur_kind == TK_COMMA) {
|
||||
advance(p);
|
||||
let lv: *node = parsebin(p, parseunary(p), 1);
|
||||
tail.next = lv;
|
||||
tail = lv;
|
||||
};
|
||||
expect_tok(p, TK_ASSIGN, "expected '=' after multi-assign lvalues");
|
||||
m.rhs = parseexpr(p);
|
||||
m.list = head;
|
||||
expect_tok(p, TK_SEMI, "expected ';' after multi-assign");
|
||||
return m;
|
||||
};
|
||||
let n: *node = newnode(p.a, N_EXPRSTMT, pf, pl, pc);
|
||||
n.lhs = parseexpr(p);
|
||||
n.lhs = e;
|
||||
expect_tok(p, TK_SEMI, "expected ';' after expression statement");
|
||||
return n;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user