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

@@ -6252,6 +6252,119 @@ cgexpr(Cg *c, Node *n, Local *locals)
cgexpr(c, n->list, locals);
break;
}
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
strcmp(n->lhs->str, "delete") == 0 && n->list &&
n->list->next == NULL) {
/* 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. Bounds are implicit
* (no index check, matching the rest of cgen).
*
* ; AX = i (cgexpr)
* ; PUSHQ AX ; 8(SP) = j counter
* ; LEAQ/MOVQ off(BP), AX ; &hdr (ident / *p base)
* ; PUSHQ AX ; (SP) = &hdr
* ; del_l:
* ; MOVQ (SP), DX ; MOVQ 8(SP), CX
* ; MOVQ 8(DX), BX ; SUBQ $1, BX
* ; CMPQ BX, CX ; JGE del_e ; j >= len-1
* ; [IMULQ esz, CX]
* ; MOVQ (DX), BX ; ADDQ CX, BX ; BX = dst
* ; word-copy esz bytes esz(BX) -> (BX)
* ; ADDQ $1, 8(SP) ; JMP del_l
* ; del_e:
* ; MOVQ (SP), DX ; SUBQ $1, 8(DX)
* ; ADDQ $16, SP */
Node *d = n->list; /* N_INDEX, checker-validated */
Node *base = d->lhs;
Type *su = type_chase_named(base->type);
Type *esub = (su && su->sub)
? type_chase_named(su->sub) : NULL;
int esz = esub ? (int)esub->size : 0;
if (esz <= 0)
fatal("#35: delete() element size unresolved "
"(rule-7)");
int hdr_lea = 0;
int hdr_off = 0;
int hdr_ok = 0;
if (base->kind == N_IDENT &&
localfind(locals, base->str) != 0) {
hdr_lea = 1;
hdr_off = localfind(locals, base->str);
hdr_ok = 1;
}
/* (*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 == N_UN &&
base->op == TK_STAR && base->lhs &&
base->lhs->kind == N_IDENT &&
localfind(locals, base->lhs->str) != 0) {
hdr_off = localfind(locals, base->lhs->str);
hdr_ok = 1;
}
if (!hdr_ok)
fatal("#35: delete() base shape unsupported "
"(rule-7: local slice ident or "
"deref-of-local only)");
cgexpr(c, d->rhs, locals); /* AX = i */
ins1(c, A_PUSHQ, areg(D_AX));
if (hdr_lea)
ins2(c, A_LEAQ, amem(D_BP, hdr_off), areg(D_AX));
else
ins2(c, A_MOVQ, amem(D_BP, hdr_off), areg(D_AX));
ins1(c, A_PUSHQ, areg(D_AX));
char *dll = mklabel(c, "del_l");
char *dle = mklabel(c, "del_e");
label(c, dll);
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_DX));
ins2(c, A_MOVQ, amem(D_SP, 8), areg(D_CX));
ins2(c, A_MOVQ, amem(D_DX, 8), areg(D_BX));
ins2(c, A_SUBQ, aimm(1), areg(D_BX));
ins2(c, A_CMPQ, areg(D_BX), areg(D_CX));
ins1(c, A_JGE, abranch(dle));
if (esz > 1) {
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
}
ins2(c, A_MOVQ, amem(D_DX, 0), areg(D_BX));
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
int dk = 0;
for (; dk + 8 <= esz; dk += 8) {
ins2(c, A_MOVQ, amem(D_BX, esz + dk), areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, dk));
}
if (dk + 4 <= esz) {
ins2(c, A_MOVL, amem(D_BX, esz + dk), areg(D_AX));
ins2(c, A_MOVL, areg(D_AX), amem(D_BX, dk));
dk += 4;
}
if (dk + 2 <= esz) {
ins2(c, A_MOVW, amem(D_BX, esz + dk), areg(D_AX));
ins2(c, A_MOVW, areg(D_AX), amem(D_BX, dk));
dk += 2;
}
if (dk + 1 <= esz) {
ins2(c, A_MOVB, amem(D_BX, esz + dk), areg(D_AX));
ins2(c, A_MOVB, areg(D_AX), amem(D_BX, dk));
dk += 1;
}
ins2(c, A_ADDQ, aimm(1), amem(D_SP, 8));
ins1(c, A_JMP, abranch(dll));
label(c, dle);
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_DX));
ins2(c, A_SUBQ, aimm(1), amem(D_DX, 8));
ins2(c, A_ADDQ, aimm(16), areg(D_SP));
break;
}
if (n->lhs && n->lhs->kind == N_IDENT &&
n->lhs->type == ty_err &&
n->lhs->str && strcmp(n->lhs->str, "alloc") == 0 &&

View File

@@ -1435,6 +1435,37 @@ cexpr(Checker *c, Node *n)
n->lhs->type = ty_err;
return n->type;
}
/* delete(xs[i]) — single-element slice removal, the
* delete-half of #35 (insert() stays deferred). harec
* ref/harec/src/check.c:1981-2027 also accepts the range
* form delete(xs[i..j]) (EXPR_SLICE) — out of scope here
* (regex fold-2b's consumers are all single-element);
* loud-rejected below, the range form stays filed on #35. */
if (n->lhs && n->lhs->kind == N_IDENT &&
n->lhs->str && strcmp(n->lhs->str, "delete") == 0) {
Node *d = n->list;
if (d == NULL || d->next != NULL)
err(c, n->pos, "delete: takes exactly one argument");
if (d != NULL) {
(void)cexpr(c, d);
if (d->kind == N_SLICE)
err(c, n->pos, "delete: range form delete(xs[i..j]) unimplemented (task #35)");
else if (d->kind != N_INDEX)
err(c, n->pos, "delete: operand must be an indexing expression xs[i]");
else {
Type *bt = d->lhs ? d->lhs->type : NULL;
while (bt && bt->kind == TY_NAMED)
bt = bt->under;
/* harec check.c:2024 wording; a
* fixed-size [N]T base lands here. */
if (bt == NULL || bt->kind != TY_SLICE)
err(c, n->pos, "delete must operate on a slice");
}
}
n->type = ty_void;
n->lhs->type = ty_err;
return n->type;
}
/* assert(cond[, msg]) / abort([msg]) — runtime checks that
* call into rt_abort. msg must be a str when present.
* Only treated as builtins when no user symbol shadows the