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

@@ -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;
};
};
};
};
};