wcc+w6c+w6c_ww: delete() builtin — single-element slice removal (part of #35)
Hare's delete(xs[i]) (ref/harec/src/check.c:1981-2027): checker accepts an N_INDEX over a slice-typed base, stamps void; loud-rejects the range form delete(xs[i..j]) (stays filed on #35 — regex fold-2b's consumers are all single-element), non-index operands, array bases, wrong arity. Lowering (both stages, converged byte-identical by construction): ascending word-copy loop shifts [i+1..len) down one esz stride, then hdr.len -= 1; cap unchanged. The move is a same-type whole-stride byte copy — src and dst are elements of the SAME slice, so no boxing exists for any element kind; one loop serves scalar/narrow/str/struct/tagged. esz off the STAMPED base type (#34/#48 discipline). Base shapes: local slice ident (LEAQ) and deref-of-local ptr-to-slice (MOVQ — the fold-2b delete_thread shape); others rule-7 loud-stop. test/804: 38 fixtures — first/middle/last/to-empty, esz 1/4/8/24/56 (MOVB/MOVL tails + 7-qword tagged), cap-unchanged, (*threads)[i], 4 checker reject rows; every accept row cs==ww asm byte-id.
This commit is contained in:
11
Makefile
11
Makefile
@@ -388,6 +388,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_tuple_sret_callee \
|
||||
$(BIN)/test_tuple_sret_receive_run \
|
||||
$(BIN)/test_append_wide_elem \
|
||||
$(BIN)/test_delete_elem \
|
||||
$(BIN)/test_struct_tuple_field_slot \
|
||||
$(BIN)/test_widen_pad_zero_run \
|
||||
$(BIN)/test_named_ptr_alias_variant_widen \
|
||||
@@ -1000,6 +1001,16 @@ $(BIN)/test_append_wide_elem: test/wcc/800_append_wide_elem.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# #35 (delete-half): delete(xs[i]) single-element slice removal — runtime
|
||||
# rows across every element kind (scalar/narrow/str/struct/56B tagged) +
|
||||
# the deref-of-local regex fold-2b shape + checker reject rows + cs==ww
|
||||
# asm byte-id (the converged-by-construction shift loop).
|
||||
$(BIN)/test_delete_elem: test/wcc/804_delete_elem.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# #237: a tuple-typed struct field must contribute its real slot width to
|
||||
# the enclosing struct's slotsize — pure cs==ww .s byte-id (frame size).
|
||||
$(BIN)/test_struct_tuple_field_slot: test/wcc/930_struct_tuple_field_slot.c \
|
||||
|
||||
113
cmd/w6c/cgen.c
113
cmd/w6c/cgen.c
@@ -6252,6 +6252,119 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
cgexpr(c, n->list, locals);
|
||||
break;
|
||||
}
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
||||
strcmp(n->lhs->str, "delete") == 0 && n->list &&
|
||||
n->list->next == NULL) {
|
||||
/* delete(xs[i]) — single-element slice removal, the
|
||||
* delete-half of #35. Shift [i+1..len) down one
|
||||
* stride, len -= 1, cap unchanged. The move is a
|
||||
* same-type whole-stride byte copy: src and dst are
|
||||
* elements of the SAME slice, so no boxing/coercion
|
||||
* exists for any element kind (str/slice 24B header,
|
||||
* tagged box, struct body) — one word-copy loop
|
||||
* serves all kinds, unlike append's value-store
|
||||
* dispatch (#34) which boxes from a foreign source.
|
||||
* Ascending j keeps src (j+1) ahead of dst (j), the
|
||||
* safe memmove-down direction. Bounds are implicit
|
||||
* (no index check, matching the rest of cgen).
|
||||
*
|
||||
* ; AX = i (cgexpr)
|
||||
* ; PUSHQ AX ; 8(SP) = j counter
|
||||
* ; LEAQ/MOVQ off(BP), AX ; &hdr (ident / *p base)
|
||||
* ; PUSHQ AX ; (SP) = &hdr
|
||||
* ; del_l:
|
||||
* ; MOVQ (SP), DX ; MOVQ 8(SP), CX
|
||||
* ; MOVQ 8(DX), BX ; SUBQ $1, BX
|
||||
* ; CMPQ BX, CX ; JGE del_e ; j >= len-1
|
||||
* ; [IMULQ esz, CX]
|
||||
* ; MOVQ (DX), BX ; ADDQ CX, BX ; BX = dst
|
||||
* ; word-copy esz bytes esz(BX) -> (BX)
|
||||
* ; ADDQ $1, 8(SP) ; JMP del_l
|
||||
* ; del_e:
|
||||
* ; MOVQ (SP), DX ; SUBQ $1, 8(DX)
|
||||
* ; ADDQ $16, SP */
|
||||
Node *d = n->list; /* N_INDEX, checker-validated */
|
||||
Node *base = d->lhs;
|
||||
Type *su = type_chase_named(base->type);
|
||||
Type *esub = (su && su->sub)
|
||||
? type_chase_named(su->sub) : NULL;
|
||||
int esz = esub ? (int)esub->size : 0;
|
||||
if (esz <= 0)
|
||||
fatal("#35: delete() element size unresolved "
|
||||
"(rule-7)");
|
||||
int hdr_lea = 0;
|
||||
int hdr_off = 0;
|
||||
int hdr_ok = 0;
|
||||
if (base->kind == N_IDENT &&
|
||||
localfind(locals, base->str) != 0) {
|
||||
hdr_lea = 1;
|
||||
hdr_off = localfind(locals, base->str);
|
||||
hdr_ok = 1;
|
||||
}
|
||||
/* (*p)[i]: the header lives behind a local
|
||||
* ptr-to-slice — the regex fold-2b delete_thread
|
||||
* shape (threads: *[]thread). */
|
||||
if (!hdr_ok && base->kind == N_UN &&
|
||||
base->op == TK_STAR && base->lhs &&
|
||||
base->lhs->kind == N_IDENT &&
|
||||
localfind(locals, base->lhs->str) != 0) {
|
||||
hdr_off = localfind(locals, base->lhs->str);
|
||||
hdr_ok = 1;
|
||||
}
|
||||
if (!hdr_ok)
|
||||
fatal("#35: delete() base shape unsupported "
|
||||
"(rule-7: local slice ident or "
|
||||
"deref-of-local only)");
|
||||
cgexpr(c, d->rhs, locals); /* AX = i */
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (hdr_lea)
|
||||
ins2(c, A_LEAQ, amem(D_BP, hdr_off), areg(D_AX));
|
||||
else
|
||||
ins2(c, A_MOVQ, amem(D_BP, hdr_off), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
char *dll = mklabel(c, "del_l");
|
||||
char *dle = mklabel(c, "del_e");
|
||||
label(c, dll);
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_DX));
|
||||
ins2(c, A_MOVQ, amem(D_SP, 8), areg(D_CX));
|
||||
ins2(c, A_MOVQ, amem(D_DX, 8), areg(D_BX));
|
||||
ins2(c, A_SUBQ, aimm(1), areg(D_BX));
|
||||
ins2(c, A_CMPQ, areg(D_BX), areg(D_CX));
|
||||
ins1(c, A_JGE, abranch(dle));
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
||||
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
|
||||
}
|
||||
ins2(c, A_MOVQ, amem(D_DX, 0), areg(D_BX));
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
|
||||
int dk = 0;
|
||||
for (; dk + 8 <= esz; dk += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_BX, esz + dk), areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, dk));
|
||||
}
|
||||
if (dk + 4 <= esz) {
|
||||
ins2(c, A_MOVL, amem(D_BX, esz + dk), areg(D_AX));
|
||||
ins2(c, A_MOVL, areg(D_AX), amem(D_BX, dk));
|
||||
dk += 4;
|
||||
}
|
||||
if (dk + 2 <= esz) {
|
||||
ins2(c, A_MOVW, amem(D_BX, esz + dk), areg(D_AX));
|
||||
ins2(c, A_MOVW, areg(D_AX), amem(D_BX, dk));
|
||||
dk += 2;
|
||||
}
|
||||
if (dk + 1 <= esz) {
|
||||
ins2(c, A_MOVB, amem(D_BX, esz + dk), areg(D_AX));
|
||||
ins2(c, A_MOVB, areg(D_AX), amem(D_BX, dk));
|
||||
dk += 1;
|
||||
}
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_SP, 8));
|
||||
ins1(c, A_JMP, abranch(dll));
|
||||
label(c, dle);
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_DX));
|
||||
ins2(c, A_SUBQ, aimm(1), amem(D_DX, 8));
|
||||
ins2(c, A_ADDQ, aimm(16), areg(D_SP));
|
||||
break;
|
||||
}
|
||||
if (n->lhs && n->lhs->kind == N_IDENT &&
|
||||
n->lhs->type == ty_err &&
|
||||
n->lhs->str && strcmp(n->lhs->str, "alloc") == 0 &&
|
||||
|
||||
@@ -1435,6 +1435,37 @@ cexpr(Checker *c, Node *n)
|
||||
n->lhs->type = ty_err;
|
||||
return n->type;
|
||||
}
|
||||
/* delete(xs[i]) — single-element slice removal, the
|
||||
* delete-half of #35 (insert() stays deferred). harec
|
||||
* ref/harec/src/check.c:1981-2027 also accepts the range
|
||||
* form delete(xs[i..j]) (EXPR_SLICE) — out of scope here
|
||||
* (regex fold-2b's consumers are all single-element);
|
||||
* loud-rejected below, the range form stays filed on #35. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT &&
|
||||
n->lhs->str && strcmp(n->lhs->str, "delete") == 0) {
|
||||
Node *d = n->list;
|
||||
if (d == NULL || d->next != NULL)
|
||||
err(c, n->pos, "delete: takes exactly one argument");
|
||||
if (d != NULL) {
|
||||
(void)cexpr(c, d);
|
||||
if (d->kind == N_SLICE)
|
||||
err(c, n->pos, "delete: range form delete(xs[i..j]) unimplemented (task #35)");
|
||||
else if (d->kind != N_INDEX)
|
||||
err(c, n->pos, "delete: operand must be an indexing expression xs[i]");
|
||||
else {
|
||||
Type *bt = d->lhs ? d->lhs->type : NULL;
|
||||
while (bt && bt->kind == TY_NAMED)
|
||||
bt = bt->under;
|
||||
/* harec check.c:2024 wording; a
|
||||
* fixed-size [N]T base lands here. */
|
||||
if (bt == NULL || bt->kind != TY_SLICE)
|
||||
err(c, n->pos, "delete must operate on a slice");
|
||||
}
|
||||
}
|
||||
n->type = ty_void;
|
||||
n->lhs->type = ty_err;
|
||||
return n->type;
|
||||
}
|
||||
/* assert(cond[, msg]) / abort([msg]) — runtime checks that
|
||||
* call into rt_abort. msg must be a str when present.
|
||||
* Only treated as builtins when no user symbol shadows the
|
||||
|
||||
@@ -10424,12 +10424,14 @@ 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` are pseudo-builtins; scopedefine
|
||||
// them so their use sites resolve. The actual semantics live in cgen.
|
||||
// `len`, `alloc`, `free`, `append`, `delete` 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);
|
||||
// #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
|
||||
@@ -12889,6 +12891,44 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
// delete(xs[i]) — single-element slice removal, the
|
||||
// delete-half of #35 (insert() deferred). 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) —
|
||||
// loud-rejected here, filed on #35 (regex fold-2b's
|
||||
// consumers are all single-element).
|
||||
if (streq(callee.str, "delete")) {
|
||||
let d: *node = e.list;
|
||||
if (d == nil || d.next != nil) {
|
||||
cerr("delete: takes exactly one argument\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
if (d != nil) {
|
||||
exprtype(c, d, nil);
|
||||
if (d.kind == nkind.N_SLICE) {
|
||||
cerr("delete: range form delete(xs[i..j]) unimplemented (task #35)\n");
|
||||
c.errs += 1;
|
||||
} else {
|
||||
if (d.kind != nkind.N_INDEX) {
|
||||
cerr("delete: 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:2024 wording; a
|
||||
// fixed-size [N]T base lands here.
|
||||
if (u == nil || u.kind != nkind.N_TSLICE) {
|
||||
cerr("delete must operate on a slice\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
};
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
@@ -24478,6 +24518,135 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// cgdelete — Hare `delete(xs[i])`: single-element slice removal, the
|
||||
// delete-half of #35. Shift [i+1..len) down one stride, len -= 1, cap
|
||||
// unchanged. The move is a same-type whole-stride byte copy: src and
|
||||
// dst are elements of the SAME slice, so no boxing/coercion exists for
|
||||
// any element kind (str/slice 24B header, tagged box, struct body) —
|
||||
// one word-copy loop serves all kinds, unlike append's value-store
|
||||
// dispatch (#34) which boxes from a foreign source. Ascending j keeps
|
||||
// src (j+1) ahead of dst (j), the safe memmove-down direction. Mirrors
|
||||
// cmd/w6c/cgen.c's N_CALL delete arm instruction-for-instruction
|
||||
// (rule-10 byte-id).
|
||||
fn cgdelete(c: *cgen, n: *node) void = {
|
||||
let d: *node = n.list; // N_INDEX, checker-validated
|
||||
let base: *node = d.lhs;
|
||||
// esz off the STAMPED base tinfo (#34/#48 discipline — never the
|
||||
// value node). Peel TY_NAMED on indexable AND element, mirroring
|
||||
// cstage's type_chase_named on both (#8 family).
|
||||
let sti: *tinfo = base.type_: *tinfo;
|
||||
for (sti != nil && sti.kind == tykind.TY_NAMED) { sti = sti.under; };
|
||||
let esub: *tinfo = nil;
|
||||
if (sti != nil) { esub = sti.sub; };
|
||||
for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; };
|
||||
let esz: i32 = 0;
|
||||
if (esub != nil) { esz = esub.size: i32; };
|
||||
if (esz <= 0) {
|
||||
let m35a: str = "#35: delete() element size unresolved (rule-7)\n";
|
||||
os.write(2, m35a.ptr, m35a.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let hdr_lea: bool = false;
|
||||
let hdr_off: i32 = 0;
|
||||
let hdr_ok: bool = false;
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, base.str);
|
||||
if (lc != nil) {
|
||||
hdr_lea = true;
|
||||
hdr_off = lc.off;
|
||||
hdr_ok = true;
|
||||
};
|
||||
};
|
||||
// (*p)[i]: the header lives behind a local ptr-to-slice — the
|
||||
// regex fold-2b delete_thread shape (threads: *[]thread).
|
||||
if (!hdr_ok && base.kind == nkind.N_UN) {
|
||||
if (base.op == tkind.TK_STAR && base.lhs != nil) {
|
||||
if (base.lhs.kind == nkind.N_IDENT) {
|
||||
let pc: *local = localfindnode(c, base.lhs.str);
|
||||
if (pc != nil) {
|
||||
hdr_off = pc.off;
|
||||
hdr_ok = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!hdr_ok) {
|
||||
let m35b: str = "#35: delete() base shape unsupported (rule-7: local slice ident or deref-of-local only)\n";
|
||||
os.write(2, m35b.ptr, m35b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgexpr(c, d.rhs); // AX = i
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (hdr_lea) {
|
||||
emitline("\tLEAQ\t");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
};
|
||||
emitoff(hdr_off: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
let dll: str = mklabel(c, "del_l");
|
||||
let dle: str = mklabel(c, "del_e");
|
||||
emitlabel(dll);
|
||||
emitline("\tMOVQ\t(SP), DX\n");
|
||||
emitline("\tMOVQ\t8(SP), CX\n");
|
||||
emitline("\tMOVQ\t8(DX), BX\n");
|
||||
emitline("\tSUBQ\t$1, BX\n");
|
||||
emitline("\tCMPQ\tBX, CX\n");
|
||||
emitline("\tJGE\t"); emitline(dle); emitline("\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(DX), BX\n");
|
||||
emitline("\tADDQ\tCX, BX\n");
|
||||
let dk: i32 = 0;
|
||||
for (dk + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 8;
|
||||
};
|
||||
if (dk + 4 <= esz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 4;
|
||||
};
|
||||
if (dk + 2 <= esz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 2;
|
||||
};
|
||||
if (dk + 1 <= esz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 1;
|
||||
};
|
||||
emitline("\tADDQ\t$1, 8(SP)\n");
|
||||
emitline("\tJMP\t"); emitline(dll); emitline("\n");
|
||||
emitlabel(dle);
|
||||
emitline("\tMOVQ\t(SP), DX\n");
|
||||
emitline("\tSUBQ\t$1, 8(DX)\n");
|
||||
emitline("\tADDQ\t$16, SP\n");
|
||||
};
|
||||
|
||||
fn cgcall(c: *cgen, n: *node) void = {
|
||||
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
||||
// special-cased before pushargsrev so the spread variant can run
|
||||
@@ -24650,6 +24819,16 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// delete(xs[i]) — #35 delete-half; mirror of cstage
|
||||
// cgen.c's N_CALL delete arm.
|
||||
if (streq(callee.str, "delete")) {
|
||||
if (n.list != nil) {
|
||||
if (n.list.next == nil) {
|
||||
cgdelete(c, n);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -4608,6 +4608,135 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// cgdelete — Hare `delete(xs[i])`: single-element slice removal, the
|
||||
// delete-half of #35. Shift [i+1..len) down one stride, len -= 1, cap
|
||||
// unchanged. The move is a same-type whole-stride byte copy: src and
|
||||
// dst are elements of the SAME slice, so no boxing/coercion exists for
|
||||
// any element kind (str/slice 24B header, tagged box, struct body) —
|
||||
// one word-copy loop serves all kinds, unlike append's value-store
|
||||
// dispatch (#34) which boxes from a foreign source. Ascending j keeps
|
||||
// src (j+1) ahead of dst (j), the safe memmove-down direction. Mirrors
|
||||
// cmd/w6c/cgen.c's N_CALL delete arm instruction-for-instruction
|
||||
// (rule-10 byte-id).
|
||||
fn cgdelete(c: *cgen, n: *node) void = {
|
||||
let d: *node = n.list; // N_INDEX, checker-validated
|
||||
let base: *node = d.lhs;
|
||||
// esz off the STAMPED base tinfo (#34/#48 discipline — never the
|
||||
// value node). Peel TY_NAMED on indexable AND element, mirroring
|
||||
// cstage's type_chase_named on both (#8 family).
|
||||
let sti: *tinfo = base.type_: *tinfo;
|
||||
for (sti != nil && sti.kind == tykind.TY_NAMED) { sti = sti.under; };
|
||||
let esub: *tinfo = nil;
|
||||
if (sti != nil) { esub = sti.sub; };
|
||||
for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; };
|
||||
let esz: i32 = 0;
|
||||
if (esub != nil) { esz = esub.size: i32; };
|
||||
if (esz <= 0) {
|
||||
let m35a: str = "#35: delete() element size unresolved (rule-7)\n";
|
||||
os.write(2, m35a.ptr, m35a.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let hdr_lea: bool = false;
|
||||
let hdr_off: i32 = 0;
|
||||
let hdr_ok: bool = false;
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, base.str);
|
||||
if (lc != nil) {
|
||||
hdr_lea = true;
|
||||
hdr_off = lc.off;
|
||||
hdr_ok = true;
|
||||
};
|
||||
};
|
||||
// (*p)[i]: the header lives behind a local ptr-to-slice — the
|
||||
// regex fold-2b delete_thread shape (threads: *[]thread).
|
||||
if (!hdr_ok && base.kind == nkind.N_UN) {
|
||||
if (base.op == tkind.TK_STAR && base.lhs != nil) {
|
||||
if (base.lhs.kind == nkind.N_IDENT) {
|
||||
let pc: *local = localfindnode(c, base.lhs.str);
|
||||
if (pc != nil) {
|
||||
hdr_off = pc.off;
|
||||
hdr_ok = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!hdr_ok) {
|
||||
let m35b: str = "#35: delete() base shape unsupported (rule-7: local slice ident or deref-of-local only)\n";
|
||||
os.write(2, m35b.ptr, m35b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgexpr(c, d.rhs); // AX = i
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (hdr_lea) {
|
||||
emitline("\tLEAQ\t");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
};
|
||||
emitoff(hdr_off: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
let dll: str = mklabel(c, "del_l");
|
||||
let dle: str = mklabel(c, "del_e");
|
||||
emitlabel(dll);
|
||||
emitline("\tMOVQ\t(SP), DX\n");
|
||||
emitline("\tMOVQ\t8(SP), CX\n");
|
||||
emitline("\tMOVQ\t8(DX), BX\n");
|
||||
emitline("\tSUBQ\t$1, BX\n");
|
||||
emitline("\tCMPQ\tBX, CX\n");
|
||||
emitline("\tJGE\t"); emitline(dle); emitline("\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(DX), BX\n");
|
||||
emitline("\tADDQ\tCX, BX\n");
|
||||
let dk: i32 = 0;
|
||||
for (dk + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 8;
|
||||
};
|
||||
if (dk + 4 <= esz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 4;
|
||||
};
|
||||
if (dk + 2 <= esz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 2;
|
||||
};
|
||||
if (dk + 1 <= esz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 1;
|
||||
};
|
||||
emitline("\tADDQ\t$1, 8(SP)\n");
|
||||
emitline("\tJMP\t"); emitline(dll); emitline("\n");
|
||||
emitlabel(dle);
|
||||
emitline("\tMOVQ\t(SP), DX\n");
|
||||
emitline("\tSUBQ\t$1, 8(DX)\n");
|
||||
emitline("\tADDQ\t$16, SP\n");
|
||||
};
|
||||
|
||||
fn cgcall(c: *cgen, n: *node) void = {
|
||||
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
||||
// special-cased before pushargsrev so the spread variant can run
|
||||
@@ -4780,6 +4909,16 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// delete(xs[i]) — #35 delete-half; mirror of cstage
|
||||
// cgen.c's N_CALL delete arm.
|
||||
if (streq(callee.str, "delete")) {
|
||||
if (n.list != nil) {
|
||||
if (n.list.next == nil) {
|
||||
cgdelete(c, n);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -97,12 +97,14 @@ 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` are pseudo-builtins; scopedefine
|
||||
// them so their use sites resolve. The actual semantics live in cgen.
|
||||
// `len`, `alloc`, `free`, `append`, `delete` 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);
|
||||
// #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
|
||||
@@ -2562,6 +2564,44 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
// delete(xs[i]) — single-element slice removal, the
|
||||
// delete-half of #35 (insert() deferred). 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) —
|
||||
// loud-rejected here, filed on #35 (regex fold-2b's
|
||||
// consumers are all single-element).
|
||||
if (streq(callee.str, "delete")) {
|
||||
let d: *node = e.list;
|
||||
if (d == nil || d.next != nil) {
|
||||
cerr("delete: takes exactly one argument\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
if (d != nil) {
|
||||
exprtype(c, d, nil);
|
||||
if (d.kind == nkind.N_SLICE) {
|
||||
cerr("delete: range form delete(xs[i..j]) unimplemented (task #35)\n");
|
||||
c.errs += 1;
|
||||
} else {
|
||||
if (d.kind != nkind.N_INDEX) {
|
||||
cerr("delete: 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:2024 wording; a
|
||||
// fixed-size [N]T base lands here.
|
||||
if (u == nil || u.kind != nkind.N_TSLICE) {
|
||||
cerr("delete must operate on a slice\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
};
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
|
||||
@@ -10424,12 +10424,14 @@ 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` are pseudo-builtins; scopedefine
|
||||
// them so their use sites resolve. The actual semantics live in cgen.
|
||||
// `len`, `alloc`, `free`, `append`, `delete` 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);
|
||||
// #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
|
||||
@@ -12889,6 +12891,44 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
// delete(xs[i]) — single-element slice removal, the
|
||||
// delete-half of #35 (insert() deferred). 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) —
|
||||
// loud-rejected here, filed on #35 (regex fold-2b's
|
||||
// consumers are all single-element).
|
||||
if (streq(callee.str, "delete")) {
|
||||
let d: *node = e.list;
|
||||
if (d == nil || d.next != nil) {
|
||||
cerr("delete: takes exactly one argument\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
if (d != nil) {
|
||||
exprtype(c, d, nil);
|
||||
if (d.kind == nkind.N_SLICE) {
|
||||
cerr("delete: range form delete(xs[i..j]) unimplemented (task #35)\n");
|
||||
c.errs += 1;
|
||||
} else {
|
||||
if (d.kind != nkind.N_INDEX) {
|
||||
cerr("delete: 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:2024 wording; a
|
||||
// fixed-size [N]T base lands here.
|
||||
if (u == nil || u.kind != nkind.N_TSLICE) {
|
||||
cerr("delete must operate on a slice\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
return tn;
|
||||
};
|
||||
};
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
@@ -24478,6 +24518,135 @@ fn cgappend(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// cgdelete — Hare `delete(xs[i])`: single-element slice removal, the
|
||||
// delete-half of #35. Shift [i+1..len) down one stride, len -= 1, cap
|
||||
// unchanged. The move is a same-type whole-stride byte copy: src and
|
||||
// dst are elements of the SAME slice, so no boxing/coercion exists for
|
||||
// any element kind (str/slice 24B header, tagged box, struct body) —
|
||||
// one word-copy loop serves all kinds, unlike append's value-store
|
||||
// dispatch (#34) which boxes from a foreign source. Ascending j keeps
|
||||
// src (j+1) ahead of dst (j), the safe memmove-down direction. Mirrors
|
||||
// cmd/w6c/cgen.c's N_CALL delete arm instruction-for-instruction
|
||||
// (rule-10 byte-id).
|
||||
fn cgdelete(c: *cgen, n: *node) void = {
|
||||
let d: *node = n.list; // N_INDEX, checker-validated
|
||||
let base: *node = d.lhs;
|
||||
// esz off the STAMPED base tinfo (#34/#48 discipline — never the
|
||||
// value node). Peel TY_NAMED on indexable AND element, mirroring
|
||||
// cstage's type_chase_named on both (#8 family).
|
||||
let sti: *tinfo = base.type_: *tinfo;
|
||||
for (sti != nil && sti.kind == tykind.TY_NAMED) { sti = sti.under; };
|
||||
let esub: *tinfo = nil;
|
||||
if (sti != nil) { esub = sti.sub; };
|
||||
for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; };
|
||||
let esz: i32 = 0;
|
||||
if (esub != nil) { esz = esub.size: i32; };
|
||||
if (esz <= 0) {
|
||||
let m35a: str = "#35: delete() element size unresolved (rule-7)\n";
|
||||
os.write(2, m35a.ptr, m35a.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let hdr_lea: bool = false;
|
||||
let hdr_off: i32 = 0;
|
||||
let hdr_ok: bool = false;
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, base.str);
|
||||
if (lc != nil) {
|
||||
hdr_lea = true;
|
||||
hdr_off = lc.off;
|
||||
hdr_ok = true;
|
||||
};
|
||||
};
|
||||
// (*p)[i]: the header lives behind a local ptr-to-slice — the
|
||||
// regex fold-2b delete_thread shape (threads: *[]thread).
|
||||
if (!hdr_ok && base.kind == nkind.N_UN) {
|
||||
if (base.op == tkind.TK_STAR && base.lhs != nil) {
|
||||
if (base.lhs.kind == nkind.N_IDENT) {
|
||||
let pc: *local = localfindnode(c, base.lhs.str);
|
||||
if (pc != nil) {
|
||||
hdr_off = pc.off;
|
||||
hdr_ok = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!hdr_ok) {
|
||||
let m35b: str = "#35: delete() base shape unsupported (rule-7: local slice ident or deref-of-local only)\n";
|
||||
os.write(2, m35b.ptr, m35b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgexpr(c, d.rhs); // AX = i
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (hdr_lea) {
|
||||
emitline("\tLEAQ\t");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
};
|
||||
emitoff(hdr_off: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
let dll: str = mklabel(c, "del_l");
|
||||
let dle: str = mklabel(c, "del_e");
|
||||
emitlabel(dll);
|
||||
emitline("\tMOVQ\t(SP), DX\n");
|
||||
emitline("\tMOVQ\t8(SP), CX\n");
|
||||
emitline("\tMOVQ\t8(DX), BX\n");
|
||||
emitline("\tSUBQ\t$1, BX\n");
|
||||
emitline("\tCMPQ\tBX, CX\n");
|
||||
emitline("\tJGE\t"); emitline(dle); emitline("\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(DX), BX\n");
|
||||
emitline("\tADDQ\tCX, BX\n");
|
||||
let dk: i32 = 0;
|
||||
for (dk + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 8;
|
||||
};
|
||||
if (dk + 4 <= esz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 4;
|
||||
};
|
||||
if (dk + 2 <= esz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 2;
|
||||
};
|
||||
if (dk + 1 <= esz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitdispreg((esz + dk): i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg(dk: i64, "BX");
|
||||
emitline("\n");
|
||||
dk += 1;
|
||||
};
|
||||
emitline("\tADDQ\t$1, 8(SP)\n");
|
||||
emitline("\tJMP\t"); emitline(dll); emitline("\n");
|
||||
emitlabel(dle);
|
||||
emitline("\tMOVQ\t(SP), DX\n");
|
||||
emitline("\tSUBQ\t$1, 8(DX)\n");
|
||||
emitline("\tADDQ\t$16, SP\n");
|
||||
};
|
||||
|
||||
fn cgcall(c: *cgen, n: *node) void = {
|
||||
// Hare-style `append(s, v)` / `append(s, items...)` builtin —
|
||||
// special-cased before pushargsrev so the spread variant can run
|
||||
@@ -24650,6 +24819,16 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// delete(xs[i]) — #35 delete-half; mirror of cstage
|
||||
// cgen.c's N_CALL delete arm.
|
||||
if (streq(callee.str, "delete")) {
|
||||
if (n.list != nil) {
|
||||
if (n.list.next == nil) {
|
||||
cgdelete(c, n);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
422
test/wcc/804_delete_elem.c
Normal file
422
test/wcc/804_delete_elem.c
Normal file
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* 804_delete_elem — cstage and wwstage agree, byte-for-byte and at
|
||||
* runtime, that `delete(xs[i])` removes element i from a slice: the
|
||||
* tail [i+1..len) shifts down one stride, len -= 1, cap unchanged
|
||||
* (the delete-half of task #35; insert() stays deferred; regex
|
||||
* fold-2b's delete_thread/failed-thread/PC-dedup consumers).
|
||||
*
|
||||
* Lowering (BOTH stages, converged byte-identical by construction —
|
||||
* there was no pre-existing reference side): push i and &hdr, then an
|
||||
* ascending word-copy loop moves element j+1 onto element j until
|
||||
* j >= len-1, then hdr.len -= 1. The element move is a same-type
|
||||
* whole-stride byte copy — src and dst are elements of the SAME
|
||||
* slice, so no boxing/coercion exists for any element kind; the rows
|
||||
* below prove that across scalar (8B), narrow (4B/1B — the MOVL/MOVB
|
||||
* copy tails), str (24B header), struct (16B) and tagged (56B) esz.
|
||||
* esz comes off the STAMPED base type (the #34/#48 discipline), so a
|
||||
* named-alias element cannot mis-stride.
|
||||
*
|
||||
* Hare's delete also accepts the range form delete(xs[i..j])
|
||||
* (ref/harec/src/check.c:1981-2027 EXPR_SLICE). Out of scope here —
|
||||
* checker loud-rejects it (reject_range row); filed on #35.
|
||||
*
|
||||
* row | shape | want
|
||||
* ----------------+----------------------------------------+------
|
||||
* i64_first | [7,11,13], delete(xs[0]) | 44
|
||||
* i64_middle | [7,11,13], delete(xs[1]) | 40
|
||||
* i64_last | [7,11,13], delete(xs[2]) (no copy, | 38
|
||||
* | loop body never runs) |
|
||||
* i32_narrow | []i32 esz=4 — the MOVL copy tail | 229
|
||||
* u8_narrow | []u8 esz=1 — the MOVB copy tail | 28
|
||||
* cap_unchanged | manual {ptr,len=3,cap=8} header over a | 91
|
||||
* | stack backing; delete middle; cap must |
|
||||
* | still read 8, tail value intact |
|
||||
* delete_to_empty | 1-element slice, delete(xs[0]), len=0 | 7
|
||||
* str_elem | []str esz=24 — 3-qword header moves | 234
|
||||
* | whole |
|
||||
* tagged_56b | [](s6|bool) esz=56 — 7-qword raw move, | 231
|
||||
* | tag+payload survive; match head + |
|
||||
* | raw-byte tail readback (#43 caveat) |
|
||||
* regex_shape | delete((*threads)[i]) behind *[]thread |
|
||||
* | — the fold-2b delete_thread shape | 24
|
||||
* | (deref-of-local base, struct element) |
|
||||
* reject_array | delete(t[0]) on [3]i64 — "delete must | BUILD_FAIL
|
||||
* | operate on a slice" |
|
||||
* reject_nonindex | delete(xs) — operand must be xs[i] | BUILD_FAIL
|
||||
* reject_range | delete(xs[0:2]) — range form deferred | BUILD_FAIL
|
||||
* reject_arity | delete(xs[0], xs[1]) | BUILD_FAIL
|
||||
*
|
||||
* Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id,
|
||||
* which subsumes the frame canary (TEXT main,$N) and the del_l/del_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 (the
|
||||
* checker reject set is part of the contract — rule 7, never a silent
|
||||
* acceptance). */
|
||||
#define BUILD_FAIL (-2147483647 - 1)
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "i64_first",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 7);\n"
|
||||
"\tappend(xs, 11);\n"
|
||||
"\tappend(xs, 13);\n"
|
||||
"\tdelete(xs[0]);\n"
|
||||
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
|
||||
"};\n",
|
||||
44 },
|
||||
|
||||
{ "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"
|
||||
"\tdelete(xs[1]);\n"
|
||||
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
|
||||
"};\n",
|
||||
40 },
|
||||
|
||||
{ "i64_last",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 7);\n"
|
||||
"\tappend(xs, 11);\n"
|
||||
"\tappend(xs, 13);\n"
|
||||
"\tdelete(xs[2]);\n"
|
||||
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
|
||||
"};\n",
|
||||
38 },
|
||||
|
||||
{ "i32_narrow",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i32 = [];\n"
|
||||
"\tappend(xs, 4i32);\n"
|
||||
"\tappend(xs, 9i32);\n"
|
||||
"\tappend(xs, 2i32);\n"
|
||||
"\tdelete(xs[0]);\n"
|
||||
"\treturn xs[0] + xs[1]*10 + len(xs)*100;\n"
|
||||
"};\n",
|
||||
229 },
|
||||
|
||||
{ "u8_narrow",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []u8 = [];\n"
|
||||
"\tappend(xs, 5u8);\n"
|
||||
"\tappend(xs, 9u8);\n"
|
||||
"\tappend(xs, 3u8);\n"
|
||||
"\tdelete(xs[1]);\n"
|
||||
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
|
||||
"};\n",
|
||||
28 },
|
||||
|
||||
{ "cap_unchanged",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8; hb[0] = 4u8; hb[1] = 6u8; hb[2] = 9u8;\n"
|
||||
"\tlet xs: []u8; xs.ptr = &hb[0]; xs.len = 3; xs.cap = 8;\n"
|
||||
"\tdelete(xs[1]);\n"
|
||||
"\treturn (xs.cap*10 + xs.len): i32 + xs[1]: i32;\n"
|
||||
"};\n",
|
||||
91 },
|
||||
|
||||
{ "delete_to_empty",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []u8 = [];\n"
|
||||
"\tappend(xs, 5u8);\n"
|
||||
"\tdelete(xs[0]);\n"
|
||||
"\treturn len(xs) + 7;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
{ "str_elem",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []str = [];\n"
|
||||
"\tlet a: str = \"abc\";\n"
|
||||
"\tlet b: str = \"de\";\n"
|
||||
"\tlet g: str = \"fghi\";\n"
|
||||
"\tappend(xs, a);\n"
|
||||
"\tappend(xs, b);\n"
|
||||
"\tappend(xs, g);\n"
|
||||
"\tdelete(xs[1]);\n"
|
||||
"\treturn (xs[0].len*10 + xs[1].len): i32 + len(xs)*100;\n"
|
||||
"};\n",
|
||||
234 },
|
||||
|
||||
/* 56B element: tag qword + 48B struct payload — seven whole-qword
|
||||
* moves; tag AND payload must both survive the raw move (a tagged
|
||||
* element at rest is just bytes; no boxing on a same-slice move).
|
||||
* Readback is split: match proves tag + head (s.a), and the tail
|
||||
* qword (f, element byte 48 -> absolute byte 104 of element 1) is
|
||||
* read RAW via a *u8 over xs.ptr — the indexed-match payload
|
||||
* cursor itself truncates past 32B (pre-existing, task #43), so a
|
||||
* match on s.f would mis-fail regardless of the move. Verified
|
||||
* truncation-free against a no-delete control. */
|
||||
{ "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 p1: s6 = s6 { a = 7, b = 8, c = 9, d = 10, e = 11, f = 12 };\n"
|
||||
"\tlet p2: s6 = s6 { a = 13, b = 14, c = 15, d = 16, e = 17, f = 18 };\n"
|
||||
"\tappend(xs, p0);\n"
|
||||
"\tappend(xs, p1);\n"
|
||||
"\tappend(xs, p2);\n"
|
||||
"\tdelete(xs[1]);\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[104]: i32;\n"
|
||||
"\treturn r + len(xs)*100;\n"
|
||||
"};\n",
|
||||
231 },
|
||||
|
||||
/* The fold-2b consumer shape: regex.ha:547-551 delete_thread takes
|
||||
* threads: *[]thread and deletes through the pointer. ww spells the
|
||||
* autoderef explicitly: delete((*threads)[i]). */
|
||||
{ "regex_shape",
|
||||
"package main;\n"
|
||||
"type thread = struct { pc: i64, matched: bool };\n"
|
||||
"fn delete_thread(i: i64, threads: *[]thread) void = {\n"
|
||||
"\tdelete((*threads)[i]);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []thread = [];\n"
|
||||
"\tappend(xs, thread { pc = 1, matched = false });\n"
|
||||
"\tappend(xs, thread { pc = 2, matched = false });\n"
|
||||
"\tappend(xs, thread { pc = 3, matched = false });\n"
|
||||
"\tdelete_thread(1, &xs);\n"
|
||||
"\tlet r: i32 = (xs[0].pc + xs[1].pc): i32;\n"
|
||||
"\treturn r + len(xs)*10;\n"
|
||||
"};\n",
|
||||
24 },
|
||||
|
||||
{ "reject_array",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: [3]i64 = [1, 2, 3];\n"
|
||||
"\tdelete(t[0]);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL },
|
||||
|
||||
{ "reject_nonindex",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []u8 = [];\n"
|
||||
"\tappend(xs, 5u8);\n"
|
||||
"\tdelete(xs);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL },
|
||||
|
||||
{ "reject_range",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []u8 = [];\n"
|
||||
"\tappend(xs, 5u8);\n"
|
||||
"\tappend(xs, 6u8);\n"
|
||||
"\tdelete(xs[0:2]);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL },
|
||||
|
||||
{ "reject_arity",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []u8 = [];\n"
|
||||
"\tappend(xs, 5u8);\n"
|
||||
"\tappend(xs, 6u8);\n"
|
||||
"\tdelete(xs[0], xs[1]);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[64], tmpdir[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/dele_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/dele_%d_d_%d", getpid(), i);
|
||||
|
||||
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>/dev/null",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
if (r->want != BUILD_FAIL)
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
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(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
|
||||
* w6c_ww and diff. Both delete lowerings are written fresh, so this is
|
||||
* the converged-by-construction gate: any drift in the shift loop, the
|
||||
* del_l/del_e label sequence, or the esz word-copy 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/dele_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs, sizeof cs, "/tmp/dele_asm_%d_%d_c.s", getpid(), i);
|
||||
snprintf(ws, sizeof ws, "/tmp/dele_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, "delete_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,
|
||||
"delete_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,
|
||||
"delete_elem: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("delete_elem: %d fixtures passed\n", total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user