From 328bcd743c0ff8139ce0f0ce58beb2c6483b2f64 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 11 May 2026 15:35:16 +0900 Subject: [PATCH] =?UTF-8?q?wcc/cgen:=20split=20cgexpr=E2=86=92cgen=5Fexpr.?= =?UTF-8?q?ww,=20cgstmt=E2=86=92cgen=5Fstmt.ww=20(rob=20pike=20#5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 6 +- selfhost/cmd/w6c/main.combined.ww | 2835 +++++++++++++------------- selfhost/cmd/wcc/cgen.ww | 1401 +------------ selfhost/cmd/wcc/cgen_expr.ww | 1077 ++++++++++ selfhost/cmd/wcc/cgen_stmt.ww | 353 ++++ selfhost/cmd/wwdump/main.combined.ww | 2835 +++++++++++++------------- 6 files changed, 4314 insertions(+), 4193 deletions(-) create mode 100644 selfhost/cmd/wcc/cgen_expr.ww create mode 100644 selfhost/cmd/wcc/cgen_stmt.ww diff --git a/Makefile b/Makefile index 2c1044e6..81f3da4d 100644 --- a/Makefile +++ b/Makefile @@ -109,7 +109,8 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \ selfhost/cmd/wcc/mem.ww selfhost/cmd/wcc/ast.ww \ selfhost/cmd/wcc/parse.ww selfhost/cmd/wcc/typ.ww \ selfhost/cmd/wcc/sym.ww selfhost/cmd/wcc/check.ww \ - selfhost/cmd/wcc/cgen.ww \ + selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgen_expr.ww \ + selfhost/cmd/wcc/cgen_stmt.ww \ lib/os/os.ww lib/strconv/strconv.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) @@ -125,7 +126,8 @@ $(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \ selfhost/cmd/wcc/mem.ww selfhost/cmd/wcc/ast.ww \ selfhost/cmd/wcc/parse.ww selfhost/cmd/wcc/typ.ww \ selfhost/cmd/wcc/sym.ww selfhost/cmd/wcc/check.ww \ - selfhost/cmd/wcc/cgen.ww \ + selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgen_expr.ww \ + selfhost/cmd/wcc/cgen_stmt.ww \ lib/os/os.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index a017c1ad..b2a6734e 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -3637,6 +3637,1440 @@ export fn checkfile(c: *checker, file: *node) void = { }; }; +// MODULE: wcc +// selfhost/cmd/wcc/cgen_expr.ww — split out of cgen.ww. +// +// Houses cgexpr — the per-expression code generator. The remainder of +// the cgen lives in cgen.ww (foundation: types, emit primitives, the +// collect* tables, FFI/module maps) and cgen_stmt.ww (cgstmt). +// +// `use cgen_expr;` is unnecessary at consumer sites — cgen.ww imports +// this file, so any caller of cgen transitively gets cgexpr. + +use os; +use mem; +use ast; +use tok; +use typ; +use sym; +use strconv; + +fn cgexpr(c: *cgen, n: *node) void = { + if (n == nil) { return; }; + let k: i32 = n.kind; + + if (k == N_INTLIT) { + // Print signed (i64), not unsigned (u64). C cgen uses + // `$%lld` so 64-bit constants with bit 63 set show up as + // negative — e.g. FNV-1a's offset basis prints as + // $-3750763034362895579, not $14695981039346656037. + emitline("\tMOVQ\t$"); + emitint(n.uval: i64); + emitline(", AX\n"); + return; + }; + if (k == N_RUNELIT) { + emitline("\tMOVQ\t$"); + emitint(n.uval: i64); + emitline(", AX\n"); + return; + }; + if (k == N_STRLIT) { + // Result is the (ptr, len) pair: ptr in AX, len in BX. Call + // sites that expect a str arg pick these up directly. + let nstr: str = n.str; + let lab: str = internstrlit(c, nstr); + emitline("\tLEAQ\t"); + os.write(1, lab.ptr, lab.len: u64); + emitline("(SB), AX\n"); + emitline("\tMOVQ\t$"); + emitint(nstr.len: i64); + emitline(", BX\n"); + return; + }; + if (k == N_TRUE) { + emitline("\tMOVQ\t$1, AX\n"); + return; + }; + if (k == N_FALSE) { + emitline("\tMOVQ\t$0, AX\n"); + return; + }; + if (k == N_NIL) { + emitline("\tMOVQ\t$0, AX\n"); + return; + }; + + if (k == N_IDENT) { + let nm: str = n.str; + let lc: *local = localfindnode(c, nm); + if (lc != nil) { + let off: i32 = lc.off; + emitline("\tMOVQ\t"); + emitoff(off: i64); + emitline("(BP), AX\n"); + // str local: also load the len half into BX. + if (isstrtype(c, lc.tnode)) { + emitline("\tMOVQ\t"); + emitoff((off + 8): i64); + emitline("(BP), BX\n"); + }; + // slice local: load (ptr, len, cap) into (AX, BX, CX). + if (isslicetype(c, lc.tnode)) { + emitline("\tMOVQ\t"); + emitoff((off + 8): i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\t"); + emitoff((off + 16): i64); + emitline("(BP), CX\n"); + }; + return; + }; + // Top-level `def` constant — load from its DATA symbol. + if (deflookup(c, nm)) { + emitline("\tMOVQ\t"); + emitsymname(c, nm); + emitline("(SB), AX\n"); + return; + }; + // Fn-name used as a value (e.g. `let f = some_fn;` or + // `... = some_fn;`). LEAQ the symbol address into AX. The + // emitsymname helper handles ffiresolve and module-mangling + // in one go, so a body-less FFI binding emits the C symbol + // it was declared with via @symbol(), not the ww-side ident. + let rt: *node = fnretlookup(c, nm); + if (rt != nil) { + emitline("\tLEAQ\t"); + emitsymname(c, nm); + emitline("(SB), AX\n"); + return; + }; + return; + }; + + if (k == N_INDEX) { + // Element-size-aware load: u8-element bases use MOVZBQ, + // 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 baselocal: *local = nil; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + baselocal = localfindnode(c, bn); + if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); }; + } else { if (base.kind == N_DOT) { + esz = indexbaseesz(c, base); + };}; + }; + cgexpr(c, idx); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let isarray: bool = false; + if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; + if (isarray) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + emitline("\tADDQ\tAX, BX\n"); + // str element (16B): load (ptr, len) into (AX, BX) so + // the value flows through the str-rhs convention. + if (esz == 16) { + emitline("\tMOVQ\t8(BX), CX\n"); + emitline("\tMOVQ\t(BX), AX\n"); + emitline("\tMOVQ\tCX, BX\n"); + return; + }; + if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } + else { emitline("\tMOVQ\t(BX), AX\n"); }; + return; + }; + // Generic fallback when base isn't a plain ident. + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, base); + emitline("\tPOPQ\tBX\n"); + emitline("\tADDQ\tBX, AX\n"); + if (esz == 16) { + emitline("\tMOVQ\t8(AX), BX\n"); + emitline("\tMOVQ\t(AX), AX\n"); + return; + }; + if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } + else { emitline("\tMOVQ\t(AX), AX\n"); }; + return; + }; + + if (k == N_MATCH) { + // match (e) { case let v: T => stmt; ... } + // + // Read the tagged-union slot and dispatch by tag. Slot + // layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings + // (`case let v: T =>`) get a fresh local slot loaded from + // slot+8 (and slot+16 for str-typed payload). + let scrut: *node = n.lhs; + let scrutoff: i32 = 0; + let scrutt: *node = nil; + if (scrut != nil) { + if (scrut.kind == N_IDENT) { + let lc: *local = localfindnode(c, scrut.str); + if (lc != nil) { + scrutoff = lc.off; + scrutt = resolvetype(c, lc.tnode); + }; + }; + }; + let endl: str = mklabel(c, "match_end"); + let cs: *node = n.list; + for (cs != nil) { + let nxt: str = mklabel(c, "match_next"); + let pat: *node = cs.lhs; + // Compute the variant tag for this arm. Default arm + // (no pattern) skips the tag check. + if (pat != nil) { + let want: i32 = 0; + if (scrutt != nil) { + if (scrutt.kind == N_TTAGGED) { + let patname: str; + patname.ptr = nil; patname.len = 0; + if (pat.kind == N_TNAME) { patname = pat.str; }; + let v: *node = scrutt.list; + let idx: i32 = 0; + let found: bool = false; + for (v != nil) { + if (v.kind == N_TNAME) { + if (streq(v.str, patname)) { + want = idx; + found = true; + v = nil; + }; + }; + if (v != nil) { + v = v.next; + idx += 1; + }; + }; + if (!found) { want = 0; }; + }; + }; + emitline("\tMOVQ\t"); + emitoff(scrutoff: i64); + emitline("(BP), AX\n"); + emitline("\tCMPQ\t$"); + emitint(want: i64); + emitline(", AX\n"); + emitline("\tJNE\t"); + emitline(nxt); + emitline("\n"); + }; + // Bind `let v: T` from the slot, if requested. + let bn: str = cs.str; + if (bn.len > 0) { + if (pat != nil) { + let bsz: i32 = 8; + if (isstrtype(c, pat)) { bsz = 16; }; + // localalloc (not localadd): match-arm + // binds don't dedup with same-named binds + // in *other* matches, since C's cgexpr + // allocates a fresh slot per match expr. + let voff: i32 = localalloc(c, bn, bsz, pat); + emitline("\tMOVQ\t"); + emitoff((scrutoff + 8): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(voff: i64); + emitline("(BP)\n"); + if (bsz == 16) { + emitline("\tMOVQ\t"); + emitoff((scrutoff + 16): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((voff + 8): i64); + emitline("(BP)\n"); + }; + }; + }; + // Body. Match arms are statements; we cgstmt them. + if (cs.body != nil) { cgstmt(c, cs.body); }; + emitline("\tJMP\t"); + emitline(endl); + emitline("\n"); + emitlabel(nxt); + cs = cs.next; + }; + emitlabel(endl); + return; + }; + + if (k == N_CAST) { + // Type casts are mostly no-ops at the asm level for our + // integer-shaped operands. Evaluate the source; AX holds + // the bits unchanged. (Sign- or zero-extending narrow loads + // to wider types is the loader's job, not cast's, in this + // minimal cgen.) + cgexpr(c, n.lhs); + return; + }; + + if (k == N_DOT) { + let lhs: *node = n.lhs; + let fld: str = n.str; + if (lhs != nil) { + if (lhs.kind == N_IDENT) { + let nm: str = lhs.str; + let lc: *local = localfindnode(c, nm); + if (lc != nil) { + let tn: *node = lc.tnode; + let lkind: i32 = -1; + if (tn != nil) { lkind = tn.kind; }; + // Pointer-to-struct: deref then field load. + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (inner != nil) { + if (inner.kind == N_TNAME) { + sname = inner.str; + }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + // str field via *struct: load len into a + // scratch first (so loading ptr into AX + // last leaves (AX=ptr, BX=len)). + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitdispreg((fi.foff + 8): i64, "BX"); + emitline(", CX\n"); + emitline("\tMOVQ\t"); + emitdispreg(fi.foff: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVQ\tCX, BX\n"); + } else { + let op: str = fieldloadop(fi); + emitline("\t"); + emitline(op); + emitline("\t"); + emitdispreg(fi.foff: i64, "BX"); + emitline(", AX\n"); + }; + return; + }; + fi = fi.finext; + }; + }; + }; + }; + // Direct struct local: field load at off+foff. + if (lkind == N_TNAME) { + let sname: str = tn.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + // str field: load both halves so chained + // `.ptr` / `.len` see (AX=ptr, BX=len). + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitoff((lc.off + fi.foff): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\t"); + emitoff((lc.off + fi.foff + 8): i64); + emitline("(BP), BX\n"); + } else { + let op: str = fieldloadop(fi); + emitline("\t"); + emitline(op); + emitline("\t"); + emitoff((lc.off + fi.foff): i64); + emitline("(BP), AX\n"); + }; + return; + }; + fi = fi.finext; + }; + }; + }; + // Array pseudo-fields: `.ptr` is the array's + // address (LEAQ); `.len` is the static element + // count (immediate). + if (lkind == N_TARRAY) { + if (streq(fld, "ptr")) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), AX\n"); + return; + }; + if (streq(fld, "len")) { + let lenn: *node = tn.rhs; + let alen: i64 = 0i64; + if (lenn != nil) { + if (lenn.kind == N_INTLIT) { alen = lenn.uval: i64; }; + }; + emitline("\tMOVQ\t$"); + emitint(alen); + emitline(", AX\n"); + return; + }; + }; + // str/slice pseudo-fields .ptr/.len/.cap on a + // direct local: load at slot+delta. + let delta: i32 = -1; + if (streq(fld, "ptr")) { delta = 0; }; + if (streq(fld, "len")) { delta = 8; }; + if (streq(fld, "cap")) { delta = 16; }; + if (delta >= 0) { + // Pointer to str/slice (`*[]u8`, `*str`): + // deref, then load at delta within the + // pointed-to header. C cgen does the same. + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let innerkind: i32 = -1; + if (inner != nil) { innerkind = inner.kind; }; + let innerstr: bool = false; + if (innerkind == N_TNAME) { + if (streq(inner.str, "str")) { innerstr = true; }; + }; + if (innerkind == N_TSLICE) { innerstr = true; }; + if (innerstr) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(delta: i64, "BX"); + emitline(", AX\n"); + return; + }; + }; + emitline("\tMOVQ\t"); + emitoff((lc.off + delta): i64); + emitline("(BP), AX\n"); + return; + }; + }; + }; + }; + // Module-qualified value reference: `mod.name` where `mod` + // is N_IDENT bound as SK_USE and the leaf isn't a local. + // Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback + // the C cgen takes when bt is NULL/tyerr. + if (lhs != nil) { + if (lhs.kind == N_IDENT) { + emitline("\tMOVQ\t"); + emitsymname(c, fld); + emitline("(SB), AX\n"); + return; + }; + }; + // Non-ident base pseudo-field: e.g. `"abc".ptr` / `"abc".len`. + // Evaluate the str-producing expression — that leaves + // (AX=ptr, BX=len). Then `.ptr` returns AX as is; `.len` + // shuffles BX→AX. Mirrors what C cgen does (it just evaluates + // the literal and picks the half it wants). + if (streq(fld, "ptr")) { cgexpr(c, lhs); return; }; + if (streq(fld, "len")) { + cgexpr(c, lhs); + emitline("\tMOVQ\tBX, AX\n"); + return; + }; + // Chained struct-field-via-ptr-via-ptr access: + // r.sym.val where r: *lrel, .sym: *lsym, .val: u64 + // Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct + // field). Outer DOT dereferences and reads `val`. Without this + // path the cgen falls through and AX retains whatever the + // inner expression left there — typically the *struct pointer + // itself, so reads silently get the pointer value instead of + // the field. (Showed up porting w6l/pass.ww.) + if (lhs != nil) { + if (lhs.kind == N_DOT) { + let innert: *node = dotinnerstructptr(c, lhs); + if (innert != nil) { + let sname: str = innert.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + cgexpr(c, lhs); // AX = ptr to inner struct + let lop: str = fieldloadop(fi); + // str field: load both halves. + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitdispreg((fi.foff + 8): i64, "AX"); + emitline(", BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", AX\n"); + return; + }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", AX\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + }; + }; + return; + }; + + if (k == N_UN) { + // Match C cgen ordering: evaluate operand first (load into AX), + // then apply the unary op. AMP / STAR override AX with the + // address / deref. The wasted load before AMP keeps our asm + // byte-identical to the C version. + cgexpr(c, n.lhs); + if (n.op == TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; + if (n.op == TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; + if (n.op == TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; + if (n.op == TK_AMP) { + let opnd: *node = n.lhs; + if (opnd != nil) { + if (opnd.kind == N_IDENT) { + let nm: str = opnd.str; + let off: i32 = localfind(c, nm); + if (off != 0) { + emitline("\tLEAQ\t"); + emitoff(off: i64); + emitline("(BP), AX\n"); + return; + }; + }; + }; + return; + }; + if (n.op == TK_NOT) { + let t: str = mklabel(c, "tt"); + let e: str = mklabel(c, "te"); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJE\t"); emitline(t); emitline("\n"); + emitline("\tMOVQ\t$0, AX\n"); + emitline("\tJMP\t"); emitline(e); emitline("\n"); + emitlabel(t); + emitline("\tMOVQ\t$1, AX\n"); + emitlabel(e); + return; + }; + return; + }; + + if (k == N_BIN) { + let unsignd: bool = nodeisunsigned(c, n.lhs); + if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; + + cgexpr(c, n.rhs); + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, n.lhs); + emitline("\tPOPQ\tBX\n"); + if (n.op == TK_PLUS) { emitline("\tADDQ\tBX, AX\n"); return; }; + if (n.op == TK_MINUS) { emitline("\tSUBQ\tBX, AX\n"); return; }; + if (n.op == TK_STAR) { emitline("\tIMULQ\tBX, AX\n"); return; }; + if (n.op == TK_SLASH) { + emitline("\tMOVQ\t$0, DX\n"); + if (unsignd) { emitline("\tDIVQ\tBX\n"); } + else { emitline("\tIDIVQ\tBX\n"); }; + return; + }; + if (n.op == TK_PERCENT) { + emitline("\tMOVQ\t$0, DX\n"); + if (unsignd) { emitline("\tDIVQ\tBX\n"); } + else { emitline("\tIDIVQ\tBX\n"); }; + emitline("\tMOVQ\tDX, AX\n"); + return; + }; + if (n.op == TK_AMP) { emitline("\tANDQ\tBX, AX\n"); return; }; + if (n.op == TK_PIPE) { emitline("\tORQ\tBX, AX\n"); return; }; + if (n.op == TK_CARET) { emitline("\tXORQ\tBX, AX\n"); return; }; + if (n.op == TK_LSHIFT) { + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tSHLQ\tCX, AX\n"); + return; + }; + if (n.op == TK_RSHIFT) { + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tSHRQ\tCX, AX\n"); + return; + }; + if (n.op == TK_AND) { emitline("\tANDQ\tBX, AX\n"); return; }; + if (n.op == TK_OR) { emitline("\tORQ\tBX, AX\n"); return; }; + + // Comparison: emit CMPQ, jump on signed/unsigned variant, + // materialise 0/1 in AX. Same shape as the C cgen. + let iscmp: bool = false; + let jcc: str = ""; + if (n.op == TK_EQ) { iscmp = true; jcc = "JE"; }; + if (n.op == TK_NEQ) { iscmp = true; jcc = "JNE"; }; + if (n.op == TK_LT) { iscmp = true; if (unsignd) { jcc = "JB"; } else { jcc = "JL"; }; }; + if (n.op == TK_LE) { iscmp = true; if (unsignd) { jcc = "JBE"; } else { jcc = "JLE"; }; }; + if (n.op == TK_GT) { iscmp = true; if (unsignd) { jcc = "JA"; } else { jcc = "JG"; }; }; + if (n.op == TK_GE) { iscmp = true; if (unsignd) { jcc = "JAE"; } else { jcc = "JGE"; }; }; + if (iscmp) { + let t: str = mklabel(c, "ct"); + let e: str = mklabel(c, "ce"); + emitline("\tCMPQ\tBX, AX\n"); + emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n"); + emitline("\tMOVQ\t$0, AX\n"); + emitline("\tJMP\t"); emitline(e); emitline("\n"); + emitlabel(t); + emitline("\tMOVQ\t$1, AX\n"); + emitlabel(e); + return; + }; + return; + }; + + if (k == N_CALL) { + let nargs: i32 = pushargsrev(c, n.list); + let i: i32 = 0; + for (i < nargs) { + emitline("\tPOPQ\t"); + emitline(argregname(i)); + emitline("\n"); + i += 1; + }; + let callee: *node = n.lhs; + let calleename: str; + calleename.ptr = nil; calleename.len = 0; + // Detect fn-pointer field call: `w.emit(args)` where `w` is + // a struct local and `emit` is an N_TFN field. Load the + // field value into AX and CALL through it. Also detect a + // bare `fp(args)` where `fp` is a local holding a function + // pointer — mirror C cgen's localfind dispatch (commit + // 635818e). Without this the call emits `CALL fp(SB)` and + // the linker rightly fails. + let isfnptrcall: bool = false; + if (callee != nil) { + if (callee.kind == N_IDENT) { + let cn: str = callee.str; + if (localfindnode(c, cn) != nil) { + isfnptrcall = true; + }; + }; + if (callee.kind == N_DOT) { + let base: *node = callee.lhs; + let fld: str = callee.str; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + let lc: *local = localfindnode(c, bn); + if (lc != nil) { + let tn: *node = lc.tnode; + if (tn != nil) { + let lkind: i32 = 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; + if (inner != nil) { + if (inner.kind == N_TNAME) { sname = inner.str; }; + }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + let ft: *node = fi.tnode; + if (ft != nil) { + if (ft.kind == N_TFN) { + isfnptrcall = true; + }; + }; + fi = nil; + } else { + fi = fi.finext; + }; + }; + }; + }; + }; + }; + }; + }; + }; + }; + if (isfnptrcall) { + // Load fn-ptr field value into AX; CALL AX. We emit the + // load AFTER the args have been popped (so AX/BX/etc + // don't get clobbered by the field load before the pops). + // `popped args` left DI/SI/etc set; AX is free. + cgexpr(c, callee); + emitline("\tCALL\tAX\n"); + } else { + emitline("\tCALL\t"); + if (callee != nil) { + if (callee.kind == N_IDENT) { + calleename = callee.str; + emitsymname(c, calleename); + } else { if (callee.kind == N_DOT) { + calleename = callee.str; + emitsymname(c, calleename); + };}; + }; + emitline("(SB)\n"); + }; + // SysV returns 16-byte aggregates in (AX, DX). Our str + // convention is (AX, BX), so shuffle for str-returning calls. + if (calleename.len > 0) { + let rt: *node = fnretlookup(c, calleename); + if (isstrtype(c, rt)) { + emitline("\tMOVQ\tDX, BX\n"); + }; + }; + return; + }; + + if (k == N_ASSIGN) { + let lhs: *node = n.lhs; + // `*p = v` — deref-assign. Element width comes from the + // pointer's declared type. Mirrors C cgen: eval rhs (AX, + // and BX if str), push, eval pointer, pop value, store. + // We default to MOVQ (8B) since most fixtures use it; for + // `*bool` / `*u8` / `*i32` we narrow via the local's tnode. + if (lhs != nil) { + if (lhs.kind == N_UN) { + if (lhs.op == TK_STAR) { + if (n.op == TK_ASSIGN) { + let inner: *node = lhs.lhs; + let elemstr: bool = false; + let storeop: str = "MOVQ"; + if (inner != nil) { + if (inner.kind == N_IDENT) { + let lc: *local = localfindnode(c, inner.str); + if (lc != nil) { + let tn: *node = lc.tnode; + if (tn != nil) { + if (tn.kind == N_TPTR) { + let pe: *node = tn.lhs; + if (pe != nil) { + if (pe.kind == N_TNAME) { + if (streq(pe.str, "str")) { elemstr = true; } + else { + let ps: i32 = primsize(pe.str); + if (ps == 1) { storeop = "MOVB"; } + else { if (ps == 4) { storeop = "MOVL"; }; }; + }; + }; + }; + }; + }; + }; + }; + }; + cgexpr(c, n.rhs); + // Push order matches C cgen + // (cmd/w6c/cgen.c:1033-1041): PUSHQ AX + // (ptr) first, then PUSHQ BX (len) if + // str, so the pop sequence is POP CX + // (len) → POP AX (ptr) → MOVQ AX, + // (BX) → MOVQ CX, 8(BX). + emitline("\tPUSHQ\tAX\n"); + if (elemstr) { emitline("\tPUSHQ\tBX\n"); }; + cgexpr(c, inner); + emitline("\tMOVQ\tAX, BX\n"); + if (elemstr) { + emitline("\tPOPQ\tCX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\tMOVQ\tAX, (BX)\n"); + emitline("\tMOVQ\tCX, 8(BX)\n"); + return; + }; + emitline("\tPOPQ\tAX\n"); + emitline("\t"); + emitline(storeop); + emitline("\tAX, (BX)\n"); + return; + }; + }; + }; + }; + // Array/slice/ptr index store: `arr[i] = v;`. Element size + // from base.tnode picks MOVB vs MOVQ. + if (lhs != nil) { + if (lhs.kind == N_INDEX) { + if (n.op == TK_ASSIGN) { + let base: *node = lhs.lhs; + let idx: *node = lhs.rhs; + let esz: i32 = 8; + let baselocal: *local = nil; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + baselocal = localfindnode(c, bn); + if (baselocal != nil) { + esz = elemsizeof(baselocal.tnode); + }; + } else { if (base.kind == N_DOT) { + esz = indexbaseesz(c, base); + };}; + }; + cgexpr(c, n.rhs); // value → AX + if (esz == 16) { emitline("\tPUSHQ\tBX\n"); }; + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, idx); // idx → AX + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tPUSHQ\tAX\n"); // scaled idx + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let isarray: bool = false; + if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; + if (isarray) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + } else { + cgexpr(c, base); + emitline("\tMOVQ\tAX, BX\n"); + }; + emitline("\tPOPQ\tAX\n"); // scaled idx + emitline("\tADDQ\tAX, BX\n"); + emitline("\tPOPQ\tAX\n"); // value + if (esz == 16) { + emitline("\tMOVQ\tAX, (BX)\n"); + emitline("\tPOPQ\tCX\n"); + emitline("\tMOVQ\tCX, 8(BX)\n"); + return; + }; + if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } + else { emitline("\tMOVQ\tAX, (BX)\n"); }; + return; + }; + }; + }; + // Struct/ptr-to-struct field assignment: `s.f = expr;` or + // `p.f = expr;`. Only plain `=` is wired (compound on field + // is rare and not yet needed by our fixtures). + if (lhs != nil) { + if (lhs.kind == N_DOT) { + let base: *node = lhs.lhs; + let fld: str = lhs.str; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + let lc: *local = localfindnode(c, bn); + if (lc != nil) { + let tn: *node = lc.tnode; + let lkind: i32 = -1; + if (tn != nil) { lkind = tn.kind; }; + // Pointer-to-struct: deref then store. + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (inner != nil) { + if (inner.kind == N_TNAME) { sname = inner.str; }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + if (n.op != TK_ASSIGN) { + // compound: load current value + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + let lop: str = fieldloadop(fi); + emitline("\t"); + emitline(lop); + emitline("\t"); + emitdispreg(fi.foff: i64, "BX"); + emitline(", BX\n"); + emitline("\tPUSHQ\tBX\n"); + }; + cgexpr(c, n.rhs); + if (n.op != TK_ASSIGN) { + emitline("\tPOPQ\tBX\n"); + // PLUSEQ is commutative; MINUSEQ + // needs lhs - rhs (BX is old lhs, + // AX is rhs). + if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; + if (n.op == TK_MINUSEQ) { + emitline("\tSUBQ\tAX, BX\n"); + emitline("\tMOVQ\tBX, AX\n"); + }; + }; + // str field via *struct: rhs left + // (AX=ptr, BX=len). Use CX as the + // address scratch so we don't clobber + // the len half before storing it. + if (n.op == TK_ASSIGN) { + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), CX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(fi.foff: i64, "CX"); + emitline("\n"); + emitline("\tMOVQ\tBX, "); + emitdispreg((fi.foff + 8): i64, "CX"); + emitline("\n"); + return; + }; + }; + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + let sop: str = fieldstoreop(fi); + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitdispreg(fi.foff: i64, "BX"); + emitline("\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + }; + // Direct struct local: store at off+foff. + if (lkind == N_TNAME) { + let sname: str = tn.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + cgexpr(c, n.rhs); + let sop: str = fieldstoreop(fi); + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitoff((lc.off + fi.foff): i64); + emitline("(BP)\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + // str/slice pseudo-field assignment. + let delta: i32 = -1; + if (streq(fld, "ptr")) { delta = 0; }; + if (streq(fld, "len")) { delta = 8; }; + if (streq(fld, "cap")) { delta = 16; }; + if (delta >= 0) { + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let innerkind: i32 = -1; + if (inner != nil) { innerkind = inner.kind; }; + let innerstr: bool = false; + if (innerkind == N_TNAME) { + if (streq(inner.str, "str")) { innerstr = true; }; + }; + if (innerkind == N_TSLICE) { innerstr = true; }; + if (innerstr) { + if (n.op != TK_ASSIGN) { + // Compound on `(*str|*slice).field`: load + // current → push → eval rhs → combine → store. + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(delta: i64, "BX"); + emitline(", BX\n"); + emitline("\tPUSHQ\tBX\n"); + cgexpr(c, n.rhs); + emitline("\tPOPQ\tBX\n"); + // PLUSEQ is commutative; MINUSEQ + // needs lhs - rhs. + if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; + if (n.op == TK_MINUSEQ) { + emitline("\tSUBQ\tAX, BX\n"); + emitline("\tMOVQ\tBX, AX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(delta: i64, "BX"); + emitline("\n"); + return; + }; + cgexpr(c, n.rhs); + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(delta: i64, "BX"); + emitline("\n"); + return; + }; + }; + cgexpr(c, n.rhs); + emitline("\tMOVQ\tAX, "); + emitoff((lc.off + delta): i64); + emitline("(BP)\n"); + return; + }; + }; + }; + }; + }; + }; + // Local-ident target — plain `=` and the simple compound + // forms (+= -= *= /=); other compounds fall back to + // "evaluate rhs, replace". Mirrors C cgen's IDENT-assign path. + if (lhs != nil) { + if (lhs.kind == N_IDENT) { + let nm: str = lhs.str; + let off: i32 = localfind(c, nm); + if (off == 0) { return; }; + // Detect str-typed local — assignment must store both + // halves (AX=ptr at +0, BX=len at +8). + let lcstr: bool = false; + let lcn: *local = localfindnode(c, nm); + if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); }; + cgexpr(c, n.rhs); + if (n.op == TK_ASSIGN) { + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + if (lcstr) { + emitline("\tMOVQ\tBX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + }; + return; + }; + if (n.op == TK_PLUSEQ) { + emitline("\tADDQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + if (n.op == TK_MINUSEQ) { + emitline("\tSUBQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + // Generic compound: load → combine in BX → store. + emitline("\tMOVQ\t"); + emitoff(off: i64); + emitline("(BP), BX\n"); + if (n.op == TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }; + if (n.op == TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }; + if (n.op == TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }; + if (n.op == TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }; + if (n.op == TK_LSHIFTEQ) { + emitline("\tMOVQ\tAX, CX\n"); + emitline("\tSHLQ\tCX, BX\n"); + }; + if (n.op == TK_RSHIFTEQ) { + emitline("\tMOVQ\tAX, CX\n"); + emitline("\tSHRQ\tCX, BX\n"); + }; + emitline("\tMOVQ\tBX, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + }; + return; + }; +}; + + +// MODULE: wcc +// selfhost/cmd/wcc/cgen_stmt.ww — split out of cgen.ww. +// +// Houses cgstmt — statements + control flow (N_BLOCK, N_IF, N_FOR, +// N_BREAK/N_CONTINUE, N_RETURN, N_LET, N_ASSIGN, etc.). The +// expression generator (cgexpr) lives in cgen_expr.ww; the foundation +// (types, emit primitives, collect* tables, FFI/module maps) lives in +// cgen.ww. + +use os; +use mem; +use ast; +use tok; +use typ; +use sym; +use strconv; + +// ---- statement cgen -------------------------------------------------- + +fn cgstmt(c: *cgen, n: *node) void = { + if (n == nil) { return; }; + let k: i32 = n.kind; + + if (k == N_BLOCK) { + let s: *node = n.list; + for (s != nil) { + cgstmt(c, s); + s = s.next; + }; + return; + }; + + if (k == N_RETURN) { + let rhs: *node = n.lhs; + if (rhs != nil) { + // Tuple return `return a, b;` — pack as (AX=v0, DX=v1). + // Matches C cgen: evaluate v1 first (PUSHQ), then v0 + // into AX, then POPQ DX. End state: AX = v0, DX = v1. + if (rhs.kind == N_TUPLE) { + let v: *node = rhs.list; + if (v != nil) { + let v2: *node = v.next; + if (v2 != nil) { + cgexpr(c, v2); + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, v); + emitline("\tPOPQ\tDX\n"); + } else { + cgexpr(c, v); + }; + }; + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.last_was_return = 1; + return; + }; + // Tagged-union return: pack as (AX=tag, DX=value0, CX=value1). + // For str variant, cgexpr leaves (AX=ptr, BX=len), so we + // shuffle DX←AX (ptr) and CX←BX (len), then load tag. + // For other variants, cgexpr leaves AX, shuffle DX←AX. + if (istaggedtype(c.fn_ret)) { + cgexpr(c, rhs); + let idx: i32 = taggedvariantindex(c, c.fn_ret, rhs); + if (nodeisstr(c, rhs)) { + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tMOVQ\tAX, DX\n"); + } else { + emitline("\tMOVQ\tAX, DX\n"); + }; + emitline("\tMOVQ\t$"); + if (idx < 0) { idx = 0; }; + emitint(idx: i64); + emitline(", AX\n"); + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.last_was_return = 1; + return; + }; + cgexpr(c, rhs); + } else { + // Bare `return;` in a void fn — zero AX so the caller + // sees a deterministic value (matches C cgen, which + // always falls through to `cgexpr_int(c, 0)`). + emitline("\tMOVQ\t$0, AX\n"); + }; + // SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX). + // cgexpr leaves str in (AX, BX); shuffle BX→DX. + if (isstrtype(c, c.fn_ret)) { + emitline("\tMOVQ\tBX, DX\n"); + }; + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.last_was_return = 1; + return; + }; + + if (k == N_EXPRSTMT) { + if (n.lhs != nil) { cgexpr(c, n.lhs); }; + c.last_was_return = 0; + return; + }; + + if (k == N_LET) { + let nm: str = n.str; + let sz: i32 = slotsize(c, n.lhs); + let off: i32 = localadd(c, nm, sz, n.lhs); + if (n.rhs != nil) { + let rhs: *node = n.rhs; + // Tagged-union init: `let r: (T | E) = expr;`. + // - If rhs is a CALL to a fn returning tagged-union, + // the result is already in (AX=tag, DX=v0, CX=v1); + // just spill all three. + // - Otherwise rhs is a bare variant value: pack tag + + // value(s). + if (istaggedtype(n.lhs)) { + let rhsreturnstagged: bool = false; + if (rhs.kind == N_CALL) { + let callee: *node = rhs.lhs; + if (callee != nil) { + let calleename: str; + calleename.ptr = nil; calleename.len = 0; + if (callee.kind == N_IDENT) { calleename = callee.str; }; + if (callee.kind == N_DOT) { calleename = callee.str; }; + if (calleename.len > 0) { + let rt: *node = fnretlookup(c, calleename); + if (istaggedtype(rt)) { rhsreturnstagged = true; }; + }; + }; + }; + cgexpr(c, rhs); + if (rhsreturnstagged) { + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tDX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + c.last_was_return = 0; + return; + }; + let tagidx: i32 = taggedvariantindex(c, n.lhs, rhs); + if (tagidx < 0) { tagidx = 0; }; + if (nodeisstr(c, rhs)) { + emitline("\tMOVQ\tAX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + } else { + emitline("\tMOVQ\tAX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + }; + emitline("\tMOVQ\t$"); + emitint(tagidx: i64); + emitline(", "); + emitoff(off: i64); + emitline("(BP)\n"); + c.last_was_return = 0; + return; + }; + // Struct literal init: `let p: point = point{x=..., y=...};`. + // For each field in the lit, evaluate its value and store at + // the field's offset within the slot. Field-name → offset + // from the struct registry. + if (rhs.kind == N_STRUCTLIT) { + let trefn: *node = rhs.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (trefn != nil) { + if (trefn.kind == N_IDENT) { sname = trefn.str; } + else { if (trefn.kind == N_TNAME) { sname = trefn.str; }; }; + }; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fieldnode: *node = rhs.list; + for (fieldnode != nil) { + if (fieldnode.kind == N_FIELD) { + let fname: str = fieldnode.str; + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fname)) { + cgexpr(c, fieldnode.lhs); + let sop: str = fieldstoreop(fi); + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitoff((off + fi.foff): i64); + emitline("(BP)\n"); + fi = nil; + } else { + fi = fi.finext; + }; + }; + }; + fieldnode = fieldnode.next; + }; + c.last_was_return = 0; + return; + }; + }; + cgexpr(c, rhs); + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + // str init: cgexpr also leaves len in BX; store both. + if (sz == 16) { + emitline("\tMOVQ\tBX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + }; + // slice init: ptr/len/cap in AX/BX/CX. + if (sz == 24) { + emitline("\tMOVQ\tBX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + }; + } 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. + if (typeis8byteprimitive(c, n.lhs)) { + emitline("\tMOVQ\t$0, "); + emitoff(off: i64); + emitline("(BP)\n"); + }; + }; + c.last_was_return = 0; + return; + }; + + if (k == N_IF) { + let els: str = mklabel(c, "else"); + let endl: str = mklabel(c, "end"); + cgexpr(c, n.cond); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJE\t"); + if (n.els != nil) { emitline(els); } + else { emitline(endl); }; + emitline("\n"); + if (n.body != nil) { cgstmt(c, n.body); }; + if (n.els != nil) { + emitline("\tJMP\t"); emitline(endl); emitline("\n"); + emitlabel(els); + cgstmt(c, n.els); + }; + emitlabel(endl); + c.last_was_return = 0; + return; + }; + + if (k == N_FOR) { + // Match C cgen's label scheme: _loop_N for the top, + // _endloop_N for the post-body merge. No separate cont + // label when there's no post-expression. + let topl: str = mklabel(c, "loop"); + let endl: str = mklabel(c, "endloop"); + + if (n.lhs != nil) { cgstmt(c, n.lhs); }; + + emitlabel(topl); + if (n.cond != nil) { + cgexpr(c, n.cond); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJE\t"); emitline(endl); emitline("\n"); + }; + + c.loop_end_buf[c.loop_top] = endl; + c.loop_cont_buf[c.loop_top] = topl; + c.loop_top += 1; + + if (n.body != nil) { cgstmt(c, n.body); }; + + c.loop_top -= 1; + + if (n.rhs != nil) { cgexpr(c, n.rhs); }; + emitline("\tJMP\t"); emitline(topl); emitline("\n"); + emitlabel(endl); + c.last_was_return = 0; + 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/w6c/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); }; + emitline("\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 = localfind(c, l0.str); + if (off != 0) { + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + }; + }; + }; + emitline("\tPOPQ\tDX\n"); + if (l1 != nil) { + if (l1.kind == N_IDENT) { + let off: i32 = localfind(c, l1.str); + if (off != 0) { + emitline("\tMOVQ\tDX, "); + emitoff(off: i64); + emitline("(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]; + emitline("\tJMP\t"); emitline(lbl); emitline("\n"); + }; + c.last_was_return = 0; + return; + }; + if (k == N_CONTINUE) { + if (c.loop_top > 0) { + let lbl: str = c.loop_cont_buf[c.loop_top - 1]; + emitline("\tJMP\t"); emitline(lbl); emitline("\n"); + }; + c.last_was_return = 0; + return; + }; + + c.last_was_return = 0; +}; + + // MODULE: wcc // selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c. // @@ -3669,6 +5103,10 @@ use tok; use typ; use sym; use strconv; +// Split files. Bundler pulls these in transitively so consumers only +// need `use cgen;`. +use cgen_expr; +use cgen_stmt; // ---- typedef alias registry ----------------------------------------- // @@ -4936,1066 +6374,6 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { return false; }; -fn cgexpr(c: *cgen, n: *node) void = { - if (n == nil) { return; }; - let k: i32 = n.kind; - - if (k == N_INTLIT) { - // Print signed (i64), not unsigned (u64). C cgen uses - // `$%lld` so 64-bit constants with bit 63 set show up as - // negative — e.g. FNV-1a's offset basis prints as - // $-3750763034362895579, not $14695981039346656037. - emitline("\tMOVQ\t$"); - emitint(n.uval: i64); - emitline(", AX\n"); - return; - }; - if (k == N_RUNELIT) { - emitline("\tMOVQ\t$"); - emitint(n.uval: i64); - emitline(", AX\n"); - return; - }; - if (k == N_STRLIT) { - // Result is the (ptr, len) pair: ptr in AX, len in BX. Call - // sites that expect a str arg pick these up directly. - let nstr: str = n.str; - let lab: str = internstrlit(c, nstr); - emitline("\tLEAQ\t"); - os.write(1, lab.ptr, lab.len: u64); - emitline("(SB), AX\n"); - emitline("\tMOVQ\t$"); - emitint(nstr.len: i64); - emitline(", BX\n"); - return; - }; - if (k == N_TRUE) { - emitline("\tMOVQ\t$1, AX\n"); - return; - }; - if (k == N_FALSE) { - emitline("\tMOVQ\t$0, AX\n"); - return; - }; - if (k == N_NIL) { - emitline("\tMOVQ\t$0, AX\n"); - return; - }; - - if (k == N_IDENT) { - let nm: str = n.str; - let lc: *local = localfindnode(c, nm); - if (lc != nil) { - let off: i32 = lc.off; - emitline("\tMOVQ\t"); - emitoff(off: i64); - emitline("(BP), AX\n"); - // str local: also load the len half into BX. - if (isstrtype(c, lc.tnode)) { - emitline("\tMOVQ\t"); - emitoff((off + 8): i64); - emitline("(BP), BX\n"); - }; - // slice local: load (ptr, len, cap) into (AX, BX, CX). - if (isslicetype(c, lc.tnode)) { - emitline("\tMOVQ\t"); - emitoff((off + 8): i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\t"); - emitoff((off + 16): i64); - emitline("(BP), CX\n"); - }; - return; - }; - // Top-level `def` constant — load from its DATA symbol. - if (deflookup(c, nm)) { - emitline("\tMOVQ\t"); - emitsymname(c, nm); - emitline("(SB), AX\n"); - return; - }; - // Fn-name used as a value (e.g. `let f = some_fn;` or - // `... = some_fn;`). LEAQ the symbol address into AX. The - // emitsymname helper handles ffiresolve and module-mangling - // in one go, so a body-less FFI binding emits the C symbol - // it was declared with via @symbol(), not the ww-side ident. - let rt: *node = fnretlookup(c, nm); - if (rt != nil) { - emitline("\tLEAQ\t"); - emitsymname(c, nm); - emitline("(SB), AX\n"); - return; - }; - return; - }; - - if (k == N_INDEX) { - // Element-size-aware load: u8-element bases use MOVZBQ, - // 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 baselocal: *local = nil; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - baselocal = localfindnode(c, bn); - if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); }; - } else { if (base.kind == N_DOT) { - esz = indexbaseesz(c, base); - };}; - }; - cgexpr(c, idx); - if (esz > 1) { - emitline("\tMOVQ\t$"); - emitint(esz: i64); - emitline(", CX\n"); - emitline("\tIMULQ\tCX, AX\n"); - }; - if (baselocal != nil) { - let tn: *node = baselocal.tnode; - let isarray: bool = false; - if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; - if (isarray) { - emitline("\tLEAQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - }; - emitline("\tADDQ\tAX, BX\n"); - // str element (16B): load (ptr, len) into (AX, BX) so - // the value flows through the str-rhs convention. - if (esz == 16) { - emitline("\tMOVQ\t8(BX), CX\n"); - emitline("\tMOVQ\t(BX), AX\n"); - emitline("\tMOVQ\tCX, BX\n"); - return; - }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { emitline("\tMOVQ\t(BX), AX\n"); }; - return; - }; - // Generic fallback when base isn't a plain ident. - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, base); - emitline("\tPOPQ\tBX\n"); - emitline("\tADDQ\tBX, AX\n"); - if (esz == 16) { - emitline("\tMOVQ\t8(AX), BX\n"); - emitline("\tMOVQ\t(AX), AX\n"); - return; - }; - if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } - else { emitline("\tMOVQ\t(AX), AX\n"); }; - return; - }; - - if (k == N_MATCH) { - // match (e) { case let v: T => stmt; ... } - // - // Read the tagged-union slot and dispatch by tag. Slot - // layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings - // (`case let v: T =>`) get a fresh local slot loaded from - // slot+8 (and slot+16 for str-typed payload). - let scrut: *node = n.lhs; - let scrutoff: i32 = 0; - let scrutt: *node = nil; - if (scrut != nil) { - if (scrut.kind == N_IDENT) { - let lc: *local = localfindnode(c, scrut.str); - if (lc != nil) { - scrutoff = lc.off; - scrutt = resolvetype(c, lc.tnode); - }; - }; - }; - let endl: str = mklabel(c, "match_end"); - let cs: *node = n.list; - for (cs != nil) { - let nxt: str = mklabel(c, "match_next"); - let pat: *node = cs.lhs; - // Compute the variant tag for this arm. Default arm - // (no pattern) skips the tag check. - if (pat != nil) { - let want: i32 = 0; - if (scrutt != nil) { - if (scrutt.kind == N_TTAGGED) { - let patname: str; - patname.ptr = nil; patname.len = 0; - if (pat.kind == N_TNAME) { patname = pat.str; }; - let v: *node = scrutt.list; - let idx: i32 = 0; - let found: bool = false; - for (v != nil) { - if (v.kind == N_TNAME) { - if (streq(v.str, patname)) { - want = idx; - found = true; - v = nil; - }; - }; - if (v != nil) { - v = v.next; - idx += 1; - }; - }; - if (!found) { want = 0; }; - }; - }; - emitline("\tMOVQ\t"); - emitoff(scrutoff: i64); - emitline("(BP), AX\n"); - emitline("\tCMPQ\t$"); - emitint(want: i64); - emitline(", AX\n"); - emitline("\tJNE\t"); - emitline(nxt); - emitline("\n"); - }; - // Bind `let v: T` from the slot, if requested. - let bn: str = cs.str; - if (bn.len > 0) { - if (pat != nil) { - let bsz: i32 = 8; - if (isstrtype(c, pat)) { bsz = 16; }; - // localalloc (not localadd): match-arm - // binds don't dedup with same-named binds - // in *other* matches, since C's cgexpr - // allocates a fresh slot per match expr. - let voff: i32 = localalloc(c, bn, bsz, pat); - emitline("\tMOVQ\t"); - emitoff((scrutoff + 8): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff(voff: i64); - emitline("(BP)\n"); - if (bsz == 16) { - emitline("\tMOVQ\t"); - emitoff((scrutoff + 16): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff((voff + 8): i64); - emitline("(BP)\n"); - }; - }; - }; - // Body. Match arms are statements; we cgstmt them. - if (cs.body != nil) { cgstmt(c, cs.body); }; - emitline("\tJMP\t"); - emitline(endl); - emitline("\n"); - emitlabel(nxt); - cs = cs.next; - }; - emitlabel(endl); - return; - }; - - if (k == N_CAST) { - // Type casts are mostly no-ops at the asm level for our - // integer-shaped operands. Evaluate the source; AX holds - // the bits unchanged. (Sign- or zero-extending narrow loads - // to wider types is the loader's job, not cast's, in this - // minimal cgen.) - cgexpr(c, n.lhs); - return; - }; - - if (k == N_DOT) { - let lhs: *node = n.lhs; - let fld: str = n.str; - if (lhs != nil) { - if (lhs.kind == N_IDENT) { - let nm: str = lhs.str; - let lc: *local = localfindnode(c, nm); - if (lc != nil) { - let tn: *node = lc.tnode; - let lkind: i32 = -1; - if (tn != nil) { lkind = tn.kind; }; - // Pointer-to-struct: deref then field load. - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (inner != nil) { - if (inner.kind == N_TNAME) { - sname = inner.str; - }; - }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - // str field via *struct: load len into a - // scratch first (so loading ptr into AX - // last leaves (AX=ptr, BX=len)). - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "BX"); - emitline(", CX\n"); - emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "BX"); - emitline(", AX\n"); - emitline("\tMOVQ\tCX, BX\n"); - } else { - let op: str = fieldloadop(fi); - emitline("\t"); - emitline(op); - emitline("\t"); - emitdispreg(fi.foff: i64, "BX"); - emitline(", AX\n"); - }; - return; - }; - fi = fi.finext; - }; - }; - }; - }; - // Direct struct local: field load at off+foff. - if (lkind == N_TNAME) { - let sname: str = tn.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - // str field: load both halves so chained - // `.ptr` / `.len` see (AX=ptr, BX=len). - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitoff((lc.off + fi.foff): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\t"); - emitoff((lc.off + fi.foff + 8): i64); - emitline("(BP), BX\n"); - } else { - let op: str = fieldloadop(fi); - emitline("\t"); - emitline(op); - emitline("\t"); - emitoff((lc.off + fi.foff): i64); - emitline("(BP), AX\n"); - }; - return; - }; - fi = fi.finext; - }; - }; - }; - // Array pseudo-fields: `.ptr` is the array's - // address (LEAQ); `.len` is the static element - // count (immediate). - if (lkind == N_TARRAY) { - if (streq(fld, "ptr")) { - emitline("\tLEAQ\t"); - emitoff(lc.off: i64); - emitline("(BP), AX\n"); - return; - }; - if (streq(fld, "len")) { - let lenn: *node = tn.rhs; - let alen: i64 = 0i64; - if (lenn != nil) { - if (lenn.kind == N_INTLIT) { alen = lenn.uval: i64; }; - }; - emitline("\tMOVQ\t$"); - emitint(alen); - emitline(", AX\n"); - return; - }; - }; - // str/slice pseudo-fields .ptr/.len/.cap on a - // direct local: load at slot+delta. - let delta: i32 = -1; - if (streq(fld, "ptr")) { delta = 0; }; - if (streq(fld, "len")) { delta = 8; }; - if (streq(fld, "cap")) { delta = 16; }; - if (delta >= 0) { - // Pointer to str/slice (`*[]u8`, `*str`): - // deref, then load at delta within the - // pointed-to header. C cgen does the same. - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let innerkind: i32 = -1; - if (inner != nil) { innerkind = inner.kind; }; - let innerstr: bool = false; - if (innerkind == N_TNAME) { - if (streq(inner.str, "str")) { innerstr = true; }; - }; - if (innerkind == N_TSLICE) { innerstr = true; }; - if (innerstr) { - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\t"); - emitdispreg(delta: i64, "BX"); - emitline(", AX\n"); - return; - }; - }; - emitline("\tMOVQ\t"); - emitoff((lc.off + delta): i64); - emitline("(BP), AX\n"); - return; - }; - }; - }; - }; - // Module-qualified value reference: `mod.name` where `mod` - // is N_IDENT bound as SK_USE and the leaf isn't a local. - // Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback - // the C cgen takes when bt is NULL/tyerr. - if (lhs != nil) { - if (lhs.kind == N_IDENT) { - emitline("\tMOVQ\t"); - emitsymname(c, fld); - emitline("(SB), AX\n"); - return; - }; - }; - // Non-ident base pseudo-field: e.g. `"abc".ptr` / `"abc".len`. - // Evaluate the str-producing expression — that leaves - // (AX=ptr, BX=len). Then `.ptr` returns AX as is; `.len` - // shuffles BX→AX. Mirrors what C cgen does (it just evaluates - // the literal and picks the half it wants). - if (streq(fld, "ptr")) { cgexpr(c, lhs); return; }; - if (streq(fld, "len")) { - cgexpr(c, lhs); - emitline("\tMOVQ\tBX, AX\n"); - return; - }; - // Chained struct-field-via-ptr-via-ptr access: - // r.sym.val where r: *lrel, .sym: *lsym, .val: u64 - // Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct - // field). Outer DOT dereferences and reads `val`. Without this - // path the cgen falls through and AX retains whatever the - // inner expression left there — typically the *struct pointer - // itself, so reads silently get the pointer value instead of - // the field. (Showed up porting w6l/pass.ww.) - if (lhs != nil) { - if (lhs.kind == N_DOT) { - let innert: *node = dotinnerstructptr(c, lhs); - if (innert != nil) { - let sname: str = innert.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { - cgexpr(c, lhs); // AX = ptr to inner struct - let lop: str = fieldloadop(fi); - // str field: load both halves. - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); - emitline(", BX\n"); - emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - emitline("\t"); - emitline(lop); - emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - fi = fi.finext; - }; - }; - }; - }; - }; - return; - }; - - if (k == N_UN) { - // Match C cgen ordering: evaluate operand first (load into AX), - // then apply the unary op. AMP / STAR override AX with the - // address / deref. The wasted load before AMP keeps our asm - // byte-identical to the C version. - cgexpr(c, n.lhs); - if (n.op == TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; - if (n.op == TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; - if (n.op == TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; - if (n.op == TK_AMP) { - let opnd: *node = n.lhs; - if (opnd != nil) { - if (opnd.kind == N_IDENT) { - let nm: str = opnd.str; - let off: i32 = localfind(c, nm); - if (off != 0) { - emitline("\tLEAQ\t"); - emitoff(off: i64); - emitline("(BP), AX\n"); - return; - }; - }; - }; - return; - }; - if (n.op == TK_NOT) { - let t: str = mklabel(c, "tt"); - let e: str = mklabel(c, "te"); - emitline("\tCMPQ\t$0, AX\n"); - emitline("\tJE\t"); emitline(t); emitline("\n"); - emitline("\tMOVQ\t$0, AX\n"); - emitline("\tJMP\t"); emitline(e); emitline("\n"); - emitlabel(t); - emitline("\tMOVQ\t$1, AX\n"); - emitlabel(e); - return; - }; - return; - }; - - if (k == N_BIN) { - let unsignd: bool = nodeisunsigned(c, n.lhs); - if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; - - cgexpr(c, n.rhs); - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, n.lhs); - emitline("\tPOPQ\tBX\n"); - if (n.op == TK_PLUS) { emitline("\tADDQ\tBX, AX\n"); return; }; - if (n.op == TK_MINUS) { emitline("\tSUBQ\tBX, AX\n"); return; }; - if (n.op == TK_STAR) { emitline("\tIMULQ\tBX, AX\n"); return; }; - if (n.op == TK_SLASH) { - emitline("\tMOVQ\t$0, DX\n"); - if (unsignd) { emitline("\tDIVQ\tBX\n"); } - else { emitline("\tIDIVQ\tBX\n"); }; - return; - }; - if (n.op == TK_PERCENT) { - emitline("\tMOVQ\t$0, DX\n"); - if (unsignd) { emitline("\tDIVQ\tBX\n"); } - else { emitline("\tIDIVQ\tBX\n"); }; - emitline("\tMOVQ\tDX, AX\n"); - return; - }; - if (n.op == TK_AMP) { emitline("\tANDQ\tBX, AX\n"); return; }; - if (n.op == TK_PIPE) { emitline("\tORQ\tBX, AX\n"); return; }; - if (n.op == TK_CARET) { emitline("\tXORQ\tBX, AX\n"); return; }; - if (n.op == TK_LSHIFT) { - emitline("\tMOVQ\tBX, CX\n"); - emitline("\tSHLQ\tCX, AX\n"); - return; - }; - if (n.op == TK_RSHIFT) { - emitline("\tMOVQ\tBX, CX\n"); - emitline("\tSHRQ\tCX, AX\n"); - return; - }; - if (n.op == TK_AND) { emitline("\tANDQ\tBX, AX\n"); return; }; - if (n.op == TK_OR) { emitline("\tORQ\tBX, AX\n"); return; }; - - // Comparison: emit CMPQ, jump on signed/unsigned variant, - // materialise 0/1 in AX. Same shape as the C cgen. - let iscmp: bool = false; - let jcc: str = ""; - if (n.op == TK_EQ) { iscmp = true; jcc = "JE"; }; - if (n.op == TK_NEQ) { iscmp = true; jcc = "JNE"; }; - if (n.op == TK_LT) { iscmp = true; if (unsignd) { jcc = "JB"; } else { jcc = "JL"; }; }; - if (n.op == TK_LE) { iscmp = true; if (unsignd) { jcc = "JBE"; } else { jcc = "JLE"; }; }; - if (n.op == TK_GT) { iscmp = true; if (unsignd) { jcc = "JA"; } else { jcc = "JG"; }; }; - if (n.op == TK_GE) { iscmp = true; if (unsignd) { jcc = "JAE"; } else { jcc = "JGE"; }; }; - if (iscmp) { - let t: str = mklabel(c, "ct"); - let e: str = mklabel(c, "ce"); - emitline("\tCMPQ\tBX, AX\n"); - emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n"); - emitline("\tMOVQ\t$0, AX\n"); - emitline("\tJMP\t"); emitline(e); emitline("\n"); - emitlabel(t); - emitline("\tMOVQ\t$1, AX\n"); - emitlabel(e); - return; - }; - return; - }; - - if (k == N_CALL) { - let nargs: i32 = pushargsrev(c, n.list); - let i: i32 = 0; - for (i < nargs) { - emitline("\tPOPQ\t"); - emitline(argregname(i)); - emitline("\n"); - i += 1; - }; - let callee: *node = n.lhs; - let calleename: str; - calleename.ptr = nil; calleename.len = 0; - // Detect fn-pointer field call: `w.emit(args)` where `w` is - // a struct local and `emit` is an N_TFN field. Load the - // field value into AX and CALL through it. Also detect a - // bare `fp(args)` where `fp` is a local holding a function - // pointer — mirror C cgen's localfind dispatch (commit - // 635818e). Without this the call emits `CALL fp(SB)` and - // the linker rightly fails. - let isfnptrcall: bool = false; - if (callee != nil) { - if (callee.kind == N_IDENT) { - let cn: str = callee.str; - if (localfindnode(c, cn) != nil) { - isfnptrcall = true; - }; - }; - if (callee.kind == N_DOT) { - let base: *node = callee.lhs; - let fld: str = callee.str; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - let lc: *local = localfindnode(c, bn); - if (lc != nil) { - let tn: *node = lc.tnode; - if (tn != nil) { - let lkind: i32 = 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; - if (inner != nil) { - if (inner.kind == N_TNAME) { sname = inner.str; }; - }; - }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - let ft: *node = fi.tnode; - if (ft != nil) { - if (ft.kind == N_TFN) { - isfnptrcall = true; - }; - }; - fi = nil; - } else { - fi = fi.finext; - }; - }; - }; - }; - }; - }; - }; - }; - }; - }; - if (isfnptrcall) { - // Load fn-ptr field value into AX; CALL AX. We emit the - // load AFTER the args have been popped (so AX/BX/etc - // don't get clobbered by the field load before the pops). - // `popped args` left DI/SI/etc set; AX is free. - cgexpr(c, callee); - emitline("\tCALL\tAX\n"); - } else { - emitline("\tCALL\t"); - if (callee != nil) { - if (callee.kind == N_IDENT) { - calleename = callee.str; - emitsymname(c, calleename); - } else { if (callee.kind == N_DOT) { - calleename = callee.str; - emitsymname(c, calleename); - };}; - }; - emitline("(SB)\n"); - }; - // SysV returns 16-byte aggregates in (AX, DX). Our str - // convention is (AX, BX), so shuffle for str-returning calls. - if (calleename.len > 0) { - let rt: *node = fnretlookup(c, calleename); - if (isstrtype(c, rt)) { - emitline("\tMOVQ\tDX, BX\n"); - }; - }; - return; - }; - - if (k == N_ASSIGN) { - let lhs: *node = n.lhs; - // `*p = v` — deref-assign. Element width comes from the - // pointer's declared type. Mirrors C cgen: eval rhs (AX, - // and BX if str), push, eval pointer, pop value, store. - // We default to MOVQ (8B) since most fixtures use it; for - // `*bool` / `*u8` / `*i32` we narrow via the local's tnode. - if (lhs != nil) { - if (lhs.kind == N_UN) { - if (lhs.op == TK_STAR) { - if (n.op == TK_ASSIGN) { - let inner: *node = lhs.lhs; - let elemstr: bool = false; - let storeop: str = "MOVQ"; - if (inner != nil) { - if (inner.kind == N_IDENT) { - let lc: *local = localfindnode(c, inner.str); - if (lc != nil) { - let tn: *node = lc.tnode; - if (tn != nil) { - if (tn.kind == N_TPTR) { - let pe: *node = tn.lhs; - if (pe != nil) { - if (pe.kind == N_TNAME) { - if (streq(pe.str, "str")) { elemstr = true; } - else { - let ps: i32 = primsize(pe.str); - if (ps == 1) { storeop = "MOVB"; } - else { if (ps == 4) { storeop = "MOVL"; }; }; - }; - }; - }; - }; - }; - }; - }; - }; - cgexpr(c, n.rhs); - // Push order matches C cgen - // (cmd/w6c/cgen.c:1033-1041): PUSHQ AX - // (ptr) first, then PUSHQ BX (len) if - // str, so the pop sequence is POP CX - // (len) → POP AX (ptr) → MOVQ AX, - // (BX) → MOVQ CX, 8(BX). - emitline("\tPUSHQ\tAX\n"); - if (elemstr) { emitline("\tPUSHQ\tBX\n"); }; - cgexpr(c, inner); - emitline("\tMOVQ\tAX, BX\n"); - if (elemstr) { - emitline("\tPOPQ\tCX\n"); - emitline("\tPOPQ\tAX\n"); - emitline("\tMOVQ\tAX, (BX)\n"); - emitline("\tMOVQ\tCX, 8(BX)\n"); - return; - }; - emitline("\tPOPQ\tAX\n"); - emitline("\t"); - emitline(storeop); - emitline("\tAX, (BX)\n"); - return; - }; - }; - }; - }; - // Array/slice/ptr index store: `arr[i] = v;`. Element size - // from base.tnode picks MOVB vs MOVQ. - if (lhs != nil) { - if (lhs.kind == N_INDEX) { - if (n.op == TK_ASSIGN) { - let base: *node = lhs.lhs; - let idx: *node = lhs.rhs; - let esz: i32 = 8; - let baselocal: *local = nil; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - baselocal = localfindnode(c, bn); - if (baselocal != nil) { - esz = elemsizeof(baselocal.tnode); - }; - } else { if (base.kind == N_DOT) { - esz = indexbaseesz(c, base); - };}; - }; - cgexpr(c, n.rhs); // value → AX - if (esz == 16) { emitline("\tPUSHQ\tBX\n"); }; - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, idx); // idx → AX - if (esz > 1) { - emitline("\tMOVQ\t$"); - emitint(esz: i64); - emitline(", CX\n"); - emitline("\tIMULQ\tCX, AX\n"); - }; - emitline("\tPUSHQ\tAX\n"); // scaled idx - if (baselocal != nil) { - let tn: *node = baselocal.tnode; - let isarray: bool = false; - if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; - if (isarray) { - emitline("\tLEAQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - }; - } else { - cgexpr(c, base); - emitline("\tMOVQ\tAX, BX\n"); - }; - emitline("\tPOPQ\tAX\n"); // scaled idx - emitline("\tADDQ\tAX, BX\n"); - emitline("\tPOPQ\tAX\n"); // value - if (esz == 16) { - emitline("\tMOVQ\tAX, (BX)\n"); - emitline("\tPOPQ\tCX\n"); - emitline("\tMOVQ\tCX, 8(BX)\n"); - return; - }; - if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } - else { emitline("\tMOVQ\tAX, (BX)\n"); }; - return; - }; - }; - }; - // Struct/ptr-to-struct field assignment: `s.f = expr;` or - // `p.f = expr;`. Only plain `=` is wired (compound on field - // is rare and not yet needed by our fixtures). - if (lhs != nil) { - if (lhs.kind == N_DOT) { - let base: *node = lhs.lhs; - let fld: str = lhs.str; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - let lc: *local = localfindnode(c, bn); - if (lc != nil) { - let tn: *node = lc.tnode; - let lkind: i32 = -1; - if (tn != nil) { lkind = tn.kind; }; - // Pointer-to-struct: deref then store. - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (inner != nil) { - if (inner.kind == N_TNAME) { sname = inner.str; }; - }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - if (n.op != TK_ASSIGN) { - // compound: load current value - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - let lop: str = fieldloadop(fi); - emitline("\t"); - emitline(lop); - emitline("\t"); - emitdispreg(fi.foff: i64, "BX"); - emitline(", BX\n"); - emitline("\tPUSHQ\tBX\n"); - }; - cgexpr(c, n.rhs); - if (n.op != TK_ASSIGN) { - emitline("\tPOPQ\tBX\n"); - // PLUSEQ is commutative; MINUSEQ - // needs lhs - rhs (BX is old lhs, - // AX is rhs). - if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; - if (n.op == TK_MINUSEQ) { - emitline("\tSUBQ\tAX, BX\n"); - emitline("\tMOVQ\tBX, AX\n"); - }; - }; - // str field via *struct: rhs left - // (AX=ptr, BX=len). Use CX as the - // address scratch so we don't clobber - // the len half before storing it. - if (n.op == TK_ASSIGN) { - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), CX\n"); - emitline("\tMOVQ\tAX, "); - emitdispreg(fi.foff: i64, "CX"); - emitline("\n"); - emitline("\tMOVQ\tBX, "); - emitdispreg((fi.foff + 8): i64, "CX"); - emitline("\n"); - return; - }; - }; - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - let sop: str = fieldstoreop(fi); - emitline("\t"); - emitline(sop); - emitline("\tAX, "); - emitdispreg(fi.foff: i64, "BX"); - emitline("\n"); - return; - }; - fi = fi.finext; - }; - }; - }; - }; - // Direct struct local: store at off+foff. - if (lkind == N_TNAME) { - let sname: str = tn.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - cgexpr(c, n.rhs); - let sop: str = fieldstoreop(fi); - emitline("\t"); - emitline(sop); - emitline("\tAX, "); - emitoff((lc.off + fi.foff): i64); - emitline("(BP)\n"); - return; - }; - fi = fi.finext; - }; - }; - }; - // str/slice pseudo-field assignment. - let delta: i32 = -1; - if (streq(fld, "ptr")) { delta = 0; }; - if (streq(fld, "len")) { delta = 8; }; - if (streq(fld, "cap")) { delta = 16; }; - if (delta >= 0) { - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let innerkind: i32 = -1; - if (inner != nil) { innerkind = inner.kind; }; - let innerstr: bool = false; - if (innerkind == N_TNAME) { - if (streq(inner.str, "str")) { innerstr = true; }; - }; - if (innerkind == N_TSLICE) { innerstr = true; }; - if (innerstr) { - if (n.op != TK_ASSIGN) { - // Compound on `(*str|*slice).field`: load - // current → push → eval rhs → combine → store. - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\t"); - emitdispreg(delta: i64, "BX"); - emitline(", BX\n"); - emitline("\tPUSHQ\tBX\n"); - cgexpr(c, n.rhs); - emitline("\tPOPQ\tBX\n"); - // PLUSEQ is commutative; MINUSEQ - // needs lhs - rhs. - if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; - if (n.op == TK_MINUSEQ) { - emitline("\tSUBQ\tAX, BX\n"); - emitline("\tMOVQ\tBX, AX\n"); - }; - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\tAX, "); - emitdispreg(delta: i64, "BX"); - emitline("\n"); - return; - }; - cgexpr(c, n.rhs); - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\tAX, "); - emitdispreg(delta: i64, "BX"); - emitline("\n"); - return; - }; - }; - cgexpr(c, n.rhs); - emitline("\tMOVQ\tAX, "); - emitoff((lc.off + delta): i64); - emitline("(BP)\n"); - return; - }; - }; - }; - }; - }; - }; - // Local-ident target — plain `=` and the simple compound - // forms (+= -= *= /=); other compounds fall back to - // "evaluate rhs, replace". Mirrors C cgen's IDENT-assign path. - if (lhs != nil) { - if (lhs.kind == N_IDENT) { - let nm: str = lhs.str; - let off: i32 = localfind(c, nm); - if (off == 0) { return; }; - // Detect str-typed local — assignment must store both - // halves (AX=ptr at +0, BX=len at +8). - let lcstr: bool = false; - let lcn: *local = localfindnode(c, nm); - if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); }; - cgexpr(c, n.rhs); - if (n.op == TK_ASSIGN) { - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - if (lcstr) { - emitline("\tMOVQ\tBX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - }; - return; - }; - if (n.op == TK_PLUSEQ) { - emitline("\tADDQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - return; - }; - if (n.op == TK_MINUSEQ) { - emitline("\tSUBQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - return; - }; - // Generic compound: load → combine in BX → store. - emitline("\tMOVQ\t"); - emitoff(off: i64); - emitline("(BP), BX\n"); - if (n.op == TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }; - if (n.op == TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }; - if (n.op == TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }; - if (n.op == TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }; - if (n.op == TK_LSHIFTEQ) { - emitline("\tMOVQ\tAX, CX\n"); - emitline("\tSHLQ\tCX, BX\n"); - }; - if (n.op == TK_RSHIFTEQ) { - emitline("\tMOVQ\tAX, CX\n"); - emitline("\tSHRQ\tCX, BX\n"); - }; - emitline("\tMOVQ\tBX, "); - emitoff(off: i64); - emitline("(BP)\n"); - return; - }; - }; - return; - }; -}; - // ---- type-driven slot sizing ---------------------------------------- fn structlookup(c: *cgen, name: str) *structinfo = { @@ -6229,343 +6607,6 @@ fn scanlocals(c: *cgen, n: *node) i32 = { return total; }; -// ---- statement cgen -------------------------------------------------- - -fn cgstmt(c: *cgen, n: *node) void = { - if (n == nil) { return; }; - let k: i32 = n.kind; - - if (k == N_BLOCK) { - let s: *node = n.list; - for (s != nil) { - cgstmt(c, s); - s = s.next; - }; - return; - }; - - if (k == N_RETURN) { - let rhs: *node = n.lhs; - if (rhs != nil) { - // Tuple return `return a, b;` — pack as (AX=v0, DX=v1). - // Matches C cgen: evaluate v1 first (PUSHQ), then v0 - // into AX, then POPQ DX. End state: AX = v0, DX = v1. - if (rhs.kind == N_TUPLE) { - let v: *node = rhs.list; - if (v != nil) { - let v2: *node = v.next; - if (v2 != nil) { - cgexpr(c, v2); - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, v); - emitline("\tPOPQ\tDX\n"); - } else { - cgexpr(c, v); - }; - }; - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.last_was_return = 1; - return; - }; - // Tagged-union return: pack as (AX=tag, DX=value0, CX=value1). - // For str variant, cgexpr leaves (AX=ptr, BX=len), so we - // shuffle DX←AX (ptr) and CX←BX (len), then load tag. - // For other variants, cgexpr leaves AX, shuffle DX←AX. - if (istaggedtype(c.fn_ret)) { - cgexpr(c, rhs); - let idx: i32 = taggedvariantindex(c, c.fn_ret, rhs); - if (nodeisstr(c, rhs)) { - emitline("\tMOVQ\tBX, CX\n"); - emitline("\tMOVQ\tAX, DX\n"); - } else { - emitline("\tMOVQ\tAX, DX\n"); - }; - emitline("\tMOVQ\t$"); - if (idx < 0) { idx = 0; }; - emitint(idx: i64); - emitline(", AX\n"); - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.last_was_return = 1; - return; - }; - cgexpr(c, rhs); - } else { - // Bare `return;` in a void fn — zero AX so the caller - // sees a deterministic value (matches C cgen, which - // always falls through to `cgexpr_int(c, 0)`). - emitline("\tMOVQ\t$0, AX\n"); - }; - // SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX). - // cgexpr leaves str in (AX, BX); shuffle BX→DX. - if (isstrtype(c, c.fn_ret)) { - emitline("\tMOVQ\tBX, DX\n"); - }; - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.last_was_return = 1; - return; - }; - - if (k == N_EXPRSTMT) { - if (n.lhs != nil) { cgexpr(c, n.lhs); }; - c.last_was_return = 0; - return; - }; - - if (k == N_LET) { - let nm: str = n.str; - let sz: i32 = slotsize(c, n.lhs); - let off: i32 = localadd(c, nm, sz, n.lhs); - if (n.rhs != nil) { - let rhs: *node = n.rhs; - // Tagged-union init: `let r: (T | E) = expr;`. - // - If rhs is a CALL to a fn returning tagged-union, - // the result is already in (AX=tag, DX=v0, CX=v1); - // just spill all three. - // - Otherwise rhs is a bare variant value: pack tag + - // value(s). - if (istaggedtype(n.lhs)) { - let rhsreturnstagged: bool = false; - if (rhs.kind == N_CALL) { - let callee: *node = rhs.lhs; - if (callee != nil) { - let calleename: str; - calleename.ptr = nil; calleename.len = 0; - if (callee.kind == N_IDENT) { calleename = callee.str; }; - if (callee.kind == N_DOT) { calleename = callee.str; }; - if (calleename.len > 0) { - let rt: *node = fnretlookup(c, calleename); - if (istaggedtype(rt)) { rhsreturnstagged = true; }; - }; - }; - }; - cgexpr(c, rhs); - if (rhsreturnstagged) { - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tDX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tCX, "); - emitoff((off + 16): i64); - emitline("(BP)\n"); - c.last_was_return = 0; - return; - }; - let tagidx: i32 = taggedvariantindex(c, n.lhs, rhs); - if (tagidx < 0) { tagidx = 0; }; - if (nodeisstr(c, rhs)) { - emitline("\tMOVQ\tAX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tBX, "); - emitoff((off + 16): i64); - emitline("(BP)\n"); - } else { - emitline("\tMOVQ\tAX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - }; - emitline("\tMOVQ\t$"); - emitint(tagidx: i64); - emitline(", "); - emitoff(off: i64); - emitline("(BP)\n"); - c.last_was_return = 0; - return; - }; - // Struct literal init: `let p: point = point{x=..., y=...};`. - // For each field in the lit, evaluate its value and store at - // the field's offset within the slot. Field-name → offset - // from the struct registry. - if (rhs.kind == N_STRUCTLIT) { - let trefn: *node = rhs.lhs; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (trefn != nil) { - if (trefn.kind == N_IDENT) { sname = trefn.str; } - else { if (trefn.kind == N_TNAME) { sname = trefn.str; }; }; - }; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fieldnode: *node = rhs.list; - for (fieldnode != nil) { - if (fieldnode.kind == N_FIELD) { - let fname: str = fieldnode.str; - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fname)) { - cgexpr(c, fieldnode.lhs); - let sop: str = fieldstoreop(fi); - emitline("\t"); - emitline(sop); - emitline("\tAX, "); - emitoff((off + fi.foff): i64); - emitline("(BP)\n"); - fi = nil; - } else { - fi = fi.finext; - }; - }; - }; - fieldnode = fieldnode.next; - }; - c.last_was_return = 0; - return; - }; - }; - cgexpr(c, rhs); - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - // str init: cgexpr also leaves len in BX; store both. - if (sz == 16) { - emitline("\tMOVQ\tBX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - }; - // slice init: ptr/len/cap in AX/BX/CX. - if (sz == 24) { - emitline("\tMOVQ\tBX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tCX, "); - emitoff((off + 16): i64); - emitline("(BP)\n"); - }; - } 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. - if (typeis8byteprimitive(c, n.lhs)) { - emitline("\tMOVQ\t$0, "); - emitoff(off: i64); - emitline("(BP)\n"); - }; - }; - c.last_was_return = 0; - return; - }; - - if (k == N_IF) { - let els: str = mklabel(c, "else"); - let endl: str = mklabel(c, "end"); - cgexpr(c, n.cond); - emitline("\tCMPQ\t$0, AX\n"); - emitline("\tJE\t"); - if (n.els != nil) { emitline(els); } - else { emitline(endl); }; - emitline("\n"); - if (n.body != nil) { cgstmt(c, n.body); }; - if (n.els != nil) { - emitline("\tJMP\t"); emitline(endl); emitline("\n"); - emitlabel(els); - cgstmt(c, n.els); - }; - emitlabel(endl); - c.last_was_return = 0; - return; - }; - - if (k == N_FOR) { - // Match C cgen's label scheme: _loop_N for the top, - // _endloop_N for the post-body merge. No separate cont - // label when there's no post-expression. - let topl: str = mklabel(c, "loop"); - let endl: str = mklabel(c, "endloop"); - - if (n.lhs != nil) { cgstmt(c, n.lhs); }; - - emitlabel(topl); - if (n.cond != nil) { - cgexpr(c, n.cond); - emitline("\tCMPQ\t$0, AX\n"); - emitline("\tJE\t"); emitline(endl); emitline("\n"); - }; - - c.loop_end_buf[c.loop_top] = endl; - c.loop_cont_buf[c.loop_top] = topl; - c.loop_top += 1; - - if (n.body != nil) { cgstmt(c, n.body); }; - - c.loop_top -= 1; - - if (n.rhs != nil) { cgexpr(c, n.rhs); }; - emitline("\tJMP\t"); emitline(topl); emitline("\n"); - emitlabel(endl); - c.last_was_return = 0; - 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/w6c/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); }; - emitline("\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 = localfind(c, l0.str); - if (off != 0) { - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - }; - }; - }; - emitline("\tPOPQ\tDX\n"); - if (l1 != nil) { - if (l1.kind == N_IDENT) { - let off: i32 = localfind(c, l1.str); - if (off != 0) { - emitline("\tMOVQ\tDX, "); - emitoff(off: i64); - emitline("(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]; - emitline("\tJMP\t"); emitline(lbl); emitline("\n"); - }; - c.last_was_return = 0; - return; - }; - if (k == N_CONTINUE) { - if (c.loop_top > 0) { - let lbl: str = c.loop_cont_buf[c.loop_top - 1]; - emitline("\tJMP\t"); emitline(lbl); emitline("\n"); - }; - c.last_was_return = 0; - return; - }; - - c.last_was_return = 0; -}; - // ---- function-level cgen --------------------------------------------- // isstrtype — true when the type expr resolves (through any diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 6dc39b07..8ded959d 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -29,6 +29,10 @@ use tok; use typ; use sym; use strconv; +// Split files. Bundler pulls these in transitively so consumers only +// need `use cgen;`. +use cgen_expr; +use cgen_stmt; // ---- typedef alias registry ----------------------------------------- // @@ -1296,1066 +1300,6 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { return false; }; -fn cgexpr(c: *cgen, n: *node) void = { - if (n == nil) { return; }; - let k: i32 = n.kind; - - if (k == N_INTLIT) { - // Print signed (i64), not unsigned (u64). C cgen uses - // `$%lld` so 64-bit constants with bit 63 set show up as - // negative — e.g. FNV-1a's offset basis prints as - // $-3750763034362895579, not $14695981039346656037. - emitline("\tMOVQ\t$"); - emitint(n.uval: i64); - emitline(", AX\n"); - return; - }; - if (k == N_RUNELIT) { - emitline("\tMOVQ\t$"); - emitint(n.uval: i64); - emitline(", AX\n"); - return; - }; - if (k == N_STRLIT) { - // Result is the (ptr, len) pair: ptr in AX, len in BX. Call - // sites that expect a str arg pick these up directly. - let nstr: str = n.str; - let lab: str = internstrlit(c, nstr); - emitline("\tLEAQ\t"); - os.write(1, lab.ptr, lab.len: u64); - emitline("(SB), AX\n"); - emitline("\tMOVQ\t$"); - emitint(nstr.len: i64); - emitline(", BX\n"); - return; - }; - if (k == N_TRUE) { - emitline("\tMOVQ\t$1, AX\n"); - return; - }; - if (k == N_FALSE) { - emitline("\tMOVQ\t$0, AX\n"); - return; - }; - if (k == N_NIL) { - emitline("\tMOVQ\t$0, AX\n"); - return; - }; - - if (k == N_IDENT) { - let nm: str = n.str; - let lc: *local = localfindnode(c, nm); - if (lc != nil) { - let off: i32 = lc.off; - emitline("\tMOVQ\t"); - emitoff(off: i64); - emitline("(BP), AX\n"); - // str local: also load the len half into BX. - if (isstrtype(c, lc.tnode)) { - emitline("\tMOVQ\t"); - emitoff((off + 8): i64); - emitline("(BP), BX\n"); - }; - // slice local: load (ptr, len, cap) into (AX, BX, CX). - if (isslicetype(c, lc.tnode)) { - emitline("\tMOVQ\t"); - emitoff((off + 8): i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\t"); - emitoff((off + 16): i64); - emitline("(BP), CX\n"); - }; - return; - }; - // Top-level `def` constant — load from its DATA symbol. - if (deflookup(c, nm)) { - emitline("\tMOVQ\t"); - emitsymname(c, nm); - emitline("(SB), AX\n"); - return; - }; - // Fn-name used as a value (e.g. `let f = some_fn;` or - // `... = some_fn;`). LEAQ the symbol address into AX. The - // emitsymname helper handles ffiresolve and module-mangling - // in one go, so a body-less FFI binding emits the C symbol - // it was declared with via @symbol(), not the ww-side ident. - let rt: *node = fnretlookup(c, nm); - if (rt != nil) { - emitline("\tLEAQ\t"); - emitsymname(c, nm); - emitline("(SB), AX\n"); - return; - }; - return; - }; - - if (k == N_INDEX) { - // Element-size-aware load: u8-element bases use MOVZBQ, - // 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 baselocal: *local = nil; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - baselocal = localfindnode(c, bn); - if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); }; - } else { if (base.kind == N_DOT) { - esz = indexbaseesz(c, base); - };}; - }; - cgexpr(c, idx); - if (esz > 1) { - emitline("\tMOVQ\t$"); - emitint(esz: i64); - emitline(", CX\n"); - emitline("\tIMULQ\tCX, AX\n"); - }; - if (baselocal != nil) { - let tn: *node = baselocal.tnode; - let isarray: bool = false; - if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; - if (isarray) { - emitline("\tLEAQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - }; - emitline("\tADDQ\tAX, BX\n"); - // str element (16B): load (ptr, len) into (AX, BX) so - // the value flows through the str-rhs convention. - if (esz == 16) { - emitline("\tMOVQ\t8(BX), CX\n"); - emitline("\tMOVQ\t(BX), AX\n"); - emitline("\tMOVQ\tCX, BX\n"); - return; - }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { emitline("\tMOVQ\t(BX), AX\n"); }; - return; - }; - // Generic fallback when base isn't a plain ident. - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, base); - emitline("\tPOPQ\tBX\n"); - emitline("\tADDQ\tBX, AX\n"); - if (esz == 16) { - emitline("\tMOVQ\t8(AX), BX\n"); - emitline("\tMOVQ\t(AX), AX\n"); - return; - }; - if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } - else { emitline("\tMOVQ\t(AX), AX\n"); }; - return; - }; - - if (k == N_MATCH) { - // match (e) { case let v: T => stmt; ... } - // - // Read the tagged-union slot and dispatch by tag. Slot - // layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings - // (`case let v: T =>`) get a fresh local slot loaded from - // slot+8 (and slot+16 for str-typed payload). - let scrut: *node = n.lhs; - let scrutoff: i32 = 0; - let scrutt: *node = nil; - if (scrut != nil) { - if (scrut.kind == N_IDENT) { - let lc: *local = localfindnode(c, scrut.str); - if (lc != nil) { - scrutoff = lc.off; - scrutt = resolvetype(c, lc.tnode); - }; - }; - }; - let endl: str = mklabel(c, "match_end"); - let cs: *node = n.list; - for (cs != nil) { - let nxt: str = mklabel(c, "match_next"); - let pat: *node = cs.lhs; - // Compute the variant tag for this arm. Default arm - // (no pattern) skips the tag check. - if (pat != nil) { - let want: i32 = 0; - if (scrutt != nil) { - if (scrutt.kind == N_TTAGGED) { - let patname: str; - patname.ptr = nil; patname.len = 0; - if (pat.kind == N_TNAME) { patname = pat.str; }; - let v: *node = scrutt.list; - let idx: i32 = 0; - let found: bool = false; - for (v != nil) { - if (v.kind == N_TNAME) { - if (streq(v.str, patname)) { - want = idx; - found = true; - v = nil; - }; - }; - if (v != nil) { - v = v.next; - idx += 1; - }; - }; - if (!found) { want = 0; }; - }; - }; - emitline("\tMOVQ\t"); - emitoff(scrutoff: i64); - emitline("(BP), AX\n"); - emitline("\tCMPQ\t$"); - emitint(want: i64); - emitline(", AX\n"); - emitline("\tJNE\t"); - emitline(nxt); - emitline("\n"); - }; - // Bind `let v: T` from the slot, if requested. - let bn: str = cs.str; - if (bn.len > 0) { - if (pat != nil) { - let bsz: i32 = 8; - if (isstrtype(c, pat)) { bsz = 16; }; - // localalloc (not localadd): match-arm - // binds don't dedup with same-named binds - // in *other* matches, since C's cgexpr - // allocates a fresh slot per match expr. - let voff: i32 = localalloc(c, bn, bsz, pat); - emitline("\tMOVQ\t"); - emitoff((scrutoff + 8): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff(voff: i64); - emitline("(BP)\n"); - if (bsz == 16) { - emitline("\tMOVQ\t"); - emitoff((scrutoff + 16): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff((voff + 8): i64); - emitline("(BP)\n"); - }; - }; - }; - // Body. Match arms are statements; we cgstmt them. - if (cs.body != nil) { cgstmt(c, cs.body); }; - emitline("\tJMP\t"); - emitline(endl); - emitline("\n"); - emitlabel(nxt); - cs = cs.next; - }; - emitlabel(endl); - return; - }; - - if (k == N_CAST) { - // Type casts are mostly no-ops at the asm level for our - // integer-shaped operands. Evaluate the source; AX holds - // the bits unchanged. (Sign- or zero-extending narrow loads - // to wider types is the loader's job, not cast's, in this - // minimal cgen.) - cgexpr(c, n.lhs); - return; - }; - - if (k == N_DOT) { - let lhs: *node = n.lhs; - let fld: str = n.str; - if (lhs != nil) { - if (lhs.kind == N_IDENT) { - let nm: str = lhs.str; - let lc: *local = localfindnode(c, nm); - if (lc != nil) { - let tn: *node = lc.tnode; - let lkind: i32 = -1; - if (tn != nil) { lkind = tn.kind; }; - // Pointer-to-struct: deref then field load. - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (inner != nil) { - if (inner.kind == N_TNAME) { - sname = inner.str; - }; - }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - // str field via *struct: load len into a - // scratch first (so loading ptr into AX - // last leaves (AX=ptr, BX=len)). - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "BX"); - emitline(", CX\n"); - emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "BX"); - emitline(", AX\n"); - emitline("\tMOVQ\tCX, BX\n"); - } else { - let op: str = fieldloadop(fi); - emitline("\t"); - emitline(op); - emitline("\t"); - emitdispreg(fi.foff: i64, "BX"); - emitline(", AX\n"); - }; - return; - }; - fi = fi.finext; - }; - }; - }; - }; - // Direct struct local: field load at off+foff. - if (lkind == N_TNAME) { - let sname: str = tn.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - // str field: load both halves so chained - // `.ptr` / `.len` see (AX=ptr, BX=len). - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitoff((lc.off + fi.foff): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\t"); - emitoff((lc.off + fi.foff + 8): i64); - emitline("(BP), BX\n"); - } else { - let op: str = fieldloadop(fi); - emitline("\t"); - emitline(op); - emitline("\t"); - emitoff((lc.off + fi.foff): i64); - emitline("(BP), AX\n"); - }; - return; - }; - fi = fi.finext; - }; - }; - }; - // Array pseudo-fields: `.ptr` is the array's - // address (LEAQ); `.len` is the static element - // count (immediate). - if (lkind == N_TARRAY) { - if (streq(fld, "ptr")) { - emitline("\tLEAQ\t"); - emitoff(lc.off: i64); - emitline("(BP), AX\n"); - return; - }; - if (streq(fld, "len")) { - let lenn: *node = tn.rhs; - let alen: i64 = 0i64; - if (lenn != nil) { - if (lenn.kind == N_INTLIT) { alen = lenn.uval: i64; }; - }; - emitline("\tMOVQ\t$"); - emitint(alen); - emitline(", AX\n"); - return; - }; - }; - // str/slice pseudo-fields .ptr/.len/.cap on a - // direct local: load at slot+delta. - let delta: i32 = -1; - if (streq(fld, "ptr")) { delta = 0; }; - if (streq(fld, "len")) { delta = 8; }; - if (streq(fld, "cap")) { delta = 16; }; - if (delta >= 0) { - // Pointer to str/slice (`*[]u8`, `*str`): - // deref, then load at delta within the - // pointed-to header. C cgen does the same. - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let innerkind: i32 = -1; - if (inner != nil) { innerkind = inner.kind; }; - let innerstr: bool = false; - if (innerkind == N_TNAME) { - if (streq(inner.str, "str")) { innerstr = true; }; - }; - if (innerkind == N_TSLICE) { innerstr = true; }; - if (innerstr) { - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\t"); - emitdispreg(delta: i64, "BX"); - emitline(", AX\n"); - return; - }; - }; - emitline("\tMOVQ\t"); - emitoff((lc.off + delta): i64); - emitline("(BP), AX\n"); - return; - }; - }; - }; - }; - // Module-qualified value reference: `mod.name` where `mod` - // is N_IDENT bound as SK_USE and the leaf isn't a local. - // Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback - // the C cgen takes when bt is NULL/tyerr. - if (lhs != nil) { - if (lhs.kind == N_IDENT) { - emitline("\tMOVQ\t"); - emitsymname(c, fld); - emitline("(SB), AX\n"); - return; - }; - }; - // Non-ident base pseudo-field: e.g. `"abc".ptr` / `"abc".len`. - // Evaluate the str-producing expression — that leaves - // (AX=ptr, BX=len). Then `.ptr` returns AX as is; `.len` - // shuffles BX→AX. Mirrors what C cgen does (it just evaluates - // the literal and picks the half it wants). - if (streq(fld, "ptr")) { cgexpr(c, lhs); return; }; - if (streq(fld, "len")) { - cgexpr(c, lhs); - emitline("\tMOVQ\tBX, AX\n"); - return; - }; - // Chained struct-field-via-ptr-via-ptr access: - // r.sym.val where r: *lrel, .sym: *lsym, .val: u64 - // Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct - // field). Outer DOT dereferences and reads `val`. Without this - // path the cgen falls through and AX retains whatever the - // inner expression left there — typically the *struct pointer - // itself, so reads silently get the pointer value instead of - // the field. (Showed up porting w6l/pass.ww.) - if (lhs != nil) { - if (lhs.kind == N_DOT) { - let innert: *node = dotinnerstructptr(c, lhs); - if (innert != nil) { - let sname: str = innert.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { - cgexpr(c, lhs); // AX = ptr to inner struct - let lop: str = fieldloadop(fi); - // str field: load both halves. - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); - emitline(", BX\n"); - emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - emitline("\t"); - emitline(lop); - emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - fi = fi.finext; - }; - }; - }; - }; - }; - return; - }; - - if (k == N_UN) { - // Match C cgen ordering: evaluate operand first (load into AX), - // then apply the unary op. AMP / STAR override AX with the - // address / deref. The wasted load before AMP keeps our asm - // byte-identical to the C version. - cgexpr(c, n.lhs); - if (n.op == TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; - if (n.op == TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; - if (n.op == TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; - if (n.op == TK_AMP) { - let opnd: *node = n.lhs; - if (opnd != nil) { - if (opnd.kind == N_IDENT) { - let nm: str = opnd.str; - let off: i32 = localfind(c, nm); - if (off != 0) { - emitline("\tLEAQ\t"); - emitoff(off: i64); - emitline("(BP), AX\n"); - return; - }; - }; - }; - return; - }; - if (n.op == TK_NOT) { - let t: str = mklabel(c, "tt"); - let e: str = mklabel(c, "te"); - emitline("\tCMPQ\t$0, AX\n"); - emitline("\tJE\t"); emitline(t); emitline("\n"); - emitline("\tMOVQ\t$0, AX\n"); - emitline("\tJMP\t"); emitline(e); emitline("\n"); - emitlabel(t); - emitline("\tMOVQ\t$1, AX\n"); - emitlabel(e); - return; - }; - return; - }; - - if (k == N_BIN) { - let unsignd: bool = nodeisunsigned(c, n.lhs); - if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; - - cgexpr(c, n.rhs); - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, n.lhs); - emitline("\tPOPQ\tBX\n"); - if (n.op == TK_PLUS) { emitline("\tADDQ\tBX, AX\n"); return; }; - if (n.op == TK_MINUS) { emitline("\tSUBQ\tBX, AX\n"); return; }; - if (n.op == TK_STAR) { emitline("\tIMULQ\tBX, AX\n"); return; }; - if (n.op == TK_SLASH) { - emitline("\tMOVQ\t$0, DX\n"); - if (unsignd) { emitline("\tDIVQ\tBX\n"); } - else { emitline("\tIDIVQ\tBX\n"); }; - return; - }; - if (n.op == TK_PERCENT) { - emitline("\tMOVQ\t$0, DX\n"); - if (unsignd) { emitline("\tDIVQ\tBX\n"); } - else { emitline("\tIDIVQ\tBX\n"); }; - emitline("\tMOVQ\tDX, AX\n"); - return; - }; - if (n.op == TK_AMP) { emitline("\tANDQ\tBX, AX\n"); return; }; - if (n.op == TK_PIPE) { emitline("\tORQ\tBX, AX\n"); return; }; - if (n.op == TK_CARET) { emitline("\tXORQ\tBX, AX\n"); return; }; - if (n.op == TK_LSHIFT) { - emitline("\tMOVQ\tBX, CX\n"); - emitline("\tSHLQ\tCX, AX\n"); - return; - }; - if (n.op == TK_RSHIFT) { - emitline("\tMOVQ\tBX, CX\n"); - emitline("\tSHRQ\tCX, AX\n"); - return; - }; - if (n.op == TK_AND) { emitline("\tANDQ\tBX, AX\n"); return; }; - if (n.op == TK_OR) { emitline("\tORQ\tBX, AX\n"); return; }; - - // Comparison: emit CMPQ, jump on signed/unsigned variant, - // materialise 0/1 in AX. Same shape as the C cgen. - let iscmp: bool = false; - let jcc: str = ""; - if (n.op == TK_EQ) { iscmp = true; jcc = "JE"; }; - if (n.op == TK_NEQ) { iscmp = true; jcc = "JNE"; }; - if (n.op == TK_LT) { iscmp = true; if (unsignd) { jcc = "JB"; } else { jcc = "JL"; }; }; - if (n.op == TK_LE) { iscmp = true; if (unsignd) { jcc = "JBE"; } else { jcc = "JLE"; }; }; - if (n.op == TK_GT) { iscmp = true; if (unsignd) { jcc = "JA"; } else { jcc = "JG"; }; }; - if (n.op == TK_GE) { iscmp = true; if (unsignd) { jcc = "JAE"; } else { jcc = "JGE"; }; }; - if (iscmp) { - let t: str = mklabel(c, "ct"); - let e: str = mklabel(c, "ce"); - emitline("\tCMPQ\tBX, AX\n"); - emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n"); - emitline("\tMOVQ\t$0, AX\n"); - emitline("\tJMP\t"); emitline(e); emitline("\n"); - emitlabel(t); - emitline("\tMOVQ\t$1, AX\n"); - emitlabel(e); - return; - }; - return; - }; - - if (k == N_CALL) { - let nargs: i32 = pushargsrev(c, n.list); - let i: i32 = 0; - for (i < nargs) { - emitline("\tPOPQ\t"); - emitline(argregname(i)); - emitline("\n"); - i += 1; - }; - let callee: *node = n.lhs; - let calleename: str; - calleename.ptr = nil; calleename.len = 0; - // Detect fn-pointer field call: `w.emit(args)` where `w` is - // a struct local and `emit` is an N_TFN field. Load the - // field value into AX and CALL through it. Also detect a - // bare `fp(args)` where `fp` is a local holding a function - // pointer — mirror C cgen's localfind dispatch (commit - // 635818e). Without this the call emits `CALL fp(SB)` and - // the linker rightly fails. - let isfnptrcall: bool = false; - if (callee != nil) { - if (callee.kind == N_IDENT) { - let cn: str = callee.str; - if (localfindnode(c, cn) != nil) { - isfnptrcall = true; - }; - }; - if (callee.kind == N_DOT) { - let base: *node = callee.lhs; - let fld: str = callee.str; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - let lc: *local = localfindnode(c, bn); - if (lc != nil) { - let tn: *node = lc.tnode; - if (tn != nil) { - let lkind: i32 = 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; - if (inner != nil) { - if (inner.kind == N_TNAME) { sname = inner.str; }; - }; - }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - let ft: *node = fi.tnode; - if (ft != nil) { - if (ft.kind == N_TFN) { - isfnptrcall = true; - }; - }; - fi = nil; - } else { - fi = fi.finext; - }; - }; - }; - }; - }; - }; - }; - }; - }; - }; - if (isfnptrcall) { - // Load fn-ptr field value into AX; CALL AX. We emit the - // load AFTER the args have been popped (so AX/BX/etc - // don't get clobbered by the field load before the pops). - // `popped args` left DI/SI/etc set; AX is free. - cgexpr(c, callee); - emitline("\tCALL\tAX\n"); - } else { - emitline("\tCALL\t"); - if (callee != nil) { - if (callee.kind == N_IDENT) { - calleename = callee.str; - emitsymname(c, calleename); - } else { if (callee.kind == N_DOT) { - calleename = callee.str; - emitsymname(c, calleename); - };}; - }; - emitline("(SB)\n"); - }; - // SysV returns 16-byte aggregates in (AX, DX). Our str - // convention is (AX, BX), so shuffle for str-returning calls. - if (calleename.len > 0) { - let rt: *node = fnretlookup(c, calleename); - if (isstrtype(c, rt)) { - emitline("\tMOVQ\tDX, BX\n"); - }; - }; - return; - }; - - if (k == N_ASSIGN) { - let lhs: *node = n.lhs; - // `*p = v` — deref-assign. Element width comes from the - // pointer's declared type. Mirrors C cgen: eval rhs (AX, - // and BX if str), push, eval pointer, pop value, store. - // We default to MOVQ (8B) since most fixtures use it; for - // `*bool` / `*u8` / `*i32` we narrow via the local's tnode. - if (lhs != nil) { - if (lhs.kind == N_UN) { - if (lhs.op == TK_STAR) { - if (n.op == TK_ASSIGN) { - let inner: *node = lhs.lhs; - let elemstr: bool = false; - let storeop: str = "MOVQ"; - if (inner != nil) { - if (inner.kind == N_IDENT) { - let lc: *local = localfindnode(c, inner.str); - if (lc != nil) { - let tn: *node = lc.tnode; - if (tn != nil) { - if (tn.kind == N_TPTR) { - let pe: *node = tn.lhs; - if (pe != nil) { - if (pe.kind == N_TNAME) { - if (streq(pe.str, "str")) { elemstr = true; } - else { - let ps: i32 = primsize(pe.str); - if (ps == 1) { storeop = "MOVB"; } - else { if (ps == 4) { storeop = "MOVL"; }; }; - }; - }; - }; - }; - }; - }; - }; - }; - cgexpr(c, n.rhs); - // Push order matches C cgen - // (cmd/w6c/cgen.c:1033-1041): PUSHQ AX - // (ptr) first, then PUSHQ BX (len) if - // str, so the pop sequence is POP CX - // (len) → POP AX (ptr) → MOVQ AX, - // (BX) → MOVQ CX, 8(BX). - emitline("\tPUSHQ\tAX\n"); - if (elemstr) { emitline("\tPUSHQ\tBX\n"); }; - cgexpr(c, inner); - emitline("\tMOVQ\tAX, BX\n"); - if (elemstr) { - emitline("\tPOPQ\tCX\n"); - emitline("\tPOPQ\tAX\n"); - emitline("\tMOVQ\tAX, (BX)\n"); - emitline("\tMOVQ\tCX, 8(BX)\n"); - return; - }; - emitline("\tPOPQ\tAX\n"); - emitline("\t"); - emitline(storeop); - emitline("\tAX, (BX)\n"); - return; - }; - }; - }; - }; - // Array/slice/ptr index store: `arr[i] = v;`. Element size - // from base.tnode picks MOVB vs MOVQ. - if (lhs != nil) { - if (lhs.kind == N_INDEX) { - if (n.op == TK_ASSIGN) { - let base: *node = lhs.lhs; - let idx: *node = lhs.rhs; - let esz: i32 = 8; - let baselocal: *local = nil; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - baselocal = localfindnode(c, bn); - if (baselocal != nil) { - esz = elemsizeof(baselocal.tnode); - }; - } else { if (base.kind == N_DOT) { - esz = indexbaseesz(c, base); - };}; - }; - cgexpr(c, n.rhs); // value → AX - if (esz == 16) { emitline("\tPUSHQ\tBX\n"); }; - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, idx); // idx → AX - if (esz > 1) { - emitline("\tMOVQ\t$"); - emitint(esz: i64); - emitline(", CX\n"); - emitline("\tIMULQ\tCX, AX\n"); - }; - emitline("\tPUSHQ\tAX\n"); // scaled idx - if (baselocal != nil) { - let tn: *node = baselocal.tnode; - let isarray: bool = false; - if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; - if (isarray) { - emitline("\tLEAQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - }; - } else { - cgexpr(c, base); - emitline("\tMOVQ\tAX, BX\n"); - }; - emitline("\tPOPQ\tAX\n"); // scaled idx - emitline("\tADDQ\tAX, BX\n"); - emitline("\tPOPQ\tAX\n"); // value - if (esz == 16) { - emitline("\tMOVQ\tAX, (BX)\n"); - emitline("\tPOPQ\tCX\n"); - emitline("\tMOVQ\tCX, 8(BX)\n"); - return; - }; - if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } - else { emitline("\tMOVQ\tAX, (BX)\n"); }; - return; - }; - }; - }; - // Struct/ptr-to-struct field assignment: `s.f = expr;` or - // `p.f = expr;`. Only plain `=` is wired (compound on field - // is rare and not yet needed by our fixtures). - if (lhs != nil) { - if (lhs.kind == N_DOT) { - let base: *node = lhs.lhs; - let fld: str = lhs.str; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - let lc: *local = localfindnode(c, bn); - if (lc != nil) { - let tn: *node = lc.tnode; - let lkind: i32 = -1; - if (tn != nil) { lkind = tn.kind; }; - // Pointer-to-struct: deref then store. - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (inner != nil) { - if (inner.kind == N_TNAME) { sname = inner.str; }; - }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - if (n.op != TK_ASSIGN) { - // compound: load current value - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - let lop: str = fieldloadop(fi); - emitline("\t"); - emitline(lop); - emitline("\t"); - emitdispreg(fi.foff: i64, "BX"); - emitline(", BX\n"); - emitline("\tPUSHQ\tBX\n"); - }; - cgexpr(c, n.rhs); - if (n.op != TK_ASSIGN) { - emitline("\tPOPQ\tBX\n"); - // PLUSEQ is commutative; MINUSEQ - // needs lhs - rhs (BX is old lhs, - // AX is rhs). - if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; - if (n.op == TK_MINUSEQ) { - emitline("\tSUBQ\tAX, BX\n"); - emitline("\tMOVQ\tBX, AX\n"); - }; - }; - // str field via *struct: rhs left - // (AX=ptr, BX=len). Use CX as the - // address scratch so we don't clobber - // the len half before storing it. - if (n.op == TK_ASSIGN) { - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), CX\n"); - emitline("\tMOVQ\tAX, "); - emitdispreg(fi.foff: i64, "CX"); - emitline("\n"); - emitline("\tMOVQ\tBX, "); - emitdispreg((fi.foff + 8): i64, "CX"); - emitline("\n"); - return; - }; - }; - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - let sop: str = fieldstoreop(fi); - emitline("\t"); - emitline(sop); - emitline("\tAX, "); - emitdispreg(fi.foff: i64, "BX"); - emitline("\n"); - return; - }; - fi = fi.finext; - }; - }; - }; - }; - // Direct struct local: store at off+foff. - if (lkind == N_TNAME) { - let sname: str = tn.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - cgexpr(c, n.rhs); - let sop: str = fieldstoreop(fi); - emitline("\t"); - emitline(sop); - emitline("\tAX, "); - emitoff((lc.off + fi.foff): i64); - emitline("(BP)\n"); - return; - }; - fi = fi.finext; - }; - }; - }; - // str/slice pseudo-field assignment. - let delta: i32 = -1; - if (streq(fld, "ptr")) { delta = 0; }; - if (streq(fld, "len")) { delta = 8; }; - if (streq(fld, "cap")) { delta = 16; }; - if (delta >= 0) { - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let innerkind: i32 = -1; - if (inner != nil) { innerkind = inner.kind; }; - let innerstr: bool = false; - if (innerkind == N_TNAME) { - if (streq(inner.str, "str")) { innerstr = true; }; - }; - if (innerkind == N_TSLICE) { innerstr = true; }; - if (innerstr) { - if (n.op != TK_ASSIGN) { - // Compound on `(*str|*slice).field`: load - // current → push → eval rhs → combine → store. - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\t"); - emitdispreg(delta: i64, "BX"); - emitline(", BX\n"); - emitline("\tPUSHQ\tBX\n"); - cgexpr(c, n.rhs); - emitline("\tPOPQ\tBX\n"); - // PLUSEQ is commutative; MINUSEQ - // needs lhs - rhs. - if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; - if (n.op == TK_MINUSEQ) { - emitline("\tSUBQ\tAX, BX\n"); - emitline("\tMOVQ\tBX, AX\n"); - }; - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\tAX, "); - emitdispreg(delta: i64, "BX"); - emitline("\n"); - return; - }; - cgexpr(c, n.rhs); - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\tAX, "); - emitdispreg(delta: i64, "BX"); - emitline("\n"); - return; - }; - }; - cgexpr(c, n.rhs); - emitline("\tMOVQ\tAX, "); - emitoff((lc.off + delta): i64); - emitline("(BP)\n"); - return; - }; - }; - }; - }; - }; - }; - // Local-ident target — plain `=` and the simple compound - // forms (+= -= *= /=); other compounds fall back to - // "evaluate rhs, replace". Mirrors C cgen's IDENT-assign path. - if (lhs != nil) { - if (lhs.kind == N_IDENT) { - let nm: str = lhs.str; - let off: i32 = localfind(c, nm); - if (off == 0) { return; }; - // Detect str-typed local — assignment must store both - // halves (AX=ptr at +0, BX=len at +8). - let lcstr: bool = false; - let lcn: *local = localfindnode(c, nm); - if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); }; - cgexpr(c, n.rhs); - if (n.op == TK_ASSIGN) { - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - if (lcstr) { - emitline("\tMOVQ\tBX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - }; - return; - }; - if (n.op == TK_PLUSEQ) { - emitline("\tADDQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - return; - }; - if (n.op == TK_MINUSEQ) { - emitline("\tSUBQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - return; - }; - // Generic compound: load → combine in BX → store. - emitline("\tMOVQ\t"); - emitoff(off: i64); - emitline("(BP), BX\n"); - if (n.op == TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }; - if (n.op == TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }; - if (n.op == TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }; - if (n.op == TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }; - if (n.op == TK_LSHIFTEQ) { - emitline("\tMOVQ\tAX, CX\n"); - emitline("\tSHLQ\tCX, BX\n"); - }; - if (n.op == TK_RSHIFTEQ) { - emitline("\tMOVQ\tAX, CX\n"); - emitline("\tSHRQ\tCX, BX\n"); - }; - emitline("\tMOVQ\tBX, "); - emitoff(off: i64); - emitline("(BP)\n"); - return; - }; - }; - return; - }; -}; - // ---- type-driven slot sizing ---------------------------------------- fn structlookup(c: *cgen, name: str) *structinfo = { @@ -2589,343 +1533,6 @@ fn scanlocals(c: *cgen, n: *node) i32 = { return total; }; -// ---- statement cgen -------------------------------------------------- - -fn cgstmt(c: *cgen, n: *node) void = { - if (n == nil) { return; }; - let k: i32 = n.kind; - - if (k == N_BLOCK) { - let s: *node = n.list; - for (s != nil) { - cgstmt(c, s); - s = s.next; - }; - return; - }; - - if (k == N_RETURN) { - let rhs: *node = n.lhs; - if (rhs != nil) { - // Tuple return `return a, b;` — pack as (AX=v0, DX=v1). - // Matches C cgen: evaluate v1 first (PUSHQ), then v0 - // into AX, then POPQ DX. End state: AX = v0, DX = v1. - if (rhs.kind == N_TUPLE) { - let v: *node = rhs.list; - if (v != nil) { - let v2: *node = v.next; - if (v2 != nil) { - cgexpr(c, v2); - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, v); - emitline("\tPOPQ\tDX\n"); - } else { - cgexpr(c, v); - }; - }; - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.last_was_return = 1; - return; - }; - // Tagged-union return: pack as (AX=tag, DX=value0, CX=value1). - // For str variant, cgexpr leaves (AX=ptr, BX=len), so we - // shuffle DX←AX (ptr) and CX←BX (len), then load tag. - // For other variants, cgexpr leaves AX, shuffle DX←AX. - if (istaggedtype(c.fn_ret)) { - cgexpr(c, rhs); - let idx: i32 = taggedvariantindex(c, c.fn_ret, rhs); - if (nodeisstr(c, rhs)) { - emitline("\tMOVQ\tBX, CX\n"); - emitline("\tMOVQ\tAX, DX\n"); - } else { - emitline("\tMOVQ\tAX, DX\n"); - }; - emitline("\tMOVQ\t$"); - if (idx < 0) { idx = 0; }; - emitint(idx: i64); - emitline(", AX\n"); - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.last_was_return = 1; - return; - }; - cgexpr(c, rhs); - } else { - // Bare `return;` in a void fn — zero AX so the caller - // sees a deterministic value (matches C cgen, which - // always falls through to `cgexpr_int(c, 0)`). - emitline("\tMOVQ\t$0, AX\n"); - }; - // SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX). - // cgexpr leaves str in (AX, BX); shuffle BX→DX. - if (isstrtype(c, c.fn_ret)) { - emitline("\tMOVQ\tBX, DX\n"); - }; - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.last_was_return = 1; - return; - }; - - if (k == N_EXPRSTMT) { - if (n.lhs != nil) { cgexpr(c, n.lhs); }; - c.last_was_return = 0; - return; - }; - - if (k == N_LET) { - let nm: str = n.str; - let sz: i32 = slotsize(c, n.lhs); - let off: i32 = localadd(c, nm, sz, n.lhs); - if (n.rhs != nil) { - let rhs: *node = n.rhs; - // Tagged-union init: `let r: (T | E) = expr;`. - // - If rhs is a CALL to a fn returning tagged-union, - // the result is already in (AX=tag, DX=v0, CX=v1); - // just spill all three. - // - Otherwise rhs is a bare variant value: pack tag + - // value(s). - if (istaggedtype(n.lhs)) { - let rhsreturnstagged: bool = false; - if (rhs.kind == N_CALL) { - let callee: *node = rhs.lhs; - if (callee != nil) { - let calleename: str; - calleename.ptr = nil; calleename.len = 0; - if (callee.kind == N_IDENT) { calleename = callee.str; }; - if (callee.kind == N_DOT) { calleename = callee.str; }; - if (calleename.len > 0) { - let rt: *node = fnretlookup(c, calleename); - if (istaggedtype(rt)) { rhsreturnstagged = true; }; - }; - }; - }; - cgexpr(c, rhs); - if (rhsreturnstagged) { - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tDX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tCX, "); - emitoff((off + 16): i64); - emitline("(BP)\n"); - c.last_was_return = 0; - return; - }; - let tagidx: i32 = taggedvariantindex(c, n.lhs, rhs); - if (tagidx < 0) { tagidx = 0; }; - if (nodeisstr(c, rhs)) { - emitline("\tMOVQ\tAX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tBX, "); - emitoff((off + 16): i64); - emitline("(BP)\n"); - } else { - emitline("\tMOVQ\tAX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - }; - emitline("\tMOVQ\t$"); - emitint(tagidx: i64); - emitline(", "); - emitoff(off: i64); - emitline("(BP)\n"); - c.last_was_return = 0; - return; - }; - // Struct literal init: `let p: point = point{x=..., y=...};`. - // For each field in the lit, evaluate its value and store at - // the field's offset within the slot. Field-name → offset - // from the struct registry. - if (rhs.kind == N_STRUCTLIT) { - let trefn: *node = rhs.lhs; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (trefn != nil) { - if (trefn.kind == N_IDENT) { sname = trefn.str; } - else { if (trefn.kind == N_TNAME) { sname = trefn.str; }; }; - }; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fieldnode: *node = rhs.list; - for (fieldnode != nil) { - if (fieldnode.kind == N_FIELD) { - let fname: str = fieldnode.str; - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fname)) { - cgexpr(c, fieldnode.lhs); - let sop: str = fieldstoreop(fi); - emitline("\t"); - emitline(sop); - emitline("\tAX, "); - emitoff((off + fi.foff): i64); - emitline("(BP)\n"); - fi = nil; - } else { - fi = fi.finext; - }; - }; - }; - fieldnode = fieldnode.next; - }; - c.last_was_return = 0; - return; - }; - }; - cgexpr(c, rhs); - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - // str init: cgexpr also leaves len in BX; store both. - if (sz == 16) { - emitline("\tMOVQ\tBX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - }; - // slice init: ptr/len/cap in AX/BX/CX. - if (sz == 24) { - emitline("\tMOVQ\tBX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tCX, "); - emitoff((off + 16): i64); - emitline("(BP)\n"); - }; - } 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. - if (typeis8byteprimitive(c, n.lhs)) { - emitline("\tMOVQ\t$0, "); - emitoff(off: i64); - emitline("(BP)\n"); - }; - }; - c.last_was_return = 0; - return; - }; - - if (k == N_IF) { - let els: str = mklabel(c, "else"); - let endl: str = mklabel(c, "end"); - cgexpr(c, n.cond); - emitline("\tCMPQ\t$0, AX\n"); - emitline("\tJE\t"); - if (n.els != nil) { emitline(els); } - else { emitline(endl); }; - emitline("\n"); - if (n.body != nil) { cgstmt(c, n.body); }; - if (n.els != nil) { - emitline("\tJMP\t"); emitline(endl); emitline("\n"); - emitlabel(els); - cgstmt(c, n.els); - }; - emitlabel(endl); - c.last_was_return = 0; - return; - }; - - if (k == N_FOR) { - // Match C cgen's label scheme: _loop_N for the top, - // _endloop_N for the post-body merge. No separate cont - // label when there's no post-expression. - let topl: str = mklabel(c, "loop"); - let endl: str = mklabel(c, "endloop"); - - if (n.lhs != nil) { cgstmt(c, n.lhs); }; - - emitlabel(topl); - if (n.cond != nil) { - cgexpr(c, n.cond); - emitline("\tCMPQ\t$0, AX\n"); - emitline("\tJE\t"); emitline(endl); emitline("\n"); - }; - - c.loop_end_buf[c.loop_top] = endl; - c.loop_cont_buf[c.loop_top] = topl; - c.loop_top += 1; - - if (n.body != nil) { cgstmt(c, n.body); }; - - c.loop_top -= 1; - - if (n.rhs != nil) { cgexpr(c, n.rhs); }; - emitline("\tJMP\t"); emitline(topl); emitline("\n"); - emitlabel(endl); - c.last_was_return = 0; - 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/w6c/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); }; - emitline("\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 = localfind(c, l0.str); - if (off != 0) { - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - }; - }; - }; - emitline("\tPOPQ\tDX\n"); - if (l1 != nil) { - if (l1.kind == N_IDENT) { - let off: i32 = localfind(c, l1.str); - if (off != 0) { - emitline("\tMOVQ\tDX, "); - emitoff(off: i64); - emitline("(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]; - emitline("\tJMP\t"); emitline(lbl); emitline("\n"); - }; - c.last_was_return = 0; - return; - }; - if (k == N_CONTINUE) { - if (c.loop_top > 0) { - let lbl: str = c.loop_cont_buf[c.loop_top - 1]; - emitline("\tJMP\t"); emitline(lbl); emitline("\n"); - }; - c.last_was_return = 0; - return; - }; - - c.last_was_return = 0; -}; - // ---- function-level cgen --------------------------------------------- // isstrtype — true when the type expr resolves (through any diff --git a/selfhost/cmd/wcc/cgen_expr.ww b/selfhost/cmd/wcc/cgen_expr.ww new file mode 100644 index 00000000..dcf2e440 --- /dev/null +++ b/selfhost/cmd/wcc/cgen_expr.ww @@ -0,0 +1,1077 @@ +// selfhost/cmd/wcc/cgen_expr.ww — split out of cgen.ww. +// +// Houses cgexpr — the per-expression code generator. The remainder of +// the cgen lives in cgen.ww (foundation: types, emit primitives, the +// collect* tables, FFI/module maps) and cgen_stmt.ww (cgstmt). +// +// `use cgen_expr;` is unnecessary at consumer sites — cgen.ww imports +// this file, so any caller of cgen transitively gets cgexpr. + +use os; +use mem; +use ast; +use tok; +use typ; +use sym; +use strconv; + +fn cgexpr(c: *cgen, n: *node) void = { + if (n == nil) { return; }; + let k: i32 = n.kind; + + if (k == N_INTLIT) { + // Print signed (i64), not unsigned (u64). C cgen uses + // `$%lld` so 64-bit constants with bit 63 set show up as + // negative — e.g. FNV-1a's offset basis prints as + // $-3750763034362895579, not $14695981039346656037. + emitline("\tMOVQ\t$"); + emitint(n.uval: i64); + emitline(", AX\n"); + return; + }; + if (k == N_RUNELIT) { + emitline("\tMOVQ\t$"); + emitint(n.uval: i64); + emitline(", AX\n"); + return; + }; + if (k == N_STRLIT) { + // Result is the (ptr, len) pair: ptr in AX, len in BX. Call + // sites that expect a str arg pick these up directly. + let nstr: str = n.str; + let lab: str = internstrlit(c, nstr); + emitline("\tLEAQ\t"); + os.write(1, lab.ptr, lab.len: u64); + emitline("(SB), AX\n"); + emitline("\tMOVQ\t$"); + emitint(nstr.len: i64); + emitline(", BX\n"); + return; + }; + if (k == N_TRUE) { + emitline("\tMOVQ\t$1, AX\n"); + return; + }; + if (k == N_FALSE) { + emitline("\tMOVQ\t$0, AX\n"); + return; + }; + if (k == N_NIL) { + emitline("\tMOVQ\t$0, AX\n"); + return; + }; + + if (k == N_IDENT) { + let nm: str = n.str; + let lc: *local = localfindnode(c, nm); + if (lc != nil) { + let off: i32 = lc.off; + emitline("\tMOVQ\t"); + emitoff(off: i64); + emitline("(BP), AX\n"); + // str local: also load the len half into BX. + if (isstrtype(c, lc.tnode)) { + emitline("\tMOVQ\t"); + emitoff((off + 8): i64); + emitline("(BP), BX\n"); + }; + // slice local: load (ptr, len, cap) into (AX, BX, CX). + if (isslicetype(c, lc.tnode)) { + emitline("\tMOVQ\t"); + emitoff((off + 8): i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\t"); + emitoff((off + 16): i64); + emitline("(BP), CX\n"); + }; + return; + }; + // Top-level `def` constant — load from its DATA symbol. + if (deflookup(c, nm)) { + emitline("\tMOVQ\t"); + emitsymname(c, nm); + emitline("(SB), AX\n"); + return; + }; + // Fn-name used as a value (e.g. `let f = some_fn;` or + // `... = some_fn;`). LEAQ the symbol address into AX. The + // emitsymname helper handles ffiresolve and module-mangling + // in one go, so a body-less FFI binding emits the C symbol + // it was declared with via @symbol(), not the ww-side ident. + let rt: *node = fnretlookup(c, nm); + if (rt != nil) { + emitline("\tLEAQ\t"); + emitsymname(c, nm); + emitline("(SB), AX\n"); + return; + }; + return; + }; + + if (k == N_INDEX) { + // Element-size-aware load: u8-element bases use MOVZBQ, + // 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 baselocal: *local = nil; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + baselocal = localfindnode(c, bn); + if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); }; + } else { if (base.kind == N_DOT) { + esz = indexbaseesz(c, base); + };}; + }; + cgexpr(c, idx); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let isarray: bool = false; + if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; + if (isarray) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + emitline("\tADDQ\tAX, BX\n"); + // str element (16B): load (ptr, len) into (AX, BX) so + // the value flows through the str-rhs convention. + if (esz == 16) { + emitline("\tMOVQ\t8(BX), CX\n"); + emitline("\tMOVQ\t(BX), AX\n"); + emitline("\tMOVQ\tCX, BX\n"); + return; + }; + if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } + else { emitline("\tMOVQ\t(BX), AX\n"); }; + return; + }; + // Generic fallback when base isn't a plain ident. + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, base); + emitline("\tPOPQ\tBX\n"); + emitline("\tADDQ\tBX, AX\n"); + if (esz == 16) { + emitline("\tMOVQ\t8(AX), BX\n"); + emitline("\tMOVQ\t(AX), AX\n"); + return; + }; + if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } + else { emitline("\tMOVQ\t(AX), AX\n"); }; + return; + }; + + if (k == N_MATCH) { + // match (e) { case let v: T => stmt; ... } + // + // Read the tagged-union slot and dispatch by tag. Slot + // layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings + // (`case let v: T =>`) get a fresh local slot loaded from + // slot+8 (and slot+16 for str-typed payload). + let scrut: *node = n.lhs; + let scrutoff: i32 = 0; + let scrutt: *node = nil; + if (scrut != nil) { + if (scrut.kind == N_IDENT) { + let lc: *local = localfindnode(c, scrut.str); + if (lc != nil) { + scrutoff = lc.off; + scrutt = resolvetype(c, lc.tnode); + }; + }; + }; + let endl: str = mklabel(c, "match_end"); + let cs: *node = n.list; + for (cs != nil) { + let nxt: str = mklabel(c, "match_next"); + let pat: *node = cs.lhs; + // Compute the variant tag for this arm. Default arm + // (no pattern) skips the tag check. + if (pat != nil) { + let want: i32 = 0; + if (scrutt != nil) { + if (scrutt.kind == N_TTAGGED) { + let patname: str; + patname.ptr = nil; patname.len = 0; + if (pat.kind == N_TNAME) { patname = pat.str; }; + let v: *node = scrutt.list; + let idx: i32 = 0; + let found: bool = false; + for (v != nil) { + if (v.kind == N_TNAME) { + if (streq(v.str, patname)) { + want = idx; + found = true; + v = nil; + }; + }; + if (v != nil) { + v = v.next; + idx += 1; + }; + }; + if (!found) { want = 0; }; + }; + }; + emitline("\tMOVQ\t"); + emitoff(scrutoff: i64); + emitline("(BP), AX\n"); + emitline("\tCMPQ\t$"); + emitint(want: i64); + emitline(", AX\n"); + emitline("\tJNE\t"); + emitline(nxt); + emitline("\n"); + }; + // Bind `let v: T` from the slot, if requested. + let bn: str = cs.str; + if (bn.len > 0) { + if (pat != nil) { + let bsz: i32 = 8; + if (isstrtype(c, pat)) { bsz = 16; }; + // localalloc (not localadd): match-arm + // binds don't dedup with same-named binds + // in *other* matches, since C's cgexpr + // allocates a fresh slot per match expr. + let voff: i32 = localalloc(c, bn, bsz, pat); + emitline("\tMOVQ\t"); + emitoff((scrutoff + 8): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(voff: i64); + emitline("(BP)\n"); + if (bsz == 16) { + emitline("\tMOVQ\t"); + emitoff((scrutoff + 16): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((voff + 8): i64); + emitline("(BP)\n"); + }; + }; + }; + // Body. Match arms are statements; we cgstmt them. + if (cs.body != nil) { cgstmt(c, cs.body); }; + emitline("\tJMP\t"); + emitline(endl); + emitline("\n"); + emitlabel(nxt); + cs = cs.next; + }; + emitlabel(endl); + return; + }; + + if (k == N_CAST) { + // Type casts are mostly no-ops at the asm level for our + // integer-shaped operands. Evaluate the source; AX holds + // the bits unchanged. (Sign- or zero-extending narrow loads + // to wider types is the loader's job, not cast's, in this + // minimal cgen.) + cgexpr(c, n.lhs); + return; + }; + + if (k == N_DOT) { + let lhs: *node = n.lhs; + let fld: str = n.str; + if (lhs != nil) { + if (lhs.kind == N_IDENT) { + let nm: str = lhs.str; + let lc: *local = localfindnode(c, nm); + if (lc != nil) { + let tn: *node = lc.tnode; + let lkind: i32 = -1; + if (tn != nil) { lkind = tn.kind; }; + // Pointer-to-struct: deref then field load. + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (inner != nil) { + if (inner.kind == N_TNAME) { + sname = inner.str; + }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + // str field via *struct: load len into a + // scratch first (so loading ptr into AX + // last leaves (AX=ptr, BX=len)). + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitdispreg((fi.foff + 8): i64, "BX"); + emitline(", CX\n"); + emitline("\tMOVQ\t"); + emitdispreg(fi.foff: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVQ\tCX, BX\n"); + } else { + let op: str = fieldloadop(fi); + emitline("\t"); + emitline(op); + emitline("\t"); + emitdispreg(fi.foff: i64, "BX"); + emitline(", AX\n"); + }; + return; + }; + fi = fi.finext; + }; + }; + }; + }; + // Direct struct local: field load at off+foff. + if (lkind == N_TNAME) { + let sname: str = tn.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + // str field: load both halves so chained + // `.ptr` / `.len` see (AX=ptr, BX=len). + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitoff((lc.off + fi.foff): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\t"); + emitoff((lc.off + fi.foff + 8): i64); + emitline("(BP), BX\n"); + } else { + let op: str = fieldloadop(fi); + emitline("\t"); + emitline(op); + emitline("\t"); + emitoff((lc.off + fi.foff): i64); + emitline("(BP), AX\n"); + }; + return; + }; + fi = fi.finext; + }; + }; + }; + // Array pseudo-fields: `.ptr` is the array's + // address (LEAQ); `.len` is the static element + // count (immediate). + if (lkind == N_TARRAY) { + if (streq(fld, "ptr")) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), AX\n"); + return; + }; + if (streq(fld, "len")) { + let lenn: *node = tn.rhs; + let alen: i64 = 0i64; + if (lenn != nil) { + if (lenn.kind == N_INTLIT) { alen = lenn.uval: i64; }; + }; + emitline("\tMOVQ\t$"); + emitint(alen); + emitline(", AX\n"); + return; + }; + }; + // str/slice pseudo-fields .ptr/.len/.cap on a + // direct local: load at slot+delta. + let delta: i32 = -1; + if (streq(fld, "ptr")) { delta = 0; }; + if (streq(fld, "len")) { delta = 8; }; + if (streq(fld, "cap")) { delta = 16; }; + if (delta >= 0) { + // Pointer to str/slice (`*[]u8`, `*str`): + // deref, then load at delta within the + // pointed-to header. C cgen does the same. + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let innerkind: i32 = -1; + if (inner != nil) { innerkind = inner.kind; }; + let innerstr: bool = false; + if (innerkind == N_TNAME) { + if (streq(inner.str, "str")) { innerstr = true; }; + }; + if (innerkind == N_TSLICE) { innerstr = true; }; + if (innerstr) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(delta: i64, "BX"); + emitline(", AX\n"); + return; + }; + }; + emitline("\tMOVQ\t"); + emitoff((lc.off + delta): i64); + emitline("(BP), AX\n"); + return; + }; + }; + }; + }; + // Module-qualified value reference: `mod.name` where `mod` + // is N_IDENT bound as SK_USE and the leaf isn't a local. + // Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback + // the C cgen takes when bt is NULL/tyerr. + if (lhs != nil) { + if (lhs.kind == N_IDENT) { + emitline("\tMOVQ\t"); + emitsymname(c, fld); + emitline("(SB), AX\n"); + return; + }; + }; + // Non-ident base pseudo-field: e.g. `"abc".ptr` / `"abc".len`. + // Evaluate the str-producing expression — that leaves + // (AX=ptr, BX=len). Then `.ptr` returns AX as is; `.len` + // shuffles BX→AX. Mirrors what C cgen does (it just evaluates + // the literal and picks the half it wants). + if (streq(fld, "ptr")) { cgexpr(c, lhs); return; }; + if (streq(fld, "len")) { + cgexpr(c, lhs); + emitline("\tMOVQ\tBX, AX\n"); + return; + }; + // Chained struct-field-via-ptr-via-ptr access: + // r.sym.val where r: *lrel, .sym: *lsym, .val: u64 + // Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct + // field). Outer DOT dereferences and reads `val`. Without this + // path the cgen falls through and AX retains whatever the + // inner expression left there — typically the *struct pointer + // itself, so reads silently get the pointer value instead of + // the field. (Showed up porting w6l/pass.ww.) + if (lhs != nil) { + if (lhs.kind == N_DOT) { + let innert: *node = dotinnerstructptr(c, lhs); + if (innert != nil) { + let sname: str = innert.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + cgexpr(c, lhs); // AX = ptr to inner struct + let lop: str = fieldloadop(fi); + // str field: load both halves. + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitdispreg((fi.foff + 8): i64, "AX"); + emitline(", BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", AX\n"); + return; + }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", AX\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + }; + }; + return; + }; + + if (k == N_UN) { + // Match C cgen ordering: evaluate operand first (load into AX), + // then apply the unary op. AMP / STAR override AX with the + // address / deref. The wasted load before AMP keeps our asm + // byte-identical to the C version. + cgexpr(c, n.lhs); + if (n.op == TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; + if (n.op == TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; + if (n.op == TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; + if (n.op == TK_AMP) { + let opnd: *node = n.lhs; + if (opnd != nil) { + if (opnd.kind == N_IDENT) { + let nm: str = opnd.str; + let off: i32 = localfind(c, nm); + if (off != 0) { + emitline("\tLEAQ\t"); + emitoff(off: i64); + emitline("(BP), AX\n"); + return; + }; + }; + }; + return; + }; + if (n.op == TK_NOT) { + let t: str = mklabel(c, "tt"); + let e: str = mklabel(c, "te"); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJE\t"); emitline(t); emitline("\n"); + emitline("\tMOVQ\t$0, AX\n"); + emitline("\tJMP\t"); emitline(e); emitline("\n"); + emitlabel(t); + emitline("\tMOVQ\t$1, AX\n"); + emitlabel(e); + return; + }; + return; + }; + + if (k == N_BIN) { + let unsignd: bool = nodeisunsigned(c, n.lhs); + if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; + + cgexpr(c, n.rhs); + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, n.lhs); + emitline("\tPOPQ\tBX\n"); + if (n.op == TK_PLUS) { emitline("\tADDQ\tBX, AX\n"); return; }; + if (n.op == TK_MINUS) { emitline("\tSUBQ\tBX, AX\n"); return; }; + if (n.op == TK_STAR) { emitline("\tIMULQ\tBX, AX\n"); return; }; + if (n.op == TK_SLASH) { + emitline("\tMOVQ\t$0, DX\n"); + if (unsignd) { emitline("\tDIVQ\tBX\n"); } + else { emitline("\tIDIVQ\tBX\n"); }; + return; + }; + if (n.op == TK_PERCENT) { + emitline("\tMOVQ\t$0, DX\n"); + if (unsignd) { emitline("\tDIVQ\tBX\n"); } + else { emitline("\tIDIVQ\tBX\n"); }; + emitline("\tMOVQ\tDX, AX\n"); + return; + }; + if (n.op == TK_AMP) { emitline("\tANDQ\tBX, AX\n"); return; }; + if (n.op == TK_PIPE) { emitline("\tORQ\tBX, AX\n"); return; }; + if (n.op == TK_CARET) { emitline("\tXORQ\tBX, AX\n"); return; }; + if (n.op == TK_LSHIFT) { + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tSHLQ\tCX, AX\n"); + return; + }; + if (n.op == TK_RSHIFT) { + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tSHRQ\tCX, AX\n"); + return; + }; + if (n.op == TK_AND) { emitline("\tANDQ\tBX, AX\n"); return; }; + if (n.op == TK_OR) { emitline("\tORQ\tBX, AX\n"); return; }; + + // Comparison: emit CMPQ, jump on signed/unsigned variant, + // materialise 0/1 in AX. Same shape as the C cgen. + let iscmp: bool = false; + let jcc: str = ""; + if (n.op == TK_EQ) { iscmp = true; jcc = "JE"; }; + if (n.op == TK_NEQ) { iscmp = true; jcc = "JNE"; }; + if (n.op == TK_LT) { iscmp = true; if (unsignd) { jcc = "JB"; } else { jcc = "JL"; }; }; + if (n.op == TK_LE) { iscmp = true; if (unsignd) { jcc = "JBE"; } else { jcc = "JLE"; }; }; + if (n.op == TK_GT) { iscmp = true; if (unsignd) { jcc = "JA"; } else { jcc = "JG"; }; }; + if (n.op == TK_GE) { iscmp = true; if (unsignd) { jcc = "JAE"; } else { jcc = "JGE"; }; }; + if (iscmp) { + let t: str = mklabel(c, "ct"); + let e: str = mklabel(c, "ce"); + emitline("\tCMPQ\tBX, AX\n"); + emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n"); + emitline("\tMOVQ\t$0, AX\n"); + emitline("\tJMP\t"); emitline(e); emitline("\n"); + emitlabel(t); + emitline("\tMOVQ\t$1, AX\n"); + emitlabel(e); + return; + }; + return; + }; + + if (k == N_CALL) { + let nargs: i32 = pushargsrev(c, n.list); + let i: i32 = 0; + for (i < nargs) { + emitline("\tPOPQ\t"); + emitline(argregname(i)); + emitline("\n"); + i += 1; + }; + let callee: *node = n.lhs; + let calleename: str; + calleename.ptr = nil; calleename.len = 0; + // Detect fn-pointer field call: `w.emit(args)` where `w` is + // a struct local and `emit` is an N_TFN field. Load the + // field value into AX and CALL through it. Also detect a + // bare `fp(args)` where `fp` is a local holding a function + // pointer — mirror C cgen's localfind dispatch (commit + // 635818e). Without this the call emits `CALL fp(SB)` and + // the linker rightly fails. + let isfnptrcall: bool = false; + if (callee != nil) { + if (callee.kind == N_IDENT) { + let cn: str = callee.str; + if (localfindnode(c, cn) != nil) { + isfnptrcall = true; + }; + }; + if (callee.kind == N_DOT) { + let base: *node = callee.lhs; + let fld: str = callee.str; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + let lc: *local = localfindnode(c, bn); + if (lc != nil) { + let tn: *node = lc.tnode; + if (tn != nil) { + let lkind: i32 = 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; + if (inner != nil) { + if (inner.kind == N_TNAME) { sname = inner.str; }; + }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + let ft: *node = fi.tnode; + if (ft != nil) { + if (ft.kind == N_TFN) { + isfnptrcall = true; + }; + }; + fi = nil; + } else { + fi = fi.finext; + }; + }; + }; + }; + }; + }; + }; + }; + }; + }; + if (isfnptrcall) { + // Load fn-ptr field value into AX; CALL AX. We emit the + // load AFTER the args have been popped (so AX/BX/etc + // don't get clobbered by the field load before the pops). + // `popped args` left DI/SI/etc set; AX is free. + cgexpr(c, callee); + emitline("\tCALL\tAX\n"); + } else { + emitline("\tCALL\t"); + if (callee != nil) { + if (callee.kind == N_IDENT) { + calleename = callee.str; + emitsymname(c, calleename); + } else { if (callee.kind == N_DOT) { + calleename = callee.str; + emitsymname(c, calleename); + };}; + }; + emitline("(SB)\n"); + }; + // SysV returns 16-byte aggregates in (AX, DX). Our str + // convention is (AX, BX), so shuffle for str-returning calls. + if (calleename.len > 0) { + let rt: *node = fnretlookup(c, calleename); + if (isstrtype(c, rt)) { + emitline("\tMOVQ\tDX, BX\n"); + }; + }; + return; + }; + + if (k == N_ASSIGN) { + let lhs: *node = n.lhs; + // `*p = v` — deref-assign. Element width comes from the + // pointer's declared type. Mirrors C cgen: eval rhs (AX, + // and BX if str), push, eval pointer, pop value, store. + // We default to MOVQ (8B) since most fixtures use it; for + // `*bool` / `*u8` / `*i32` we narrow via the local's tnode. + if (lhs != nil) { + if (lhs.kind == N_UN) { + if (lhs.op == TK_STAR) { + if (n.op == TK_ASSIGN) { + let inner: *node = lhs.lhs; + let elemstr: bool = false; + let storeop: str = "MOVQ"; + if (inner != nil) { + if (inner.kind == N_IDENT) { + let lc: *local = localfindnode(c, inner.str); + if (lc != nil) { + let tn: *node = lc.tnode; + if (tn != nil) { + if (tn.kind == N_TPTR) { + let pe: *node = tn.lhs; + if (pe != nil) { + if (pe.kind == N_TNAME) { + if (streq(pe.str, "str")) { elemstr = true; } + else { + let ps: i32 = primsize(pe.str); + if (ps == 1) { storeop = "MOVB"; } + else { if (ps == 4) { storeop = "MOVL"; }; }; + }; + }; + }; + }; + }; + }; + }; + }; + cgexpr(c, n.rhs); + // Push order matches C cgen + // (cmd/w6c/cgen.c:1033-1041): PUSHQ AX + // (ptr) first, then PUSHQ BX (len) if + // str, so the pop sequence is POP CX + // (len) → POP AX (ptr) → MOVQ AX, + // (BX) → MOVQ CX, 8(BX). + emitline("\tPUSHQ\tAX\n"); + if (elemstr) { emitline("\tPUSHQ\tBX\n"); }; + cgexpr(c, inner); + emitline("\tMOVQ\tAX, BX\n"); + if (elemstr) { + emitline("\tPOPQ\tCX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\tMOVQ\tAX, (BX)\n"); + emitline("\tMOVQ\tCX, 8(BX)\n"); + return; + }; + emitline("\tPOPQ\tAX\n"); + emitline("\t"); + emitline(storeop); + emitline("\tAX, (BX)\n"); + return; + }; + }; + }; + }; + // Array/slice/ptr index store: `arr[i] = v;`. Element size + // from base.tnode picks MOVB vs MOVQ. + if (lhs != nil) { + if (lhs.kind == N_INDEX) { + if (n.op == TK_ASSIGN) { + let base: *node = lhs.lhs; + let idx: *node = lhs.rhs; + let esz: i32 = 8; + let baselocal: *local = nil; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + baselocal = localfindnode(c, bn); + if (baselocal != nil) { + esz = elemsizeof(baselocal.tnode); + }; + } else { if (base.kind == N_DOT) { + esz = indexbaseesz(c, base); + };}; + }; + cgexpr(c, n.rhs); // value → AX + if (esz == 16) { emitline("\tPUSHQ\tBX\n"); }; + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, idx); // idx → AX + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tPUSHQ\tAX\n"); // scaled idx + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let isarray: bool = false; + if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; + if (isarray) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + } else { + cgexpr(c, base); + emitline("\tMOVQ\tAX, BX\n"); + }; + emitline("\tPOPQ\tAX\n"); // scaled idx + emitline("\tADDQ\tAX, BX\n"); + emitline("\tPOPQ\tAX\n"); // value + if (esz == 16) { + emitline("\tMOVQ\tAX, (BX)\n"); + emitline("\tPOPQ\tCX\n"); + emitline("\tMOVQ\tCX, 8(BX)\n"); + return; + }; + if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } + else { emitline("\tMOVQ\tAX, (BX)\n"); }; + return; + }; + }; + }; + // Struct/ptr-to-struct field assignment: `s.f = expr;` or + // `p.f = expr;`. Only plain `=` is wired (compound on field + // is rare and not yet needed by our fixtures). + if (lhs != nil) { + if (lhs.kind == N_DOT) { + let base: *node = lhs.lhs; + let fld: str = lhs.str; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + let lc: *local = localfindnode(c, bn); + if (lc != nil) { + let tn: *node = lc.tnode; + let lkind: i32 = -1; + if (tn != nil) { lkind = tn.kind; }; + // Pointer-to-struct: deref then store. + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (inner != nil) { + if (inner.kind == N_TNAME) { sname = inner.str; }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + if (n.op != TK_ASSIGN) { + // compound: load current value + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + let lop: str = fieldloadop(fi); + emitline("\t"); + emitline(lop); + emitline("\t"); + emitdispreg(fi.foff: i64, "BX"); + emitline(", BX\n"); + emitline("\tPUSHQ\tBX\n"); + }; + cgexpr(c, n.rhs); + if (n.op != TK_ASSIGN) { + emitline("\tPOPQ\tBX\n"); + // PLUSEQ is commutative; MINUSEQ + // needs lhs - rhs (BX is old lhs, + // AX is rhs). + if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; + if (n.op == TK_MINUSEQ) { + emitline("\tSUBQ\tAX, BX\n"); + emitline("\tMOVQ\tBX, AX\n"); + }; + }; + // str field via *struct: rhs left + // (AX=ptr, BX=len). Use CX as the + // address scratch so we don't clobber + // the len half before storing it. + if (n.op == TK_ASSIGN) { + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), CX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(fi.foff: i64, "CX"); + emitline("\n"); + emitline("\tMOVQ\tBX, "); + emitdispreg((fi.foff + 8): i64, "CX"); + emitline("\n"); + return; + }; + }; + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + let sop: str = fieldstoreop(fi); + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitdispreg(fi.foff: i64, "BX"); + emitline("\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + }; + // Direct struct local: store at off+foff. + if (lkind == N_TNAME) { + let sname: str = tn.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + cgexpr(c, n.rhs); + let sop: str = fieldstoreop(fi); + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitoff((lc.off + fi.foff): i64); + emitline("(BP)\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + // str/slice pseudo-field assignment. + let delta: i32 = -1; + if (streq(fld, "ptr")) { delta = 0; }; + if (streq(fld, "len")) { delta = 8; }; + if (streq(fld, "cap")) { delta = 16; }; + if (delta >= 0) { + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let innerkind: i32 = -1; + if (inner != nil) { innerkind = inner.kind; }; + let innerstr: bool = false; + if (innerkind == N_TNAME) { + if (streq(inner.str, "str")) { innerstr = true; }; + }; + if (innerkind == N_TSLICE) { innerstr = true; }; + if (innerstr) { + if (n.op != TK_ASSIGN) { + // Compound on `(*str|*slice).field`: load + // current → push → eval rhs → combine → store. + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(delta: i64, "BX"); + emitline(", BX\n"); + emitline("\tPUSHQ\tBX\n"); + cgexpr(c, n.rhs); + emitline("\tPOPQ\tBX\n"); + // PLUSEQ is commutative; MINUSEQ + // needs lhs - rhs. + if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; + if (n.op == TK_MINUSEQ) { + emitline("\tSUBQ\tAX, BX\n"); + emitline("\tMOVQ\tBX, AX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(delta: i64, "BX"); + emitline("\n"); + return; + }; + cgexpr(c, n.rhs); + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(delta: i64, "BX"); + emitline("\n"); + return; + }; + }; + cgexpr(c, n.rhs); + emitline("\tMOVQ\tAX, "); + emitoff((lc.off + delta): i64); + emitline("(BP)\n"); + return; + }; + }; + }; + }; + }; + }; + // Local-ident target — plain `=` and the simple compound + // forms (+= -= *= /=); other compounds fall back to + // "evaluate rhs, replace". Mirrors C cgen's IDENT-assign path. + if (lhs != nil) { + if (lhs.kind == N_IDENT) { + let nm: str = lhs.str; + let off: i32 = localfind(c, nm); + if (off == 0) { return; }; + // Detect str-typed local — assignment must store both + // halves (AX=ptr at +0, BX=len at +8). + let lcstr: bool = false; + let lcn: *local = localfindnode(c, nm); + if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); }; + cgexpr(c, n.rhs); + if (n.op == TK_ASSIGN) { + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + if (lcstr) { + emitline("\tMOVQ\tBX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + }; + return; + }; + if (n.op == TK_PLUSEQ) { + emitline("\tADDQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + if (n.op == TK_MINUSEQ) { + emitline("\tSUBQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + // Generic compound: load → combine in BX → store. + emitline("\tMOVQ\t"); + emitoff(off: i64); + emitline("(BP), BX\n"); + if (n.op == TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }; + if (n.op == TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }; + if (n.op == TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }; + if (n.op == TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }; + if (n.op == TK_LSHIFTEQ) { + emitline("\tMOVQ\tAX, CX\n"); + emitline("\tSHLQ\tCX, BX\n"); + }; + if (n.op == TK_RSHIFTEQ) { + emitline("\tMOVQ\tAX, CX\n"); + emitline("\tSHRQ\tCX, BX\n"); + }; + emitline("\tMOVQ\tBX, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + }; + return; + }; +}; + diff --git a/selfhost/cmd/wcc/cgen_stmt.ww b/selfhost/cmd/wcc/cgen_stmt.ww new file mode 100644 index 00000000..bee371a1 --- /dev/null +++ b/selfhost/cmd/wcc/cgen_stmt.ww @@ -0,0 +1,353 @@ +// selfhost/cmd/wcc/cgen_stmt.ww — split out of cgen.ww. +// +// Houses cgstmt — statements + control flow (N_BLOCK, N_IF, N_FOR, +// N_BREAK/N_CONTINUE, N_RETURN, N_LET, N_ASSIGN, etc.). The +// expression generator (cgexpr) lives in cgen_expr.ww; the foundation +// (types, emit primitives, collect* tables, FFI/module maps) lives in +// cgen.ww. + +use os; +use mem; +use ast; +use tok; +use typ; +use sym; +use strconv; + +// ---- statement cgen -------------------------------------------------- + +fn cgstmt(c: *cgen, n: *node) void = { + if (n == nil) { return; }; + let k: i32 = n.kind; + + if (k == N_BLOCK) { + let s: *node = n.list; + for (s != nil) { + cgstmt(c, s); + s = s.next; + }; + return; + }; + + if (k == N_RETURN) { + let rhs: *node = n.lhs; + if (rhs != nil) { + // Tuple return `return a, b;` — pack as (AX=v0, DX=v1). + // Matches C cgen: evaluate v1 first (PUSHQ), then v0 + // into AX, then POPQ DX. End state: AX = v0, DX = v1. + if (rhs.kind == N_TUPLE) { + let v: *node = rhs.list; + if (v != nil) { + let v2: *node = v.next; + if (v2 != nil) { + cgexpr(c, v2); + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, v); + emitline("\tPOPQ\tDX\n"); + } else { + cgexpr(c, v); + }; + }; + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.last_was_return = 1; + return; + }; + // Tagged-union return: pack as (AX=tag, DX=value0, CX=value1). + // For str variant, cgexpr leaves (AX=ptr, BX=len), so we + // shuffle DX←AX (ptr) and CX←BX (len), then load tag. + // For other variants, cgexpr leaves AX, shuffle DX←AX. + if (istaggedtype(c.fn_ret)) { + cgexpr(c, rhs); + let idx: i32 = taggedvariantindex(c, c.fn_ret, rhs); + if (nodeisstr(c, rhs)) { + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tMOVQ\tAX, DX\n"); + } else { + emitline("\tMOVQ\tAX, DX\n"); + }; + emitline("\tMOVQ\t$"); + if (idx < 0) { idx = 0; }; + emitint(idx: i64); + emitline(", AX\n"); + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.last_was_return = 1; + return; + }; + cgexpr(c, rhs); + } else { + // Bare `return;` in a void fn — zero AX so the caller + // sees a deterministic value (matches C cgen, which + // always falls through to `cgexpr_int(c, 0)`). + emitline("\tMOVQ\t$0, AX\n"); + }; + // SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX). + // cgexpr leaves str in (AX, BX); shuffle BX→DX. + if (isstrtype(c, c.fn_ret)) { + emitline("\tMOVQ\tBX, DX\n"); + }; + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.last_was_return = 1; + return; + }; + + if (k == N_EXPRSTMT) { + if (n.lhs != nil) { cgexpr(c, n.lhs); }; + c.last_was_return = 0; + return; + }; + + if (k == N_LET) { + let nm: str = n.str; + let sz: i32 = slotsize(c, n.lhs); + let off: i32 = localadd(c, nm, sz, n.lhs); + if (n.rhs != nil) { + let rhs: *node = n.rhs; + // Tagged-union init: `let r: (T | E) = expr;`. + // - If rhs is a CALL to a fn returning tagged-union, + // the result is already in (AX=tag, DX=v0, CX=v1); + // just spill all three. + // - Otherwise rhs is a bare variant value: pack tag + + // value(s). + if (istaggedtype(n.lhs)) { + let rhsreturnstagged: bool = false; + if (rhs.kind == N_CALL) { + let callee: *node = rhs.lhs; + if (callee != nil) { + let calleename: str; + calleename.ptr = nil; calleename.len = 0; + if (callee.kind == N_IDENT) { calleename = callee.str; }; + if (callee.kind == N_DOT) { calleename = callee.str; }; + if (calleename.len > 0) { + let rt: *node = fnretlookup(c, calleename); + if (istaggedtype(rt)) { rhsreturnstagged = true; }; + }; + }; + }; + cgexpr(c, rhs); + if (rhsreturnstagged) { + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tDX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + c.last_was_return = 0; + return; + }; + let tagidx: i32 = taggedvariantindex(c, n.lhs, rhs); + if (tagidx < 0) { tagidx = 0; }; + if (nodeisstr(c, rhs)) { + emitline("\tMOVQ\tAX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + } else { + emitline("\tMOVQ\tAX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + }; + emitline("\tMOVQ\t$"); + emitint(tagidx: i64); + emitline(", "); + emitoff(off: i64); + emitline("(BP)\n"); + c.last_was_return = 0; + return; + }; + // Struct literal init: `let p: point = point{x=..., y=...};`. + // For each field in the lit, evaluate its value and store at + // the field's offset within the slot. Field-name → offset + // from the struct registry. + if (rhs.kind == N_STRUCTLIT) { + let trefn: *node = rhs.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (trefn != nil) { + if (trefn.kind == N_IDENT) { sname = trefn.str; } + else { if (trefn.kind == N_TNAME) { sname = trefn.str; }; }; + }; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fieldnode: *node = rhs.list; + for (fieldnode != nil) { + if (fieldnode.kind == N_FIELD) { + let fname: str = fieldnode.str; + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fname)) { + cgexpr(c, fieldnode.lhs); + let sop: str = fieldstoreop(fi); + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitoff((off + fi.foff): i64); + emitline("(BP)\n"); + fi = nil; + } else { + fi = fi.finext; + }; + }; + }; + fieldnode = fieldnode.next; + }; + c.last_was_return = 0; + return; + }; + }; + cgexpr(c, rhs); + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + // str init: cgexpr also leaves len in BX; store both. + if (sz == 16) { + emitline("\tMOVQ\tBX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + }; + // slice init: ptr/len/cap in AX/BX/CX. + if (sz == 24) { + emitline("\tMOVQ\tBX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + }; + } 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. + if (typeis8byteprimitive(c, n.lhs)) { + emitline("\tMOVQ\t$0, "); + emitoff(off: i64); + emitline("(BP)\n"); + }; + }; + c.last_was_return = 0; + return; + }; + + if (k == N_IF) { + let els: str = mklabel(c, "else"); + let endl: str = mklabel(c, "end"); + cgexpr(c, n.cond); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJE\t"); + if (n.els != nil) { emitline(els); } + else { emitline(endl); }; + emitline("\n"); + if (n.body != nil) { cgstmt(c, n.body); }; + if (n.els != nil) { + emitline("\tJMP\t"); emitline(endl); emitline("\n"); + emitlabel(els); + cgstmt(c, n.els); + }; + emitlabel(endl); + c.last_was_return = 0; + return; + }; + + if (k == N_FOR) { + // Match C cgen's label scheme: _loop_N for the top, + // _endloop_N for the post-body merge. No separate cont + // label when there's no post-expression. + let topl: str = mklabel(c, "loop"); + let endl: str = mklabel(c, "endloop"); + + if (n.lhs != nil) { cgstmt(c, n.lhs); }; + + emitlabel(topl); + if (n.cond != nil) { + cgexpr(c, n.cond); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJE\t"); emitline(endl); emitline("\n"); + }; + + c.loop_end_buf[c.loop_top] = endl; + c.loop_cont_buf[c.loop_top] = topl; + c.loop_top += 1; + + if (n.body != nil) { cgstmt(c, n.body); }; + + c.loop_top -= 1; + + if (n.rhs != nil) { cgexpr(c, n.rhs); }; + emitline("\tJMP\t"); emitline(topl); emitline("\n"); + emitlabel(endl); + c.last_was_return = 0; + 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/w6c/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); }; + emitline("\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 = localfind(c, l0.str); + if (off != 0) { + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + }; + }; + }; + emitline("\tPOPQ\tDX\n"); + if (l1 != nil) { + if (l1.kind == N_IDENT) { + let off: i32 = localfind(c, l1.str); + if (off != 0) { + emitline("\tMOVQ\tDX, "); + emitoff(off: i64); + emitline("(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]; + emitline("\tJMP\t"); emitline(lbl); emitline("\n"); + }; + c.last_was_return = 0; + return; + }; + if (k == N_CONTINUE) { + if (c.loop_top > 0) { + let lbl: str = c.loop_cont_buf[c.loop_top - 1]; + emitline("\tJMP\t"); emitline(lbl); emitline("\n"); + }; + c.last_was_return = 0; + return; + }; + + c.last_was_return = 0; +}; + diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 789f56cf..c04153ad 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -3637,6 +3637,1440 @@ export fn checkfile(c: *checker, file: *node) void = { }; }; +// MODULE: wcc +// selfhost/cmd/wcc/cgen_expr.ww — split out of cgen.ww. +// +// Houses cgexpr — the per-expression code generator. The remainder of +// the cgen lives in cgen.ww (foundation: types, emit primitives, the +// collect* tables, FFI/module maps) and cgen_stmt.ww (cgstmt). +// +// `use cgen_expr;` is unnecessary at consumer sites — cgen.ww imports +// this file, so any caller of cgen transitively gets cgexpr. + +use os; +use mem; +use ast; +use tok; +use typ; +use sym; +use strconv; + +fn cgexpr(c: *cgen, n: *node) void = { + if (n == nil) { return; }; + let k: i32 = n.kind; + + if (k == N_INTLIT) { + // Print signed (i64), not unsigned (u64). C cgen uses + // `$%lld` so 64-bit constants with bit 63 set show up as + // negative — e.g. FNV-1a's offset basis prints as + // $-3750763034362895579, not $14695981039346656037. + emitline("\tMOVQ\t$"); + emitint(n.uval: i64); + emitline(", AX\n"); + return; + }; + if (k == N_RUNELIT) { + emitline("\tMOVQ\t$"); + emitint(n.uval: i64); + emitline(", AX\n"); + return; + }; + if (k == N_STRLIT) { + // Result is the (ptr, len) pair: ptr in AX, len in BX. Call + // sites that expect a str arg pick these up directly. + let nstr: str = n.str; + let lab: str = internstrlit(c, nstr); + emitline("\tLEAQ\t"); + os.write(1, lab.ptr, lab.len: u64); + emitline("(SB), AX\n"); + emitline("\tMOVQ\t$"); + emitint(nstr.len: i64); + emitline(", BX\n"); + return; + }; + if (k == N_TRUE) { + emitline("\tMOVQ\t$1, AX\n"); + return; + }; + if (k == N_FALSE) { + emitline("\tMOVQ\t$0, AX\n"); + return; + }; + if (k == N_NIL) { + emitline("\tMOVQ\t$0, AX\n"); + return; + }; + + if (k == N_IDENT) { + let nm: str = n.str; + let lc: *local = localfindnode(c, nm); + if (lc != nil) { + let off: i32 = lc.off; + emitline("\tMOVQ\t"); + emitoff(off: i64); + emitline("(BP), AX\n"); + // str local: also load the len half into BX. + if (isstrtype(c, lc.tnode)) { + emitline("\tMOVQ\t"); + emitoff((off + 8): i64); + emitline("(BP), BX\n"); + }; + // slice local: load (ptr, len, cap) into (AX, BX, CX). + if (isslicetype(c, lc.tnode)) { + emitline("\tMOVQ\t"); + emitoff((off + 8): i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\t"); + emitoff((off + 16): i64); + emitline("(BP), CX\n"); + }; + return; + }; + // Top-level `def` constant — load from its DATA symbol. + if (deflookup(c, nm)) { + emitline("\tMOVQ\t"); + emitsymname(c, nm); + emitline("(SB), AX\n"); + return; + }; + // Fn-name used as a value (e.g. `let f = some_fn;` or + // `... = some_fn;`). LEAQ the symbol address into AX. The + // emitsymname helper handles ffiresolve and module-mangling + // in one go, so a body-less FFI binding emits the C symbol + // it was declared with via @symbol(), not the ww-side ident. + let rt: *node = fnretlookup(c, nm); + if (rt != nil) { + emitline("\tLEAQ\t"); + emitsymname(c, nm); + emitline("(SB), AX\n"); + return; + }; + return; + }; + + if (k == N_INDEX) { + // Element-size-aware load: u8-element bases use MOVZBQ, + // 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 baselocal: *local = nil; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + baselocal = localfindnode(c, bn); + if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); }; + } else { if (base.kind == N_DOT) { + esz = indexbaseesz(c, base); + };}; + }; + cgexpr(c, idx); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let isarray: bool = false; + if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; + if (isarray) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + emitline("\tADDQ\tAX, BX\n"); + // str element (16B): load (ptr, len) into (AX, BX) so + // the value flows through the str-rhs convention. + if (esz == 16) { + emitline("\tMOVQ\t8(BX), CX\n"); + emitline("\tMOVQ\t(BX), AX\n"); + emitline("\tMOVQ\tCX, BX\n"); + return; + }; + if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } + else { emitline("\tMOVQ\t(BX), AX\n"); }; + return; + }; + // Generic fallback when base isn't a plain ident. + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, base); + emitline("\tPOPQ\tBX\n"); + emitline("\tADDQ\tBX, AX\n"); + if (esz == 16) { + emitline("\tMOVQ\t8(AX), BX\n"); + emitline("\tMOVQ\t(AX), AX\n"); + return; + }; + if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } + else { emitline("\tMOVQ\t(AX), AX\n"); }; + return; + }; + + if (k == N_MATCH) { + // match (e) { case let v: T => stmt; ... } + // + // Read the tagged-union slot and dispatch by tag. Slot + // layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings + // (`case let v: T =>`) get a fresh local slot loaded from + // slot+8 (and slot+16 for str-typed payload). + let scrut: *node = n.lhs; + let scrutoff: i32 = 0; + let scrutt: *node = nil; + if (scrut != nil) { + if (scrut.kind == N_IDENT) { + let lc: *local = localfindnode(c, scrut.str); + if (lc != nil) { + scrutoff = lc.off; + scrutt = resolvetype(c, lc.tnode); + }; + }; + }; + let endl: str = mklabel(c, "match_end"); + let cs: *node = n.list; + for (cs != nil) { + let nxt: str = mklabel(c, "match_next"); + let pat: *node = cs.lhs; + // Compute the variant tag for this arm. Default arm + // (no pattern) skips the tag check. + if (pat != nil) { + let want: i32 = 0; + if (scrutt != nil) { + if (scrutt.kind == N_TTAGGED) { + let patname: str; + patname.ptr = nil; patname.len = 0; + if (pat.kind == N_TNAME) { patname = pat.str; }; + let v: *node = scrutt.list; + let idx: i32 = 0; + let found: bool = false; + for (v != nil) { + if (v.kind == N_TNAME) { + if (streq(v.str, patname)) { + want = idx; + found = true; + v = nil; + }; + }; + if (v != nil) { + v = v.next; + idx += 1; + }; + }; + if (!found) { want = 0; }; + }; + }; + emitline("\tMOVQ\t"); + emitoff(scrutoff: i64); + emitline("(BP), AX\n"); + emitline("\tCMPQ\t$"); + emitint(want: i64); + emitline(", AX\n"); + emitline("\tJNE\t"); + emitline(nxt); + emitline("\n"); + }; + // Bind `let v: T` from the slot, if requested. + let bn: str = cs.str; + if (bn.len > 0) { + if (pat != nil) { + let bsz: i32 = 8; + if (isstrtype(c, pat)) { bsz = 16; }; + // localalloc (not localadd): match-arm + // binds don't dedup with same-named binds + // in *other* matches, since C's cgexpr + // allocates a fresh slot per match expr. + let voff: i32 = localalloc(c, bn, bsz, pat); + emitline("\tMOVQ\t"); + emitoff((scrutoff + 8): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(voff: i64); + emitline("(BP)\n"); + if (bsz == 16) { + emitline("\tMOVQ\t"); + emitoff((scrutoff + 16): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((voff + 8): i64); + emitline("(BP)\n"); + }; + }; + }; + // Body. Match arms are statements; we cgstmt them. + if (cs.body != nil) { cgstmt(c, cs.body); }; + emitline("\tJMP\t"); + emitline(endl); + emitline("\n"); + emitlabel(nxt); + cs = cs.next; + }; + emitlabel(endl); + return; + }; + + if (k == N_CAST) { + // Type casts are mostly no-ops at the asm level for our + // integer-shaped operands. Evaluate the source; AX holds + // the bits unchanged. (Sign- or zero-extending narrow loads + // to wider types is the loader's job, not cast's, in this + // minimal cgen.) + cgexpr(c, n.lhs); + return; + }; + + if (k == N_DOT) { + let lhs: *node = n.lhs; + let fld: str = n.str; + if (lhs != nil) { + if (lhs.kind == N_IDENT) { + let nm: str = lhs.str; + let lc: *local = localfindnode(c, nm); + if (lc != nil) { + let tn: *node = lc.tnode; + let lkind: i32 = -1; + if (tn != nil) { lkind = tn.kind; }; + // Pointer-to-struct: deref then field load. + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (inner != nil) { + if (inner.kind == N_TNAME) { + sname = inner.str; + }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + // str field via *struct: load len into a + // scratch first (so loading ptr into AX + // last leaves (AX=ptr, BX=len)). + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitdispreg((fi.foff + 8): i64, "BX"); + emitline(", CX\n"); + emitline("\tMOVQ\t"); + emitdispreg(fi.foff: i64, "BX"); + emitline(", AX\n"); + emitline("\tMOVQ\tCX, BX\n"); + } else { + let op: str = fieldloadop(fi); + emitline("\t"); + emitline(op); + emitline("\t"); + emitdispreg(fi.foff: i64, "BX"); + emitline(", AX\n"); + }; + return; + }; + fi = fi.finext; + }; + }; + }; + }; + // Direct struct local: field load at off+foff. + if (lkind == N_TNAME) { + let sname: str = tn.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + // str field: load both halves so chained + // `.ptr` / `.len` see (AX=ptr, BX=len). + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitoff((lc.off + fi.foff): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\t"); + emitoff((lc.off + fi.foff + 8): i64); + emitline("(BP), BX\n"); + } else { + let op: str = fieldloadop(fi); + emitline("\t"); + emitline(op); + emitline("\t"); + emitoff((lc.off + fi.foff): i64); + emitline("(BP), AX\n"); + }; + return; + }; + fi = fi.finext; + }; + }; + }; + // Array pseudo-fields: `.ptr` is the array's + // address (LEAQ); `.len` is the static element + // count (immediate). + if (lkind == N_TARRAY) { + if (streq(fld, "ptr")) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), AX\n"); + return; + }; + if (streq(fld, "len")) { + let lenn: *node = tn.rhs; + let alen: i64 = 0i64; + if (lenn != nil) { + if (lenn.kind == N_INTLIT) { alen = lenn.uval: i64; }; + }; + emitline("\tMOVQ\t$"); + emitint(alen); + emitline(", AX\n"); + return; + }; + }; + // str/slice pseudo-fields .ptr/.len/.cap on a + // direct local: load at slot+delta. + let delta: i32 = -1; + if (streq(fld, "ptr")) { delta = 0; }; + if (streq(fld, "len")) { delta = 8; }; + if (streq(fld, "cap")) { delta = 16; }; + if (delta >= 0) { + // Pointer to str/slice (`*[]u8`, `*str`): + // deref, then load at delta within the + // pointed-to header. C cgen does the same. + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let innerkind: i32 = -1; + if (inner != nil) { innerkind = inner.kind; }; + let innerstr: bool = false; + if (innerkind == N_TNAME) { + if (streq(inner.str, "str")) { innerstr = true; }; + }; + if (innerkind == N_TSLICE) { innerstr = true; }; + if (innerstr) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(delta: i64, "BX"); + emitline(", AX\n"); + return; + }; + }; + emitline("\tMOVQ\t"); + emitoff((lc.off + delta): i64); + emitline("(BP), AX\n"); + return; + }; + }; + }; + }; + // Module-qualified value reference: `mod.name` where `mod` + // is N_IDENT bound as SK_USE and the leaf isn't a local. + // Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback + // the C cgen takes when bt is NULL/tyerr. + if (lhs != nil) { + if (lhs.kind == N_IDENT) { + emitline("\tMOVQ\t"); + emitsymname(c, fld); + emitline("(SB), AX\n"); + return; + }; + }; + // Non-ident base pseudo-field: e.g. `"abc".ptr` / `"abc".len`. + // Evaluate the str-producing expression — that leaves + // (AX=ptr, BX=len). Then `.ptr` returns AX as is; `.len` + // shuffles BX→AX. Mirrors what C cgen does (it just evaluates + // the literal and picks the half it wants). + if (streq(fld, "ptr")) { cgexpr(c, lhs); return; }; + if (streq(fld, "len")) { + cgexpr(c, lhs); + emitline("\tMOVQ\tBX, AX\n"); + return; + }; + // Chained struct-field-via-ptr-via-ptr access: + // r.sym.val where r: *lrel, .sym: *lsym, .val: u64 + // Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct + // field). Outer DOT dereferences and reads `val`. Without this + // path the cgen falls through and AX retains whatever the + // inner expression left there — typically the *struct pointer + // itself, so reads silently get the pointer value instead of + // the field. (Showed up porting w6l/pass.ww.) + if (lhs != nil) { + if (lhs.kind == N_DOT) { + let innert: *node = dotinnerstructptr(c, lhs); + if (innert != nil) { + let sname: str = innert.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + cgexpr(c, lhs); // AX = ptr to inner struct + let lop: str = fieldloadop(fi); + // str field: load both halves. + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitdispreg((fi.foff + 8): i64, "AX"); + emitline(", BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", AX\n"); + return; + }; + emitline("\t"); + emitline(lop); + emitline("\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", AX\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + }; + }; + return; + }; + + if (k == N_UN) { + // Match C cgen ordering: evaluate operand first (load into AX), + // then apply the unary op. AMP / STAR override AX with the + // address / deref. The wasted load before AMP keeps our asm + // byte-identical to the C version. + cgexpr(c, n.lhs); + if (n.op == TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; + if (n.op == TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; + if (n.op == TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; + if (n.op == TK_AMP) { + let opnd: *node = n.lhs; + if (opnd != nil) { + if (opnd.kind == N_IDENT) { + let nm: str = opnd.str; + let off: i32 = localfind(c, nm); + if (off != 0) { + emitline("\tLEAQ\t"); + emitoff(off: i64); + emitline("(BP), AX\n"); + return; + }; + }; + }; + return; + }; + if (n.op == TK_NOT) { + let t: str = mklabel(c, "tt"); + let e: str = mklabel(c, "te"); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJE\t"); emitline(t); emitline("\n"); + emitline("\tMOVQ\t$0, AX\n"); + emitline("\tJMP\t"); emitline(e); emitline("\n"); + emitlabel(t); + emitline("\tMOVQ\t$1, AX\n"); + emitlabel(e); + return; + }; + return; + }; + + if (k == N_BIN) { + let unsignd: bool = nodeisunsigned(c, n.lhs); + if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; + + cgexpr(c, n.rhs); + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, n.lhs); + emitline("\tPOPQ\tBX\n"); + if (n.op == TK_PLUS) { emitline("\tADDQ\tBX, AX\n"); return; }; + if (n.op == TK_MINUS) { emitline("\tSUBQ\tBX, AX\n"); return; }; + if (n.op == TK_STAR) { emitline("\tIMULQ\tBX, AX\n"); return; }; + if (n.op == TK_SLASH) { + emitline("\tMOVQ\t$0, DX\n"); + if (unsignd) { emitline("\tDIVQ\tBX\n"); } + else { emitline("\tIDIVQ\tBX\n"); }; + return; + }; + if (n.op == TK_PERCENT) { + emitline("\tMOVQ\t$0, DX\n"); + if (unsignd) { emitline("\tDIVQ\tBX\n"); } + else { emitline("\tIDIVQ\tBX\n"); }; + emitline("\tMOVQ\tDX, AX\n"); + return; + }; + if (n.op == TK_AMP) { emitline("\tANDQ\tBX, AX\n"); return; }; + if (n.op == TK_PIPE) { emitline("\tORQ\tBX, AX\n"); return; }; + if (n.op == TK_CARET) { emitline("\tXORQ\tBX, AX\n"); return; }; + if (n.op == TK_LSHIFT) { + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tSHLQ\tCX, AX\n"); + return; + }; + if (n.op == TK_RSHIFT) { + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tSHRQ\tCX, AX\n"); + return; + }; + if (n.op == TK_AND) { emitline("\tANDQ\tBX, AX\n"); return; }; + if (n.op == TK_OR) { emitline("\tORQ\tBX, AX\n"); return; }; + + // Comparison: emit CMPQ, jump on signed/unsigned variant, + // materialise 0/1 in AX. Same shape as the C cgen. + let iscmp: bool = false; + let jcc: str = ""; + if (n.op == TK_EQ) { iscmp = true; jcc = "JE"; }; + if (n.op == TK_NEQ) { iscmp = true; jcc = "JNE"; }; + if (n.op == TK_LT) { iscmp = true; if (unsignd) { jcc = "JB"; } else { jcc = "JL"; }; }; + if (n.op == TK_LE) { iscmp = true; if (unsignd) { jcc = "JBE"; } else { jcc = "JLE"; }; }; + if (n.op == TK_GT) { iscmp = true; if (unsignd) { jcc = "JA"; } else { jcc = "JG"; }; }; + if (n.op == TK_GE) { iscmp = true; if (unsignd) { jcc = "JAE"; } else { jcc = "JGE"; }; }; + if (iscmp) { + let t: str = mklabel(c, "ct"); + let e: str = mklabel(c, "ce"); + emitline("\tCMPQ\tBX, AX\n"); + emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n"); + emitline("\tMOVQ\t$0, AX\n"); + emitline("\tJMP\t"); emitline(e); emitline("\n"); + emitlabel(t); + emitline("\tMOVQ\t$1, AX\n"); + emitlabel(e); + return; + }; + return; + }; + + if (k == N_CALL) { + let nargs: i32 = pushargsrev(c, n.list); + let i: i32 = 0; + for (i < nargs) { + emitline("\tPOPQ\t"); + emitline(argregname(i)); + emitline("\n"); + i += 1; + }; + let callee: *node = n.lhs; + let calleename: str; + calleename.ptr = nil; calleename.len = 0; + // Detect fn-pointer field call: `w.emit(args)` where `w` is + // a struct local and `emit` is an N_TFN field. Load the + // field value into AX and CALL through it. Also detect a + // bare `fp(args)` where `fp` is a local holding a function + // pointer — mirror C cgen's localfind dispatch (commit + // 635818e). Without this the call emits `CALL fp(SB)` and + // the linker rightly fails. + let isfnptrcall: bool = false; + if (callee != nil) { + if (callee.kind == N_IDENT) { + let cn: str = callee.str; + if (localfindnode(c, cn) != nil) { + isfnptrcall = true; + }; + }; + if (callee.kind == N_DOT) { + let base: *node = callee.lhs; + let fld: str = callee.str; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + let lc: *local = localfindnode(c, bn); + if (lc != nil) { + let tn: *node = lc.tnode; + if (tn != nil) { + let lkind: i32 = 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; + if (inner != nil) { + if (inner.kind == N_TNAME) { sname = inner.str; }; + }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + let ft: *node = fi.tnode; + if (ft != nil) { + if (ft.kind == N_TFN) { + isfnptrcall = true; + }; + }; + fi = nil; + } else { + fi = fi.finext; + }; + }; + }; + }; + }; + }; + }; + }; + }; + }; + if (isfnptrcall) { + // Load fn-ptr field value into AX; CALL AX. We emit the + // load AFTER the args have been popped (so AX/BX/etc + // don't get clobbered by the field load before the pops). + // `popped args` left DI/SI/etc set; AX is free. + cgexpr(c, callee); + emitline("\tCALL\tAX\n"); + } else { + emitline("\tCALL\t"); + if (callee != nil) { + if (callee.kind == N_IDENT) { + calleename = callee.str; + emitsymname(c, calleename); + } else { if (callee.kind == N_DOT) { + calleename = callee.str; + emitsymname(c, calleename); + };}; + }; + emitline("(SB)\n"); + }; + // SysV returns 16-byte aggregates in (AX, DX). Our str + // convention is (AX, BX), so shuffle for str-returning calls. + if (calleename.len > 0) { + let rt: *node = fnretlookup(c, calleename); + if (isstrtype(c, rt)) { + emitline("\tMOVQ\tDX, BX\n"); + }; + }; + return; + }; + + if (k == N_ASSIGN) { + let lhs: *node = n.lhs; + // `*p = v` — deref-assign. Element width comes from the + // pointer's declared type. Mirrors C cgen: eval rhs (AX, + // and BX if str), push, eval pointer, pop value, store. + // We default to MOVQ (8B) since most fixtures use it; for + // `*bool` / `*u8` / `*i32` we narrow via the local's tnode. + if (lhs != nil) { + if (lhs.kind == N_UN) { + if (lhs.op == TK_STAR) { + if (n.op == TK_ASSIGN) { + let inner: *node = lhs.lhs; + let elemstr: bool = false; + let storeop: str = "MOVQ"; + if (inner != nil) { + if (inner.kind == N_IDENT) { + let lc: *local = localfindnode(c, inner.str); + if (lc != nil) { + let tn: *node = lc.tnode; + if (tn != nil) { + if (tn.kind == N_TPTR) { + let pe: *node = tn.lhs; + if (pe != nil) { + if (pe.kind == N_TNAME) { + if (streq(pe.str, "str")) { elemstr = true; } + else { + let ps: i32 = primsize(pe.str); + if (ps == 1) { storeop = "MOVB"; } + else { if (ps == 4) { storeop = "MOVL"; }; }; + }; + }; + }; + }; + }; + }; + }; + }; + cgexpr(c, n.rhs); + // Push order matches C cgen + // (cmd/w6c/cgen.c:1033-1041): PUSHQ AX + // (ptr) first, then PUSHQ BX (len) if + // str, so the pop sequence is POP CX + // (len) → POP AX (ptr) → MOVQ AX, + // (BX) → MOVQ CX, 8(BX). + emitline("\tPUSHQ\tAX\n"); + if (elemstr) { emitline("\tPUSHQ\tBX\n"); }; + cgexpr(c, inner); + emitline("\tMOVQ\tAX, BX\n"); + if (elemstr) { + emitline("\tPOPQ\tCX\n"); + emitline("\tPOPQ\tAX\n"); + emitline("\tMOVQ\tAX, (BX)\n"); + emitline("\tMOVQ\tCX, 8(BX)\n"); + return; + }; + emitline("\tPOPQ\tAX\n"); + emitline("\t"); + emitline(storeop); + emitline("\tAX, (BX)\n"); + return; + }; + }; + }; + }; + // Array/slice/ptr index store: `arr[i] = v;`. Element size + // from base.tnode picks MOVB vs MOVQ. + if (lhs != nil) { + if (lhs.kind == N_INDEX) { + if (n.op == TK_ASSIGN) { + let base: *node = lhs.lhs; + let idx: *node = lhs.rhs; + let esz: i32 = 8; + let baselocal: *local = nil; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + baselocal = localfindnode(c, bn); + if (baselocal != nil) { + esz = elemsizeof(baselocal.tnode); + }; + } else { if (base.kind == N_DOT) { + esz = indexbaseesz(c, base); + };}; + }; + cgexpr(c, n.rhs); // value → AX + if (esz == 16) { emitline("\tPUSHQ\tBX\n"); }; + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, idx); // idx → AX + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tPUSHQ\tAX\n"); // scaled idx + if (baselocal != nil) { + let tn: *node = baselocal.tnode; + let isarray: bool = false; + if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; + if (isarray) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + } else { + cgexpr(c, base); + emitline("\tMOVQ\tAX, BX\n"); + }; + emitline("\tPOPQ\tAX\n"); // scaled idx + emitline("\tADDQ\tAX, BX\n"); + emitline("\tPOPQ\tAX\n"); // value + if (esz == 16) { + emitline("\tMOVQ\tAX, (BX)\n"); + emitline("\tPOPQ\tCX\n"); + emitline("\tMOVQ\tCX, 8(BX)\n"); + return; + }; + if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } + else { emitline("\tMOVQ\tAX, (BX)\n"); }; + return; + }; + }; + }; + // Struct/ptr-to-struct field assignment: `s.f = expr;` or + // `p.f = expr;`. Only plain `=` is wired (compound on field + // is rare and not yet needed by our fixtures). + if (lhs != nil) { + if (lhs.kind == N_DOT) { + let base: *node = lhs.lhs; + let fld: str = lhs.str; + if (base != nil) { + if (base.kind == N_IDENT) { + let bn: str = base.str; + let lc: *local = localfindnode(c, bn); + if (lc != nil) { + let tn: *node = lc.tnode; + let lkind: i32 = -1; + if (tn != nil) { lkind = tn.kind; }; + // Pointer-to-struct: deref then store. + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (inner != nil) { + if (inner.kind == N_TNAME) { sname = inner.str; }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + if (n.op != TK_ASSIGN) { + // compound: load current value + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + let lop: str = fieldloadop(fi); + emitline("\t"); + emitline(lop); + emitline("\t"); + emitdispreg(fi.foff: i64, "BX"); + emitline(", BX\n"); + emitline("\tPUSHQ\tBX\n"); + }; + cgexpr(c, n.rhs); + if (n.op != TK_ASSIGN) { + emitline("\tPOPQ\tBX\n"); + // PLUSEQ is commutative; MINUSEQ + // needs lhs - rhs (BX is old lhs, + // AX is rhs). + if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; + if (n.op == TK_MINUSEQ) { + emitline("\tSUBQ\tAX, BX\n"); + emitline("\tMOVQ\tBX, AX\n"); + }; + }; + // str field via *struct: rhs left + // (AX=ptr, BX=len). Use CX as the + // address scratch so we don't clobber + // the len half before storing it. + if (n.op == TK_ASSIGN) { + if (isstrtype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), CX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(fi.foff: i64, "CX"); + emitline("\n"); + emitline("\tMOVQ\tBX, "); + emitdispreg((fi.foff + 8): i64, "CX"); + emitline("\n"); + return; + }; + }; + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + let sop: str = fieldstoreop(fi); + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitdispreg(fi.foff: i64, "BX"); + emitline("\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + }; + // Direct struct local: store at off+foff. + if (lkind == N_TNAME) { + let sname: str = tn.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fld)) { + cgexpr(c, n.rhs); + let sop: str = fieldstoreop(fi); + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitoff((lc.off + fi.foff): i64); + emitline("(BP)\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + // str/slice pseudo-field assignment. + let delta: i32 = -1; + if (streq(fld, "ptr")) { delta = 0; }; + if (streq(fld, "len")) { delta = 8; }; + if (streq(fld, "cap")) { delta = 16; }; + if (delta >= 0) { + if (lkind == N_TPTR) { + let inner: *node = tn.lhs; + let innerkind: i32 = -1; + if (inner != nil) { innerkind = inner.kind; }; + let innerstr: bool = false; + if (innerkind == N_TNAME) { + if (streq(inner.str, "str")) { innerstr = true; }; + }; + if (innerkind == N_TSLICE) { innerstr = true; }; + if (innerstr) { + if (n.op != TK_ASSIGN) { + // Compound on `(*str|*slice).field`: load + // current → push → eval rhs → combine → store. + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\t"); + emitdispreg(delta: i64, "BX"); + emitline(", BX\n"); + emitline("\tPUSHQ\tBX\n"); + cgexpr(c, n.rhs); + emitline("\tPOPQ\tBX\n"); + // PLUSEQ is commutative; MINUSEQ + // needs lhs - rhs. + if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; + if (n.op == TK_MINUSEQ) { + emitline("\tSUBQ\tAX, BX\n"); + emitline("\tMOVQ\tBX, AX\n"); + }; + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(delta: i64, "BX"); + emitline("\n"); + return; + }; + cgexpr(c, n.rhs); + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), BX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(delta: i64, "BX"); + emitline("\n"); + return; + }; + }; + cgexpr(c, n.rhs); + emitline("\tMOVQ\tAX, "); + emitoff((lc.off + delta): i64); + emitline("(BP)\n"); + return; + }; + }; + }; + }; + }; + }; + // Local-ident target — plain `=` and the simple compound + // forms (+= -= *= /=); other compounds fall back to + // "evaluate rhs, replace". Mirrors C cgen's IDENT-assign path. + if (lhs != nil) { + if (lhs.kind == N_IDENT) { + let nm: str = lhs.str; + let off: i32 = localfind(c, nm); + if (off == 0) { return; }; + // Detect str-typed local — assignment must store both + // halves (AX=ptr at +0, BX=len at +8). + let lcstr: bool = false; + let lcn: *local = localfindnode(c, nm); + if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); }; + cgexpr(c, n.rhs); + if (n.op == TK_ASSIGN) { + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + if (lcstr) { + emitline("\tMOVQ\tBX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + }; + return; + }; + if (n.op == TK_PLUSEQ) { + emitline("\tADDQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + if (n.op == TK_MINUSEQ) { + emitline("\tSUBQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + // Generic compound: load → combine in BX → store. + emitline("\tMOVQ\t"); + emitoff(off: i64); + emitline("(BP), BX\n"); + if (n.op == TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }; + if (n.op == TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }; + if (n.op == TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }; + if (n.op == TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }; + if (n.op == TK_LSHIFTEQ) { + emitline("\tMOVQ\tAX, CX\n"); + emitline("\tSHLQ\tCX, BX\n"); + }; + if (n.op == TK_RSHIFTEQ) { + emitline("\tMOVQ\tAX, CX\n"); + emitline("\tSHRQ\tCX, BX\n"); + }; + emitline("\tMOVQ\tBX, "); + emitoff(off: i64); + emitline("(BP)\n"); + return; + }; + }; + return; + }; +}; + + +// MODULE: wcc +// selfhost/cmd/wcc/cgen_stmt.ww — split out of cgen.ww. +// +// Houses cgstmt — statements + control flow (N_BLOCK, N_IF, N_FOR, +// N_BREAK/N_CONTINUE, N_RETURN, N_LET, N_ASSIGN, etc.). The +// expression generator (cgexpr) lives in cgen_expr.ww; the foundation +// (types, emit primitives, collect* tables, FFI/module maps) lives in +// cgen.ww. + +use os; +use mem; +use ast; +use tok; +use typ; +use sym; +use strconv; + +// ---- statement cgen -------------------------------------------------- + +fn cgstmt(c: *cgen, n: *node) void = { + if (n == nil) { return; }; + let k: i32 = n.kind; + + if (k == N_BLOCK) { + let s: *node = n.list; + for (s != nil) { + cgstmt(c, s); + s = s.next; + }; + return; + }; + + if (k == N_RETURN) { + let rhs: *node = n.lhs; + if (rhs != nil) { + // Tuple return `return a, b;` — pack as (AX=v0, DX=v1). + // Matches C cgen: evaluate v1 first (PUSHQ), then v0 + // into AX, then POPQ DX. End state: AX = v0, DX = v1. + if (rhs.kind == N_TUPLE) { + let v: *node = rhs.list; + if (v != nil) { + let v2: *node = v.next; + if (v2 != nil) { + cgexpr(c, v2); + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, v); + emitline("\tPOPQ\tDX\n"); + } else { + cgexpr(c, v); + }; + }; + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.last_was_return = 1; + return; + }; + // Tagged-union return: pack as (AX=tag, DX=value0, CX=value1). + // For str variant, cgexpr leaves (AX=ptr, BX=len), so we + // shuffle DX←AX (ptr) and CX←BX (len), then load tag. + // For other variants, cgexpr leaves AX, shuffle DX←AX. + if (istaggedtype(c.fn_ret)) { + cgexpr(c, rhs); + let idx: i32 = taggedvariantindex(c, c.fn_ret, rhs); + if (nodeisstr(c, rhs)) { + emitline("\tMOVQ\tBX, CX\n"); + emitline("\tMOVQ\tAX, DX\n"); + } else { + emitline("\tMOVQ\tAX, DX\n"); + }; + emitline("\tMOVQ\t$"); + if (idx < 0) { idx = 0; }; + emitint(idx: i64); + emitline(", AX\n"); + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.last_was_return = 1; + return; + }; + cgexpr(c, rhs); + } else { + // Bare `return;` in a void fn — zero AX so the caller + // sees a deterministic value (matches C cgen, which + // always falls through to `cgexpr_int(c, 0)`). + emitline("\tMOVQ\t$0, AX\n"); + }; + // SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX). + // cgexpr leaves str in (AX, BX); shuffle BX→DX. + if (isstrtype(c, c.fn_ret)) { + emitline("\tMOVQ\tBX, DX\n"); + }; + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.last_was_return = 1; + return; + }; + + if (k == N_EXPRSTMT) { + if (n.lhs != nil) { cgexpr(c, n.lhs); }; + c.last_was_return = 0; + return; + }; + + if (k == N_LET) { + let nm: str = n.str; + let sz: i32 = slotsize(c, n.lhs); + let off: i32 = localadd(c, nm, sz, n.lhs); + if (n.rhs != nil) { + let rhs: *node = n.rhs; + // Tagged-union init: `let r: (T | E) = expr;`. + // - If rhs is a CALL to a fn returning tagged-union, + // the result is already in (AX=tag, DX=v0, CX=v1); + // just spill all three. + // - Otherwise rhs is a bare variant value: pack tag + + // value(s). + if (istaggedtype(n.lhs)) { + let rhsreturnstagged: bool = false; + if (rhs.kind == N_CALL) { + let callee: *node = rhs.lhs; + if (callee != nil) { + let calleename: str; + calleename.ptr = nil; calleename.len = 0; + if (callee.kind == N_IDENT) { calleename = callee.str; }; + if (callee.kind == N_DOT) { calleename = callee.str; }; + if (calleename.len > 0) { + let rt: *node = fnretlookup(c, calleename); + if (istaggedtype(rt)) { rhsreturnstagged = true; }; + }; + }; + }; + cgexpr(c, rhs); + if (rhsreturnstagged) { + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tDX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + c.last_was_return = 0; + return; + }; + let tagidx: i32 = taggedvariantindex(c, n.lhs, rhs); + if (tagidx < 0) { tagidx = 0; }; + if (nodeisstr(c, rhs)) { + emitline("\tMOVQ\tAX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + } else { + emitline("\tMOVQ\tAX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + }; + emitline("\tMOVQ\t$"); + emitint(tagidx: i64); + emitline(", "); + emitoff(off: i64); + emitline("(BP)\n"); + c.last_was_return = 0; + return; + }; + // Struct literal init: `let p: point = point{x=..., y=...};`. + // For each field in the lit, evaluate its value and store at + // the field's offset within the slot. Field-name → offset + // from the struct registry. + if (rhs.kind == N_STRUCTLIT) { + let trefn: *node = rhs.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (trefn != nil) { + if (trefn.kind == N_IDENT) { sname = trefn.str; } + else { if (trefn.kind == N_TNAME) { sname = trefn.str; }; }; + }; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fieldnode: *node = rhs.list; + for (fieldnode != nil) { + if (fieldnode.kind == N_FIELD) { + let fname: str = fieldnode.str; + let fi: *fieldinfo = si.fields; + for (fi != nil) { + let fn_: str = fi.fname; + if (streq(fn_, fname)) { + cgexpr(c, fieldnode.lhs); + let sop: str = fieldstoreop(fi); + emitline("\t"); + emitline(sop); + emitline("\tAX, "); + emitoff((off + fi.foff): i64); + emitline("(BP)\n"); + fi = nil; + } else { + fi = fi.finext; + }; + }; + }; + fieldnode = fieldnode.next; + }; + c.last_was_return = 0; + return; + }; + }; + cgexpr(c, rhs); + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + // str init: cgexpr also leaves len in BX; store both. + if (sz == 16) { + emitline("\tMOVQ\tBX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + }; + // slice init: ptr/len/cap in AX/BX/CX. + if (sz == 24) { + emitline("\tMOVQ\tBX, "); + emitoff((off + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((off + 16): i64); + emitline("(BP)\n"); + }; + } 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. + if (typeis8byteprimitive(c, n.lhs)) { + emitline("\tMOVQ\t$0, "); + emitoff(off: i64); + emitline("(BP)\n"); + }; + }; + c.last_was_return = 0; + return; + }; + + if (k == N_IF) { + let els: str = mklabel(c, "else"); + let endl: str = mklabel(c, "end"); + cgexpr(c, n.cond); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJE\t"); + if (n.els != nil) { emitline(els); } + else { emitline(endl); }; + emitline("\n"); + if (n.body != nil) { cgstmt(c, n.body); }; + if (n.els != nil) { + emitline("\tJMP\t"); emitline(endl); emitline("\n"); + emitlabel(els); + cgstmt(c, n.els); + }; + emitlabel(endl); + c.last_was_return = 0; + return; + }; + + if (k == N_FOR) { + // Match C cgen's label scheme: _loop_N for the top, + // _endloop_N for the post-body merge. No separate cont + // label when there's no post-expression. + let topl: str = mklabel(c, "loop"); + let endl: str = mklabel(c, "endloop"); + + if (n.lhs != nil) { cgstmt(c, n.lhs); }; + + emitlabel(topl); + if (n.cond != nil) { + cgexpr(c, n.cond); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJE\t"); emitline(endl); emitline("\n"); + }; + + c.loop_end_buf[c.loop_top] = endl; + c.loop_cont_buf[c.loop_top] = topl; + c.loop_top += 1; + + if (n.body != nil) { cgstmt(c, n.body); }; + + c.loop_top -= 1; + + if (n.rhs != nil) { cgexpr(c, n.rhs); }; + emitline("\tJMP\t"); emitline(topl); emitline("\n"); + emitlabel(endl); + c.last_was_return = 0; + 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/w6c/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); }; + emitline("\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 = localfind(c, l0.str); + if (off != 0) { + emitline("\tMOVQ\tAX, "); + emitoff(off: i64); + emitline("(BP)\n"); + }; + }; + }; + emitline("\tPOPQ\tDX\n"); + if (l1 != nil) { + if (l1.kind == N_IDENT) { + let off: i32 = localfind(c, l1.str); + if (off != 0) { + emitline("\tMOVQ\tDX, "); + emitoff(off: i64); + emitline("(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]; + emitline("\tJMP\t"); emitline(lbl); emitline("\n"); + }; + c.last_was_return = 0; + return; + }; + if (k == N_CONTINUE) { + if (c.loop_top > 0) { + let lbl: str = c.loop_cont_buf[c.loop_top - 1]; + emitline("\tJMP\t"); emitline(lbl); emitline("\n"); + }; + c.last_was_return = 0; + return; + }; + + c.last_was_return = 0; +}; + + // MODULE: wcc // selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c. // @@ -3669,6 +5103,10 @@ use tok; use typ; use sym; use strconv; +// Split files. Bundler pulls these in transitively so consumers only +// need `use cgen;`. +use cgen_expr; +use cgen_stmt; // ---- typedef alias registry ----------------------------------------- // @@ -4936,1066 +6374,6 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = { return false; }; -fn cgexpr(c: *cgen, n: *node) void = { - if (n == nil) { return; }; - let k: i32 = n.kind; - - if (k == N_INTLIT) { - // Print signed (i64), not unsigned (u64). C cgen uses - // `$%lld` so 64-bit constants with bit 63 set show up as - // negative — e.g. FNV-1a's offset basis prints as - // $-3750763034362895579, not $14695981039346656037. - emitline("\tMOVQ\t$"); - emitint(n.uval: i64); - emitline(", AX\n"); - return; - }; - if (k == N_RUNELIT) { - emitline("\tMOVQ\t$"); - emitint(n.uval: i64); - emitline(", AX\n"); - return; - }; - if (k == N_STRLIT) { - // Result is the (ptr, len) pair: ptr in AX, len in BX. Call - // sites that expect a str arg pick these up directly. - let nstr: str = n.str; - let lab: str = internstrlit(c, nstr); - emitline("\tLEAQ\t"); - os.write(1, lab.ptr, lab.len: u64); - emitline("(SB), AX\n"); - emitline("\tMOVQ\t$"); - emitint(nstr.len: i64); - emitline(", BX\n"); - return; - }; - if (k == N_TRUE) { - emitline("\tMOVQ\t$1, AX\n"); - return; - }; - if (k == N_FALSE) { - emitline("\tMOVQ\t$0, AX\n"); - return; - }; - if (k == N_NIL) { - emitline("\tMOVQ\t$0, AX\n"); - return; - }; - - if (k == N_IDENT) { - let nm: str = n.str; - let lc: *local = localfindnode(c, nm); - if (lc != nil) { - let off: i32 = lc.off; - emitline("\tMOVQ\t"); - emitoff(off: i64); - emitline("(BP), AX\n"); - // str local: also load the len half into BX. - if (isstrtype(c, lc.tnode)) { - emitline("\tMOVQ\t"); - emitoff((off + 8): i64); - emitline("(BP), BX\n"); - }; - // slice local: load (ptr, len, cap) into (AX, BX, CX). - if (isslicetype(c, lc.tnode)) { - emitline("\tMOVQ\t"); - emitoff((off + 8): i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\t"); - emitoff((off + 16): i64); - emitline("(BP), CX\n"); - }; - return; - }; - // Top-level `def` constant — load from its DATA symbol. - if (deflookup(c, nm)) { - emitline("\tMOVQ\t"); - emitsymname(c, nm); - emitline("(SB), AX\n"); - return; - }; - // Fn-name used as a value (e.g. `let f = some_fn;` or - // `... = some_fn;`). LEAQ the symbol address into AX. The - // emitsymname helper handles ffiresolve and module-mangling - // in one go, so a body-less FFI binding emits the C symbol - // it was declared with via @symbol(), not the ww-side ident. - let rt: *node = fnretlookup(c, nm); - if (rt != nil) { - emitline("\tLEAQ\t"); - emitsymname(c, nm); - emitline("(SB), AX\n"); - return; - }; - return; - }; - - if (k == N_INDEX) { - // Element-size-aware load: u8-element bases use MOVZBQ, - // 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 baselocal: *local = nil; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - baselocal = localfindnode(c, bn); - if (baselocal != nil) { esz = elemsizeof(baselocal.tnode); }; - } else { if (base.kind == N_DOT) { - esz = indexbaseesz(c, base); - };}; - }; - cgexpr(c, idx); - if (esz > 1) { - emitline("\tMOVQ\t$"); - emitint(esz: i64); - emitline(", CX\n"); - emitline("\tIMULQ\tCX, AX\n"); - }; - if (baselocal != nil) { - let tn: *node = baselocal.tnode; - let isarray: bool = false; - if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; - if (isarray) { - emitline("\tLEAQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - }; - emitline("\tADDQ\tAX, BX\n"); - // str element (16B): load (ptr, len) into (AX, BX) so - // the value flows through the str-rhs convention. - if (esz == 16) { - emitline("\tMOVQ\t8(BX), CX\n"); - emitline("\tMOVQ\t(BX), AX\n"); - emitline("\tMOVQ\tCX, BX\n"); - return; - }; - if (esz == 1) { emitline("\tMOVZBQ\t(BX), AX\n"); } - else { emitline("\tMOVQ\t(BX), AX\n"); }; - return; - }; - // Generic fallback when base isn't a plain ident. - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, base); - emitline("\tPOPQ\tBX\n"); - emitline("\tADDQ\tBX, AX\n"); - if (esz == 16) { - emitline("\tMOVQ\t8(AX), BX\n"); - emitline("\tMOVQ\t(AX), AX\n"); - return; - }; - if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } - else { emitline("\tMOVQ\t(AX), AX\n"); }; - return; - }; - - if (k == N_MATCH) { - // match (e) { case let v: T => stmt; ... } - // - // Read the tagged-union slot and dispatch by tag. Slot - // layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings - // (`case let v: T =>`) get a fresh local slot loaded from - // slot+8 (and slot+16 for str-typed payload). - let scrut: *node = n.lhs; - let scrutoff: i32 = 0; - let scrutt: *node = nil; - if (scrut != nil) { - if (scrut.kind == N_IDENT) { - let lc: *local = localfindnode(c, scrut.str); - if (lc != nil) { - scrutoff = lc.off; - scrutt = resolvetype(c, lc.tnode); - }; - }; - }; - let endl: str = mklabel(c, "match_end"); - let cs: *node = n.list; - for (cs != nil) { - let nxt: str = mklabel(c, "match_next"); - let pat: *node = cs.lhs; - // Compute the variant tag for this arm. Default arm - // (no pattern) skips the tag check. - if (pat != nil) { - let want: i32 = 0; - if (scrutt != nil) { - if (scrutt.kind == N_TTAGGED) { - let patname: str; - patname.ptr = nil; patname.len = 0; - if (pat.kind == N_TNAME) { patname = pat.str; }; - let v: *node = scrutt.list; - let idx: i32 = 0; - let found: bool = false; - for (v != nil) { - if (v.kind == N_TNAME) { - if (streq(v.str, patname)) { - want = idx; - found = true; - v = nil; - }; - }; - if (v != nil) { - v = v.next; - idx += 1; - }; - }; - if (!found) { want = 0; }; - }; - }; - emitline("\tMOVQ\t"); - emitoff(scrutoff: i64); - emitline("(BP), AX\n"); - emitline("\tCMPQ\t$"); - emitint(want: i64); - emitline(", AX\n"); - emitline("\tJNE\t"); - emitline(nxt); - emitline("\n"); - }; - // Bind `let v: T` from the slot, if requested. - let bn: str = cs.str; - if (bn.len > 0) { - if (pat != nil) { - let bsz: i32 = 8; - if (isstrtype(c, pat)) { bsz = 16; }; - // localalloc (not localadd): match-arm - // binds don't dedup with same-named binds - // in *other* matches, since C's cgexpr - // allocates a fresh slot per match expr. - let voff: i32 = localalloc(c, bn, bsz, pat); - emitline("\tMOVQ\t"); - emitoff((scrutoff + 8): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff(voff: i64); - emitline("(BP)\n"); - if (bsz == 16) { - emitline("\tMOVQ\t"); - emitoff((scrutoff + 16): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff((voff + 8): i64); - emitline("(BP)\n"); - }; - }; - }; - // Body. Match arms are statements; we cgstmt them. - if (cs.body != nil) { cgstmt(c, cs.body); }; - emitline("\tJMP\t"); - emitline(endl); - emitline("\n"); - emitlabel(nxt); - cs = cs.next; - }; - emitlabel(endl); - return; - }; - - if (k == N_CAST) { - // Type casts are mostly no-ops at the asm level for our - // integer-shaped operands. Evaluate the source; AX holds - // the bits unchanged. (Sign- or zero-extending narrow loads - // to wider types is the loader's job, not cast's, in this - // minimal cgen.) - cgexpr(c, n.lhs); - return; - }; - - if (k == N_DOT) { - let lhs: *node = n.lhs; - let fld: str = n.str; - if (lhs != nil) { - if (lhs.kind == N_IDENT) { - let nm: str = lhs.str; - let lc: *local = localfindnode(c, nm); - if (lc != nil) { - let tn: *node = lc.tnode; - let lkind: i32 = -1; - if (tn != nil) { lkind = tn.kind; }; - // Pointer-to-struct: deref then field load. - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (inner != nil) { - if (inner.kind == N_TNAME) { - sname = inner.str; - }; - }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - // str field via *struct: load len into a - // scratch first (so loading ptr into AX - // last leaves (AX=ptr, BX=len)). - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "BX"); - emitline(", CX\n"); - emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "BX"); - emitline(", AX\n"); - emitline("\tMOVQ\tCX, BX\n"); - } else { - let op: str = fieldloadop(fi); - emitline("\t"); - emitline(op); - emitline("\t"); - emitdispreg(fi.foff: i64, "BX"); - emitline(", AX\n"); - }; - return; - }; - fi = fi.finext; - }; - }; - }; - }; - // Direct struct local: field load at off+foff. - if (lkind == N_TNAME) { - let sname: str = tn.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - // str field: load both halves so chained - // `.ptr` / `.len` see (AX=ptr, BX=len). - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitoff((lc.off + fi.foff): i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\t"); - emitoff((lc.off + fi.foff + 8): i64); - emitline("(BP), BX\n"); - } else { - let op: str = fieldloadop(fi); - emitline("\t"); - emitline(op); - emitline("\t"); - emitoff((lc.off + fi.foff): i64); - emitline("(BP), AX\n"); - }; - return; - }; - fi = fi.finext; - }; - }; - }; - // Array pseudo-fields: `.ptr` is the array's - // address (LEAQ); `.len` is the static element - // count (immediate). - if (lkind == N_TARRAY) { - if (streq(fld, "ptr")) { - emitline("\tLEAQ\t"); - emitoff(lc.off: i64); - emitline("(BP), AX\n"); - return; - }; - if (streq(fld, "len")) { - let lenn: *node = tn.rhs; - let alen: i64 = 0i64; - if (lenn != nil) { - if (lenn.kind == N_INTLIT) { alen = lenn.uval: i64; }; - }; - emitline("\tMOVQ\t$"); - emitint(alen); - emitline(", AX\n"); - return; - }; - }; - // str/slice pseudo-fields .ptr/.len/.cap on a - // direct local: load at slot+delta. - let delta: i32 = -1; - if (streq(fld, "ptr")) { delta = 0; }; - if (streq(fld, "len")) { delta = 8; }; - if (streq(fld, "cap")) { delta = 16; }; - if (delta >= 0) { - // Pointer to str/slice (`*[]u8`, `*str`): - // deref, then load at delta within the - // pointed-to header. C cgen does the same. - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let innerkind: i32 = -1; - if (inner != nil) { innerkind = inner.kind; }; - let innerstr: bool = false; - if (innerkind == N_TNAME) { - if (streq(inner.str, "str")) { innerstr = true; }; - }; - if (innerkind == N_TSLICE) { innerstr = true; }; - if (innerstr) { - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\t"); - emitdispreg(delta: i64, "BX"); - emitline(", AX\n"); - return; - }; - }; - emitline("\tMOVQ\t"); - emitoff((lc.off + delta): i64); - emitline("(BP), AX\n"); - return; - }; - }; - }; - }; - // Module-qualified value reference: `mod.name` where `mod` - // is N_IDENT bound as SK_USE and the leaf isn't a local. - // Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback - // the C cgen takes when bt is NULL/tyerr. - if (lhs != nil) { - if (lhs.kind == N_IDENT) { - emitline("\tMOVQ\t"); - emitsymname(c, fld); - emitline("(SB), AX\n"); - return; - }; - }; - // Non-ident base pseudo-field: e.g. `"abc".ptr` / `"abc".len`. - // Evaluate the str-producing expression — that leaves - // (AX=ptr, BX=len). Then `.ptr` returns AX as is; `.len` - // shuffles BX→AX. Mirrors what C cgen does (it just evaluates - // the literal and picks the half it wants). - if (streq(fld, "ptr")) { cgexpr(c, lhs); return; }; - if (streq(fld, "len")) { - cgexpr(c, lhs); - emitline("\tMOVQ\tBX, AX\n"); - return; - }; - // Chained struct-field-via-ptr-via-ptr access: - // r.sym.val where r: *lrel, .sym: *lsym, .val: u64 - // Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct - // field). Outer DOT dereferences and reads `val`. Without this - // path the cgen falls through and AX retains whatever the - // inner expression left there — typically the *struct pointer - // itself, so reads silently get the pointer value instead of - // the field. (Showed up porting w6l/pass.ww.) - if (lhs != nil) { - if (lhs.kind == N_DOT) { - let innert: *node = dotinnerstructptr(c, lhs); - if (innert != nil) { - let sname: str = innert.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - if (streq(fi.fname, fld)) { - cgexpr(c, lhs); // AX = ptr to inner struct - let lop: str = fieldloadop(fi); - // str field: load both halves. - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitdispreg((fi.foff + 8): i64, "AX"); - emitline(", BX\n"); - emitline("\tMOVQ\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - emitline("\t"); - emitline(lop); - emitline("\t"); - emitdispreg(fi.foff: i64, "AX"); - emitline(", AX\n"); - return; - }; - fi = fi.finext; - }; - }; - }; - }; - }; - return; - }; - - if (k == N_UN) { - // Match C cgen ordering: evaluate operand first (load into AX), - // then apply the unary op. AMP / STAR override AX with the - // address / deref. The wasted load before AMP keeps our asm - // byte-identical to the C version. - cgexpr(c, n.lhs); - if (n.op == TK_MINUS) { emitline("\tNEGQ\tAX\n"); return; }; - if (n.op == TK_TILDE) { emitline("\tNOTQ\tAX\n"); return; }; - if (n.op == TK_STAR) { emitline("\tMOVQ\t(AX), AX\n"); return; }; - if (n.op == TK_AMP) { - let opnd: *node = n.lhs; - if (opnd != nil) { - if (opnd.kind == N_IDENT) { - let nm: str = opnd.str; - let off: i32 = localfind(c, nm); - if (off != 0) { - emitline("\tLEAQ\t"); - emitoff(off: i64); - emitline("(BP), AX\n"); - return; - }; - }; - }; - return; - }; - if (n.op == TK_NOT) { - let t: str = mklabel(c, "tt"); - let e: str = mklabel(c, "te"); - emitline("\tCMPQ\t$0, AX\n"); - emitline("\tJE\t"); emitline(t); emitline("\n"); - emitline("\tMOVQ\t$0, AX\n"); - emitline("\tJMP\t"); emitline(e); emitline("\n"); - emitlabel(t); - emitline("\tMOVQ\t$1, AX\n"); - emitlabel(e); - return; - }; - return; - }; - - if (k == N_BIN) { - let unsignd: bool = nodeisunsigned(c, n.lhs); - if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; - - cgexpr(c, n.rhs); - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, n.lhs); - emitline("\tPOPQ\tBX\n"); - if (n.op == TK_PLUS) { emitline("\tADDQ\tBX, AX\n"); return; }; - if (n.op == TK_MINUS) { emitline("\tSUBQ\tBX, AX\n"); return; }; - if (n.op == TK_STAR) { emitline("\tIMULQ\tBX, AX\n"); return; }; - if (n.op == TK_SLASH) { - emitline("\tMOVQ\t$0, DX\n"); - if (unsignd) { emitline("\tDIVQ\tBX\n"); } - else { emitline("\tIDIVQ\tBX\n"); }; - return; - }; - if (n.op == TK_PERCENT) { - emitline("\tMOVQ\t$0, DX\n"); - if (unsignd) { emitline("\tDIVQ\tBX\n"); } - else { emitline("\tIDIVQ\tBX\n"); }; - emitline("\tMOVQ\tDX, AX\n"); - return; - }; - if (n.op == TK_AMP) { emitline("\tANDQ\tBX, AX\n"); return; }; - if (n.op == TK_PIPE) { emitline("\tORQ\tBX, AX\n"); return; }; - if (n.op == TK_CARET) { emitline("\tXORQ\tBX, AX\n"); return; }; - if (n.op == TK_LSHIFT) { - emitline("\tMOVQ\tBX, CX\n"); - emitline("\tSHLQ\tCX, AX\n"); - return; - }; - if (n.op == TK_RSHIFT) { - emitline("\tMOVQ\tBX, CX\n"); - emitline("\tSHRQ\tCX, AX\n"); - return; - }; - if (n.op == TK_AND) { emitline("\tANDQ\tBX, AX\n"); return; }; - if (n.op == TK_OR) { emitline("\tORQ\tBX, AX\n"); return; }; - - // Comparison: emit CMPQ, jump on signed/unsigned variant, - // materialise 0/1 in AX. Same shape as the C cgen. - let iscmp: bool = false; - let jcc: str = ""; - if (n.op == TK_EQ) { iscmp = true; jcc = "JE"; }; - if (n.op == TK_NEQ) { iscmp = true; jcc = "JNE"; }; - if (n.op == TK_LT) { iscmp = true; if (unsignd) { jcc = "JB"; } else { jcc = "JL"; }; }; - if (n.op == TK_LE) { iscmp = true; if (unsignd) { jcc = "JBE"; } else { jcc = "JLE"; }; }; - if (n.op == TK_GT) { iscmp = true; if (unsignd) { jcc = "JA"; } else { jcc = "JG"; }; }; - if (n.op == TK_GE) { iscmp = true; if (unsignd) { jcc = "JAE"; } else { jcc = "JGE"; }; }; - if (iscmp) { - let t: str = mklabel(c, "ct"); - let e: str = mklabel(c, "ce"); - emitline("\tCMPQ\tBX, AX\n"); - emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n"); - emitline("\tMOVQ\t$0, AX\n"); - emitline("\tJMP\t"); emitline(e); emitline("\n"); - emitlabel(t); - emitline("\tMOVQ\t$1, AX\n"); - emitlabel(e); - return; - }; - return; - }; - - if (k == N_CALL) { - let nargs: i32 = pushargsrev(c, n.list); - let i: i32 = 0; - for (i < nargs) { - emitline("\tPOPQ\t"); - emitline(argregname(i)); - emitline("\n"); - i += 1; - }; - let callee: *node = n.lhs; - let calleename: str; - calleename.ptr = nil; calleename.len = 0; - // Detect fn-pointer field call: `w.emit(args)` where `w` is - // a struct local and `emit` is an N_TFN field. Load the - // field value into AX and CALL through it. Also detect a - // bare `fp(args)` where `fp` is a local holding a function - // pointer — mirror C cgen's localfind dispatch (commit - // 635818e). Without this the call emits `CALL fp(SB)` and - // the linker rightly fails. - let isfnptrcall: bool = false; - if (callee != nil) { - if (callee.kind == N_IDENT) { - let cn: str = callee.str; - if (localfindnode(c, cn) != nil) { - isfnptrcall = true; - }; - }; - if (callee.kind == N_DOT) { - let base: *node = callee.lhs; - let fld: str = callee.str; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - let lc: *local = localfindnode(c, bn); - if (lc != nil) { - let tn: *node = lc.tnode; - if (tn != nil) { - let lkind: i32 = 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; - if (inner != nil) { - if (inner.kind == N_TNAME) { sname = inner.str; }; - }; - }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - let ft: *node = fi.tnode; - if (ft != nil) { - if (ft.kind == N_TFN) { - isfnptrcall = true; - }; - }; - fi = nil; - } else { - fi = fi.finext; - }; - }; - }; - }; - }; - }; - }; - }; - }; - }; - if (isfnptrcall) { - // Load fn-ptr field value into AX; CALL AX. We emit the - // load AFTER the args have been popped (so AX/BX/etc - // don't get clobbered by the field load before the pops). - // `popped args` left DI/SI/etc set; AX is free. - cgexpr(c, callee); - emitline("\tCALL\tAX\n"); - } else { - emitline("\tCALL\t"); - if (callee != nil) { - if (callee.kind == N_IDENT) { - calleename = callee.str; - emitsymname(c, calleename); - } else { if (callee.kind == N_DOT) { - calleename = callee.str; - emitsymname(c, calleename); - };}; - }; - emitline("(SB)\n"); - }; - // SysV returns 16-byte aggregates in (AX, DX). Our str - // convention is (AX, BX), so shuffle for str-returning calls. - if (calleename.len > 0) { - let rt: *node = fnretlookup(c, calleename); - if (isstrtype(c, rt)) { - emitline("\tMOVQ\tDX, BX\n"); - }; - }; - return; - }; - - if (k == N_ASSIGN) { - let lhs: *node = n.lhs; - // `*p = v` — deref-assign. Element width comes from the - // pointer's declared type. Mirrors C cgen: eval rhs (AX, - // and BX if str), push, eval pointer, pop value, store. - // We default to MOVQ (8B) since most fixtures use it; for - // `*bool` / `*u8` / `*i32` we narrow via the local's tnode. - if (lhs != nil) { - if (lhs.kind == N_UN) { - if (lhs.op == TK_STAR) { - if (n.op == TK_ASSIGN) { - let inner: *node = lhs.lhs; - let elemstr: bool = false; - let storeop: str = "MOVQ"; - if (inner != nil) { - if (inner.kind == N_IDENT) { - let lc: *local = localfindnode(c, inner.str); - if (lc != nil) { - let tn: *node = lc.tnode; - if (tn != nil) { - if (tn.kind == N_TPTR) { - let pe: *node = tn.lhs; - if (pe != nil) { - if (pe.kind == N_TNAME) { - if (streq(pe.str, "str")) { elemstr = true; } - else { - let ps: i32 = primsize(pe.str); - if (ps == 1) { storeop = "MOVB"; } - else { if (ps == 4) { storeop = "MOVL"; }; }; - }; - }; - }; - }; - }; - }; - }; - }; - cgexpr(c, n.rhs); - // Push order matches C cgen - // (cmd/w6c/cgen.c:1033-1041): PUSHQ AX - // (ptr) first, then PUSHQ BX (len) if - // str, so the pop sequence is POP CX - // (len) → POP AX (ptr) → MOVQ AX, - // (BX) → MOVQ CX, 8(BX). - emitline("\tPUSHQ\tAX\n"); - if (elemstr) { emitline("\tPUSHQ\tBX\n"); }; - cgexpr(c, inner); - emitline("\tMOVQ\tAX, BX\n"); - if (elemstr) { - emitline("\tPOPQ\tCX\n"); - emitline("\tPOPQ\tAX\n"); - emitline("\tMOVQ\tAX, (BX)\n"); - emitline("\tMOVQ\tCX, 8(BX)\n"); - return; - }; - emitline("\tPOPQ\tAX\n"); - emitline("\t"); - emitline(storeop); - emitline("\tAX, (BX)\n"); - return; - }; - }; - }; - }; - // Array/slice/ptr index store: `arr[i] = v;`. Element size - // from base.tnode picks MOVB vs MOVQ. - if (lhs != nil) { - if (lhs.kind == N_INDEX) { - if (n.op == TK_ASSIGN) { - let base: *node = lhs.lhs; - let idx: *node = lhs.rhs; - let esz: i32 = 8; - let baselocal: *local = nil; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - baselocal = localfindnode(c, bn); - if (baselocal != nil) { - esz = elemsizeof(baselocal.tnode); - }; - } else { if (base.kind == N_DOT) { - esz = indexbaseesz(c, base); - };}; - }; - cgexpr(c, n.rhs); // value → AX - if (esz == 16) { emitline("\tPUSHQ\tBX\n"); }; - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, idx); // idx → AX - if (esz > 1) { - emitline("\tMOVQ\t$"); - emitint(esz: i64); - emitline(", CX\n"); - emitline("\tIMULQ\tCX, AX\n"); - }; - emitline("\tPUSHQ\tAX\n"); // scaled idx - if (baselocal != nil) { - let tn: *node = baselocal.tnode; - let isarray: bool = false; - if (tn != nil) { if (tn.kind == N_TARRAY) { isarray = true; }; }; - if (isarray) { - emitline("\tLEAQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), BX\n"); - }; - } else { - cgexpr(c, base); - emitline("\tMOVQ\tAX, BX\n"); - }; - emitline("\tPOPQ\tAX\n"); // scaled idx - emitline("\tADDQ\tAX, BX\n"); - emitline("\tPOPQ\tAX\n"); // value - if (esz == 16) { - emitline("\tMOVQ\tAX, (BX)\n"); - emitline("\tPOPQ\tCX\n"); - emitline("\tMOVQ\tCX, 8(BX)\n"); - return; - }; - if (esz == 1) { emitline("\tMOVB\tAX, (BX)\n"); } - else { emitline("\tMOVQ\tAX, (BX)\n"); }; - return; - }; - }; - }; - // Struct/ptr-to-struct field assignment: `s.f = expr;` or - // `p.f = expr;`. Only plain `=` is wired (compound on field - // is rare and not yet needed by our fixtures). - if (lhs != nil) { - if (lhs.kind == N_DOT) { - let base: *node = lhs.lhs; - let fld: str = lhs.str; - if (base != nil) { - if (base.kind == N_IDENT) { - let bn: str = base.str; - let lc: *local = localfindnode(c, bn); - if (lc != nil) { - let tn: *node = lc.tnode; - let lkind: i32 = -1; - if (tn != nil) { lkind = tn.kind; }; - // Pointer-to-struct: deref then store. - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (inner != nil) { - if (inner.kind == N_TNAME) { sname = inner.str; }; - }; - if (sname.len > 0) { - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - if (n.op != TK_ASSIGN) { - // compound: load current value - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - let lop: str = fieldloadop(fi); - emitline("\t"); - emitline(lop); - emitline("\t"); - emitdispreg(fi.foff: i64, "BX"); - emitline(", BX\n"); - emitline("\tPUSHQ\tBX\n"); - }; - cgexpr(c, n.rhs); - if (n.op != TK_ASSIGN) { - emitline("\tPOPQ\tBX\n"); - // PLUSEQ is commutative; MINUSEQ - // needs lhs - rhs (BX is old lhs, - // AX is rhs). - if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; - if (n.op == TK_MINUSEQ) { - emitline("\tSUBQ\tAX, BX\n"); - emitline("\tMOVQ\tBX, AX\n"); - }; - }; - // str field via *struct: rhs left - // (AX=ptr, BX=len). Use CX as the - // address scratch so we don't clobber - // the len half before storing it. - if (n.op == TK_ASSIGN) { - if (isstrtype(c, fi.tnode)) { - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), CX\n"); - emitline("\tMOVQ\tAX, "); - emitdispreg(fi.foff: i64, "CX"); - emitline("\n"); - emitline("\tMOVQ\tBX, "); - emitdispreg((fi.foff + 8): i64, "CX"); - emitline("\n"); - return; - }; - }; - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - let sop: str = fieldstoreop(fi); - emitline("\t"); - emitline(sop); - emitline("\tAX, "); - emitdispreg(fi.foff: i64, "BX"); - emitline("\n"); - return; - }; - fi = fi.finext; - }; - }; - }; - }; - // Direct struct local: store at off+foff. - if (lkind == N_TNAME) { - let sname: str = tn.str; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fld)) { - cgexpr(c, n.rhs); - let sop: str = fieldstoreop(fi); - emitline("\t"); - emitline(sop); - emitline("\tAX, "); - emitoff((lc.off + fi.foff): i64); - emitline("(BP)\n"); - return; - }; - fi = fi.finext; - }; - }; - }; - // str/slice pseudo-field assignment. - let delta: i32 = -1; - if (streq(fld, "ptr")) { delta = 0; }; - if (streq(fld, "len")) { delta = 8; }; - if (streq(fld, "cap")) { delta = 16; }; - if (delta >= 0) { - if (lkind == N_TPTR) { - let inner: *node = tn.lhs; - let innerkind: i32 = -1; - if (inner != nil) { innerkind = inner.kind; }; - let innerstr: bool = false; - if (innerkind == N_TNAME) { - if (streq(inner.str, "str")) { innerstr = true; }; - }; - if (innerkind == N_TSLICE) { innerstr = true; }; - if (innerstr) { - if (n.op != TK_ASSIGN) { - // Compound on `(*str|*slice).field`: load - // current → push → eval rhs → combine → store. - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\t"); - emitdispreg(delta: i64, "BX"); - emitline(", BX\n"); - emitline("\tPUSHQ\tBX\n"); - cgexpr(c, n.rhs); - emitline("\tPOPQ\tBX\n"); - // PLUSEQ is commutative; MINUSEQ - // needs lhs - rhs. - if (n.op == TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); }; - if (n.op == TK_MINUSEQ) { - emitline("\tSUBQ\tAX, BX\n"); - emitline("\tMOVQ\tBX, AX\n"); - }; - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\tAX, "); - emitdispreg(delta: i64, "BX"); - emitline("\n"); - return; - }; - cgexpr(c, n.rhs); - emitline("\tMOVQ\t"); - emitoff(lc.off: i64); - emitline("(BP), BX\n"); - emitline("\tMOVQ\tAX, "); - emitdispreg(delta: i64, "BX"); - emitline("\n"); - return; - }; - }; - cgexpr(c, n.rhs); - emitline("\tMOVQ\tAX, "); - emitoff((lc.off + delta): i64); - emitline("(BP)\n"); - return; - }; - }; - }; - }; - }; - }; - // Local-ident target — plain `=` and the simple compound - // forms (+= -= *= /=); other compounds fall back to - // "evaluate rhs, replace". Mirrors C cgen's IDENT-assign path. - if (lhs != nil) { - if (lhs.kind == N_IDENT) { - let nm: str = lhs.str; - let off: i32 = localfind(c, nm); - if (off == 0) { return; }; - // Detect str-typed local — assignment must store both - // halves (AX=ptr at +0, BX=len at +8). - let lcstr: bool = false; - let lcn: *local = localfindnode(c, nm); - if (lcn != nil) { lcstr = isstrtype(c, lcn.tnode); }; - cgexpr(c, n.rhs); - if (n.op == TK_ASSIGN) { - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - if (lcstr) { - emitline("\tMOVQ\tBX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - }; - return; - }; - if (n.op == TK_PLUSEQ) { - emitline("\tADDQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - return; - }; - if (n.op == TK_MINUSEQ) { - emitline("\tSUBQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - return; - }; - // Generic compound: load → combine in BX → store. - emitline("\tMOVQ\t"); - emitoff(off: i64); - emitline("(BP), BX\n"); - if (n.op == TK_STAREQ) { emitline("\tIMULQ\tAX, BX\n"); }; - if (n.op == TK_AMPEQ) { emitline("\tANDQ\tAX, BX\n"); }; - if (n.op == TK_PIPEEQ) { emitline("\tORQ\tAX, BX\n"); }; - if (n.op == TK_CARETEQ) { emitline("\tXORQ\tAX, BX\n"); }; - if (n.op == TK_LSHIFTEQ) { - emitline("\tMOVQ\tAX, CX\n"); - emitline("\tSHLQ\tCX, BX\n"); - }; - if (n.op == TK_RSHIFTEQ) { - emitline("\tMOVQ\tAX, CX\n"); - emitline("\tSHRQ\tCX, BX\n"); - }; - emitline("\tMOVQ\tBX, "); - emitoff(off: i64); - emitline("(BP)\n"); - return; - }; - }; - return; - }; -}; - // ---- type-driven slot sizing ---------------------------------------- fn structlookup(c: *cgen, name: str) *structinfo = { @@ -6229,343 +6607,6 @@ fn scanlocals(c: *cgen, n: *node) i32 = { return total; }; -// ---- statement cgen -------------------------------------------------- - -fn cgstmt(c: *cgen, n: *node) void = { - if (n == nil) { return; }; - let k: i32 = n.kind; - - if (k == N_BLOCK) { - let s: *node = n.list; - for (s != nil) { - cgstmt(c, s); - s = s.next; - }; - return; - }; - - if (k == N_RETURN) { - let rhs: *node = n.lhs; - if (rhs != nil) { - // Tuple return `return a, b;` — pack as (AX=v0, DX=v1). - // Matches C cgen: evaluate v1 first (PUSHQ), then v0 - // into AX, then POPQ DX. End state: AX = v0, DX = v1. - if (rhs.kind == N_TUPLE) { - let v: *node = rhs.list; - if (v != nil) { - let v2: *node = v.next; - if (v2 != nil) { - cgexpr(c, v2); - emitline("\tPUSHQ\tAX\n"); - cgexpr(c, v); - emitline("\tPOPQ\tDX\n"); - } else { - cgexpr(c, v); - }; - }; - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.last_was_return = 1; - return; - }; - // Tagged-union return: pack as (AX=tag, DX=value0, CX=value1). - // For str variant, cgexpr leaves (AX=ptr, BX=len), so we - // shuffle DX←AX (ptr) and CX←BX (len), then load tag. - // For other variants, cgexpr leaves AX, shuffle DX←AX. - if (istaggedtype(c.fn_ret)) { - cgexpr(c, rhs); - let idx: i32 = taggedvariantindex(c, c.fn_ret, rhs); - if (nodeisstr(c, rhs)) { - emitline("\tMOVQ\tBX, CX\n"); - emitline("\tMOVQ\tAX, DX\n"); - } else { - emitline("\tMOVQ\tAX, DX\n"); - }; - emitline("\tMOVQ\t$"); - if (idx < 0) { idx = 0; }; - emitint(idx: i64); - emitline(", AX\n"); - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.last_was_return = 1; - return; - }; - cgexpr(c, rhs); - } else { - // Bare `return;` in a void fn — zero AX so the caller - // sees a deterministic value (matches C cgen, which - // always falls through to `cgexpr_int(c, 0)`). - emitline("\tMOVQ\t$0, AX\n"); - }; - // SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX). - // cgexpr leaves str in (AX, BX); shuffle BX→DX. - if (isstrtype(c, c.fn_ret)) { - emitline("\tMOVQ\tBX, DX\n"); - }; - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.last_was_return = 1; - return; - }; - - if (k == N_EXPRSTMT) { - if (n.lhs != nil) { cgexpr(c, n.lhs); }; - c.last_was_return = 0; - return; - }; - - if (k == N_LET) { - let nm: str = n.str; - let sz: i32 = slotsize(c, n.lhs); - let off: i32 = localadd(c, nm, sz, n.lhs); - if (n.rhs != nil) { - let rhs: *node = n.rhs; - // Tagged-union init: `let r: (T | E) = expr;`. - // - If rhs is a CALL to a fn returning tagged-union, - // the result is already in (AX=tag, DX=v0, CX=v1); - // just spill all three. - // - Otherwise rhs is a bare variant value: pack tag + - // value(s). - if (istaggedtype(n.lhs)) { - let rhsreturnstagged: bool = false; - if (rhs.kind == N_CALL) { - let callee: *node = rhs.lhs; - if (callee != nil) { - let calleename: str; - calleename.ptr = nil; calleename.len = 0; - if (callee.kind == N_IDENT) { calleename = callee.str; }; - if (callee.kind == N_DOT) { calleename = callee.str; }; - if (calleename.len > 0) { - let rt: *node = fnretlookup(c, calleename); - if (istaggedtype(rt)) { rhsreturnstagged = true; }; - }; - }; - }; - cgexpr(c, rhs); - if (rhsreturnstagged) { - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tDX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tCX, "); - emitoff((off + 16): i64); - emitline("(BP)\n"); - c.last_was_return = 0; - return; - }; - let tagidx: i32 = taggedvariantindex(c, n.lhs, rhs); - if (tagidx < 0) { tagidx = 0; }; - if (nodeisstr(c, rhs)) { - emitline("\tMOVQ\tAX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tBX, "); - emitoff((off + 16): i64); - emitline("(BP)\n"); - } else { - emitline("\tMOVQ\tAX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - }; - emitline("\tMOVQ\t$"); - emitint(tagidx: i64); - emitline(", "); - emitoff(off: i64); - emitline("(BP)\n"); - c.last_was_return = 0; - return; - }; - // Struct literal init: `let p: point = point{x=..., y=...};`. - // For each field in the lit, evaluate its value and store at - // the field's offset within the slot. Field-name → offset - // from the struct registry. - if (rhs.kind == N_STRUCTLIT) { - let trefn: *node = rhs.lhs; - let sname: str; - sname.ptr = nil; sname.len = 0; - if (trefn != nil) { - if (trefn.kind == N_IDENT) { sname = trefn.str; } - else { if (trefn.kind == N_TNAME) { sname = trefn.str; }; }; - }; - let si: *structinfo = structlookup(c, sname); - if (si != nil) { - let fieldnode: *node = rhs.list; - for (fieldnode != nil) { - if (fieldnode.kind == N_FIELD) { - let fname: str = fieldnode.str; - let fi: *fieldinfo = si.fields; - for (fi != nil) { - let fn_: str = fi.fname; - if (streq(fn_, fname)) { - cgexpr(c, fieldnode.lhs); - let sop: str = fieldstoreop(fi); - emitline("\t"); - emitline(sop); - emitline("\tAX, "); - emitoff((off + fi.foff): i64); - emitline("(BP)\n"); - fi = nil; - } else { - fi = fi.finext; - }; - }; - }; - fieldnode = fieldnode.next; - }; - c.last_was_return = 0; - return; - }; - }; - cgexpr(c, rhs); - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - // str init: cgexpr also leaves len in BX; store both. - if (sz == 16) { - emitline("\tMOVQ\tBX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - }; - // slice init: ptr/len/cap in AX/BX/CX. - if (sz == 24) { - emitline("\tMOVQ\tBX, "); - emitoff((off + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tCX, "); - emitoff((off + 16): i64); - emitline("(BP)\n"); - }; - } 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. - if (typeis8byteprimitive(c, n.lhs)) { - emitline("\tMOVQ\t$0, "); - emitoff(off: i64); - emitline("(BP)\n"); - }; - }; - c.last_was_return = 0; - return; - }; - - if (k == N_IF) { - let els: str = mklabel(c, "else"); - let endl: str = mklabel(c, "end"); - cgexpr(c, n.cond); - emitline("\tCMPQ\t$0, AX\n"); - emitline("\tJE\t"); - if (n.els != nil) { emitline(els); } - else { emitline(endl); }; - emitline("\n"); - if (n.body != nil) { cgstmt(c, n.body); }; - if (n.els != nil) { - emitline("\tJMP\t"); emitline(endl); emitline("\n"); - emitlabel(els); - cgstmt(c, n.els); - }; - emitlabel(endl); - c.last_was_return = 0; - return; - }; - - if (k == N_FOR) { - // Match C cgen's label scheme: _loop_N for the top, - // _endloop_N for the post-body merge. No separate cont - // label when there's no post-expression. - let topl: str = mklabel(c, "loop"); - let endl: str = mklabel(c, "endloop"); - - if (n.lhs != nil) { cgstmt(c, n.lhs); }; - - emitlabel(topl); - if (n.cond != nil) { - cgexpr(c, n.cond); - emitline("\tCMPQ\t$0, AX\n"); - emitline("\tJE\t"); emitline(endl); emitline("\n"); - }; - - c.loop_end_buf[c.loop_top] = endl; - c.loop_cont_buf[c.loop_top] = topl; - c.loop_top += 1; - - if (n.body != nil) { cgstmt(c, n.body); }; - - c.loop_top -= 1; - - if (n.rhs != nil) { cgexpr(c, n.rhs); }; - emitline("\tJMP\t"); emitline(topl); emitline("\n"); - emitlabel(endl); - c.last_was_return = 0; - 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/w6c/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); }; - emitline("\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 = localfind(c, l0.str); - if (off != 0) { - emitline("\tMOVQ\tAX, "); - emitoff(off: i64); - emitline("(BP)\n"); - }; - }; - }; - emitline("\tPOPQ\tDX\n"); - if (l1 != nil) { - if (l1.kind == N_IDENT) { - let off: i32 = localfind(c, l1.str); - if (off != 0) { - emitline("\tMOVQ\tDX, "); - emitoff(off: i64); - emitline("(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]; - emitline("\tJMP\t"); emitline(lbl); emitline("\n"); - }; - c.last_was_return = 0; - return; - }; - if (k == N_CONTINUE) { - if (c.loop_top > 0) { - let lbl: str = c.loop_cont_buf[c.loop_top - 1]; - emitline("\tJMP\t"); emitline(lbl); emitline("\n"); - }; - c.last_was_return = 0; - return; - }; - - c.last_was_return = 0; -}; - // ---- function-level cgen --------------------------------------------- // isstrtype — true when the type expr resolves (through any