selfhost: port forrange — N_FORRANGE parser + cgen + tuple destructure
This commit is contained in:
@@ -140,32 +140,101 @@ fn parsefor(p: *parser) *node = {
|
||||
let pc: i32 = p.curcol;
|
||||
advance(p); // past `for`
|
||||
expecttok(p, tkind.TK_LPAREN, "expected '(' after for");
|
||||
let n: *node = newnode(p.a, nkind.N_FOR, pf, pl, pc);
|
||||
// Three forms (matching C parser):
|
||||
|
||||
// Four forms (matching C parser):
|
||||
// for (cond) — only cond
|
||||
// for (init; cond; post) — full
|
||||
// for (true) — infinite (cond is nkind.N_TRUE)
|
||||
// Distinguish by counting ';'. Look at first chunk: if it's a
|
||||
// `let` stmt that's the init. Otherwise, parse expr; if next is
|
||||
// ';' it was cond. If we see two ';' total after init, post is
|
||||
// next. Simpler: peek for `let` to decide init form.
|
||||
// for (init; cond; post) — C-style 3-clause
|
||||
// for (let x .. expr) — Hare-style range, single binding
|
||||
// for (let (a, b) .. expr) — range with tuple destructure
|
||||
// Range and 3-clause both lead with `let`, so we commit to consuming
|
||||
// `let` then disambiguate by looking at what follows.
|
||||
if (p.curkind == tkind.TK_LET) {
|
||||
n.lhs = parseletlocal(p); // init (consumes its own ';')
|
||||
advance(p); // past `let`
|
||||
|
||||
// Tuple destructure: `for (let (a, b) .. expr)`.
|
||||
if (p.curkind == tkind.TK_LPAREN) {
|
||||
advance(p);
|
||||
let names: *node = nil;
|
||||
let ntail: *node = nil;
|
||||
for (true) {
|
||||
let npf: str = p.curfile;
|
||||
let npl: i32 = p.curline;
|
||||
let npc: i32 = p.curcol;
|
||||
let e: *node = newnode(p.a, nkind.N_IDENT, npf, npl, npc);
|
||||
let nm: str;
|
||||
expectbindname(p, &nm);
|
||||
e.str = nm;
|
||||
if (names == nil) { names = e; }
|
||||
else { ntail.next = e; };
|
||||
ntail = e;
|
||||
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
||||
};
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' in for-range names");
|
||||
expecttok(p, tkind.TK_DOTDOT, "expected '..' after for-range names");
|
||||
let rng: *node = newnode(p.a, nkind.N_FORRANGE, pf, pl, pc);
|
||||
rng.list = names;
|
||||
rng.lhs = parseexpr(p);
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
rng.body = parseblock(p);
|
||||
if (accepttok(p, tkind.TK_ELSE)) { rng.els = parseblock(p); };
|
||||
return rng;
|
||||
};
|
||||
|
||||
// Single binding range or C-style let-init. We need to consume
|
||||
// the IDENT/UNDER to know which: if followed by '..' it's a
|
||||
// range; otherwise build a synthetic LET for the C-style for-init
|
||||
// with the consumed name baked in.
|
||||
if (p.curkind == tkind.TK_IDENT || p.curkind == tkind.TK_UNDER) {
|
||||
let isunder: bool = (p.curkind == tkind.TK_UNDER);
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (!isunder) { nm = p.curtext; };
|
||||
let lpf: str = p.curfile;
|
||||
let lpl: i32 = p.curline;
|
||||
let lpc: i32 = p.curcol;
|
||||
advance(p); // consume IDENT/UNDER
|
||||
|
||||
if (p.curkind == tkind.TK_DOTDOT) {
|
||||
advance(p);
|
||||
let rng: *node = newnode(p.a, nkind.N_FORRANGE, pf, pl, pc);
|
||||
rng.str = nm; // "" for `_`
|
||||
rng.lhs = parseexpr(p);
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
rng.body = parseblock(p);
|
||||
if (accepttok(p, tkind.TK_ELSE)) { rng.els = parseblock(p); };
|
||||
return rng;
|
||||
};
|
||||
|
||||
// Not a range — finish the let manually and continue as
|
||||
// a 3-clause for-init.
|
||||
let first: *node = newnode(p.a, nkind.N_LET, lpf, lpl, lpc);
|
||||
first.str = nm;
|
||||
if (accepttok(p, tkind.TK_COLON)) { first.lhs = parsetype(p); };
|
||||
if (accepttok(p, tkind.TK_ASSIGN)) { first.rhs = parseexpr(p); };
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after for-init let");
|
||||
let n: *node = newnode(p.a, nkind.N_FOR, pf, pl, pc);
|
||||
n.lhs = first;
|
||||
n.cond = parseexpr(p);
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after for cond");
|
||||
n.rhs = parseexpr(p);
|
||||
} else {
|
||||
// Parse one expr. If next is ';', it's a 3-clause without init.
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
n.body = parseblock(p);
|
||||
if (accepttok(p, tkind.TK_ELSE)) { n.els = parseblock(p); };
|
||||
return n;
|
||||
};
|
||||
|
||||
errmsg(p, "expected name after 'let' in for");
|
||||
};
|
||||
|
||||
// for (cond) or for (cond; post)
|
||||
let n: *node = newnode(p.a, nkind.N_FOR, pf, pl, pc);
|
||||
let first: *node = parseexpr(p);
|
||||
if (accepttok(p, tkind.TK_SEMI)) {
|
||||
// cond ; post
|
||||
n.cond = first;
|
||||
n.rhs = parseexpr(p);
|
||||
} else {
|
||||
// just (cond)
|
||||
n.cond = first;
|
||||
};
|
||||
};
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
n.body = parseblock(p);
|
||||
// Optional `else { ... }` — runs at normal cond-false exit; skipped
|
||||
|
||||
@@ -2715,32 +2715,101 @@ fn parsefor(p: *parser) *node = {
|
||||
let pc: i32 = p.curcol;
|
||||
advance(p); // past `for`
|
||||
expecttok(p, tkind.TK_LPAREN, "expected '(' after for");
|
||||
let n: *node = newnode(p.a, nkind.N_FOR, pf, pl, pc);
|
||||
// Three forms (matching C parser):
|
||||
|
||||
// Four forms (matching C parser):
|
||||
// for (cond) — only cond
|
||||
// for (init; cond; post) — full
|
||||
// for (true) — infinite (cond is nkind.N_TRUE)
|
||||
// Distinguish by counting ';'. Look at first chunk: if it's a
|
||||
// `let` stmt that's the init. Otherwise, parse expr; if next is
|
||||
// ';' it was cond. If we see two ';' total after init, post is
|
||||
// next. Simpler: peek for `let` to decide init form.
|
||||
// for (init; cond; post) — C-style 3-clause
|
||||
// for (let x .. expr) — Hare-style range, single binding
|
||||
// for (let (a, b) .. expr) — range with tuple destructure
|
||||
// Range and 3-clause both lead with `let`, so we commit to consuming
|
||||
// `let` then disambiguate by looking at what follows.
|
||||
if (p.curkind == tkind.TK_LET) {
|
||||
n.lhs = parseletlocal(p); // init (consumes its own ';')
|
||||
advance(p); // past `let`
|
||||
|
||||
// Tuple destructure: `for (let (a, b) .. expr)`.
|
||||
if (p.curkind == tkind.TK_LPAREN) {
|
||||
advance(p);
|
||||
let names: *node = nil;
|
||||
let ntail: *node = nil;
|
||||
for (true) {
|
||||
let npf: str = p.curfile;
|
||||
let npl: i32 = p.curline;
|
||||
let npc: i32 = p.curcol;
|
||||
let e: *node = newnode(p.a, nkind.N_IDENT, npf, npl, npc);
|
||||
let nm: str;
|
||||
expectbindname(p, &nm);
|
||||
e.str = nm;
|
||||
if (names == nil) { names = e; }
|
||||
else { ntail.next = e; };
|
||||
ntail = e;
|
||||
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
||||
};
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' in for-range names");
|
||||
expecttok(p, tkind.TK_DOTDOT, "expected '..' after for-range names");
|
||||
let rng: *node = newnode(p.a, nkind.N_FORRANGE, pf, pl, pc);
|
||||
rng.list = names;
|
||||
rng.lhs = parseexpr(p);
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
rng.body = parseblock(p);
|
||||
if (accepttok(p, tkind.TK_ELSE)) { rng.els = parseblock(p); };
|
||||
return rng;
|
||||
};
|
||||
|
||||
// Single binding range or C-style let-init. We need to consume
|
||||
// the IDENT/UNDER to know which: if followed by '..' it's a
|
||||
// range; otherwise build a synthetic LET for the C-style for-init
|
||||
// with the consumed name baked in.
|
||||
if (p.curkind == tkind.TK_IDENT || p.curkind == tkind.TK_UNDER) {
|
||||
let isunder: bool = (p.curkind == tkind.TK_UNDER);
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (!isunder) { nm = p.curtext; };
|
||||
let lpf: str = p.curfile;
|
||||
let lpl: i32 = p.curline;
|
||||
let lpc: i32 = p.curcol;
|
||||
advance(p); // consume IDENT/UNDER
|
||||
|
||||
if (p.curkind == tkind.TK_DOTDOT) {
|
||||
advance(p);
|
||||
let rng: *node = newnode(p.a, nkind.N_FORRANGE, pf, pl, pc);
|
||||
rng.str = nm; // "" for `_`
|
||||
rng.lhs = parseexpr(p);
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
rng.body = parseblock(p);
|
||||
if (accepttok(p, tkind.TK_ELSE)) { rng.els = parseblock(p); };
|
||||
return rng;
|
||||
};
|
||||
|
||||
// Not a range — finish the let manually and continue as
|
||||
// a 3-clause for-init.
|
||||
let first: *node = newnode(p.a, nkind.N_LET, lpf, lpl, lpc);
|
||||
first.str = nm;
|
||||
if (accepttok(p, tkind.TK_COLON)) { first.lhs = parsetype(p); };
|
||||
if (accepttok(p, tkind.TK_ASSIGN)) { first.rhs = parseexpr(p); };
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after for-init let");
|
||||
let n: *node = newnode(p.a, nkind.N_FOR, pf, pl, pc);
|
||||
n.lhs = first;
|
||||
n.cond = parseexpr(p);
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after for cond");
|
||||
n.rhs = parseexpr(p);
|
||||
} else {
|
||||
// Parse one expr. If next is ';', it's a 3-clause without init.
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
n.body = parseblock(p);
|
||||
if (accepttok(p, tkind.TK_ELSE)) { n.els = parseblock(p); };
|
||||
return n;
|
||||
};
|
||||
|
||||
errmsg(p, "expected name after 'let' in for");
|
||||
};
|
||||
|
||||
// for (cond) or for (cond; post)
|
||||
let n: *node = newnode(p.a, nkind.N_FOR, pf, pl, pc);
|
||||
let first: *node = parseexpr(p);
|
||||
if (accepttok(p, tkind.TK_SEMI)) {
|
||||
// cond ; post
|
||||
n.cond = first;
|
||||
n.rhs = parseexpr(p);
|
||||
} else {
|
||||
// just (cond)
|
||||
n.cond = first;
|
||||
};
|
||||
};
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
n.body = parseblock(p);
|
||||
// Optional `else { ... }` — runs at normal cond-false exit; skipped
|
||||
@@ -4119,6 +4188,32 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// `for (let x .. slice) body` / `for (let (a, b) .. slice) body` —
|
||||
// each binding name becomes a fresh local. Walk the slice expr first
|
||||
// so its idents resolve before the bindings shadow anything, then
|
||||
// install bindings and walk the body/else.
|
||||
if (k == nkind.N_FORRANGE) {
|
||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||
if (n.list != nil) {
|
||||
let m: *node = n.list;
|
||||
for (m != nil) {
|
||||
let bnm: str = m.str;
|
||||
if (bnm.len > 0) {
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, m);
|
||||
};
|
||||
m = m.next;
|
||||
};
|
||||
} else {
|
||||
let bnm: str = n.str;
|
||||
if (bnm.len > 0) {
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, n);
|
||||
};
|
||||
};
|
||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||
if (n.els != nil) { resolvewalk(c, n.els); };
|
||||
return;
|
||||
};
|
||||
|
||||
// `match (e) { case let v: T => stmt; ... }` — the binding `v`
|
||||
// is declared by the case arm and visible inside its body.
|
||||
if (k == nkind.N_MCASE) {
|
||||
@@ -8411,6 +8506,8 @@ fn cgstmt(c: *cgen, n: *node) void = {
|
||||
|
||||
if (k == nkind.N_FOR) { cgfor(c, n); return; };
|
||||
|
||||
if (k == nkind.N_FORRANGE) { cgforrange(c, n); return; };
|
||||
|
||||
if (k == nkind.N_SWITCH) { cgswitch(c, n); return; };
|
||||
|
||||
if (k == nkind.N_MASSIGN) { cgmassign(c, n); return; };
|
||||
@@ -9095,6 +9192,271 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// paramfieldsize — raw byte size of a tuple-field type. Mirrors the
|
||||
// `tp->type->size` read in C cgen N_FORRANGE: 1 for i8/u8/bool, 4 for
|
||||
// i32/u32, 8 for i64/u64/*T/fn/slice-elt, 16 for str, default 8.
|
||||
fn paramfieldsize(t: *node) i32 = {
|
||||
if (t == nil) { return 8; };
|
||||
let k: nkind = t.kind;
|
||||
if (k == nkind.N_TPTR) { return 8; };
|
||||
if (k == nkind.N_TFN) { return 8; };
|
||||
if (k == nkind.N_TCHAN) { return 8; };
|
||||
if (k == nkind.N_TNAME) {
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "str")) { return 16; };
|
||||
let ps: i32 = primsize(nm);
|
||||
if (ps > 0) { return ps; };
|
||||
};
|
||||
return 8;
|
||||
};
|
||||
|
||||
// paramissigned — does this primitive type need sign-extending on a
|
||||
// narrow (4B) load? Mirrors C cgen's `binds[b].signed_field` flag.
|
||||
fn paramissigned(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "i8")) { return true; };
|
||||
if (streq(nm, "i16")) { return true; };
|
||||
if (streq(nm, "i32")) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// cgforrange — lower `for (let x .. slice) body` (and the tuple-
|
||||
// destructure cousin `for (let (a, b) .. slice) body`). The body is
|
||||
// wrapped in a counted loop driven by stack-spilled `.rgi`/`.rgl`.
|
||||
// Each iteration computes the element address `s.ptr + i*esz` and
|
||||
// either loads the whole element into the named local or pulls each
|
||||
// tuple field into its own local. Mirrors cmd/w6c/cgen.c N_FORRANGE
|
||||
// byte-for-byte (label names + labelseq consumption order).
|
||||
fn cgforrange(c: *cgen, n: *node) void = {
|
||||
let slc: *node = n.lhs;
|
||||
let slclocal: *local = nil;
|
||||
let slctn: *node = nil;
|
||||
if (slc != nil) {
|
||||
if (slc.kind == nkind.N_IDENT) {
|
||||
slclocal = localfindnode(c, slc.str);
|
||||
if (slclocal != nil) { slctn = slclocal.tnode; };
|
||||
};
|
||||
};
|
||||
// Element type — peek through TSLICE/TARRAY for the tuple param walk.
|
||||
let elemt: *node = nil;
|
||||
if (slctn != nil) {
|
||||
let sk: nkind = slctn.kind;
|
||||
if (sk == nkind.N_TSLICE) { elemt = slctn.lhs; };
|
||||
if (sk == nkind.N_TARRAY) { elemt = slctn.lhs; };
|
||||
};
|
||||
// esz: raw elem byte size. For tuple-element slices `[](T0, T1)`,
|
||||
// C cgen reads the resolved tuple's size (sum of raw param sizes,
|
||||
// no slot-padding) so e.g. `(i64, i64)` is 16, `(i32, i32)` is 8.
|
||||
// elemsizeof returns 8 for non-primitive elem, which would be
|
||||
// wrong here — compute from the tuple param walk instead.
|
||||
let esz: i32 = elemsizeof(slctn);
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TTUPLE) {
|
||||
let total: i32 = 0;
|
||||
let p: *node = elemt.list;
|
||||
for (p != nil) {
|
||||
total += paramfieldsize(p);
|
||||
p = p.next;
|
||||
};
|
||||
esz = total;
|
||||
};
|
||||
};
|
||||
let destruct: bool = (n.list != nil);
|
||||
|
||||
// .rgi (counter) + .rgl (length) scratch slots.
|
||||
let iname: str = mkscratchname(c, "rgi");
|
||||
let lname: str = mkscratchname(c, "rgl");
|
||||
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
|
||||
let nbinds: i32 = 0;
|
||||
|
||||
if (destruct) {
|
||||
let tp: *node = nil;
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TTUPLE) { tp = elemt.list; };
|
||||
};
|
||||
let field_off: i32 = 0;
|
||||
let m: *node = n.list;
|
||||
for (m != nil) {
|
||||
if (nbinds >= 8) { m = nil; }
|
||||
else {
|
||||
let fsz: i32 = 8;
|
||||
let signf: bool = false;
|
||||
if (tp != nil) {
|
||||
fsz = paramfieldsize(tp);
|
||||
signf = paramissigned(tp);
|
||||
};
|
||||
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; };
|
||||
let bnm: str = m.str;
|
||||
if (bnm.len > 0) {
|
||||
bind_off[nbinds] = localadd(c, bnm, slot_sz, nil): i64;
|
||||
} else {
|
||||
bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64;
|
||||
};
|
||||
field_off += fsz;
|
||||
nbinds += 1;
|
||||
if (tp != nil) { tp = tp.next; };
|
||||
m = m.next;
|
||||
};
|
||||
};
|
||||
} else {
|
||||
let slot_sz: i32 = esz;
|
||||
if (slot_sz < 8) { slot_sz = 8; };
|
||||
bind_sz[0] = esz: i64;
|
||||
bind_foff[0] = 0i64;
|
||||
// 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; };
|
||||
if (n.str.len > 0) {
|
||||
bind_off[0] = localadd(c, n.str, slot_sz, nil): i64;
|
||||
} else {
|
||||
bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64;
|
||||
};
|
||||
nbinds = 1;
|
||||
};
|
||||
|
||||
// init: ioff(BP) = 0
|
||||
emitline("\tMOVQ\t$0, ");
|
||||
emitoff(ioff: i64);
|
||||
emitline("(BP)\n");
|
||||
|
||||
// loff(BP) = len
|
||||
let isarr: bool = false;
|
||||
let isslicestr: bool = false;
|
||||
if (slctn != nil) {
|
||||
let tk: nkind = slctn.kind;
|
||||
if (tk == nkind.N_TSLICE) { isslicestr = true; };
|
||||
if (tk == nkind.N_TARRAY) { isarr = true; };
|
||||
if (tk == nkind.N_TNAME) {
|
||||
if (streq(slctn.str, "str")) { isslicestr = true; };
|
||||
};
|
||||
};
|
||||
if (isslicestr) {
|
||||
if (slc.kind == nkind.N_IDENT) {
|
||||
if (slclocal != nil) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((slclocal.off + 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
} else { if (isarr) {
|
||||
let alen: i64 = 0i64;
|
||||
if (slctn.rhs != nil) {
|
||||
if (slctn.rhs.kind == nkind.N_INTLIT) { alen = slctn.rhs.uval: i64; };
|
||||
};
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(alen);
|
||||
emitline(", ");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
cgexpr(c, slc);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP)\n");
|
||||
};};
|
||||
|
||||
let loopl: str = mklabel(c, "rloop");
|
||||
let endl: str = mklabel(c, "rend");
|
||||
let naturall: str = endl;
|
||||
if (n.els != nil) { naturall = mklabel(c, "relseloop"); };
|
||||
|
||||
c.loopcontbuf[c.looptop] = loopl;
|
||||
c.loopendbuf[c.looptop] = endl;
|
||||
c.looptop += 1;
|
||||
|
||||
emitlabel(loopl);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(ioff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tCMPQ\tBX, AX\n");
|
||||
emitline("\tJGE\t"); emitline(naturall); emitline("\n");
|
||||
|
||||
// BX = base + i*esz
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (slc.kind == nkind.N_IDENT) {
|
||||
if (slclocal != nil) {
|
||||
if (isarr) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(slclocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(slclocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
|
||||
// Per-binding load from BX+foff.
|
||||
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"; };
|
||||
};};
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff(bind_foff[b]);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(bind_off[b]);
|
||||
emitline("(BP)\n");
|
||||
b += 1;
|
||||
};
|
||||
|
||||
if (n.body != nil) { cgstmt(c, n.body); };
|
||||
|
||||
c.looptop -= 1;
|
||||
|
||||
emitline("\tADDQ\t$1, ");
|
||||
emitoff(ioff: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tJMP\t"); emitline(loopl); emitline("\n");
|
||||
if (n.els != nil) {
|
||||
emitlabel(naturall);
|
||||
cgstmt(c, n.els);
|
||||
};
|
||||
emitlabel(endl);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
// cgswitch — lower `switch (e) { case 1, 2: ...; case: default; }` to
|
||||
// a chain of compares against the scrutinee. Scrutinee lands in a
|
||||
// fresh 8B local slot so case bodies can spill SP without losing it.
|
||||
@@ -9269,6 +9631,33 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
// ".sw_<labelseq>" at cgen time — unique per switch — so it must
|
||||
// not dedup. Count it here so the frame SUBQ matches.
|
||||
if (n.kind == nkind.N_SWITCH) { total += 8; };
|
||||
// `for (let x .. s)` allocates two 8B scratch slots — `.rgi_<seq>`
|
||||
// (counter) and `.rgl_<seq>` (length) — plus one slot per binding.
|
||||
// Per-binding sz defaults to 8 (covers scalar primitives + ptrs).
|
||||
// `str` tuple-fields would need 16 — selfhost doesn't yet emit
|
||||
// those, so the simple count tracks C cgen for current fixtures.
|
||||
if (n.kind == nkind.N_FORRANGE) {
|
||||
total += 16; // .rgi + .rgl scratch
|
||||
if (n.list != nil) {
|
||||
let m: *node = n.list;
|
||||
for (m != nil) {
|
||||
let bnm: str = m.str;
|
||||
if (bnm.len == 0) {
|
||||
total += 8; // discard binding still gets a slot
|
||||
} else { if (!scanseenmark(c, bnm)) {
|
||||
total += 8;
|
||||
};};
|
||||
m = m.next;
|
||||
};
|
||||
} else {
|
||||
let bnm: str = n.str;
|
||||
if (bnm.len == 0) {
|
||||
total += 8;
|
||||
} else { if (!scanseenmark(c, bnm)) {
|
||||
total += 8;
|
||||
};};
|
||||
};
|
||||
};
|
||||
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||
// Crucially we do NOT dedup these against c.locals: C cgen
|
||||
// handles a match as an expression with a by-value locals copy,
|
||||
|
||||
@@ -92,6 +92,33 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
// ".sw_<labelseq>" at cgen time — unique per switch — so it must
|
||||
// not dedup. Count it here so the frame SUBQ matches.
|
||||
if (n.kind == nkind.N_SWITCH) { total += 8; };
|
||||
// `for (let x .. s)` allocates two 8B scratch slots — `.rgi_<seq>`
|
||||
// (counter) and `.rgl_<seq>` (length) — plus one slot per binding.
|
||||
// Per-binding sz defaults to 8 (covers scalar primitives + ptrs).
|
||||
// `str` tuple-fields would need 16 — selfhost doesn't yet emit
|
||||
// those, so the simple count tracks C cgen for current fixtures.
|
||||
if (n.kind == nkind.N_FORRANGE) {
|
||||
total += 16; // .rgi + .rgl scratch
|
||||
if (n.list != nil) {
|
||||
let m: *node = n.list;
|
||||
for (m != nil) {
|
||||
let bnm: str = m.str;
|
||||
if (bnm.len == 0) {
|
||||
total += 8; // discard binding still gets a slot
|
||||
} else { if (!scanseenmark(c, bnm)) {
|
||||
total += 8;
|
||||
};};
|
||||
m = m.next;
|
||||
};
|
||||
} else {
|
||||
let bnm: str = n.str;
|
||||
if (bnm.len == 0) {
|
||||
total += 8;
|
||||
} else { if (!scanseenmark(c, bnm)) {
|
||||
total += 8;
|
||||
};};
|
||||
};
|
||||
};
|
||||
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||
// Crucially we do NOT dedup these against c.locals: C cgen
|
||||
// handles a match as an expression with a by-value locals copy,
|
||||
|
||||
@@ -34,6 +34,8 @@ fn cgstmt(c: *cgen, n: *node) void = {
|
||||
|
||||
if (k == nkind.N_FOR) { cgfor(c, n); return; };
|
||||
|
||||
if (k == nkind.N_FORRANGE) { cgforrange(c, n); return; };
|
||||
|
||||
if (k == nkind.N_SWITCH) { cgswitch(c, n); return; };
|
||||
|
||||
if (k == nkind.N_MASSIGN) { cgmassign(c, n); return; };
|
||||
@@ -718,6 +720,271 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// paramfieldsize — raw byte size of a tuple-field type. Mirrors the
|
||||
// `tp->type->size` read in C cgen N_FORRANGE: 1 for i8/u8/bool, 4 for
|
||||
// i32/u32, 8 for i64/u64/*T/fn/slice-elt, 16 for str, default 8.
|
||||
fn paramfieldsize(t: *node) i32 = {
|
||||
if (t == nil) { return 8; };
|
||||
let k: nkind = t.kind;
|
||||
if (k == nkind.N_TPTR) { return 8; };
|
||||
if (k == nkind.N_TFN) { return 8; };
|
||||
if (k == nkind.N_TCHAN) { return 8; };
|
||||
if (k == nkind.N_TNAME) {
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "str")) { return 16; };
|
||||
let ps: i32 = primsize(nm);
|
||||
if (ps > 0) { return ps; };
|
||||
};
|
||||
return 8;
|
||||
};
|
||||
|
||||
// paramissigned — does this primitive type need sign-extending on a
|
||||
// narrow (4B) load? Mirrors C cgen's `binds[b].signed_field` flag.
|
||||
fn paramissigned(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "i8")) { return true; };
|
||||
if (streq(nm, "i16")) { return true; };
|
||||
if (streq(nm, "i32")) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// cgforrange — lower `for (let x .. slice) body` (and the tuple-
|
||||
// destructure cousin `for (let (a, b) .. slice) body`). The body is
|
||||
// wrapped in a counted loop driven by stack-spilled `.rgi`/`.rgl`.
|
||||
// Each iteration computes the element address `s.ptr + i*esz` and
|
||||
// either loads the whole element into the named local or pulls each
|
||||
// tuple field into its own local. Mirrors cmd/w6c/cgen.c N_FORRANGE
|
||||
// byte-for-byte (label names + labelseq consumption order).
|
||||
fn cgforrange(c: *cgen, n: *node) void = {
|
||||
let slc: *node = n.lhs;
|
||||
let slclocal: *local = nil;
|
||||
let slctn: *node = nil;
|
||||
if (slc != nil) {
|
||||
if (slc.kind == nkind.N_IDENT) {
|
||||
slclocal = localfindnode(c, slc.str);
|
||||
if (slclocal != nil) { slctn = slclocal.tnode; };
|
||||
};
|
||||
};
|
||||
// Element type — peek through TSLICE/TARRAY for the tuple param walk.
|
||||
let elemt: *node = nil;
|
||||
if (slctn != nil) {
|
||||
let sk: nkind = slctn.kind;
|
||||
if (sk == nkind.N_TSLICE) { elemt = slctn.lhs; };
|
||||
if (sk == nkind.N_TARRAY) { elemt = slctn.lhs; };
|
||||
};
|
||||
// esz: raw elem byte size. For tuple-element slices `[](T0, T1)`,
|
||||
// C cgen reads the resolved tuple's size (sum of raw param sizes,
|
||||
// no slot-padding) so e.g. `(i64, i64)` is 16, `(i32, i32)` is 8.
|
||||
// elemsizeof returns 8 for non-primitive elem, which would be
|
||||
// wrong here — compute from the tuple param walk instead.
|
||||
let esz: i32 = elemsizeof(slctn);
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TTUPLE) {
|
||||
let total: i32 = 0;
|
||||
let p: *node = elemt.list;
|
||||
for (p != nil) {
|
||||
total += paramfieldsize(p);
|
||||
p = p.next;
|
||||
};
|
||||
esz = total;
|
||||
};
|
||||
};
|
||||
let destruct: bool = (n.list != nil);
|
||||
|
||||
// .rgi (counter) + .rgl (length) scratch slots.
|
||||
let iname: str = mkscratchname(c, "rgi");
|
||||
let lname: str = mkscratchname(c, "rgl");
|
||||
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
|
||||
let nbinds: i32 = 0;
|
||||
|
||||
if (destruct) {
|
||||
let tp: *node = nil;
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TTUPLE) { tp = elemt.list; };
|
||||
};
|
||||
let field_off: i32 = 0;
|
||||
let m: *node = n.list;
|
||||
for (m != nil) {
|
||||
if (nbinds >= 8) { m = nil; }
|
||||
else {
|
||||
let fsz: i32 = 8;
|
||||
let signf: bool = false;
|
||||
if (tp != nil) {
|
||||
fsz = paramfieldsize(tp);
|
||||
signf = paramissigned(tp);
|
||||
};
|
||||
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; };
|
||||
let bnm: str = m.str;
|
||||
if (bnm.len > 0) {
|
||||
bind_off[nbinds] = localadd(c, bnm, slot_sz, nil): i64;
|
||||
} else {
|
||||
bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64;
|
||||
};
|
||||
field_off += fsz;
|
||||
nbinds += 1;
|
||||
if (tp != nil) { tp = tp.next; };
|
||||
m = m.next;
|
||||
};
|
||||
};
|
||||
} else {
|
||||
let slot_sz: i32 = esz;
|
||||
if (slot_sz < 8) { slot_sz = 8; };
|
||||
bind_sz[0] = esz: i64;
|
||||
bind_foff[0] = 0i64;
|
||||
// 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; };
|
||||
if (n.str.len > 0) {
|
||||
bind_off[0] = localadd(c, n.str, slot_sz, nil): i64;
|
||||
} else {
|
||||
bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64;
|
||||
};
|
||||
nbinds = 1;
|
||||
};
|
||||
|
||||
// init: ioff(BP) = 0
|
||||
emitline("\tMOVQ\t$0, ");
|
||||
emitoff(ioff: i64);
|
||||
emitline("(BP)\n");
|
||||
|
||||
// loff(BP) = len
|
||||
let isarr: bool = false;
|
||||
let isslicestr: bool = false;
|
||||
if (slctn != nil) {
|
||||
let tk: nkind = slctn.kind;
|
||||
if (tk == nkind.N_TSLICE) { isslicestr = true; };
|
||||
if (tk == nkind.N_TARRAY) { isarr = true; };
|
||||
if (tk == nkind.N_TNAME) {
|
||||
if (streq(slctn.str, "str")) { isslicestr = true; };
|
||||
};
|
||||
};
|
||||
if (isslicestr) {
|
||||
if (slc.kind == nkind.N_IDENT) {
|
||||
if (slclocal != nil) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((slclocal.off + 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
} else { if (isarr) {
|
||||
let alen: i64 = 0i64;
|
||||
if (slctn.rhs != nil) {
|
||||
if (slctn.rhs.kind == nkind.N_INTLIT) { alen = slctn.rhs.uval: i64; };
|
||||
};
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(alen);
|
||||
emitline(", ");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
cgexpr(c, slc);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP)\n");
|
||||
};};
|
||||
|
||||
let loopl: str = mklabel(c, "rloop");
|
||||
let endl: str = mklabel(c, "rend");
|
||||
let naturall: str = endl;
|
||||
if (n.els != nil) { naturall = mklabel(c, "relseloop"); };
|
||||
|
||||
c.loopcontbuf[c.looptop] = loopl;
|
||||
c.loopendbuf[c.looptop] = endl;
|
||||
c.looptop += 1;
|
||||
|
||||
emitlabel(loopl);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(ioff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tCMPQ\tBX, AX\n");
|
||||
emitline("\tJGE\t"); emitline(naturall); emitline("\n");
|
||||
|
||||
// BX = base + i*esz
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (slc.kind == nkind.N_IDENT) {
|
||||
if (slclocal != nil) {
|
||||
if (isarr) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(slclocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(slclocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
|
||||
// Per-binding load from BX+foff.
|
||||
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"; };
|
||||
};};
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff(bind_foff[b]);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(bind_off[b]);
|
||||
emitline("(BP)\n");
|
||||
b += 1;
|
||||
};
|
||||
|
||||
if (n.body != nil) { cgstmt(c, n.body); };
|
||||
|
||||
c.looptop -= 1;
|
||||
|
||||
emitline("\tADDQ\t$1, ");
|
||||
emitoff(ioff: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tJMP\t"); emitline(loopl); emitline("\n");
|
||||
if (n.els != nil) {
|
||||
emitlabel(naturall);
|
||||
cgstmt(c, n.els);
|
||||
};
|
||||
emitlabel(endl);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
// cgswitch — lower `switch (e) { case 1, 2: ...; case: default; }` to
|
||||
// a chain of compares against the scrutinee. Scrutinee lands in a
|
||||
// fresh 8B local slot so case bodies can spill SP without losing it.
|
||||
|
||||
@@ -154,6 +154,32 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// `for (let x .. slice) body` / `for (let (a, b) .. slice) body` —
|
||||
// each binding name becomes a fresh local. Walk the slice expr first
|
||||
// so its idents resolve before the bindings shadow anything, then
|
||||
// install bindings and walk the body/else.
|
||||
if (k == nkind.N_FORRANGE) {
|
||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||
if (n.list != nil) {
|
||||
let m: *node = n.list;
|
||||
for (m != nil) {
|
||||
let bnm: str = m.str;
|
||||
if (bnm.len > 0) {
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, m);
|
||||
};
|
||||
m = m.next;
|
||||
};
|
||||
} else {
|
||||
let bnm: str = n.str;
|
||||
if (bnm.len > 0) {
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, n);
|
||||
};
|
||||
};
|
||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||
if (n.els != nil) { resolvewalk(c, n.els); };
|
||||
return;
|
||||
};
|
||||
|
||||
// `match (e) { case let v: T => stmt; ... }` — the binding `v`
|
||||
// is declared by the case arm and visible inside its body.
|
||||
if (k == nkind.N_MCASE) {
|
||||
|
||||
@@ -2715,32 +2715,101 @@ fn parsefor(p: *parser) *node = {
|
||||
let pc: i32 = p.curcol;
|
||||
advance(p); // past `for`
|
||||
expecttok(p, tkind.TK_LPAREN, "expected '(' after for");
|
||||
let n: *node = newnode(p.a, nkind.N_FOR, pf, pl, pc);
|
||||
// Three forms (matching C parser):
|
||||
|
||||
// Four forms (matching C parser):
|
||||
// for (cond) — only cond
|
||||
// for (init; cond; post) — full
|
||||
// for (true) — infinite (cond is nkind.N_TRUE)
|
||||
// Distinguish by counting ';'. Look at first chunk: if it's a
|
||||
// `let` stmt that's the init. Otherwise, parse expr; if next is
|
||||
// ';' it was cond. If we see two ';' total after init, post is
|
||||
// next. Simpler: peek for `let` to decide init form.
|
||||
// for (init; cond; post) — C-style 3-clause
|
||||
// for (let x .. expr) — Hare-style range, single binding
|
||||
// for (let (a, b) .. expr) — range with tuple destructure
|
||||
// Range and 3-clause both lead with `let`, so we commit to consuming
|
||||
// `let` then disambiguate by looking at what follows.
|
||||
if (p.curkind == tkind.TK_LET) {
|
||||
n.lhs = parseletlocal(p); // init (consumes its own ';')
|
||||
advance(p); // past `let`
|
||||
|
||||
// Tuple destructure: `for (let (a, b) .. expr)`.
|
||||
if (p.curkind == tkind.TK_LPAREN) {
|
||||
advance(p);
|
||||
let names: *node = nil;
|
||||
let ntail: *node = nil;
|
||||
for (true) {
|
||||
let npf: str = p.curfile;
|
||||
let npl: i32 = p.curline;
|
||||
let npc: i32 = p.curcol;
|
||||
let e: *node = newnode(p.a, nkind.N_IDENT, npf, npl, npc);
|
||||
let nm: str;
|
||||
expectbindname(p, &nm);
|
||||
e.str = nm;
|
||||
if (names == nil) { names = e; }
|
||||
else { ntail.next = e; };
|
||||
ntail = e;
|
||||
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
||||
};
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' in for-range names");
|
||||
expecttok(p, tkind.TK_DOTDOT, "expected '..' after for-range names");
|
||||
let rng: *node = newnode(p.a, nkind.N_FORRANGE, pf, pl, pc);
|
||||
rng.list = names;
|
||||
rng.lhs = parseexpr(p);
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
rng.body = parseblock(p);
|
||||
if (accepttok(p, tkind.TK_ELSE)) { rng.els = parseblock(p); };
|
||||
return rng;
|
||||
};
|
||||
|
||||
// Single binding range or C-style let-init. We need to consume
|
||||
// the IDENT/UNDER to know which: if followed by '..' it's a
|
||||
// range; otherwise build a synthetic LET for the C-style for-init
|
||||
// with the consumed name baked in.
|
||||
if (p.curkind == tkind.TK_IDENT || p.curkind == tkind.TK_UNDER) {
|
||||
let isunder: bool = (p.curkind == tkind.TK_UNDER);
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (!isunder) { nm = p.curtext; };
|
||||
let lpf: str = p.curfile;
|
||||
let lpl: i32 = p.curline;
|
||||
let lpc: i32 = p.curcol;
|
||||
advance(p); // consume IDENT/UNDER
|
||||
|
||||
if (p.curkind == tkind.TK_DOTDOT) {
|
||||
advance(p);
|
||||
let rng: *node = newnode(p.a, nkind.N_FORRANGE, pf, pl, pc);
|
||||
rng.str = nm; // "" for `_`
|
||||
rng.lhs = parseexpr(p);
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
rng.body = parseblock(p);
|
||||
if (accepttok(p, tkind.TK_ELSE)) { rng.els = parseblock(p); };
|
||||
return rng;
|
||||
};
|
||||
|
||||
// Not a range — finish the let manually and continue as
|
||||
// a 3-clause for-init.
|
||||
let first: *node = newnode(p.a, nkind.N_LET, lpf, lpl, lpc);
|
||||
first.str = nm;
|
||||
if (accepttok(p, tkind.TK_COLON)) { first.lhs = parsetype(p); };
|
||||
if (accepttok(p, tkind.TK_ASSIGN)) { first.rhs = parseexpr(p); };
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after for-init let");
|
||||
let n: *node = newnode(p.a, nkind.N_FOR, pf, pl, pc);
|
||||
n.lhs = first;
|
||||
n.cond = parseexpr(p);
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after for cond");
|
||||
n.rhs = parseexpr(p);
|
||||
} else {
|
||||
// Parse one expr. If next is ';', it's a 3-clause without init.
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
n.body = parseblock(p);
|
||||
if (accepttok(p, tkind.TK_ELSE)) { n.els = parseblock(p); };
|
||||
return n;
|
||||
};
|
||||
|
||||
errmsg(p, "expected name after 'let' in for");
|
||||
};
|
||||
|
||||
// for (cond) or for (cond; post)
|
||||
let n: *node = newnode(p.a, nkind.N_FOR, pf, pl, pc);
|
||||
let first: *node = parseexpr(p);
|
||||
if (accepttok(p, tkind.TK_SEMI)) {
|
||||
// cond ; post
|
||||
n.cond = first;
|
||||
n.rhs = parseexpr(p);
|
||||
} else {
|
||||
// just (cond)
|
||||
n.cond = first;
|
||||
};
|
||||
};
|
||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after for");
|
||||
n.body = parseblock(p);
|
||||
// Optional `else { ... }` — runs at normal cond-false exit; skipped
|
||||
@@ -4119,6 +4188,32 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// `for (let x .. slice) body` / `for (let (a, b) .. slice) body` —
|
||||
// each binding name becomes a fresh local. Walk the slice expr first
|
||||
// so its idents resolve before the bindings shadow anything, then
|
||||
// install bindings and walk the body/else.
|
||||
if (k == nkind.N_FORRANGE) {
|
||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||
if (n.list != nil) {
|
||||
let m: *node = n.list;
|
||||
for (m != nil) {
|
||||
let bnm: str = m.str;
|
||||
if (bnm.len > 0) {
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, m);
|
||||
};
|
||||
m = m.next;
|
||||
};
|
||||
} else {
|
||||
let bnm: str = n.str;
|
||||
if (bnm.len > 0) {
|
||||
scopedefine(c.cur, bnm, skind.SK_VAR, nil, n);
|
||||
};
|
||||
};
|
||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||
if (n.els != nil) { resolvewalk(c, n.els); };
|
||||
return;
|
||||
};
|
||||
|
||||
// `match (e) { case let v: T => stmt; ... }` — the binding `v`
|
||||
// is declared by the case arm and visible inside its body.
|
||||
if (k == nkind.N_MCASE) {
|
||||
@@ -8411,6 +8506,8 @@ fn cgstmt(c: *cgen, n: *node) void = {
|
||||
|
||||
if (k == nkind.N_FOR) { cgfor(c, n); return; };
|
||||
|
||||
if (k == nkind.N_FORRANGE) { cgforrange(c, n); return; };
|
||||
|
||||
if (k == nkind.N_SWITCH) { cgswitch(c, n); return; };
|
||||
|
||||
if (k == nkind.N_MASSIGN) { cgmassign(c, n); return; };
|
||||
@@ -9095,6 +9192,271 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// paramfieldsize — raw byte size of a tuple-field type. Mirrors the
|
||||
// `tp->type->size` read in C cgen N_FORRANGE: 1 for i8/u8/bool, 4 for
|
||||
// i32/u32, 8 for i64/u64/*T/fn/slice-elt, 16 for str, default 8.
|
||||
fn paramfieldsize(t: *node) i32 = {
|
||||
if (t == nil) { return 8; };
|
||||
let k: nkind = t.kind;
|
||||
if (k == nkind.N_TPTR) { return 8; };
|
||||
if (k == nkind.N_TFN) { return 8; };
|
||||
if (k == nkind.N_TCHAN) { return 8; };
|
||||
if (k == nkind.N_TNAME) {
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "str")) { return 16; };
|
||||
let ps: i32 = primsize(nm);
|
||||
if (ps > 0) { return ps; };
|
||||
};
|
||||
return 8;
|
||||
};
|
||||
|
||||
// paramissigned — does this primitive type need sign-extending on a
|
||||
// narrow (4B) load? Mirrors C cgen's `binds[b].signed_field` flag.
|
||||
fn paramissigned(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
if (t.kind != nkind.N_TNAME) { return false; };
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "i8")) { return true; };
|
||||
if (streq(nm, "i16")) { return true; };
|
||||
if (streq(nm, "i32")) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// cgforrange — lower `for (let x .. slice) body` (and the tuple-
|
||||
// destructure cousin `for (let (a, b) .. slice) body`). The body is
|
||||
// wrapped in a counted loop driven by stack-spilled `.rgi`/`.rgl`.
|
||||
// Each iteration computes the element address `s.ptr + i*esz` and
|
||||
// either loads the whole element into the named local or pulls each
|
||||
// tuple field into its own local. Mirrors cmd/w6c/cgen.c N_FORRANGE
|
||||
// byte-for-byte (label names + labelseq consumption order).
|
||||
fn cgforrange(c: *cgen, n: *node) void = {
|
||||
let slc: *node = n.lhs;
|
||||
let slclocal: *local = nil;
|
||||
let slctn: *node = nil;
|
||||
if (slc != nil) {
|
||||
if (slc.kind == nkind.N_IDENT) {
|
||||
slclocal = localfindnode(c, slc.str);
|
||||
if (slclocal != nil) { slctn = slclocal.tnode; };
|
||||
};
|
||||
};
|
||||
// Element type — peek through TSLICE/TARRAY for the tuple param walk.
|
||||
let elemt: *node = nil;
|
||||
if (slctn != nil) {
|
||||
let sk: nkind = slctn.kind;
|
||||
if (sk == nkind.N_TSLICE) { elemt = slctn.lhs; };
|
||||
if (sk == nkind.N_TARRAY) { elemt = slctn.lhs; };
|
||||
};
|
||||
// esz: raw elem byte size. For tuple-element slices `[](T0, T1)`,
|
||||
// C cgen reads the resolved tuple's size (sum of raw param sizes,
|
||||
// no slot-padding) so e.g. `(i64, i64)` is 16, `(i32, i32)` is 8.
|
||||
// elemsizeof returns 8 for non-primitive elem, which would be
|
||||
// wrong here — compute from the tuple param walk instead.
|
||||
let esz: i32 = elemsizeof(slctn);
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TTUPLE) {
|
||||
let total: i32 = 0;
|
||||
let p: *node = elemt.list;
|
||||
for (p != nil) {
|
||||
total += paramfieldsize(p);
|
||||
p = p.next;
|
||||
};
|
||||
esz = total;
|
||||
};
|
||||
};
|
||||
let destruct: bool = (n.list != nil);
|
||||
|
||||
// .rgi (counter) + .rgl (length) scratch slots.
|
||||
let iname: str = mkscratchname(c, "rgi");
|
||||
let lname: str = mkscratchname(c, "rgl");
|
||||
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
|
||||
let nbinds: i32 = 0;
|
||||
|
||||
if (destruct) {
|
||||
let tp: *node = nil;
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TTUPLE) { tp = elemt.list; };
|
||||
};
|
||||
let field_off: i32 = 0;
|
||||
let m: *node = n.list;
|
||||
for (m != nil) {
|
||||
if (nbinds >= 8) { m = nil; }
|
||||
else {
|
||||
let fsz: i32 = 8;
|
||||
let signf: bool = false;
|
||||
if (tp != nil) {
|
||||
fsz = paramfieldsize(tp);
|
||||
signf = paramissigned(tp);
|
||||
};
|
||||
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; };
|
||||
let bnm: str = m.str;
|
||||
if (bnm.len > 0) {
|
||||
bind_off[nbinds] = localadd(c, bnm, slot_sz, nil): i64;
|
||||
} else {
|
||||
bind_off[nbinds] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64;
|
||||
};
|
||||
field_off += fsz;
|
||||
nbinds += 1;
|
||||
if (tp != nil) { tp = tp.next; };
|
||||
m = m.next;
|
||||
};
|
||||
};
|
||||
} else {
|
||||
let slot_sz: i32 = esz;
|
||||
if (slot_sz < 8) { slot_sz = 8; };
|
||||
bind_sz[0] = esz: i64;
|
||||
bind_foff[0] = 0i64;
|
||||
// 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; };
|
||||
if (n.str.len > 0) {
|
||||
bind_off[0] = localadd(c, n.str, slot_sz, nil): i64;
|
||||
} else {
|
||||
bind_off[0] = localalloc(c, mkscratchname(c, "fr"), slot_sz, nil): i64;
|
||||
};
|
||||
nbinds = 1;
|
||||
};
|
||||
|
||||
// init: ioff(BP) = 0
|
||||
emitline("\tMOVQ\t$0, ");
|
||||
emitoff(ioff: i64);
|
||||
emitline("(BP)\n");
|
||||
|
||||
// loff(BP) = len
|
||||
let isarr: bool = false;
|
||||
let isslicestr: bool = false;
|
||||
if (slctn != nil) {
|
||||
let tk: nkind = slctn.kind;
|
||||
if (tk == nkind.N_TSLICE) { isslicestr = true; };
|
||||
if (tk == nkind.N_TARRAY) { isarr = true; };
|
||||
if (tk == nkind.N_TNAME) {
|
||||
if (streq(slctn.str, "str")) { isslicestr = true; };
|
||||
};
|
||||
};
|
||||
if (isslicestr) {
|
||||
if (slc.kind == nkind.N_IDENT) {
|
||||
if (slclocal != nil) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((slclocal.off + 8): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
} else { if (isarr) {
|
||||
let alen: i64 = 0i64;
|
||||
if (slctn.rhs != nil) {
|
||||
if (slctn.rhs.kind == nkind.N_INTLIT) { alen = slctn.rhs.uval: i64; };
|
||||
};
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(alen);
|
||||
emitline(", ");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
cgexpr(c, slc);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP)\n");
|
||||
};};
|
||||
|
||||
let loopl: str = mklabel(c, "rloop");
|
||||
let endl: str = mklabel(c, "rend");
|
||||
let naturall: str = endl;
|
||||
if (n.els != nil) { naturall = mklabel(c, "relseloop"); };
|
||||
|
||||
c.loopcontbuf[c.looptop] = loopl;
|
||||
c.loopendbuf[c.looptop] = endl;
|
||||
c.looptop += 1;
|
||||
|
||||
emitlabel(loopl);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(ioff: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(loff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tCMPQ\tBX, AX\n");
|
||||
emitline("\tJGE\t"); emitline(naturall); emitline("\n");
|
||||
|
||||
// BX = base + i*esz
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (slc.kind == nkind.N_IDENT) {
|
||||
if (slclocal != nil) {
|
||||
if (isarr) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(slclocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(slclocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
|
||||
// Per-binding load from BX+foff.
|
||||
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"; };
|
||||
};};
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff(bind_foff[b]);
|
||||
emitline("(BX), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(bind_off[b]);
|
||||
emitline("(BP)\n");
|
||||
b += 1;
|
||||
};
|
||||
|
||||
if (n.body != nil) { cgstmt(c, n.body); };
|
||||
|
||||
c.looptop -= 1;
|
||||
|
||||
emitline("\tADDQ\t$1, ");
|
||||
emitoff(ioff: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tJMP\t"); emitline(loopl); emitline("\n");
|
||||
if (n.els != nil) {
|
||||
emitlabel(naturall);
|
||||
cgstmt(c, n.els);
|
||||
};
|
||||
emitlabel(endl);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
// cgswitch — lower `switch (e) { case 1, 2: ...; case: default; }` to
|
||||
// a chain of compares against the scrutinee. Scrutinee lands in a
|
||||
// fresh 8B local slot so case bodies can spill SP without losing it.
|
||||
@@ -9269,6 +9631,33 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
// ".sw_<labelseq>" at cgen time — unique per switch — so it must
|
||||
// not dedup. Count it here so the frame SUBQ matches.
|
||||
if (n.kind == nkind.N_SWITCH) { total += 8; };
|
||||
// `for (let x .. s)` allocates two 8B scratch slots — `.rgi_<seq>`
|
||||
// (counter) and `.rgl_<seq>` (length) — plus one slot per binding.
|
||||
// Per-binding sz defaults to 8 (covers scalar primitives + ptrs).
|
||||
// `str` tuple-fields would need 16 — selfhost doesn't yet emit
|
||||
// those, so the simple count tracks C cgen for current fixtures.
|
||||
if (n.kind == nkind.N_FORRANGE) {
|
||||
total += 16; // .rgi + .rgl scratch
|
||||
if (n.list != nil) {
|
||||
let m: *node = n.list;
|
||||
for (m != nil) {
|
||||
let bnm: str = m.str;
|
||||
if (bnm.len == 0) {
|
||||
total += 8; // discard binding still gets a slot
|
||||
} else { if (!scanseenmark(c, bnm)) {
|
||||
total += 8;
|
||||
};};
|
||||
m = m.next;
|
||||
};
|
||||
} else {
|
||||
let bnm: str = n.str;
|
||||
if (bnm.len == 0) {
|
||||
total += 8;
|
||||
} else { if (!scanseenmark(c, bnm)) {
|
||||
total += 8;
|
||||
};};
|
||||
};
|
||||
};
|
||||
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||
// Crucially we do NOT dedup these against c.locals: C cgen
|
||||
// handles a match as an expression with a by-value locals copy,
|
||||
|
||||
@@ -345,6 +345,32 @@ probe_ww_compile(const char *bin)
|
||||
" dec(&c); dec(&c); dec(&c);\n"
|
||||
" return c.x;\n"
|
||||
"};", 2 },
|
||||
/* 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
|
||||
* load is MOVZBQ. Sum 10+20+30+40 = 100. */
|
||||
{ "use os;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: []u8;\n"
|
||||
" s.ptr = nil; s.len = 0; s.cap = 0;\n"
|
||||
" append(s, 10u8, 20u8, 30u8, 40u8);\n"
|
||||
" let total: i32 = 0;\n"
|
||||
" for (let b .. s) { total += b: i32; };\n"
|
||||
" return total;\n"
|
||||
"};", 100 },
|
||||
/* for-range with tuple destructure on `(i64, i64)`. Element
|
||||
* size is 16 (sum of raw param sizes); each binding loads
|
||||
* via BX+foff. Buf has (1,10) (2,20); sum = 33. */
|
||||
{ "fn main() i32 = {\n"
|
||||
" let buf: [4]i64;\n"
|
||||
" buf[0] = 1i64; buf[1] = 10i64; buf[2] = 2i64; buf[3] = 20i64;\n"
|
||||
" let s: [](i64, i64);\n"
|
||||
" s.ptr = buf.ptr: *(i64, i64);\n"
|
||||
" s.len = 2; s.cap = 2;\n"
|
||||
" let total: i64 = 0i64;\n"
|
||||
" for (let (k, v) .. s) { total += k + v; };\n"
|
||||
" return total: i32;\n"
|
||||
"};", 33 },
|
||||
/* append(s, v, ...) builtin — Hare's rt::ensure model.
|
||||
* Each value: PUSHQ AX, ADDQ $1 to s.len, LEAQ s/MOVQ esz
|
||||
* args for rt_ensure, then write into the freshly-grown
|
||||
|
||||
@@ -178,6 +178,27 @@ main(void)
|
||||
" append(dst, src...);\n"
|
||||
" return dst.len: i32;\n"
|
||||
"};" },
|
||||
{ "forrange",
|
||||
"use os;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s: []u8;\n"
|
||||
" s.ptr = nil; s.len = 0; s.cap = 0;\n"
|
||||
" append(s, 10u8, 20u8, 30u8);\n"
|
||||
" let total: i32 = 0;\n"
|
||||
" for (let b .. s) { total += b: i32; };\n"
|
||||
" return total;\n"
|
||||
"};" },
|
||||
{ "forrange_tuple",
|
||||
"fn main() i32 = {\n"
|
||||
" let buf: [4]i64;\n"
|
||||
" buf[0] = 1i64; buf[1] = 10i64; buf[2] = 2i64; buf[3] = 20i64;\n"
|
||||
" let s: [](i64, i64);\n"
|
||||
" s.ptr = buf.ptr: *(i64, i64);\n"
|
||||
" s.len = 2; s.cap = 2;\n"
|
||||
" let total: i64 = 0i64;\n"
|
||||
" for (let (k, v) .. s) { total += k + v; };\n"
|
||||
" return total: i32;\n"
|
||||
"};" },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user