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

@@ -97,14 +97,15 @@ 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`, `delete` are pseudo-builtins;
// scopedefine them so their use sites resolve. The actual semantics
// live in cgen.
// `len`, `alloc`, `free`, `append`, `delete`, `insert` 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);
scopedefine(c.top, "insert", 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
@@ -2592,7 +2593,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
return tn;
};
// delete(xs[i]) — single-element slice removal, the
// delete-half of #35 (insert() deferred). Mirrors
// delete-half of #35 (insert() is the twin arm below). 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) —
@@ -2629,6 +2630,56 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
e.type_ = tinfofornode(c, tn): *void;
return tn;
};
// insert(xs[idx], v) — single-element slice insertion
// before idx, delete()'s twin (the insert-half of
// #35). Mirrors cstage cmd/wcc/check.c's insert arm.
// 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 spread form and the with-length
// form (harec :821/:837) stay filed on #35; a range
// PLACE is not Hare (harec asserts ACCESS_INDEX at
// :784) — rejected, no task cite.
if (streq(callee.str, "insert")) {
let d: *node = e.list;
if (d == nil || d.next == nil || d.next.next != nil) {
cerr("insert: takes exactly two arguments\n");
c.errs += 1;
};
if (d != nil) {
exprtype(c, d, nil);
if (d.kind == nkind.N_SLICE) {
cerr("insert: range place is invalid; operand must be an indexing expression xs[i]\n");
c.errs += 1;
} else {
if (d.kind != nkind.N_INDEX) {
cerr("insert: 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:807 wording; a
// fixed-size [N]T base lands here.
if (u == nil || u.kind != nkind.N_TSLICE) {
cerr("insert must operate on a slice\n");
c.errs += 1;
};
};
};
if (d.next != nil) {
if (d.next.kind == nkind.N_SPREAD) {
cerr("insert: spread form insert(xs[i], vs...) unimplemented (task #35)\n");
c.errs += 1;
} else {
exprtype(c, d.next, nil);
};
};
};
let tn: *node = mktname(c, "void");
e.type_ = tinfofornode(c, tn): *void;
return tn;
};
};
let nm: str;
nm.ptr = nil; nm.len = 0;