selfhost: port append + spread — N_SPREAD parser + rt_ensure builtin
This commit is contained in:
@@ -217,6 +217,14 @@ fn parsearglist(p: *parser, closekind: tkind, headout: **node) void = {
|
|||||||
let tail: *node = nil;
|
let tail: *node = nil;
|
||||||
for (true) {
|
for (true) {
|
||||||
let e: *node = parseexpr(p);
|
let e: *node = parseexpr(p);
|
||||||
|
// Hare-style spread: `expr...` in an arg slot becomes a
|
||||||
|
// marker the callee/builtin can iterate over. Mirrors
|
||||||
|
// cmd/wcc/parse.c. The only consumer today is `append`.
|
||||||
|
if (accepttok(p, tkind.TK_ELLIPSIS)) {
|
||||||
|
let sp: *node = newnode(p.a, nkind.N_SPREAD, e.file, e.line, e.col);
|
||||||
|
sp.lhs = e;
|
||||||
|
e = sp;
|
||||||
|
};
|
||||||
if (head == nil) { head = e; tail = e; }
|
if (head == nil) { head = e; tail = e; }
|
||||||
else { tail.next = e; tail = e; };
|
else { tail.next = e; tail = e; };
|
||||||
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
||||||
|
|||||||
@@ -2333,6 +2333,14 @@ fn parsearglist(p: *parser, closekind: tkind, headout: **node) void = {
|
|||||||
let tail: *node = nil;
|
let tail: *node = nil;
|
||||||
for (true) {
|
for (true) {
|
||||||
let e: *node = parseexpr(p);
|
let e: *node = parseexpr(p);
|
||||||
|
// Hare-style spread: `expr...` in an arg slot becomes a
|
||||||
|
// marker the callee/builtin can iterate over. Mirrors
|
||||||
|
// cmd/wcc/parse.c. The only consumer today is `append`.
|
||||||
|
if (accepttok(p, tkind.TK_ELLIPSIS)) {
|
||||||
|
let sp: *node = newnode(p.a, nkind.N_SPREAD, e.file, e.line, e.col);
|
||||||
|
sp.lhs = e;
|
||||||
|
e = sp;
|
||||||
|
};
|
||||||
if (head == nil) { head = e; tail = e; }
|
if (head == nil) { head = e; tail = e; }
|
||||||
else { tail.next = e; tail = e; };
|
else { tail.next = e; tail = e; };
|
||||||
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
||||||
@@ -4013,11 +4021,12 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so
|
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
|
||||||
// their use sites resolve. The actual semantics live in cgen.
|
// them so their use sites resolve. The actual semantics live in cgen.
|
||||||
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "alloc", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "alloc", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "free", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "free", skind.SK_FN, nil, nil);
|
||||||
|
scopedefine(c.top, "append", skind.SK_FN, nil, nil);
|
||||||
};
|
};
|
||||||
|
|
||||||
// installdecl — install the top-level decl's name into the top scope.
|
// installdecl — install the top-level decl's name into the top scope.
|
||||||
@@ -7413,7 +7422,152 @@ fn cgbin(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
|
||||||
|
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
||||||
|
// Each value gets:
|
||||||
|
// ; cgexpr → AX
|
||||||
|
// ; PUSHQ AX
|
||||||
|
// ; ADDQ $1, s.len(BP)
|
||||||
|
// ; LEAQ s(BP), DI ; arg1 = &s
|
||||||
|
// ; MOVQ esz, SI ; arg2 = membsz
|
||||||
|
// ; CALL rt_ensure(SB)
|
||||||
|
// ; MOVQ s.len(BP), CX ; CX = new len
|
||||||
|
// ; SUBQ $1, CX ; CX = slot index
|
||||||
|
// ; [IMULQ esz, CX] ; byte offset (esz>1)
|
||||||
|
// ; MOVQ s.ptr(BP), BX
|
||||||
|
// ; ADDQ CX, BX
|
||||||
|
// ; POPQ AX
|
||||||
|
// ; MOV* AX, (BX) ; store (MOVB / MOVQ)
|
||||||
|
// nkind.N_SPREAD wraps the same body in a counted loop over items.len.
|
||||||
|
fn cgappend(c: *cgen, n: *node) void = {
|
||||||
|
let sn: *node = n.list;
|
||||||
|
if (sn == nil) { return; };
|
||||||
|
if (sn.kind != nkind.N_IDENT) { return; };
|
||||||
|
let snlocal: *local = localfindnode(c, sn.str);
|
||||||
|
if (snlocal == nil) { return; };
|
||||||
|
let sn_off: i32 = snlocal.off;
|
||||||
|
let esz: i32 = elemsizeof(snlocal.tnode);
|
||||||
|
let store_op: str = "MOVQ";
|
||||||
|
if (esz == 1) { store_op = "MOVB"; };
|
||||||
|
|
||||||
|
let vn: *node = sn.next;
|
||||||
|
for (vn != nil) {
|
||||||
|
if (vn.kind == nkind.N_SPREAD) {
|
||||||
|
let it: *node = vn.lhs;
|
||||||
|
if (it == nil) { vn = vn.next; continue; };
|
||||||
|
if (it.kind != nkind.N_IDENT) { vn = vn.next; continue; };
|
||||||
|
let itlocal: *local = localfindnode(c, it.str);
|
||||||
|
if (itlocal == nil) { vn = vn.next; continue; };
|
||||||
|
let it_off: i32 = itlocal.off;
|
||||||
|
let load_op: str = "MOVQ";
|
||||||
|
if (esz == 1) { load_op = "MOVZBQ"; };
|
||||||
|
emitline("\tSUBQ\t$8, SP\n");
|
||||||
|
emitline("\tMOVQ\t$0, (SP)\n");
|
||||||
|
let ll: str = mklabel(c, "spr_l");
|
||||||
|
let le: str = mklabel(c, "spr_e");
|
||||||
|
emitlabel(ll);
|
||||||
|
emitline("\tMOVQ\t(SP), CX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((it_off + 8): i64);
|
||||||
|
emitline("(BP), DX\n");
|
||||||
|
emitline("\tCMPQ\tDX, CX\n");
|
||||||
|
emitline("\tJGE\t"); emitline(le); emitline("\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(it_off: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
emitline("\t"); emitline(load_op); emitline("\t(BX), AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tADDQ\t$1, ");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), DI\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", SI\n");
|
||||||
|
emitline("\tCALL\trt_ensure(SB)\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP), CX\n");
|
||||||
|
emitline("\tSUBQ\t$1, CX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
emitline("\tPOPQ\tAX\n");
|
||||||
|
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
|
||||||
|
emitline("\tADDQ\t$1, (SP)\n");
|
||||||
|
emitline("\tJMP\t"); emitline(ll); emitline("\n");
|
||||||
|
emitlabel(le);
|
||||||
|
emitline("\tADDQ\t$8, SP\n");
|
||||||
|
vn = vn.next;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
cgexpr(c, vn);
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tADDQ\t$1, ");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), DI\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", SI\n");
|
||||||
|
emitline("\tCALL\trt_ensure(SB)\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP), CX\n");
|
||||||
|
emitline("\tSUBQ\t$1, CX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
emitline("\tPOPQ\tAX\n");
|
||||||
|
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
|
||||||
|
vn = vn.next;
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
fn cgcall(c: *cgen, n: *node) void = {
|
fn cgcall(c: *cgen, n: *node) void = {
|
||||||
|
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
||||||
|
// special-cased before pushargsrev so the spread variant can run
|
||||||
|
// a counted loop over the items slice instead of a normal call.
|
||||||
|
let callee: *node = n.lhs;
|
||||||
|
if (callee != nil) {
|
||||||
|
if (callee.kind == nkind.N_IDENT) {
|
||||||
|
if (streq(callee.str, "append")) {
|
||||||
|
if (n.list != nil) {
|
||||||
|
if (n.list.next != nil) {
|
||||||
|
cgappend(c, n);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
let nargs: i32 = pushargsrev(c, n.list);
|
let nargs: i32 = pushargsrev(c, n.list);
|
||||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
||||||
|
|||||||
@@ -1389,7 +1389,152 @@ fn cgbin(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
|
||||||
|
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
||||||
|
// Each value gets:
|
||||||
|
// ; cgexpr → AX
|
||||||
|
// ; PUSHQ AX
|
||||||
|
// ; ADDQ $1, s.len(BP)
|
||||||
|
// ; LEAQ s(BP), DI ; arg1 = &s
|
||||||
|
// ; MOVQ esz, SI ; arg2 = membsz
|
||||||
|
// ; CALL rt_ensure(SB)
|
||||||
|
// ; MOVQ s.len(BP), CX ; CX = new len
|
||||||
|
// ; SUBQ $1, CX ; CX = slot index
|
||||||
|
// ; [IMULQ esz, CX] ; byte offset (esz>1)
|
||||||
|
// ; MOVQ s.ptr(BP), BX
|
||||||
|
// ; ADDQ CX, BX
|
||||||
|
// ; POPQ AX
|
||||||
|
// ; MOV* AX, (BX) ; store (MOVB / MOVQ)
|
||||||
|
// nkind.N_SPREAD wraps the same body in a counted loop over items.len.
|
||||||
|
fn cgappend(c: *cgen, n: *node) void = {
|
||||||
|
let sn: *node = n.list;
|
||||||
|
if (sn == nil) { return; };
|
||||||
|
if (sn.kind != nkind.N_IDENT) { return; };
|
||||||
|
let snlocal: *local = localfindnode(c, sn.str);
|
||||||
|
if (snlocal == nil) { return; };
|
||||||
|
let sn_off: i32 = snlocal.off;
|
||||||
|
let esz: i32 = elemsizeof(snlocal.tnode);
|
||||||
|
let store_op: str = "MOVQ";
|
||||||
|
if (esz == 1) { store_op = "MOVB"; };
|
||||||
|
|
||||||
|
let vn: *node = sn.next;
|
||||||
|
for (vn != nil) {
|
||||||
|
if (vn.kind == nkind.N_SPREAD) {
|
||||||
|
let it: *node = vn.lhs;
|
||||||
|
if (it == nil) { vn = vn.next; continue; };
|
||||||
|
if (it.kind != nkind.N_IDENT) { vn = vn.next; continue; };
|
||||||
|
let itlocal: *local = localfindnode(c, it.str);
|
||||||
|
if (itlocal == nil) { vn = vn.next; continue; };
|
||||||
|
let it_off: i32 = itlocal.off;
|
||||||
|
let load_op: str = "MOVQ";
|
||||||
|
if (esz == 1) { load_op = "MOVZBQ"; };
|
||||||
|
emitline("\tSUBQ\t$8, SP\n");
|
||||||
|
emitline("\tMOVQ\t$0, (SP)\n");
|
||||||
|
let ll: str = mklabel(c, "spr_l");
|
||||||
|
let le: str = mklabel(c, "spr_e");
|
||||||
|
emitlabel(ll);
|
||||||
|
emitline("\tMOVQ\t(SP), CX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((it_off + 8): i64);
|
||||||
|
emitline("(BP), DX\n");
|
||||||
|
emitline("\tCMPQ\tDX, CX\n");
|
||||||
|
emitline("\tJGE\t"); emitline(le); emitline("\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(it_off: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
emitline("\t"); emitline(load_op); emitline("\t(BX), AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tADDQ\t$1, ");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), DI\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", SI\n");
|
||||||
|
emitline("\tCALL\trt_ensure(SB)\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP), CX\n");
|
||||||
|
emitline("\tSUBQ\t$1, CX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
emitline("\tPOPQ\tAX\n");
|
||||||
|
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
|
||||||
|
emitline("\tADDQ\t$1, (SP)\n");
|
||||||
|
emitline("\tJMP\t"); emitline(ll); emitline("\n");
|
||||||
|
emitlabel(le);
|
||||||
|
emitline("\tADDQ\t$8, SP\n");
|
||||||
|
vn = vn.next;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
cgexpr(c, vn);
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tADDQ\t$1, ");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), DI\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", SI\n");
|
||||||
|
emitline("\tCALL\trt_ensure(SB)\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP), CX\n");
|
||||||
|
emitline("\tSUBQ\t$1, CX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
emitline("\tPOPQ\tAX\n");
|
||||||
|
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
|
||||||
|
vn = vn.next;
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
fn cgcall(c: *cgen, n: *node) void = {
|
fn cgcall(c: *cgen, n: *node) void = {
|
||||||
|
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
||||||
|
// special-cased before pushargsrev so the spread variant can run
|
||||||
|
// a counted loop over the items slice instead of a normal call.
|
||||||
|
let callee: *node = n.lhs;
|
||||||
|
if (callee != nil) {
|
||||||
|
if (callee.kind == nkind.N_IDENT) {
|
||||||
|
if (streq(callee.str, "append")) {
|
||||||
|
if (n.list != nil) {
|
||||||
|
if (n.list.next != nil) {
|
||||||
|
cgappend(c, n);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
let nargs: i32 = pushargsrev(c, n.list);
|
let nargs: i32 = pushargsrev(c, n.list);
|
||||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
||||||
|
|||||||
@@ -56,11 +56,12 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so
|
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
|
||||||
// their use sites resolve. The actual semantics live in cgen.
|
// them so their use sites resolve. The actual semantics live in cgen.
|
||||||
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "alloc", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "alloc", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "free", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "free", skind.SK_FN, nil, nil);
|
||||||
|
scopedefine(c.top, "append", skind.SK_FN, nil, nil);
|
||||||
};
|
};
|
||||||
|
|
||||||
// installdecl — install the top-level decl's name into the top scope.
|
// installdecl — install the top-level decl's name into the top scope.
|
||||||
|
|||||||
@@ -2333,6 +2333,14 @@ fn parsearglist(p: *parser, closekind: tkind, headout: **node) void = {
|
|||||||
let tail: *node = nil;
|
let tail: *node = nil;
|
||||||
for (true) {
|
for (true) {
|
||||||
let e: *node = parseexpr(p);
|
let e: *node = parseexpr(p);
|
||||||
|
// Hare-style spread: `expr...` in an arg slot becomes a
|
||||||
|
// marker the callee/builtin can iterate over. Mirrors
|
||||||
|
// cmd/wcc/parse.c. The only consumer today is `append`.
|
||||||
|
if (accepttok(p, tkind.TK_ELLIPSIS)) {
|
||||||
|
let sp: *node = newnode(p.a, nkind.N_SPREAD, e.file, e.line, e.col);
|
||||||
|
sp.lhs = e;
|
||||||
|
e = sp;
|
||||||
|
};
|
||||||
if (head == nil) { head = e; tail = e; }
|
if (head == nil) { head = e; tail = e; }
|
||||||
else { tail.next = e; tail = e; };
|
else { tail.next = e; tail = e; };
|
||||||
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
||||||
@@ -4013,11 +4021,12 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
scopedefine(c.top, "never", skind.SK_TYPE, c.tc.tynever, nil);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free` are pseudo-builtins; scopedefine them so
|
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
|
||||||
// their use sites resolve. The actual semantics live in cgen.
|
// them so their use sites resolve. The actual semantics live in cgen.
|
||||||
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "alloc", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "alloc", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "free", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "free", skind.SK_FN, nil, nil);
|
||||||
|
scopedefine(c.top, "append", skind.SK_FN, nil, nil);
|
||||||
};
|
};
|
||||||
|
|
||||||
// installdecl — install the top-level decl's name into the top scope.
|
// installdecl — install the top-level decl's name into the top scope.
|
||||||
@@ -7413,7 +7422,152 @@ fn cgbin(c: *cgen, n: *node) void = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
|
||||||
|
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
||||||
|
// Each value gets:
|
||||||
|
// ; cgexpr → AX
|
||||||
|
// ; PUSHQ AX
|
||||||
|
// ; ADDQ $1, s.len(BP)
|
||||||
|
// ; LEAQ s(BP), DI ; arg1 = &s
|
||||||
|
// ; MOVQ esz, SI ; arg2 = membsz
|
||||||
|
// ; CALL rt_ensure(SB)
|
||||||
|
// ; MOVQ s.len(BP), CX ; CX = new len
|
||||||
|
// ; SUBQ $1, CX ; CX = slot index
|
||||||
|
// ; [IMULQ esz, CX] ; byte offset (esz>1)
|
||||||
|
// ; MOVQ s.ptr(BP), BX
|
||||||
|
// ; ADDQ CX, BX
|
||||||
|
// ; POPQ AX
|
||||||
|
// ; MOV* AX, (BX) ; store (MOVB / MOVQ)
|
||||||
|
// nkind.N_SPREAD wraps the same body in a counted loop over items.len.
|
||||||
|
fn cgappend(c: *cgen, n: *node) void = {
|
||||||
|
let sn: *node = n.list;
|
||||||
|
if (sn == nil) { return; };
|
||||||
|
if (sn.kind != nkind.N_IDENT) { return; };
|
||||||
|
let snlocal: *local = localfindnode(c, sn.str);
|
||||||
|
if (snlocal == nil) { return; };
|
||||||
|
let sn_off: i32 = snlocal.off;
|
||||||
|
let esz: i32 = elemsizeof(snlocal.tnode);
|
||||||
|
let store_op: str = "MOVQ";
|
||||||
|
if (esz == 1) { store_op = "MOVB"; };
|
||||||
|
|
||||||
|
let vn: *node = sn.next;
|
||||||
|
for (vn != nil) {
|
||||||
|
if (vn.kind == nkind.N_SPREAD) {
|
||||||
|
let it: *node = vn.lhs;
|
||||||
|
if (it == nil) { vn = vn.next; continue; };
|
||||||
|
if (it.kind != nkind.N_IDENT) { vn = vn.next; continue; };
|
||||||
|
let itlocal: *local = localfindnode(c, it.str);
|
||||||
|
if (itlocal == nil) { vn = vn.next; continue; };
|
||||||
|
let it_off: i32 = itlocal.off;
|
||||||
|
let load_op: str = "MOVQ";
|
||||||
|
if (esz == 1) { load_op = "MOVZBQ"; };
|
||||||
|
emitline("\tSUBQ\t$8, SP\n");
|
||||||
|
emitline("\tMOVQ\t$0, (SP)\n");
|
||||||
|
let ll: str = mklabel(c, "spr_l");
|
||||||
|
let le: str = mklabel(c, "spr_e");
|
||||||
|
emitlabel(ll);
|
||||||
|
emitline("\tMOVQ\t(SP), CX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((it_off + 8): i64);
|
||||||
|
emitline("(BP), DX\n");
|
||||||
|
emitline("\tCMPQ\tDX, CX\n");
|
||||||
|
emitline("\tJGE\t"); emitline(le); emitline("\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(it_off: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
emitline("\t"); emitline(load_op); emitline("\t(BX), AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tADDQ\t$1, ");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), DI\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", SI\n");
|
||||||
|
emitline("\tCALL\trt_ensure(SB)\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP), CX\n");
|
||||||
|
emitline("\tSUBQ\t$1, CX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
emitline("\tPOPQ\tAX\n");
|
||||||
|
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
|
||||||
|
emitline("\tADDQ\t$1, (SP)\n");
|
||||||
|
emitline("\tJMP\t"); emitline(ll); emitline("\n");
|
||||||
|
emitlabel(le);
|
||||||
|
emitline("\tADDQ\t$8, SP\n");
|
||||||
|
vn = vn.next;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
cgexpr(c, vn);
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tADDQ\t$1, ");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), DI\n");
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", SI\n");
|
||||||
|
emitline("\tCALL\trt_ensure(SB)\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((sn_off + 8): i64);
|
||||||
|
emitline("(BP), CX\n");
|
||||||
|
emitline("\tSUBQ\t$1, CX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(sn_off: i64);
|
||||||
|
emitline("(BP), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
emitline("\tPOPQ\tAX\n");
|
||||||
|
emitline("\t"); emitline(store_op); emitline("\tAX, (BX)\n");
|
||||||
|
vn = vn.next;
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
fn cgcall(c: *cgen, n: *node) void = {
|
fn cgcall(c: *cgen, n: *node) void = {
|
||||||
|
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
||||||
|
// special-cased before pushargsrev so the spread variant can run
|
||||||
|
// a counted loop over the items slice instead of a normal call.
|
||||||
|
let callee: *node = n.lhs;
|
||||||
|
if (callee != nil) {
|
||||||
|
if (callee.kind == nkind.N_IDENT) {
|
||||||
|
if (streq(callee.str, "append")) {
|
||||||
|
if (n.list != nil) {
|
||||||
|
if (n.list.next != nil) {
|
||||||
|
cgappend(c, n);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
let nargs: i32 = pushargsrev(c, n.list);
|
let nargs: i32 = pushargsrev(c, n.list);
|
||||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||||
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
// SUBQ+MOVSD; pop into the XMM stream (X0..X7). Everything else
|
||||||
|
|||||||
@@ -345,6 +345,35 @@ probe_ww_compile(const char *bin)
|
|||||||
" dec(&c); dec(&c); dec(&c);\n"
|
" dec(&c); dec(&c); dec(&c);\n"
|
||||||
" return c.x;\n"
|
" return c.x;\n"
|
||||||
"};", 2 },
|
"};", 2 },
|
||||||
|
/* append(s, v, ...) builtin — Hare's rt::ensure model.
|
||||||
|
* Each value: PUSHQ AX, ADDQ $1 to s.len, LEAQ s/MOVQ esz
|
||||||
|
* args for rt_ensure, then write into the freshly-grown
|
||||||
|
* slot. Returns s.len = 3 after appending three u8s. */
|
||||||
|
{ "use os;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let s: []u8;\n"
|
||||||
|
" s.ptr = nil; s.len = 0; s.cap = 0;\n"
|
||||||
|
" append(s, 65u8, 66u8, 67u8);\n"
|
||||||
|
" return s.len: i32;\n"
|
||||||
|
"};", 3 },
|
||||||
|
/* append(s, items...) spread variant — wraps the per-value
|
||||||
|
* body in a counted loop over items.len. Combined with
|
||||||
|
* single-value appends in the same fn. dst ends up with
|
||||||
|
* [1, 10, 20, 30] — sum = 61. */
|
||||||
|
{ "use os;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let src: []i64;\n"
|
||||||
|
" src.ptr = nil; src.len = 0; src.cap = 0;\n"
|
||||||
|
" append(src, 10i64, 20i64, 30i64);\n"
|
||||||
|
" let dst: []i64;\n"
|
||||||
|
" dst.ptr = nil; dst.len = 0; dst.cap = 0;\n"
|
||||||
|
" append(dst, 1i64);\n"
|
||||||
|
" append(dst, src...);\n"
|
||||||
|
" let total: i64 = 0i64;\n"
|
||||||
|
" let i: i32 = 0;\n"
|
||||||
|
" for (i < dst.len) { total += dst[i]; i += 1; };\n"
|
||||||
|
" return total: i32;\n"
|
||||||
|
"};", 61 },
|
||||||
/* switch with multi-expr cases + default. Lowers to a chain
|
/* switch with multi-expr cases + default. Lowers to a chain
|
||||||
* of CMPQ + JE; the scrutinee lands in a fresh 8B local slot
|
* of CMPQ + JE; the scrutinee lands in a fresh 8B local slot
|
||||||
* (".sw_N") so case bodies can spill SP without losing it.
|
* (".sw_N") so case bodies can spill SP without losing it.
|
||||||
|
|||||||
@@ -159,6 +159,25 @@ main(void)
|
|||||||
" return -1;\n"
|
" return -1;\n"
|
||||||
"};\n"
|
"};\n"
|
||||||
"fn main() i32 = { return classify(2); };" },
|
"fn main() i32 = { return classify(2); };" },
|
||||||
|
{ "append",
|
||||||
|
"use os;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let s: []u8;\n"
|
||||||
|
" s.ptr = nil; s.len = 0; s.cap = 0;\n"
|
||||||
|
" append(s, 65u8, 66u8, 67u8);\n"
|
||||||
|
" return s.len: i32;\n"
|
||||||
|
"};" },
|
||||||
|
{ "append_spread",
|
||||||
|
"use os;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let src: []i64;\n"
|
||||||
|
" src.ptr = nil; src.len = 0; src.cap = 0;\n"
|
||||||
|
" append(src, 10i64, 20i64);\n"
|
||||||
|
" let dst: []i64;\n"
|
||||||
|
" dst.ptr = nil; dst.len = 0; dst.cap = 0;\n"
|
||||||
|
" append(dst, src...);\n"
|
||||||
|
" return dst.len: i32;\n"
|
||||||
|
"};" },
|
||||||
{ NULL, NULL },
|
{ NULL, NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user