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:
14
Makefile
14
Makefile
@@ -390,6 +390,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_tuple_sret_receive_run \
|
$(BIN)/test_tuple_sret_receive_run \
|
||||||
$(BIN)/test_append_wide_elem \
|
$(BIN)/test_append_wide_elem \
|
||||||
$(BIN)/test_delete_elem \
|
$(BIN)/test_delete_elem \
|
||||||
|
$(BIN)/test_insert_elem \
|
||||||
$(BIN)/test_placeaddr_store \
|
$(BIN)/test_placeaddr_store \
|
||||||
$(BIN)/test_tryprop_multisuccess \
|
$(BIN)/test_tryprop_multisuccess \
|
||||||
$(BIN)/test_append_place \
|
$(BIN)/test_append_place \
|
||||||
@@ -1015,6 +1016,19 @@ $(BIN)/test_delete_elem: test/wcc/804_delete_elem.c \
|
|||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
# #35 (insert-half): insert(xs[idx], v) single-element slice insertion —
|
||||||
|
# delete()'s twin, lowered as append-desugar + rotate-right. Runtime rows
|
||||||
|
# across every element kind (scalar/narrow/str/struct/56B tagged incl.
|
||||||
|
# the regex fold-3 typed-local + cast-rvalue consumer shapes) + idx==len
|
||||||
|
# end-insert + pre-grow idx evaluation + the deref-of-local base +
|
||||||
|
# checker reject rows w/ exact diagnostic text + cs==ww asm byte-id
|
||||||
|
# (the converged-by-construction rotate loop + @insscr frame slot).
|
||||||
|
$(BIN)/test_insert_elem: test/wcc/807_insert_elem.c \
|
||||||
|
$(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||||
|
$(BIN)/ww_ww \
|
||||||
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
# F6 (task #4, regex fold-2b): (*ts)[i].field = v / OP= v through the
|
# F6 (task #4, regex fold-2b): (*ts)[i].field = v / OP= v through the
|
||||||
# cgplaceaddr resolver — runtime rows across widths/signedness/str +
|
# cgplaceaddr resolver — runtime rows across widths/signedness/str +
|
||||||
# compound ops + loud-tail reject rows w/ exact diagnostic text + cs==ww
|
# compound ops + loud-tail reject rows w/ exact diagnostic text + cs==ww
|
||||||
|
|||||||
195
cmd/w6c/cgen.c
195
cmd/w6c/cgen.c
@@ -6839,6 +6839,201 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ins2(c, A_ADDQ, aimm(16), areg(D_SP));
|
ins2(c, A_ADDQ, aimm(16), areg(D_SP));
|
||||||
break;
|
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 &&
|
if (n->lhs && n->lhs->kind == N_IDENT &&
|
||||||
n->lhs->type == ty_err &&
|
n->lhs->type == ty_err &&
|
||||||
n->lhs->str && strcmp(n->lhs->str, "alloc") == 0 &&
|
n->lhs->str && strcmp(n->lhs->str, "alloc") == 0 &&
|
||||||
|
|||||||
@@ -1436,7 +1436,7 @@ cexpr(Checker *c, Node *n)
|
|||||||
return n->type;
|
return n->type;
|
||||||
}
|
}
|
||||||
/* delete(xs[i]) — single-element slice removal, the
|
/* 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
|
* ref/harec/src/check.c:1981-2027 also accepts the range
|
||||||
* form delete(xs[i..j]) (EXPR_SLICE) — out of scope here
|
* form delete(xs[i..j]) (EXPR_SLICE) — out of scope here
|
||||||
* (regex fold-2b's consumers are all single-element);
|
* (regex fold-2b's consumers are all single-element);
|
||||||
@@ -1466,6 +1466,50 @@ cexpr(Checker *c, Node *n)
|
|||||||
n->lhs->type = ty_err;
|
n->lhs->type = ty_err;
|
||||||
return n->type;
|
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
|
/* assert(cond[, msg]) / abort([msg]) — runtime checks that
|
||||||
* call into rt_abort. msg must be a str when present.
|
* call into rt_abort. msg must be a str when present.
|
||||||
* Only treated as builtins when no user symbol shadows the
|
* Only treated as builtins when no user symbol shadows the
|
||||||
|
|||||||
@@ -10424,14 +10424,15 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
|
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free`, `append`, `delete` are pseudo-builtins;
|
// `len`, `alloc`, `free`, `append`, `delete`, `insert` are
|
||||||
// scopedefine them so their use sites resolve. The actual semantics
|
// pseudo-builtins; scopedefine them so their use sites resolve.
|
||||||
// live in cgen.
|
// The actual semantics live in cgen.
|
||||||
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "alloc", 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, "free", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "append", 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, "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 —
|
// #42: typed builtins folded to integer literals at check time —
|
||||||
// `size(T)` / `align(T)` (arg is a type-expression planted by the
|
// `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
|
// parser at lib/ww/parse/expr.ww:254-267) and `offset(e.f)` (arg is
|
||||||
@@ -12919,7 +12920,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
|||||||
return tn;
|
return tn;
|
||||||
};
|
};
|
||||||
// delete(xs[i]) — single-element slice removal, the
|
// 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
|
// cstage cmd/wcc/check.c's delete arm. harec
|
||||||
// ref/harec/src/check.c:1981-2027 also accepts the
|
// ref/harec/src/check.c:1981-2027 also accepts the
|
||||||
// range form delete(xs[i..j]) (EXPR_SLICE) —
|
// range form delete(xs[i..j]) (EXPR_SLICE) —
|
||||||
@@ -12956,6 +12957,56 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
|||||||
e.type_ = tinfofornode(c, tn): *void;
|
e.type_ = tinfofornode(c, tn): *void;
|
||||||
return tn;
|
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;
|
let nm: str;
|
||||||
nm.ptr = nil; nm.len = 0;
|
nm.ptr = nil; nm.len = 0;
|
||||||
@@ -25439,6 +25490,250 @@ fn cgdelete(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tADDQ\t$16, SP\n");
|
emitline("\tADDQ\t$16, SP\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// cginsert — Hare `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): cgappend
|
||||||
|
// 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 cgdelete'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). Mirrors cmd/w6c/
|
||||||
|
// cgen.c's N_CALL insert arm instruction-for-instruction (rule-10
|
||||||
|
// byte-id).
|
||||||
|
fn cginsert(c: *cgen, n: *node) void = {
|
||||||
|
let d: *node = n.list; // N_INDEX, checker-validated
|
||||||
|
let base: *node = d.lhs;
|
||||||
|
let v: *node = d.next;
|
||||||
|
// 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 m35c: str = "#35: insert() element size unresolved (rule-7)\n";
|
||||||
|
os.write(2, m35c.ptr, m35c.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 —
|
||||||
|
// delete's regex_shape twin.
|
||||||
|
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 m35d: str = "#35: insert() base shape unsupported (rule-7: local slice ident or deref-of-local only)\n";
|
||||||
|
os.write(2, m35d.ptr, m35d.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
// Fresh esz-sized slot per SITE (esz varies; an @-name dedup
|
||||||
|
// would mis-share across element types). Allocated BEFORE the
|
||||||
|
// cgappend body's own scratch allocs — cstage order.
|
||||||
|
let insscr: i32 = localalloc(c, "@insscr", esz, nil);
|
||||||
|
cgexpr(c, d.rhs); // AX = idx
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
// Desugar in place and route through cgappend: 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. The callee rename keeps the AST
|
||||||
|
// consistent with cstage's re-dispatch.
|
||||||
|
n.lhs.str = "append";
|
||||||
|
n.list = base;
|
||||||
|
base.next = v;
|
||||||
|
cgappend(c, n);
|
||||||
|
if (hdr_lea) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
};
|
||||||
|
emitoff(hdr_off: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tMOVQ\tAX, DX\n");
|
||||||
|
emitline("\tMOVQ\t8(DX), CX\n");
|
||||||
|
emitline("\tSUBQ\t$1, CX\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 ik: i32 = 0;
|
||||||
|
for (ik + 8 <= esz) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 8;
|
||||||
|
};
|
||||||
|
if (ik + 4 <= esz) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 4;
|
||||||
|
};
|
||||||
|
if (ik + 2 <= esz) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 2;
|
||||||
|
};
|
||||||
|
if (ik + 1 <= esz) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 1;
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t8(DX), AX\n");
|
||||||
|
emitline("\tSUBQ\t$2, AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
let ill: str = mklabel(c, "ins_l");
|
||||||
|
let ile: str = mklabel(c, "ins_e");
|
||||||
|
emitlabel(ill);
|
||||||
|
emitline("\tMOVQ\t(SP), CX\n");
|
||||||
|
emitline("\tMOVQ\t16(SP), DX\n");
|
||||||
|
emitline("\tCMPQ\tDX, CX\n");
|
||||||
|
emitline("\tJL\t"); emitline(ile); emitline("\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t8(SP), DX\n");
|
||||||
|
emitline("\tMOVQ\t(DX), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
ik = 0;
|
||||||
|
for (ik + 8 <= esz) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 8;
|
||||||
|
};
|
||||||
|
if (ik + 4 <= esz) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 4;
|
||||||
|
};
|
||||||
|
if (ik + 2 <= esz) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 2;
|
||||||
|
};
|
||||||
|
if (ik + 1 <= esz) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 1;
|
||||||
|
};
|
||||||
|
emitline("\tSUBQ\t$1, (SP)\n");
|
||||||
|
emitline("\tJMP\t"); emitline(ill); emitline("\n");
|
||||||
|
emitlabel(ile);
|
||||||
|
emitline("\tMOVQ\t16(SP), CX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t8(SP), DX\n");
|
||||||
|
emitline("\tMOVQ\t(DX), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
ik = 0;
|
||||||
|
for (ik + 8 <= esz) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 8;
|
||||||
|
};
|
||||||
|
if (ik + 4 <= esz) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 4;
|
||||||
|
};
|
||||||
|
if (ik + 2 <= esz) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 2;
|
||||||
|
};
|
||||||
|
if (ik + 1 <= esz) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 1;
|
||||||
|
};
|
||||||
|
emitline("\tADDQ\t$24, SP\n");
|
||||||
|
};
|
||||||
|
|
||||||
fn cgcall(c: *cgen, n: *node) void = {
|
fn cgcall(c: *cgen, n: *node) void = {
|
||||||
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
||||||
// special-cased before pushargsrev so the spread variant can run
|
// special-cased before pushargsrev so the spread variant can run
|
||||||
@@ -25644,6 +25939,18 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// insert(xs[idx], v) — #35 insert-half; mirror of
|
||||||
|
// cstage cgen.c's N_CALL insert arm.
|
||||||
|
if (streq(callee.str, "insert")) {
|
||||||
|
if (n.list != nil) {
|
||||||
|
if (n.list.next != nil) {
|
||||||
|
if (n.list.next.next == nil) {
|
||||||
|
cginsert(c, n);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5272,6 +5272,250 @@ fn cgdelete(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tADDQ\t$16, SP\n");
|
emitline("\tADDQ\t$16, SP\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// cginsert — Hare `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): cgappend
|
||||||
|
// 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 cgdelete'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). Mirrors cmd/w6c/
|
||||||
|
// cgen.c's N_CALL insert arm instruction-for-instruction (rule-10
|
||||||
|
// byte-id).
|
||||||
|
fn cginsert(c: *cgen, n: *node) void = {
|
||||||
|
let d: *node = n.list; // N_INDEX, checker-validated
|
||||||
|
let base: *node = d.lhs;
|
||||||
|
let v: *node = d.next;
|
||||||
|
// 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 m35c: str = "#35: insert() element size unresolved (rule-7)\n";
|
||||||
|
os.write(2, m35c.ptr, m35c.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 —
|
||||||
|
// delete's regex_shape twin.
|
||||||
|
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 m35d: str = "#35: insert() base shape unsupported (rule-7: local slice ident or deref-of-local only)\n";
|
||||||
|
os.write(2, m35d.ptr, m35d.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
// Fresh esz-sized slot per SITE (esz varies; an @-name dedup
|
||||||
|
// would mis-share across element types). Allocated BEFORE the
|
||||||
|
// cgappend body's own scratch allocs — cstage order.
|
||||||
|
let insscr: i32 = localalloc(c, "@insscr", esz, nil);
|
||||||
|
cgexpr(c, d.rhs); // AX = idx
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
// Desugar in place and route through cgappend: 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. The callee rename keeps the AST
|
||||||
|
// consistent with cstage's re-dispatch.
|
||||||
|
n.lhs.str = "append";
|
||||||
|
n.list = base;
|
||||||
|
base.next = v;
|
||||||
|
cgappend(c, n);
|
||||||
|
if (hdr_lea) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
};
|
||||||
|
emitoff(hdr_off: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tMOVQ\tAX, DX\n");
|
||||||
|
emitline("\tMOVQ\t8(DX), CX\n");
|
||||||
|
emitline("\tSUBQ\t$1, CX\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 ik: i32 = 0;
|
||||||
|
for (ik + 8 <= esz) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 8;
|
||||||
|
};
|
||||||
|
if (ik + 4 <= esz) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 4;
|
||||||
|
};
|
||||||
|
if (ik + 2 <= esz) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 2;
|
||||||
|
};
|
||||||
|
if (ik + 1 <= esz) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 1;
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t8(DX), AX\n");
|
||||||
|
emitline("\tSUBQ\t$2, AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
let ill: str = mklabel(c, "ins_l");
|
||||||
|
let ile: str = mklabel(c, "ins_e");
|
||||||
|
emitlabel(ill);
|
||||||
|
emitline("\tMOVQ\t(SP), CX\n");
|
||||||
|
emitline("\tMOVQ\t16(SP), DX\n");
|
||||||
|
emitline("\tCMPQ\tDX, CX\n");
|
||||||
|
emitline("\tJL\t"); emitline(ile); emitline("\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t8(SP), DX\n");
|
||||||
|
emitline("\tMOVQ\t(DX), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
ik = 0;
|
||||||
|
for (ik + 8 <= esz) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 8;
|
||||||
|
};
|
||||||
|
if (ik + 4 <= esz) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 4;
|
||||||
|
};
|
||||||
|
if (ik + 2 <= esz) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 2;
|
||||||
|
};
|
||||||
|
if (ik + 1 <= esz) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 1;
|
||||||
|
};
|
||||||
|
emitline("\tSUBQ\t$1, (SP)\n");
|
||||||
|
emitline("\tJMP\t"); emitline(ill); emitline("\n");
|
||||||
|
emitlabel(ile);
|
||||||
|
emitline("\tMOVQ\t16(SP), CX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t8(SP), DX\n");
|
||||||
|
emitline("\tMOVQ\t(DX), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
ik = 0;
|
||||||
|
for (ik + 8 <= esz) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 8;
|
||||||
|
};
|
||||||
|
if (ik + 4 <= esz) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 4;
|
||||||
|
};
|
||||||
|
if (ik + 2 <= esz) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 2;
|
||||||
|
};
|
||||||
|
if (ik + 1 <= esz) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 1;
|
||||||
|
};
|
||||||
|
emitline("\tADDQ\t$24, SP\n");
|
||||||
|
};
|
||||||
|
|
||||||
fn cgcall(c: *cgen, n: *node) void = {
|
fn cgcall(c: *cgen, n: *node) void = {
|
||||||
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
||||||
// special-cased before pushargsrev so the spread variant can run
|
// special-cased before pushargsrev so the spread variant can run
|
||||||
@@ -5477,6 +5721,18 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// insert(xs[idx], v) — #35 insert-half; mirror of
|
||||||
|
// cstage cgen.c's N_CALL insert arm.
|
||||||
|
if (streq(callee.str, "insert")) {
|
||||||
|
if (n.list != nil) {
|
||||||
|
if (n.list.next != nil) {
|
||||||
|
if (n.list.next.next == nil) {
|
||||||
|
cginsert(c, n);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -97,14 +97,15 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
|
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free`, `append`, `delete` are pseudo-builtins;
|
// `len`, `alloc`, `free`, `append`, `delete`, `insert` are
|
||||||
// scopedefine them so their use sites resolve. The actual semantics
|
// pseudo-builtins; scopedefine them so their use sites resolve.
|
||||||
// live in cgen.
|
// The actual semantics live in cgen.
|
||||||
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "alloc", 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, "free", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "append", 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, "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 —
|
// #42: typed builtins folded to integer literals at check time —
|
||||||
// `size(T)` / `align(T)` (arg is a type-expression planted by the
|
// `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
|
// 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;
|
return tn;
|
||||||
};
|
};
|
||||||
// delete(xs[i]) — single-element slice removal, the
|
// 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
|
// cstage cmd/wcc/check.c's delete arm. harec
|
||||||
// ref/harec/src/check.c:1981-2027 also accepts the
|
// ref/harec/src/check.c:1981-2027 also accepts the
|
||||||
// range form delete(xs[i..j]) (EXPR_SLICE) —
|
// 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;
|
e.type_ = tinfofornode(c, tn): *void;
|
||||||
return tn;
|
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;
|
let nm: str;
|
||||||
nm.ptr = nil; nm.len = 0;
|
nm.ptr = nil; nm.len = 0;
|
||||||
|
|||||||
@@ -10424,14 +10424,15 @@ fn seedprimitives(c: *checker) void = {
|
|||||||
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
|
scopedefine(c.top, "nomem", skind.SK_TYPE, nil, nomemdecl);
|
||||||
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
// `nil`, `true`, `false` are keywords — handled at the lex/parser
|
||||||
// level, no symbol needed.
|
// level, no symbol needed.
|
||||||
// `len`, `alloc`, `free`, `append`, `delete` are pseudo-builtins;
|
// `len`, `alloc`, `free`, `append`, `delete`, `insert` are
|
||||||
// scopedefine them so their use sites resolve. The actual semantics
|
// pseudo-builtins; scopedefine them so their use sites resolve.
|
||||||
// live in cgen.
|
// The actual semantics live in cgen.
|
||||||
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
scopedefine(c.top, "len", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "alloc", 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, "free", skind.SK_FN, nil, nil);
|
||||||
scopedefine(c.top, "append", 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, "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 —
|
// #42: typed builtins folded to integer literals at check time —
|
||||||
// `size(T)` / `align(T)` (arg is a type-expression planted by the
|
// `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
|
// parser at lib/ww/parse/expr.ww:254-267) and `offset(e.f)` (arg is
|
||||||
@@ -12919,7 +12920,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
|||||||
return tn;
|
return tn;
|
||||||
};
|
};
|
||||||
// delete(xs[i]) — single-element slice removal, the
|
// 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
|
// cstage cmd/wcc/check.c's delete arm. harec
|
||||||
// ref/harec/src/check.c:1981-2027 also accepts the
|
// ref/harec/src/check.c:1981-2027 also accepts the
|
||||||
// range form delete(xs[i..j]) (EXPR_SLICE) —
|
// range form delete(xs[i..j]) (EXPR_SLICE) —
|
||||||
@@ -12956,6 +12957,56 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
|||||||
e.type_ = tinfofornode(c, tn): *void;
|
e.type_ = tinfofornode(c, tn): *void;
|
||||||
return tn;
|
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;
|
let nm: str;
|
||||||
nm.ptr = nil; nm.len = 0;
|
nm.ptr = nil; nm.len = 0;
|
||||||
@@ -25439,6 +25490,250 @@ fn cgdelete(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tADDQ\t$16, SP\n");
|
emitline("\tADDQ\t$16, SP\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// cginsert — Hare `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): cgappend
|
||||||
|
// 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 cgdelete'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). Mirrors cmd/w6c/
|
||||||
|
// cgen.c's N_CALL insert arm instruction-for-instruction (rule-10
|
||||||
|
// byte-id).
|
||||||
|
fn cginsert(c: *cgen, n: *node) void = {
|
||||||
|
let d: *node = n.list; // N_INDEX, checker-validated
|
||||||
|
let base: *node = d.lhs;
|
||||||
|
let v: *node = d.next;
|
||||||
|
// 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 m35c: str = "#35: insert() element size unresolved (rule-7)\n";
|
||||||
|
os.write(2, m35c.ptr, m35c.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 —
|
||||||
|
// delete's regex_shape twin.
|
||||||
|
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 m35d: str = "#35: insert() base shape unsupported (rule-7: local slice ident or deref-of-local only)\n";
|
||||||
|
os.write(2, m35d.ptr, m35d.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
// Fresh esz-sized slot per SITE (esz varies; an @-name dedup
|
||||||
|
// would mis-share across element types). Allocated BEFORE the
|
||||||
|
// cgappend body's own scratch allocs — cstage order.
|
||||||
|
let insscr: i32 = localalloc(c, "@insscr", esz, nil);
|
||||||
|
cgexpr(c, d.rhs); // AX = idx
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
// Desugar in place and route through cgappend: 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. The callee rename keeps the AST
|
||||||
|
// consistent with cstage's re-dispatch.
|
||||||
|
n.lhs.str = "append";
|
||||||
|
n.list = base;
|
||||||
|
base.next = v;
|
||||||
|
cgappend(c, n);
|
||||||
|
if (hdr_lea) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
} else {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
};
|
||||||
|
emitoff(hdr_off: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
emitline("\tMOVQ\tAX, DX\n");
|
||||||
|
emitline("\tMOVQ\t8(DX), CX\n");
|
||||||
|
emitline("\tSUBQ\t$1, CX\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 ik: i32 = 0;
|
||||||
|
for (ik + 8 <= esz) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 8;
|
||||||
|
};
|
||||||
|
if (ik + 4 <= esz) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 4;
|
||||||
|
};
|
||||||
|
if (ik + 2 <= esz) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 2;
|
||||||
|
};
|
||||||
|
if (ik + 1 <= esz) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
ik += 1;
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t8(DX), AX\n");
|
||||||
|
emitline("\tSUBQ\t$2, AX\n");
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
let ill: str = mklabel(c, "ins_l");
|
||||||
|
let ile: str = mklabel(c, "ins_e");
|
||||||
|
emitlabel(ill);
|
||||||
|
emitline("\tMOVQ\t(SP), CX\n");
|
||||||
|
emitline("\tMOVQ\t16(SP), DX\n");
|
||||||
|
emitline("\tCMPQ\tDX, CX\n");
|
||||||
|
emitline("\tJL\t"); emitline(ile); emitline("\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t8(SP), DX\n");
|
||||||
|
emitline("\tMOVQ\t(DX), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
ik = 0;
|
||||||
|
for (ik + 8 <= esz) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 8;
|
||||||
|
};
|
||||||
|
if (ik + 4 <= esz) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 4;
|
||||||
|
};
|
||||||
|
if (ik + 2 <= esz) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 2;
|
||||||
|
};
|
||||||
|
if (ik + 1 <= esz) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitdispreg((esz + ik): i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 1;
|
||||||
|
};
|
||||||
|
emitline("\tSUBQ\t$1, (SP)\n");
|
||||||
|
emitline("\tJMP\t"); emitline(ill); emitline("\n");
|
||||||
|
emitlabel(ile);
|
||||||
|
emitline("\tMOVQ\t16(SP), CX\n");
|
||||||
|
if (esz > 1) {
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(esz: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tIMULQ\tAX, CX\n");
|
||||||
|
};
|
||||||
|
emitline("\tMOVQ\t8(SP), DX\n");
|
||||||
|
emitline("\tMOVQ\t(DX), BX\n");
|
||||||
|
emitline("\tADDQ\tCX, BX\n");
|
||||||
|
ik = 0;
|
||||||
|
for (ik + 8 <= esz) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 8;
|
||||||
|
};
|
||||||
|
if (ik + 4 <= esz) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 4;
|
||||||
|
};
|
||||||
|
if (ik + 2 <= esz) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 2;
|
||||||
|
};
|
||||||
|
if (ik + 1 <= esz) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitoff((insscr + ik): i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitdispreg(ik: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
ik += 1;
|
||||||
|
};
|
||||||
|
emitline("\tADDQ\t$24, SP\n");
|
||||||
|
};
|
||||||
|
|
||||||
fn cgcall(c: *cgen, n: *node) void = {
|
fn cgcall(c: *cgen, n: *node) void = {
|
||||||
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
||||||
// special-cased before pushargsrev so the spread variant can run
|
// special-cased before pushargsrev so the spread variant can run
|
||||||
@@ -25644,6 +25939,18 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// insert(xs[idx], v) — #35 insert-half; mirror of
|
||||||
|
// cstage cgen.c's N_CALL insert arm.
|
||||||
|
if (streq(callee.str, "insert")) {
|
||||||
|
if (n.list != nil) {
|
||||||
|
if (n.list.next != nil) {
|
||||||
|
if (n.list.next.next == nil) {
|
||||||
|
cginsert(c, n);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
615
test/wcc/807_insert_elem.c
Normal file
615
test/wcc/807_insert_elem.c
Normal file
@@ -0,0 +1,615 @@
|
|||||||
|
/*
|
||||||
|
* 807_insert_elem — cstage and wwstage agree, byte-for-byte and at
|
||||||
|
* runtime, that `insert(xs[idx], v)` inserts v BEFORE idx: len += 1,
|
||||||
|
* the tail [idx..oldlen) shifts up one stride, v lands at idx
|
||||||
|
* (the insert-half of task #35, delete()'s twin; regex fold-3's
|
||||||
|
* `|`/`?`/`*` compile-arm consumers, regex.ha:347/419/441).
|
||||||
|
*
|
||||||
|
* Lowering (BOTH stages, converged byte-identical by construction)
|
||||||
|
* is a DESUGAR: append(xs, v) — reusing append's grow (rt_ensure)
|
||||||
|
* and the whole #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 an 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 — the pregrow_len_idx row pins
|
||||||
|
* insert(xs[len(xs)-1], v) reading the pre-grow len; an end-insert
|
||||||
|
* via len(xs) cannot discriminate, see the end_insert_len row).
|
||||||
|
*
|
||||||
|
* idx == len is a legal end-insert per harec's shared append/insert
|
||||||
|
* checker arm (ref/harec/src/check.c:745, "insert" at :786; the
|
||||||
|
* ref/hare os/exec/platform_cmd.ha:86 `insert(cmd.env[len(...)...]`
|
||||||
|
* idiom) — pinned by the i64_end / end_insert_len rows.
|
||||||
|
*
|
||||||
|
* row | shape | want
|
||||||
|
* ----------------+----------------------------------------+------
|
||||||
|
* i64_front | [7,11,13], insert(xs[0], 5) | 115
|
||||||
|
* i64_middle | [7,11,13], insert(xs[1], 5) | 97
|
||||||
|
* i64_end | [7,11,13], insert(xs[3], 5) (idx==len, | 157
|
||||||
|
* | rotate loop never runs) |
|
||||||
|
* end_insert_len | insert(xs[len(xs)], v) — idx==len | 13
|
||||||
|
* | spelled through a dynamic len read |
|
||||||
|
* pregrow_len_idx | insert(xs[len(xs)-1], v) — the eval- | 48
|
||||||
|
* | order pin: pre-grow idx=1 -> [7,13,11],|
|
||||||
|
* | post-grow idx=2 -> [7,11,13] |
|
||||||
|
* i32_narrow | []i32 esz=4 — the MOVL copy tail | 42
|
||||||
|
* u16_narrow | []u16 esz=2 — the MOVW copy tail | 43
|
||||||
|
* u8_narrow | []u8 esz=1 — the MOVB copy tail | 44
|
||||||
|
* empty_insert | insert(xs[0], v) on an empty slice | 15
|
||||||
|
* | (grow 0->1; rotate degenerates) |
|
||||||
|
* str_elem | []str esz=24 — 3-qword headers move | 45
|
||||||
|
* | whole |
|
||||||
|
* struct_elem | []p2t esz=16 — struct body insert + | 46
|
||||||
|
* | survivor shift |
|
||||||
|
* tagged_56b | [](s6|bool) esz=56, value from a TYPED |
|
||||||
|
* | LOCAL (the regex newinst shape, |
|
||||||
|
* | ha:347); tag+payload survive the | 217
|
||||||
|
* | rotate; match head + raw tail byte |
|
||||||
|
* tagged_cast | insert(insts[k], (7: inst_split)) — |
|
||||||
|
* | CAST-rvalue value boxed by append's | 27
|
||||||
|
* | widen choke-point (ha:419/441 shape) |
|
||||||
|
* regex_shape | insert((*p)[i], v) behind *[]i64 — |
|
||||||
|
* | deref-of-local base, delete's | 171
|
||||||
|
* | regex_shape twin |
|
||||||
|
* insert_in_loop | front-insert 1,2,3,4 -> [4,3,2,1] — | 47
|
||||||
|
* | len bookkeeping under iteration + |
|
||||||
|
* | per-position checks |
|
||||||
|
*
|
||||||
|
* Wants stay under 256 (the exit-status byte); the if-ladder rows
|
||||||
|
* return a distinct small failure code per check, so a wrong element
|
||||||
|
* pinpoints itself.
|
||||||
|
* reject_array | insert(t[0], v) on [3]i64 | BUILD_FAIL
|
||||||
|
* reject_nonindex | insert(xs, v) | BUILD_FAIL
|
||||||
|
* reject_range | insert(xs[0:1], v) — not Hare (harec | BUILD_FAIL
|
||||||
|
* | only parses an index place) |
|
||||||
|
* reject_arity1 | insert(xs[0]) | BUILD_FAIL
|
||||||
|
* reject_arity3 | insert(xs[0], a, b) — also covers the | BUILD_FAIL
|
||||||
|
* | harec with-length form (#35) |
|
||||||
|
* reject_spread | insert(xs[0], vs...) — multi form | BUILD_FAIL
|
||||||
|
* | deferred, message cites task #35 |
|
||||||
|
*
|
||||||
|
* BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring,
|
||||||
|
* both stages) — a build that fails for any other reason (parse error,
|
||||||
|
* crash) is a vacuous reject and fails the row.
|
||||||
|
*
|
||||||
|
* Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id,
|
||||||
|
* which subsumes the frame canary (TEXT main,$N — @insscr sizing) and
|
||||||
|
* the ins_l/ins_e label-counter symmetry.
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
static int
|
||||||
|
runwait(const char *cmd)
|
||||||
|
{
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc == -1) return -1;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* want == BUILD_FAIL: the row must FAIL to build on both stages AND
|
||||||
|
* emit expect_err on stderr (the checker reject set — message text
|
||||||
|
* included, esp. the #35 cite on the deferred spread form — is part
|
||||||
|
* of the contract: rule 7, never a silent acceptance; without the
|
||||||
|
* message check a row would pass vacuously on any unrelated build
|
||||||
|
* failure). */
|
||||||
|
#define BUILD_FAIL (-2147483647 - 1)
|
||||||
|
|
||||||
|
struct row {
|
||||||
|
const char *label;
|
||||||
|
const char *src;
|
||||||
|
int want;
|
||||||
|
const char *expect_err; /* BUILD_FAIL rows: required stderr substring */
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct row rows[] = {
|
||||||
|
{ "i64_front",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []i64 = [];\n"
|
||||||
|
"\tappend(xs, 7);\n"
|
||||||
|
"\tappend(xs, 11);\n"
|
||||||
|
"\tappend(xs, 13);\n"
|
||||||
|
"\tinsert(xs[0], 5);\n"
|
||||||
|
"\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n"
|
||||||
|
"};\n",
|
||||||
|
115, NULL },
|
||||||
|
|
||||||
|
{ "i64_middle",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []i64 = [];\n"
|
||||||
|
"\tappend(xs, 7);\n"
|
||||||
|
"\tappend(xs, 11);\n"
|
||||||
|
"\tappend(xs, 13);\n"
|
||||||
|
"\tinsert(xs[1], 5);\n"
|
||||||
|
"\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n"
|
||||||
|
"};\n",
|
||||||
|
97, NULL },
|
||||||
|
|
||||||
|
{ "i64_end",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []i64 = [];\n"
|
||||||
|
"\tappend(xs, 7);\n"
|
||||||
|
"\tappend(xs, 11);\n"
|
||||||
|
"\tappend(xs, 13);\n"
|
||||||
|
"\tinsert(xs[3], 5);\n"
|
||||||
|
"\treturn (xs[0] + xs[1]*10): i32 + len(xs)*10;\n"
|
||||||
|
"};\n",
|
||||||
|
157, NULL },
|
||||||
|
|
||||||
|
/* idx == len spelled THROUGH a dynamic len(xs) read (the
|
||||||
|
* ref/hare os/exec idiom). NOTE: this row does NOT discriminate
|
||||||
|
* idx eval order — under the desugar, post-grow idx=newlen
|
||||||
|
* degenerates the rotate and lands v at the end too; the
|
||||||
|
* pregrow_len_idx row below is the eval-order pin. */
|
||||||
|
{ "end_insert_len",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []i64 = [];\n"
|
||||||
|
"\tappend(xs, 7);\n"
|
||||||
|
"\tappend(xs, 11);\n"
|
||||||
|
"\tinsert(xs[len(xs)], 13);\n"
|
||||||
|
"\tif (len(xs) != 3) { return 1; };\n"
|
||||||
|
"\treturn xs[2]: i32;\n"
|
||||||
|
"};\n",
|
||||||
|
13, NULL },
|
||||||
|
|
||||||
|
/* THE pre-grow eval-order pin (Hare's left-to-right operand
|
||||||
|
* order): idx = len(xs)-1 reads the PRE-grow len -> idx=1 ->
|
||||||
|
* [7,13,11]; a post-grow evaluation would compute idx=2 and
|
||||||
|
* produce a plain end-insert [7,11,13] — every position is
|
||||||
|
* checked, so the orders are distinguishable. */
|
||||||
|
{ "pregrow_len_idx",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []i64 = [];\n"
|
||||||
|
"\tappend(xs, 7);\n"
|
||||||
|
"\tappend(xs, 11);\n"
|
||||||
|
"\tinsert(xs[len(xs) - 1], 13);\n"
|
||||||
|
"\tif (len(xs) != 3) { return 1; };\n"
|
||||||
|
"\tif (xs[0] != 7) { return 2; };\n"
|
||||||
|
"\tif (xs[1] != 13) { return 3; };\n"
|
||||||
|
"\tif (xs[2] != 11) { return 4; };\n"
|
||||||
|
"\treturn 48;\n"
|
||||||
|
"};\n",
|
||||||
|
48, NULL },
|
||||||
|
|
||||||
|
{ "i32_narrow",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []i32 = [];\n"
|
||||||
|
"\tappend(xs, 4i32);\n"
|
||||||
|
"\tappend(xs, 2i32);\n"
|
||||||
|
"\tinsert(xs[1], 9i32);\n"
|
||||||
|
"\tif (len(xs) != 3) { return 1; };\n"
|
||||||
|
"\tif (xs[0] != 4i32) { return 2; };\n"
|
||||||
|
"\tif (xs[1] != 9i32) { return 3; };\n"
|
||||||
|
"\tif (xs[2] != 2i32) { return 4; };\n"
|
||||||
|
"\treturn 42;\n"
|
||||||
|
"};\n",
|
||||||
|
42, NULL },
|
||||||
|
|
||||||
|
{ "u16_narrow",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []u16 = [];\n"
|
||||||
|
"\tappend(xs, 4u16);\n"
|
||||||
|
"\tappend(xs, 2u16);\n"
|
||||||
|
"\tinsert(xs[1], 9u16);\n"
|
||||||
|
"\tif (len(xs) != 3) { return 1; };\n"
|
||||||
|
"\tif (xs[0] != 4u16) { return 2; };\n"
|
||||||
|
"\tif (xs[1] != 9u16) { return 3; };\n"
|
||||||
|
"\tif (xs[2] != 2u16) { return 4; };\n"
|
||||||
|
"\treturn 43;\n"
|
||||||
|
"};\n",
|
||||||
|
43, NULL },
|
||||||
|
|
||||||
|
{ "u8_narrow",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []u8 = [];\n"
|
||||||
|
"\tappend(xs, 5u8);\n"
|
||||||
|
"\tappend(xs, 3u8);\n"
|
||||||
|
"\tinsert(xs[0], 2u8);\n"
|
||||||
|
"\tif (len(xs) != 3) { return 1; };\n"
|
||||||
|
"\tif (xs[0] != 2u8) { return 2; };\n"
|
||||||
|
"\tif (xs[1] != 5u8) { return 3; };\n"
|
||||||
|
"\tif (xs[2] != 3u8) { return 4; };\n"
|
||||||
|
"\treturn 44;\n"
|
||||||
|
"};\n",
|
||||||
|
44, NULL },
|
||||||
|
|
||||||
|
{ "empty_insert",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []i64 = [];\n"
|
||||||
|
"\tinsert(xs[0], 5);\n"
|
||||||
|
"\treturn xs[0]: i32 + len(xs)*10;\n"
|
||||||
|
"};\n",
|
||||||
|
15, NULL },
|
||||||
|
|
||||||
|
{ "str_elem",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []str = [];\n"
|
||||||
|
"\tlet a: str = \"abc\";\n"
|
||||||
|
"\tlet g: str = \"fghi\";\n"
|
||||||
|
"\tappend(xs, a);\n"
|
||||||
|
"\tappend(xs, g);\n"
|
||||||
|
"\tlet b: str = \"de\";\n"
|
||||||
|
"\tinsert(xs[1], b);\n"
|
||||||
|
"\tif (len(xs) != 3) { return 1; };\n"
|
||||||
|
"\tif (xs[0].len != 3) { return 2; };\n"
|
||||||
|
"\tif (xs[1].len != 2) { return 3; };\n"
|
||||||
|
"\tif (xs[2].len != 4) { return 4; };\n"
|
||||||
|
"\treturn 45;\n"
|
||||||
|
"};\n",
|
||||||
|
45, NULL },
|
||||||
|
|
||||||
|
{ "struct_elem",
|
||||||
|
"package main;\n"
|
||||||
|
"type p2t = struct { x: i64, y: i64 };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []p2t = [];\n"
|
||||||
|
"\tappend(xs, p2t { x = 1, y = 2 });\n"
|
||||||
|
"\tappend(xs, p2t { x = 5, y = 6 });\n"
|
||||||
|
"\tinsert(xs[1], p2t { x = 3, y = 4 });\n"
|
||||||
|
"\tif (len(xs) != 3) { return 1; };\n"
|
||||||
|
"\tif (xs[0].x != 1 || xs[0].y != 2) { return 2; };\n"
|
||||||
|
"\tif (xs[1].x != 3 || xs[1].y != 4) { return 3; };\n"
|
||||||
|
"\tif (xs[2].x != 5 || xs[2].y != 6) { return 4; };\n"
|
||||||
|
"\treturn 46;\n"
|
||||||
|
"};\n",
|
||||||
|
46, NULL },
|
||||||
|
|
||||||
|
/* 56B element from a TYPED LOCAL — the regex fold-3 newinst
|
||||||
|
* shape (regex.ha:347: `insert(insts[split_idx], newinst)`).
|
||||||
|
* Tag qword + 48B payload: seven whole-qword moves through
|
||||||
|
* @insscr; tag AND payload must survive both the append store
|
||||||
|
* and the rotate. Readback is split like 804's tagged_56b row:
|
||||||
|
* match proves tag + head (s.a), and the tail qword of the
|
||||||
|
* SHIFTED element (f = 18, element 2 byte 48 -> absolute byte
|
||||||
|
* 160) is read RAW via a *u8 over xs.ptr — the indexed-match
|
||||||
|
* payload cursor truncates past 32B (pre-existing, task #43). */
|
||||||
|
{ "tagged_56b",
|
||||||
|
"package main;\n"
|
||||||
|
"type s6 = struct { a: i64, b: i64, c: i64, d: i64, e: i64, f: i64 };\n"
|
||||||
|
"type cell = (s6 | bool);\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []cell = [];\n"
|
||||||
|
"\tlet p0: s6 = s6 { a = 1, b = 2, c = 3, d = 4, e = 5, f = 6 };\n"
|
||||||
|
"\tlet p2: s6 = s6 { a = 13, b = 14, c = 15, d = 16, e = 17, f = 18 };\n"
|
||||||
|
"\tappend(xs, p0);\n"
|
||||||
|
"\tappend(xs, p2);\n"
|
||||||
|
"\tlet p1: cell = (s6 { a = 7, b = 8, c = 9, d = 10, e = 11, f = 12 });\n"
|
||||||
|
"\tinsert(xs[1], p1);\n"
|
||||||
|
"\tlet r: i32 = 0;\n"
|
||||||
|
"\tmatch (xs[1]) {\n"
|
||||||
|
"\tcase let s: s6 => r = s.a: i32;\n"
|
||||||
|
"\tcase bool => r = 99;\n"
|
||||||
|
"\t};\n"
|
||||||
|
"\tlet bp: *u8 = xs.ptr: *u8;\n"
|
||||||
|
"\tr += (bp[160]: i32) * 10;\n"
|
||||||
|
"\treturn r + len(xs)*10;\n"
|
||||||
|
"};\n",
|
||||||
|
217, NULL },
|
||||||
|
|
||||||
|
/* CAST-rvalue value into a tagged slice — the regex ha:419/441
|
||||||
|
* shape `insert(insts[term_start_idx], after_idx: inst_split)`;
|
||||||
|
* append's widen choke-point boxes it post-grow. */
|
||||||
|
{ "tagged_cast",
|
||||||
|
"package main;\n"
|
||||||
|
"type inst_lit = rune;\n"
|
||||||
|
"type inst_split = i64;\n"
|
||||||
|
"type inst = (inst_lit | inst_split);\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet insts: []inst = [];\n"
|
||||||
|
"\tappend(insts, ('a': inst_lit));\n"
|
||||||
|
"\tappend(insts, ('b': inst_lit));\n"
|
||||||
|
"\tinsert(insts[1], (7: inst_split));\n"
|
||||||
|
"\tif (len(insts) != 3) { return 1; };\n"
|
||||||
|
"\tlet r: i32 = 0;\n"
|
||||||
|
"\tmatch (insts[1]) {\n"
|
||||||
|
"\tcase let z: inst_split => r += (z: i32);\n"
|
||||||
|
"\tcase => return 2;\n"
|
||||||
|
"\t};\n"
|
||||||
|
"\tmatch (insts[2]) {\n"
|
||||||
|
"\tcase let l: inst_lit => { if ((l: rune) == 'b') { r += 20; }; };\n"
|
||||||
|
"\tcase => return 3;\n"
|
||||||
|
"\t};\n"
|
||||||
|
"\treturn r;\n"
|
||||||
|
"};\n",
|
||||||
|
27, NULL },
|
||||||
|
|
||||||
|
/* The deref-of-local base — delete's regex_shape twin: the
|
||||||
|
* header lives behind a *[]i64 param. */
|
||||||
|
{ "regex_shape",
|
||||||
|
"package main;\n"
|
||||||
|
"fn ins_at(i: i64, p: *[]i64, v: i64) void = {\n"
|
||||||
|
"\tinsert((*p)[i], v);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []i64 = [];\n"
|
||||||
|
"\tappend(xs, 1);\n"
|
||||||
|
"\tappend(xs, 3);\n"
|
||||||
|
"\tins_at(1, &xs, 2);\n"
|
||||||
|
"\tif (len(xs) != 3) { return 1; };\n"
|
||||||
|
"\treturn (xs[0] + xs[1]*10 + xs[2]*50): i32;\n"
|
||||||
|
"};\n",
|
||||||
|
171, NULL },
|
||||||
|
|
||||||
|
/* Pins len bookkeeping under iteration: each front-insert must
|
||||||
|
* see the grown len and shift the whole accumulated tail;
|
||||||
|
* per-position checks of [4,3,2,1] make any wrong order, missed
|
||||||
|
* shift, or stale len visible. */
|
||||||
|
{ "insert_in_loop",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []i64 = [];\n"
|
||||||
|
"\tlet v: i64 = 1;\n"
|
||||||
|
"\tfor (v <= 4) {\n"
|
||||||
|
"\t\tinsert(xs[0], v);\n"
|
||||||
|
"\t\tv += 1;\n"
|
||||||
|
"\t};\n"
|
||||||
|
"\tif (len(xs) != 4) { return 1; };\n"
|
||||||
|
"\tlet k: i32 = 0;\n"
|
||||||
|
"\tfor (k < 4) {\n"
|
||||||
|
"\t\tif (xs[k] != (4 - k): i64) { return 2 + k; };\n"
|
||||||
|
"\t\tk += 1;\n"
|
||||||
|
"\t};\n"
|
||||||
|
"\treturn 47;\n"
|
||||||
|
"};\n",
|
||||||
|
47, NULL },
|
||||||
|
|
||||||
|
{ "reject_array",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet t: [3]i64 = [1, 2, 3];\n"
|
||||||
|
"\tinsert(t[0], 9);\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n",
|
||||||
|
BUILD_FAIL, "insert must operate on a slice" },
|
||||||
|
|
||||||
|
{ "reject_nonindex",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []u8 = [];\n"
|
||||||
|
"\tappend(xs, 5u8);\n"
|
||||||
|
"\tinsert(xs, 6u8);\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n",
|
||||||
|
BUILD_FAIL, "insert: operand must be an indexing expression" },
|
||||||
|
|
||||||
|
{ "reject_range",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []u8 = [];\n"
|
||||||
|
"\tappend(xs, 5u8);\n"
|
||||||
|
"\tappend(xs, 6u8);\n"
|
||||||
|
"\tinsert(xs[0:1], 7u8);\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n",
|
||||||
|
BUILD_FAIL, "insert: range place is invalid" },
|
||||||
|
|
||||||
|
{ "reject_arity1",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []u8 = [];\n"
|
||||||
|
"\tappend(xs, 5u8);\n"
|
||||||
|
"\tinsert(xs[0]);\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n",
|
||||||
|
BUILD_FAIL, "insert: takes exactly two arguments" },
|
||||||
|
|
||||||
|
{ "reject_arity3",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []u8 = [];\n"
|
||||||
|
"\tappend(xs, 5u8);\n"
|
||||||
|
"\tinsert(xs[0], 6u8, 7u8);\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n",
|
||||||
|
BUILD_FAIL, "insert: takes exactly two arguments" },
|
||||||
|
|
||||||
|
{ "reject_spread",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
"\tlet xs: []u8 = [];\n"
|
||||||
|
"\tlet vs: []u8 = [];\n"
|
||||||
|
"\tappend(vs, 1u8);\n"
|
||||||
|
"\tinsert(xs[0], vs...);\n"
|
||||||
|
"\treturn 0;\n"
|
||||||
|
"};\n",
|
||||||
|
BUILD_FAIL, "insert: spread form insert(xs[i], vs...) unimplemented (task #35)" },
|
||||||
|
};
|
||||||
|
|
||||||
|
/* errlog_has — the build-failure stderr must carry the row's expected
|
||||||
|
* diagnostic; any other failure (parse error, crash) is a vacuous
|
||||||
|
* reject and must not pass. */
|
||||||
|
static int
|
||||||
|
errlog_has(const char *path, const char *needle)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return 0;
|
||||||
|
char buf[8192];
|
||||||
|
size_t got = fread(buf, 1, sizeof buf - 1, f);
|
||||||
|
fclose(f);
|
||||||
|
buf[got] = '\0';
|
||||||
|
return strstr(buf, needle) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_driver(const char *driver, const struct row *r, int i)
|
||||||
|
{
|
||||||
|
char src[64], tmpdir[64], errlog[80], cmd[1200];
|
||||||
|
snprintf(src, sizeof src, "/tmp/inse_%d_%d.ww", getpid(), i);
|
||||||
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/inse_%d_d_%d", getpid(), i);
|
||||||
|
snprintf(errlog, sizeof errlog, "%s.err", src);
|
||||||
|
|
||||||
|
FILE *f = fopen(src, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fputs(r->src, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
mkdir(tmpdir, 0755);
|
||||||
|
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>%s",
|
||||||
|
tmpdir, driver, src, errlog);
|
||||||
|
if (runwait(cmd) != 0) {
|
||||||
|
int rc = -1;
|
||||||
|
if (r->want != BUILD_FAIL) {
|
||||||
|
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||||
|
r->label, driver);
|
||||||
|
} else if (r->expect_err &&
|
||||||
|
!errlog_has(errlog, r->expect_err)) {
|
||||||
|
fprintf(stderr, "row[%s]: %s build failed without "
|
||||||
|
"expected diagnostic \"%s\"\n",
|
||||||
|
r->label, driver, r->expect_err);
|
||||||
|
rc = -3; /* failed, but for the wrong reason */
|
||||||
|
}
|
||||||
|
unlink(src); unlink(errlog); rmdir(tmpdir);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *base = strrchr(src, '/');
|
||||||
|
base = base ? base + 1 : src;
|
||||||
|
char outbin[128];
|
||||||
|
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||||
|
char *dot = strrchr(outbin, '.');
|
||||||
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||||
|
int got = runwait(outbin);
|
||||||
|
|
||||||
|
unlink(src); unlink(errlog); unlink(outbin); rmdir(tmpdir);
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
|
||||||
|
* w6c_ww and diff. Both insert lowerings are written fresh, so this is
|
||||||
|
* the converged-by-construction gate: any drift in the rotate loop,
|
||||||
|
* the ins_l/ins_e label sequence, the @insscr frame slot, or the
|
||||||
|
* reused append body shows here. */
|
||||||
|
static int
|
||||||
|
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||||
|
{
|
||||||
|
char src[64], cs[64], ws[64], cmd[1024];
|
||||||
|
snprintf(src, sizeof src, "/tmp/inse_asm_%d_%d.ww", getpid(), i);
|
||||||
|
snprintf(cs, sizeof cs, "/tmp/inse_asm_%d_%d_c.s", getpid(), i);
|
||||||
|
snprintf(ws, sizeof ws, "/tmp/inse_asm_%d_%d_w.s", getpid(), i);
|
||||||
|
|
||||||
|
FILE *f = fopen(src, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fputs(r->src, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||||
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||||
|
unlink(src);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||||
|
bin, ws, src);
|
||||||
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||||
|
unlink(src); unlink(cs);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *fc = fopen(cs, "rb");
|
||||||
|
FILE *fw = fopen(ws, "rb");
|
||||||
|
int rc = 0;
|
||||||
|
if (!fc || !fw) {
|
||||||
|
rc = -1;
|
||||||
|
} else {
|
||||||
|
for (;;) {
|
||||||
|
int a = fgetc(fc);
|
||||||
|
int b = fgetc(fw);
|
||||||
|
if (a != b) { rc = -1; break; }
|
||||||
|
if (a == EOF) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fc) fclose(fc);
|
||||||
|
if (fw) fclose(fw);
|
||||||
|
if (rc != 0)
|
||||||
|
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||||
|
r->label);
|
||||||
|
unlink(src); unlink(cs); unlink(ws);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
const char *bin = getenv("BIN");
|
||||||
|
if (!bin) bin = "out/bin";
|
||||||
|
char absbin[1024];
|
||||||
|
if (bin[0] != '/') {
|
||||||
|
char cwd[1024];
|
||||||
|
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||||
|
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||||
|
bin = absbin;
|
||||||
|
}
|
||||||
|
|
||||||
|
char cdrv[1024];
|
||||||
|
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||||
|
char wdrv[1024];
|
||||||
|
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||||
|
|
||||||
|
struct { const char *name; const char *path; int gated_on_existence; }
|
||||||
|
drivers[] = {
|
||||||
|
{ "cstage", cdrv, 0 },
|
||||||
|
{ "wwstage", wdrv, 1 },
|
||||||
|
{ NULL, NULL, 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||||
|
int total = 0, fail = 0;
|
||||||
|
|
||||||
|
for (int d = 0; drivers[d].name; d++) {
|
||||||
|
if (drivers[d].gated_on_existence
|
||||||
|
&& access(drivers[d].path, X_OK) != 0) {
|
||||||
|
fprintf(stderr, "insert_elem: skip %s (no %s)\n",
|
||||||
|
drivers[d].name, drivers[d].path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||||
|
total++;
|
||||||
|
int bad = rows[i].want == BUILD_FAIL
|
||||||
|
? (got != -1) : (got != rows[i].want);
|
||||||
|
if (bad) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"insert_elem[%s][%s]: exit=%d want=%d\n",
|
||||||
|
drivers[d].name, rows[i].label,
|
||||||
|
got, rows[i].want);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (access(wdrv, X_OK) == 0) {
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (rows[i].want == BUILD_FAIL)
|
||||||
|
continue;
|
||||||
|
total++;
|
||||||
|
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"insert_elem: %d/%d fixtures failed\n", fail, total);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("insert_elem: %d fixtures passed\n", total);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user