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

Hare's insert(xs[idx], v) (ref/harec/src/check.c:745
check_expr_append_insert — append/insert share the checker arm,
"insert" at :786): checker accepts an INDEX place over a slice plus
one value, stamps void; idx == len is a legal end-insert (the
ref/hare os/exec/platform_cmd.ha:86 idiom). Loud-rejects with exact
texts: spread form insert(xs[i], vs...) (filed, #35 — also covers
harec's with-length form via the arity check), range place (not
Hare; harec only parses ACCESS_INDEX, :784), non-index operands,
array bases, wrong arity. delete()-parity throughout.

Lowering (both stages, converged byte-identical by construction) is
a DESUGAR: append(xs, v) — reusing append's grow (rt_ensure) and the
entire #34 value-store dispatch (scalar / str-slice header / tagged
widen / struct fill) verbatim, one boxing choke-point — lands v at
slot len-1; then a rotate-right of [idx, len) moves it home through
a fresh per-site esz frame scratch (@insscr). The rotate is delete's
shift loop in reverse (descending j, the safe memmove-up direction)
and is a same-slice whole-stride raw byte move — no boxing exists
for any element kind. idx evaluates BEFORE the grow (Hare's
left-to-right operand order — pinned by the pregrow_len_idx row,
insert(xs[len(xs)-1], v): pre-grow [7,13,11] vs post-grow [7,11,13];
an idx==len(xs) end-insert cannot discriminate, the rotate
degenerates either way). Base shapes: local slice ident (LEAQ) and
deref-of-local ptr-to-slice (MOVQ); others rule-7 loud-stop, like
delete.

test/807: 57 fixtures — front/middle/end + idx==len via len(xs) +
the pre-grow eval-order pin, esz 1/2/4/8/16/24/56 (MOVB/MOVW/MOVL
tails, struct body, str header, 7-qword tagged from a typed local
[the regex fold-3 ha:347 newinst shape] and from a cast rvalue
[ha:419/441]), empty-slice grow, (*p)[i] deref base, front-insert
loop, 6 checker reject rows with diagnostic-text checks; every
accept row cs==ww asm byte-id.
This commit is contained in:
2026-06-04 16:50:22 +09:00
parent 48904e5ef3
commit 9861f73bbb
8 changed files with 1802 additions and 13 deletions

View File

@@ -6839,6 +6839,201 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_ADDQ, aimm(16), areg(D_SP));
break;
}
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
strcmp(n->lhs->str, "insert") == 0 && n->list &&
n->list->next && n->list->next->next == NULL) {
/* insert(xs[idx], v) — delete()'s twin, the
* insert-half of #35: insert v BEFORE idx, idx==len
* is a legal end-insert. Lowered as a DESUGAR to
* append(xs, v) + a rotate-right of [idx, len):
* the append arm below contributes grow (rt_ensure)
* and the whole #34 value-store dispatch (scalar /
* str-slice header / tagged widen / struct fill)
* verbatim — one boxing choke-point, byte-id by
* construction — landing v at slot len-1; the
* rotate then moves it home through an esz frame
* scratch. The rotate is delete's shift loop in
* reverse (descending j keeps src j behind dst j+1,
* the safe memmove-up direction) and, like
* delete's, is a same-slice whole-stride raw byte
* move — no boxing exists for any element kind.
* idx evaluates BEFORE the grow (Hare's
* left-to-right operand order: insert(xs[len(xs)],
* v) sees the pre-grow len); v's evaluation point
* inherits append's per-kind rules. Bounds are
* implicit (no index check, matching delete).
*
* ; AX = idx (cgexpr) ; PUSHQ AX
* ; ...append(xs, v) body (grow + store at end,
* ; push-balanced)...
* ; LEAQ/MOVQ off(BP), AX ; &hdr (ident / *p)
* ; PUSHQ AX ; (SP)=&hdr 8(SP)=idx
* ; save elem[len-1] -> @insscr (word copy)
* ; MOVQ 8(DX), AX ; SUBQ $2, AX ; PUSHQ AX
* ; ; (SP)=j=len-2
* ; ins_l:
* ; MOVQ (SP), CX ; MOVQ 16(SP), DX
* ; CMPQ DX, CX ; JL ins_e ; j < idx
* ; [IMULQ esz, CX]
* ; MOVQ 8(SP), DX ; MOVQ (DX), BX ; ADDQ CX, BX
* ; word-copy esz bytes (BX) -> esz(BX)
* ; SUBQ $1, (SP) ; JMP ins_l
* ; ins_e:
* ; store @insscr -> elem[idx] (word copy)
* ; ADDQ $24, SP */
Node *d = n->list; /* N_INDEX, checker-validated */
Node *base = d->lhs;
Node *v = d->next;
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: insert() 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]: header behind a local ptr-to-slice —
* delete's regex_shape twin. */
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: insert() base shape unsupported "
"(rule-7: local slice ident or "
"deref-of-local only)");
/* Fresh esz-sized slot per SITE (esz varies; an
* @-name dedup would mis-share across element
* types). */
int ins_scr = local_alloc(c, &locals, "@insscr",
esz, cg_frame);
cgexpr(c, d->rhs, locals); /* AX = idx */
ins1(c, A_PUSHQ, areg(D_AX));
/* Desugar in place and re-dispatch into the append
* arm: cgen is single-pass, base is an lhs node
* (never on a sibling chain), and the checker has
* already validated this call — the mutation is
* dead after this emission. */
n->lhs->str = "append";
n->list = base;
base->next = v;
cgexpr(c, n, locals);
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));
ins2(c, A_MOVQ, areg(D_AX), areg(D_DX));
ins2(c, A_MOVQ, amem(D_DX, 8), areg(D_CX));
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
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 ik = 0;
for (; ik + 8 <= esz; ik += 8) {
ins2(c, A_MOVQ, amem(D_BX, ik), areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, ins_scr + ik));
}
if (ik + 4 <= esz) {
ins2(c, A_MOVL, amem(D_BX, ik), areg(D_AX));
ins2(c, A_MOVL, areg(D_AX), amem(D_BP, ins_scr + ik));
ik += 4;
}
if (ik + 2 <= esz) {
ins2(c, A_MOVW, amem(D_BX, ik), areg(D_AX));
ins2(c, A_MOVW, areg(D_AX), amem(D_BP, ins_scr + ik));
ik += 2;
}
if (ik + 1 <= esz) {
ins2(c, A_MOVB, amem(D_BX, ik), areg(D_AX));
ins2(c, A_MOVB, areg(D_AX), amem(D_BP, ins_scr + ik));
ik += 1;
}
ins2(c, A_MOVQ, amem(D_DX, 8), areg(D_AX));
ins2(c, A_SUBQ, aimm(2), areg(D_AX));
ins1(c, A_PUSHQ, areg(D_AX));
char *ill = mklabel(c, "ins_l");
char *ile = mklabel(c, "ins_e");
label(c, ill);
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_CX));
ins2(c, A_MOVQ, amem(D_SP, 16), areg(D_DX));
ins2(c, A_CMPQ, areg(D_DX), areg(D_CX));
ins1(c, A_JL, abranch(ile));
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_SP, 8), areg(D_DX));
ins2(c, A_MOVQ, amem(D_DX, 0), areg(D_BX));
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
ik = 0;
for (; ik + 8 <= esz; ik += 8) {
ins2(c, A_MOVQ, amem(D_BX, ik), areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, esz + ik));
}
if (ik + 4 <= esz) {
ins2(c, A_MOVL, amem(D_BX, ik), areg(D_AX));
ins2(c, A_MOVL, areg(D_AX), amem(D_BX, esz + ik));
ik += 4;
}
if (ik + 2 <= esz) {
ins2(c, A_MOVW, amem(D_BX, ik), areg(D_AX));
ins2(c, A_MOVW, areg(D_AX), amem(D_BX, esz + ik));
ik += 2;
}
if (ik + 1 <= esz) {
ins2(c, A_MOVB, amem(D_BX, ik), areg(D_AX));
ins2(c, A_MOVB, areg(D_AX), amem(D_BX, esz + ik));
ik += 1;
}
ins2(c, A_SUBQ, aimm(1), amem(D_SP, 0));
ins1(c, A_JMP, abranch(ill));
label(c, ile);
ins2(c, A_MOVQ, amem(D_SP, 16), areg(D_CX));
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_SP, 8), areg(D_DX));
ins2(c, A_MOVQ, amem(D_DX, 0), areg(D_BX));
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
ik = 0;
for (; ik + 8 <= esz; ik += 8) {
ins2(c, A_MOVQ, amem(D_BP, ins_scr + ik), areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, ik));
}
if (ik + 4 <= esz) {
ins2(c, A_MOVL, amem(D_BP, ins_scr + ik), areg(D_AX));
ins2(c, A_MOVL, areg(D_AX), amem(D_BX, ik));
ik += 4;
}
if (ik + 2 <= esz) {
ins2(c, A_MOVW, amem(D_BP, ins_scr + ik), areg(D_AX));
ins2(c, A_MOVW, areg(D_AX), amem(D_BX, ik));
ik += 2;
}
if (ik + 1 <= esz) {
ins2(c, A_MOVB, amem(D_BP, ins_scr + ik), areg(D_AX));
ins2(c, A_MOVB, areg(D_AX), amem(D_BX, ik));
ik += 1;
}
ins2(c, A_ADDQ, aimm(24), 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

@@ -1436,7 +1436,7 @@ cexpr(Checker *c, Node *n)
return n->type;
}
/* delete(xs[i]) — single-element slice removal, the
* delete-half of #35 (insert() stays deferred). harec
* delete-half of #35 (insert() is the twin arm below). 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);
@@ -1466,6 +1466,50 @@ cexpr(Checker *c, Node *n)
n->lhs->type = ty_err;
return n->type;
}
/* insert(xs[idx], v) — single-element slice insertion
* before idx, delete()'s twin (the insert-half of #35).
* harec models append/insert in ONE checker arm
* (ref/harec/src/check.c:745 check_expr_append_insert;
* "insert" at :786): operand 1 must be an indexing place
* over a slice; idx == len is a legal end-insert (the
* ref/hare os/exec platform_cmd.ha:86 idiom). The spread
* form insert(xs[i], vs...) and the with-length form
* (harec :821/:837) stay filed on #35 — regex fold-3's
* consumers are all single-value. A range PLACE is not
* Hare (harec asserts ACCESS_INDEX at :784; the form
* never parses there) — rejected, no task cite. */
if (n->lhs && n->lhs->kind == N_IDENT &&
n->lhs->str && strcmp(n->lhs->str, "insert") == 0) {
Node *d = n->list;
if (d == NULL || d->next == NULL ||
d->next->next != NULL)
err(c, n->pos, "insert: takes exactly two arguments");
if (d != NULL) {
(void)cexpr(c, d);
if (d->kind == N_SLICE)
err(c, n->pos, "insert: range place is invalid; operand must be an indexing expression xs[i]");
else if (d->kind != N_INDEX)
err(c, n->pos, "insert: 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:807 wording; a
* fixed-size [N]T base lands here. */
if (bt == NULL || bt->kind != TY_SLICE)
err(c, n->pos, "insert must operate on a slice");
}
if (d->next != NULL) {
if (d->next->kind == N_SPREAD)
err(c, n->pos, "insert: spread form insert(xs[i], vs...) unimplemented (task #35)");
else
(void)cexpr(c, d->next);
}
}
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