wcc+w6c+w6c_ww: delete() builtin — single-element slice removal (part of #35)

Hare's delete(xs[i]) (ref/harec/src/check.c:1981-2027): checker accepts
an N_INDEX over a slice-typed base, stamps void; loud-rejects the range
form delete(xs[i..j]) (stays filed on #35 — regex fold-2b's consumers
are all single-element), non-index operands, array bases, wrong arity.

Lowering (both stages, converged byte-identical by construction):
ascending word-copy loop shifts [i+1..len) down one esz stride, then
hdr.len -= 1; cap unchanged. The move is a same-type whole-stride byte
copy — src and dst are elements of the SAME slice, so no boxing exists
for any element kind; one loop serves scalar/narrow/str/struct/tagged.
esz off the STAMPED base type (#34/#48 discipline). Base shapes: local
slice ident (LEAQ) and deref-of-local ptr-to-slice (MOVQ — the fold-2b
delete_thread shape); others rule-7 loud-stop.

test/804: 38 fixtures — first/middle/last/to-empty, esz 1/4/8/24/56
(MOVB/MOVL tails + 7-qword tagged), cap-unchanged, (*threads)[i], 4
checker reject rows; every accept row cs==ww asm byte-id.
This commit is contained in:
2026-06-04 06:41:12 +09:00
parent e93be8495a
commit 37febab9d5
8 changed files with 1120 additions and 6 deletions

View File

@@ -10424,12 +10424,14 @@ fn seedprimitives(c: *checker) void = {
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
// `nil`, `true`, `false` are keywords — handled at the lex/parser
// level, no symbol needed.
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
// them so their use sites resolve. The actual semantics live in cgen.
// `len`, `alloc`, `free`, `append`, `delete` are pseudo-builtins;
// scopedefine them so their use sites resolve. The actual semantics
// live in cgen.
scopedefine(c.top, "len", 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, "append", skind.SK_FN, nil, nil);
scopedefine(c.top, "delete", skind.SK_FN, nil, nil);
// #42: typed builtins folded to integer literals at check time —
// `size(T)` / `align(T)` (arg is a type-expression planted by the
// parser at lib/ww/parse/expr.ww:254-267) and `offset(e.f)` (arg is
@@ -12889,6 +12891,44 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
e.type_ = tinfofornode(c, tn): *void;
return tn;
};
// delete(xs[i]) — single-element slice removal, the
// delete-half of #35 (insert() deferred). Mirrors
// cstage cmd/wcc/check.c's delete arm. harec
// ref/harec/src/check.c:1981-2027 also accepts the
// range form delete(xs[i..j]) (EXPR_SLICE) —
// loud-rejected here, filed on #35 (regex fold-2b's
// consumers are all single-element).
if (streq(callee.str, "delete")) {
let d: *node = e.list;
if (d == nil || d.next != nil) {
cerr("delete: takes exactly one argument\n");
c.errs += 1;
};
if (d != nil) {
exprtype(c, d, nil);
if (d.kind == nkind.N_SLICE) {
cerr("delete: range form delete(xs[i..j]) unimplemented (task #35)\n");
c.errs += 1;
} else {
if (d.kind != nkind.N_INDEX) {
cerr("delete: operand must be an indexing expression xs[i]\n");
c.errs += 1;
} else {
let basetn: *node = exprtype(c, d.lhs, nil);
let u: *node = resolvealias(c, unwrapbang(basetn));
// harec check.c:2024 wording; a
// fixed-size [N]T base lands here.
if (u == nil || u.kind != nkind.N_TSLICE) {
cerr("delete must operate on a slice\n");
c.errs += 1;
};
};
};
};
let tn: *node = mktname(c, "void");
e.type_ = tinfofornode(c, tn): *void;
return tn;
};
};
let nm: str;
nm.ptr = nil; nm.len = 0;
@@ -24478,6 +24518,135 @@ fn cgappend(c: *cgen, n: *node) void = {
return;
};
// cgdelete — Hare `delete(xs[i])`: single-element slice removal, the
// delete-half of #35. Shift [i+1..len) down one stride, len -= 1, cap
// unchanged. The move is a same-type whole-stride byte copy: src and
// dst are elements of the SAME slice, so no boxing/coercion exists for
// any element kind (str/slice 24B header, tagged box, struct body) —
// one word-copy loop serves all kinds, unlike append's value-store
// dispatch (#34) which boxes from a foreign source. Ascending j keeps
// src (j+1) ahead of dst (j), the safe memmove-down direction. Mirrors
// cmd/w6c/cgen.c's N_CALL delete arm instruction-for-instruction
// (rule-10 byte-id).
fn cgdelete(c: *cgen, n: *node) void = {
let d: *node = n.list; // N_INDEX, checker-validated
let base: *node = d.lhs;
// esz off the STAMPED base tinfo (#34/#48 discipline — never the
// value node). Peel TY_NAMED on indexable AND element, mirroring
// cstage's type_chase_named on both (#8 family).
let sti: *tinfo = base.type_: *tinfo;
for (sti != nil && sti.kind == tykind.TY_NAMED) { sti = sti.under; };
let esub: *tinfo = nil;
if (sti != nil) { esub = sti.sub; };
for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; };
let esz: i32 = 0;
if (esub != nil) { esz = esub.size: i32; };
if (esz <= 0) {
let m35a: str = "#35: delete() element size unresolved (rule-7)\n";
os.write(2, m35a.ptr, m35a.len: u64);
os.exit(1);
};
let hdr_lea: bool = false;
let hdr_off: i32 = 0;
let hdr_ok: bool = false;
if (base.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, base.str);
if (lc != nil) {
hdr_lea = true;
hdr_off = lc.off;
hdr_ok = true;
};
};
// (*p)[i]: the header lives behind a local ptr-to-slice — the
// regex fold-2b delete_thread shape (threads: *[]thread).
if (!hdr_ok && base.kind == nkind.N_UN) {
if (base.op == tkind.TK_STAR && base.lhs != nil) {
if (base.lhs.kind == nkind.N_IDENT) {
let pc: *local = localfindnode(c, base.lhs.str);
if (pc != nil) {
hdr_off = pc.off;
hdr_ok = true;
};
};
};
};
if (!hdr_ok) {
let m35b: str = "#35: delete() base shape unsupported (rule-7: local slice ident or deref-of-local only)\n";
os.write(2, m35b.ptr, m35b.len: u64);
os.exit(1);
};
cgexpr(c, d.rhs); // AX = i
emitline("\tPUSHQ\tAX\n");
if (hdr_lea) {
emitline("\tLEAQ\t");
} else {
emitline("\tMOVQ\t");
};
emitoff(hdr_off: i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
let dll: str = mklabel(c, "del_l");
let dle: str = mklabel(c, "del_e");
emitlabel(dll);
emitline("\tMOVQ\t(SP), DX\n");
emitline("\tMOVQ\t8(SP), CX\n");
emitline("\tMOVQ\t8(DX), BX\n");
emitline("\tSUBQ\t$1, BX\n");
emitline("\tCMPQ\tBX, CX\n");
emitline("\tJGE\t"); emitline(dle); emitline("\n");
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", AX\n");
emitline("\tIMULQ\tAX, CX\n");
};
emitline("\tMOVQ\t(DX), BX\n");
emitline("\tADDQ\tCX, BX\n");
let dk: i32 = 0;
for (dk + 8 <= esz) {
emitline("\tMOVQ\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 8;
};
if (dk + 4 <= esz) {
emitline("\tMOVL\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVL\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 4;
};
if (dk + 2 <= esz) {
emitline("\tMOVW\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVW\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 2;
};
if (dk + 1 <= esz) {
emitline("\tMOVB\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVB\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 1;
};
emitline("\tADDQ\t$1, 8(SP)\n");
emitline("\tJMP\t"); emitline(dll); emitline("\n");
emitlabel(dle);
emitline("\tMOVQ\t(SP), DX\n");
emitline("\tSUBQ\t$1, 8(DX)\n");
emitline("\tADDQ\t$16, SP\n");
};
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
@@ -24650,6 +24819,16 @@ fn cgcall(c: *cgen, n: *node) void = {
};
};
};
// delete(xs[i]) — #35 delete-half; mirror of cstage
// cgen.c's N_CALL delete arm.
if (streq(callee.str, "delete")) {
if (n.list != nil) {
if (n.list.next == nil) {
cgdelete(c, n);
return;
};
};
};
};
};

View File

@@ -4608,6 +4608,135 @@ fn cgappend(c: *cgen, n: *node) void = {
return;
};
// cgdelete — Hare `delete(xs[i])`: single-element slice removal, the
// delete-half of #35. Shift [i+1..len) down one stride, len -= 1, cap
// unchanged. The move is a same-type whole-stride byte copy: src and
// dst are elements of the SAME slice, so no boxing/coercion exists for
// any element kind (str/slice 24B header, tagged box, struct body) —
// one word-copy loop serves all kinds, unlike append's value-store
// dispatch (#34) which boxes from a foreign source. Ascending j keeps
// src (j+1) ahead of dst (j), the safe memmove-down direction. Mirrors
// cmd/w6c/cgen.c's N_CALL delete arm instruction-for-instruction
// (rule-10 byte-id).
fn cgdelete(c: *cgen, n: *node) void = {
let d: *node = n.list; // N_INDEX, checker-validated
let base: *node = d.lhs;
// esz off the STAMPED base tinfo (#34/#48 discipline — never the
// value node). Peel TY_NAMED on indexable AND element, mirroring
// cstage's type_chase_named on both (#8 family).
let sti: *tinfo = base.type_: *tinfo;
for (sti != nil && sti.kind == tykind.TY_NAMED) { sti = sti.under; };
let esub: *tinfo = nil;
if (sti != nil) { esub = sti.sub; };
for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; };
let esz: i32 = 0;
if (esub != nil) { esz = esub.size: i32; };
if (esz <= 0) {
let m35a: str = "#35: delete() element size unresolved (rule-7)\n";
os.write(2, m35a.ptr, m35a.len: u64);
os.exit(1);
};
let hdr_lea: bool = false;
let hdr_off: i32 = 0;
let hdr_ok: bool = false;
if (base.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, base.str);
if (lc != nil) {
hdr_lea = true;
hdr_off = lc.off;
hdr_ok = true;
};
};
// (*p)[i]: the header lives behind a local ptr-to-slice — the
// regex fold-2b delete_thread shape (threads: *[]thread).
if (!hdr_ok && base.kind == nkind.N_UN) {
if (base.op == tkind.TK_STAR && base.lhs != nil) {
if (base.lhs.kind == nkind.N_IDENT) {
let pc: *local = localfindnode(c, base.lhs.str);
if (pc != nil) {
hdr_off = pc.off;
hdr_ok = true;
};
};
};
};
if (!hdr_ok) {
let m35b: str = "#35: delete() base shape unsupported (rule-7: local slice ident or deref-of-local only)\n";
os.write(2, m35b.ptr, m35b.len: u64);
os.exit(1);
};
cgexpr(c, d.rhs); // AX = i
emitline("\tPUSHQ\tAX\n");
if (hdr_lea) {
emitline("\tLEAQ\t");
} else {
emitline("\tMOVQ\t");
};
emitoff(hdr_off: i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
let dll: str = mklabel(c, "del_l");
let dle: str = mklabel(c, "del_e");
emitlabel(dll);
emitline("\tMOVQ\t(SP), DX\n");
emitline("\tMOVQ\t8(SP), CX\n");
emitline("\tMOVQ\t8(DX), BX\n");
emitline("\tSUBQ\t$1, BX\n");
emitline("\tCMPQ\tBX, CX\n");
emitline("\tJGE\t"); emitline(dle); emitline("\n");
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", AX\n");
emitline("\tIMULQ\tAX, CX\n");
};
emitline("\tMOVQ\t(DX), BX\n");
emitline("\tADDQ\tCX, BX\n");
let dk: i32 = 0;
for (dk + 8 <= esz) {
emitline("\tMOVQ\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 8;
};
if (dk + 4 <= esz) {
emitline("\tMOVL\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVL\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 4;
};
if (dk + 2 <= esz) {
emitline("\tMOVW\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVW\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 2;
};
if (dk + 1 <= esz) {
emitline("\tMOVB\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVB\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 1;
};
emitline("\tADDQ\t$1, 8(SP)\n");
emitline("\tJMP\t"); emitline(dll); emitline("\n");
emitlabel(dle);
emitline("\tMOVQ\t(SP), DX\n");
emitline("\tSUBQ\t$1, 8(DX)\n");
emitline("\tADDQ\t$16, SP\n");
};
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
@@ -4780,6 +4909,16 @@ fn cgcall(c: *cgen, n: *node) void = {
};
};
};
// delete(xs[i]) — #35 delete-half; mirror of cstage
// cgen.c's N_CALL delete arm.
if (streq(callee.str, "delete")) {
if (n.list != nil) {
if (n.list.next == nil) {
cgdelete(c, n);
return;
};
};
};
};
};

View File

@@ -97,12 +97,14 @@ fn seedprimitives(c: *checker) void = {
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
// `nil`, `true`, `false` are keywords — handled at the lex/parser
// level, no symbol needed.
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
// them so their use sites resolve. The actual semantics live in cgen.
// `len`, `alloc`, `free`, `append`, `delete` are pseudo-builtins;
// scopedefine them so their use sites resolve. The actual semantics
// live in cgen.
scopedefine(c.top, "len", 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, "append", skind.SK_FN, nil, nil);
scopedefine(c.top, "delete", skind.SK_FN, nil, nil);
// #42: typed builtins folded to integer literals at check time —
// `size(T)` / `align(T)` (arg is a type-expression planted by the
// parser at lib/ww/parse/expr.ww:254-267) and `offset(e.f)` (arg is
@@ -2562,6 +2564,44 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
e.type_ = tinfofornode(c, tn): *void;
return tn;
};
// delete(xs[i]) — single-element slice removal, the
// delete-half of #35 (insert() deferred). Mirrors
// cstage cmd/wcc/check.c's delete arm. harec
// ref/harec/src/check.c:1981-2027 also accepts the
// range form delete(xs[i..j]) (EXPR_SLICE) —
// loud-rejected here, filed on #35 (regex fold-2b's
// consumers are all single-element).
if (streq(callee.str, "delete")) {
let d: *node = e.list;
if (d == nil || d.next != nil) {
cerr("delete: takes exactly one argument\n");
c.errs += 1;
};
if (d != nil) {
exprtype(c, d, nil);
if (d.kind == nkind.N_SLICE) {
cerr("delete: range form delete(xs[i..j]) unimplemented (task #35)\n");
c.errs += 1;
} else {
if (d.kind != nkind.N_INDEX) {
cerr("delete: operand must be an indexing expression xs[i]\n");
c.errs += 1;
} else {
let basetn: *node = exprtype(c, d.lhs, nil);
let u: *node = resolvealias(c, unwrapbang(basetn));
// harec check.c:2024 wording; a
// fixed-size [N]T base lands here.
if (u == nil || u.kind != nkind.N_TSLICE) {
cerr("delete must operate on a slice\n");
c.errs += 1;
};
};
};
};
let tn: *node = mktname(c, "void");
e.type_ = tinfofornode(c, tn): *void;
return tn;
};
};
let nm: str;
nm.ptr = nil; nm.len = 0;

View File

@@ -10424,12 +10424,14 @@ fn seedprimitives(c: *checker) void = {
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
// `nil`, `true`, `false` are keywords — handled at the lex/parser
// level, no symbol needed.
// `len`, `alloc`, `free`, `append` are pseudo-builtins; scopedefine
// them so their use sites resolve. The actual semantics live in cgen.
// `len`, `alloc`, `free`, `append`, `delete` are pseudo-builtins;
// scopedefine them so their use sites resolve. The actual semantics
// live in cgen.
scopedefine(c.top, "len", 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, "append", skind.SK_FN, nil, nil);
scopedefine(c.top, "delete", skind.SK_FN, nil, nil);
// #42: typed builtins folded to integer literals at check time —
// `size(T)` / `align(T)` (arg is a type-expression planted by the
// parser at lib/ww/parse/expr.ww:254-267) and `offset(e.f)` (arg is
@@ -12889,6 +12891,44 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
e.type_ = tinfofornode(c, tn): *void;
return tn;
};
// delete(xs[i]) — single-element slice removal, the
// delete-half of #35 (insert() deferred). Mirrors
// cstage cmd/wcc/check.c's delete arm. harec
// ref/harec/src/check.c:1981-2027 also accepts the
// range form delete(xs[i..j]) (EXPR_SLICE) —
// loud-rejected here, filed on #35 (regex fold-2b's
// consumers are all single-element).
if (streq(callee.str, "delete")) {
let d: *node = e.list;
if (d == nil || d.next != nil) {
cerr("delete: takes exactly one argument\n");
c.errs += 1;
};
if (d != nil) {
exprtype(c, d, nil);
if (d.kind == nkind.N_SLICE) {
cerr("delete: range form delete(xs[i..j]) unimplemented (task #35)\n");
c.errs += 1;
} else {
if (d.kind != nkind.N_INDEX) {
cerr("delete: operand must be an indexing expression xs[i]\n");
c.errs += 1;
} else {
let basetn: *node = exprtype(c, d.lhs, nil);
let u: *node = resolvealias(c, unwrapbang(basetn));
// harec check.c:2024 wording; a
// fixed-size [N]T base lands here.
if (u == nil || u.kind != nkind.N_TSLICE) {
cerr("delete must operate on a slice\n");
c.errs += 1;
};
};
};
};
let tn: *node = mktname(c, "void");
e.type_ = tinfofornode(c, tn): *void;
return tn;
};
};
let nm: str;
nm.ptr = nil; nm.len = 0;
@@ -24478,6 +24518,135 @@ fn cgappend(c: *cgen, n: *node) void = {
return;
};
// cgdelete — Hare `delete(xs[i])`: single-element slice removal, the
// delete-half of #35. Shift [i+1..len) down one stride, len -= 1, cap
// unchanged. The move is a same-type whole-stride byte copy: src and
// dst are elements of the SAME slice, so no boxing/coercion exists for
// any element kind (str/slice 24B header, tagged box, struct body) —
// one word-copy loop serves all kinds, unlike append's value-store
// dispatch (#34) which boxes from a foreign source. Ascending j keeps
// src (j+1) ahead of dst (j), the safe memmove-down direction. Mirrors
// cmd/w6c/cgen.c's N_CALL delete arm instruction-for-instruction
// (rule-10 byte-id).
fn cgdelete(c: *cgen, n: *node) void = {
let d: *node = n.list; // N_INDEX, checker-validated
let base: *node = d.lhs;
// esz off the STAMPED base tinfo (#34/#48 discipline — never the
// value node). Peel TY_NAMED on indexable AND element, mirroring
// cstage's type_chase_named on both (#8 family).
let sti: *tinfo = base.type_: *tinfo;
for (sti != nil && sti.kind == tykind.TY_NAMED) { sti = sti.under; };
let esub: *tinfo = nil;
if (sti != nil) { esub = sti.sub; };
for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; };
let esz: i32 = 0;
if (esub != nil) { esz = esub.size: i32; };
if (esz <= 0) {
let m35a: str = "#35: delete() element size unresolved (rule-7)\n";
os.write(2, m35a.ptr, m35a.len: u64);
os.exit(1);
};
let hdr_lea: bool = false;
let hdr_off: i32 = 0;
let hdr_ok: bool = false;
if (base.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, base.str);
if (lc != nil) {
hdr_lea = true;
hdr_off = lc.off;
hdr_ok = true;
};
};
// (*p)[i]: the header lives behind a local ptr-to-slice — the
// regex fold-2b delete_thread shape (threads: *[]thread).
if (!hdr_ok && base.kind == nkind.N_UN) {
if (base.op == tkind.TK_STAR && base.lhs != nil) {
if (base.lhs.kind == nkind.N_IDENT) {
let pc: *local = localfindnode(c, base.lhs.str);
if (pc != nil) {
hdr_off = pc.off;
hdr_ok = true;
};
};
};
};
if (!hdr_ok) {
let m35b: str = "#35: delete() base shape unsupported (rule-7: local slice ident or deref-of-local only)\n";
os.write(2, m35b.ptr, m35b.len: u64);
os.exit(1);
};
cgexpr(c, d.rhs); // AX = i
emitline("\tPUSHQ\tAX\n");
if (hdr_lea) {
emitline("\tLEAQ\t");
} else {
emitline("\tMOVQ\t");
};
emitoff(hdr_off: i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
let dll: str = mklabel(c, "del_l");
let dle: str = mklabel(c, "del_e");
emitlabel(dll);
emitline("\tMOVQ\t(SP), DX\n");
emitline("\tMOVQ\t8(SP), CX\n");
emitline("\tMOVQ\t8(DX), BX\n");
emitline("\tSUBQ\t$1, BX\n");
emitline("\tCMPQ\tBX, CX\n");
emitline("\tJGE\t"); emitline(dle); emitline("\n");
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", AX\n");
emitline("\tIMULQ\tAX, CX\n");
};
emitline("\tMOVQ\t(DX), BX\n");
emitline("\tADDQ\tCX, BX\n");
let dk: i32 = 0;
for (dk + 8 <= esz) {
emitline("\tMOVQ\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 8;
};
if (dk + 4 <= esz) {
emitline("\tMOVL\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVL\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 4;
};
if (dk + 2 <= esz) {
emitline("\tMOVW\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVW\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 2;
};
if (dk + 1 <= esz) {
emitline("\tMOVB\t");
emitdispreg((esz + dk): i64, "BX");
emitline(", AX\n");
emitline("\tMOVB\tAX, ");
emitdispreg(dk: i64, "BX");
emitline("\n");
dk += 1;
};
emitline("\tADDQ\t$1, 8(SP)\n");
emitline("\tJMP\t"); emitline(dll); emitline("\n");
emitlabel(dle);
emitline("\tMOVQ\t(SP), DX\n");
emitline("\tSUBQ\t$1, 8(DX)\n");
emitline("\tADDQ\t$16, SP\n");
};
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
@@ -24650,6 +24819,16 @@ fn cgcall(c: *cgen, n: *node) void = {
};
};
};
// delete(xs[i]) — #35 delete-half; mirror of cstage
// cgen.c's N_CALL delete arm.
if (streq(callee.str, "delete")) {
if (n.list != nil) {
if (n.list.next == nil) {
cgdelete(c, n);
return;
};
};
};
};
};