wcc+w6c+w6c_ww: delete() range form delete(xs[lo:hi]) (fold-5a P2)
Hare's delete also takes a slicing place (harec check.c:1981-2027 EXPR_SLICE; Hare spells it delete(xs[i..j])): remove [lo, hi) — shift [hi..len) down count = hi-lo strides, len -= count, cap unchanged; lo defaults 0, hi defaults len, so delete(xs[:]) clears the slice with storage retained. Checker accepts N_SLICE next to N_INDEX (object must chase to a slice, harec :2024); the old range-unimplemented reject and its #35 cite drop. Lowering (both stages, converged byte-identical by construction) is the single-element arm's same-slice whole-stride word-copy loop with a DYNAMIC src offset (count*esz via a src register) instead of the constant one-stride. Base shapes: local slice ident, deref-of-local, plus NEW indexed local-slice base xs[g][lo:hi] — the fold-5a consumer shape (regex.ha:333 delete(jump_idxs[group_level][..]); outer stride off the type table). Bounds stay implicit, inheriting the documented single-element posture (no index checks anywhere in cgen). Operands evaluate left-to-right, exactly once, before the shift (harec order); only the header ADDRESS is taken before operand eval, so a bound expression's writes through the slice land before the copy. test/809: 64 fixtures — full/explicit/re-clear/head/mid/tail/empty a:a/end-boundary len:len/explicit 0:0 on a never-appended (nil-ptr) slice, single-vs-range equivalence, cap preservation, esz 1/2/4/8/16/24 copy tails against the dynamic src, operand order-of-eval (lo/hi CALLs fire once each, in order) + aliasing-visibility pins, the EXACT [][]size regex consumer shape, deref base, 2 reject rows w/ diagnostic text; every accept row cs==ww asm byte-id. test/804: reject_range row retired (form now accepted), reject_nonindex text follows the widened message.
This commit is contained in:
15
Makefile
15
Makefile
@@ -393,6 +393,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_tuple_sret_receive_run \
|
||||
$(BIN)/test_append_wide_elem \
|
||||
$(BIN)/test_delete_elem \
|
||||
$(BIN)/test_delete_range \
|
||||
$(BIN)/test_insert_elem \
|
||||
$(BIN)/test_arrlit_overlong \
|
||||
$(BIN)/test_placeaddr_store \
|
||||
@@ -1024,6 +1025,20 @@ $(BIN)/test_delete_elem: test/wcc/804_delete_elem.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# #35 (range-half, fold-5a prereq P2): delete(xs[lo:hi]) range slice
|
||||
# removal — runtime rows across defaults (full/head/tail/empty/end
|
||||
# boundary), the esz copy-tail spread against the dynamic src offset
|
||||
# (1/2/4/8/16/24), the single-elem equivalence, cap preservation,
|
||||
# operand order-of-eval + aliasing pins, the deref-of-local base, the
|
||||
# indexed-base regex.ha:333 consumer shape EXACT + checker reject rows
|
||||
# w/ diagnostic text + cs==ww asm byte-id (the converged-by-construction
|
||||
# rdl_l/rdl_e shift loop).
|
||||
$(BIN)/test_delete_range: test/wcc/809_delete_range.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(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.
|
||||
|
||||
193
cmd/w6c/cgen.c
193
cmd/w6c/cgen.c
@@ -6853,7 +6853,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* ; del_e:
|
||||
* ; MOVQ (SP), DX ; SUBQ $1, 8(DX)
|
||||
* ; ADDQ $16, SP */
|
||||
Node *d = n->list; /* N_INDEX, checker-validated */
|
||||
Node *d = n->list; /* N_INDEX or N_SLICE,
|
||||
* checker-validated */
|
||||
Node *base = d->lhs;
|
||||
Type *su = type_chase_named(base->type);
|
||||
Type *esub = (su && su->sub)
|
||||
@@ -6862,6 +6863,196 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (esz <= 0)
|
||||
fatal("#35: delete() element size unresolved "
|
||||
"(rule-7)");
|
||||
if (d->kind == N_SLICE) {
|
||||
/* delete(xs[lo:hi]) — range slice removal
|
||||
* (fold-5a prereq P2; harec check.c:1994
|
||||
* EXPR_SLICE). Shift [hi..len) down count =
|
||||
* hi-lo strides, len -= count, cap
|
||||
* unchanged; lo defaults 0, hi defaults
|
||||
* len. delete(xs[:]) never enters the copy
|
||||
* loop (lo+count == len at entry) and
|
||||
* zeroes len. The per-element move is the
|
||||
* single-element arm's same-slice
|
||||
* whole-stride word copy with a DYNAMIC
|
||||
* src offset (count*esz, via a src
|
||||
* register) instead of the constant
|
||||
* one-stride. Ascending j keeps src >=
|
||||
* dst, the safe memmove-down direction.
|
||||
* Bounds are implicit (no range check,
|
||||
* matching the single-element arm and the
|
||||
* rest of cgen).
|
||||
*
|
||||
* ; &hdr -> AX (ident / *p / xs[g])
|
||||
* ; PUSHQ AX ; 16(SP)=&hdr
|
||||
* ; lo -> AX ($0 default) ; PUSHQ AX
|
||||
* ; ; 8(SP)=j
|
||||
* ; count = hi - lo (hi: cgexpr or len)
|
||||
* ; PUSHQ AX ; (SP)=count
|
||||
* ; rdl_l:
|
||||
* ; MOVQ 16(SP), DX ; MOVQ 8(SP), CX
|
||||
* ; MOVQ (SP), AX ; ADDQ CX, AX
|
||||
* ; MOVQ 8(DX), BX
|
||||
* ; CMPQ BX, AX ; JGE rdl_e
|
||||
* ; ; j+count>=len
|
||||
* ; [IMULQ esz, CX]
|
||||
* ; MOVQ (DX), BX ; ADDQ CX, BX ; dst
|
||||
* ; MOVQ (SP), CX ; [IMULQ esz, CX]
|
||||
* ; ADDQ BX, CX ; src
|
||||
* ; word-copy esz bytes (CX) -> (BX)
|
||||
* ; ADDQ $1, 8(SP) ; JMP rdl_l
|
||||
* ; rdl_e:
|
||||
* ; MOVQ 16(SP), DX ; MOVQ (SP), AX
|
||||
* ; MOVQ 8(DX), BX ; SUBQ AX, BX
|
||||
* ; MOVQ BX, 8(DX) ; len-=count
|
||||
* ; ADDQ $24, SP */
|
||||
int hdr_lea = 0;
|
||||
int hdr_off = 0;
|
||||
int hdr_ok = 0;
|
||||
int hdr_idx = 0;
|
||||
int osz = 0;
|
||||
if (base->kind == N_IDENT &&
|
||||
localfind(locals, base->str) != 0) {
|
||||
hdr_lea = 1;
|
||||
hdr_off = localfind(locals, base->str);
|
||||
hdr_ok = 1;
|
||||
}
|
||||
/* (*p)[lo:hi]: header behind a local
|
||||
* ptr-to-slice — the single-element arm'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;
|
||||
}
|
||||
/* xs[g][lo:hi]: the header IS element g of
|
||||
* an outer local slice — the fold-5a
|
||||
* consumer shape (ref/hare/regex/regex.ha:333
|
||||
* delete(jump_idxs[group_level][..])).
|
||||
* Outer stride = the inner header type's
|
||||
* own table size (su). */
|
||||
if (!hdr_ok && base->kind == N_INDEX &&
|
||||
base->lhs &&
|
||||
base->lhs->kind == N_IDENT &&
|
||||
localfind(locals, base->lhs->str) != 0) {
|
||||
hdr_idx = 1;
|
||||
hdr_off = localfind(locals,
|
||||
base->lhs->str);
|
||||
osz = su ? (int)su->size : 0;
|
||||
if (osz <= 0)
|
||||
fatal("#35: delete() outer "
|
||||
"element size unresolved "
|
||||
"(rule-7)");
|
||||
hdr_ok = 1;
|
||||
}
|
||||
if (!hdr_ok)
|
||||
fatal("#35: delete() range base "
|
||||
"shape unsupported (rule-7: "
|
||||
"local slice ident, "
|
||||
"deref-of-local, or indexed "
|
||||
"local slice only)");
|
||||
if (hdr_idx) {
|
||||
cgexpr(c, base->rhs, locals);
|
||||
if (osz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(osz),
|
||||
areg(D_CX));
|
||||
ins2(c, A_IMULQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
}
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, hdr_off), areg(D_CX));
|
||||
ins2(c, A_ADDQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
} else 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));
|
||||
if (d->rhs)
|
||||
cgexpr(c, d->rhs, locals);
|
||||
else
|
||||
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (d->cond)
|
||||
cgexpr(c, d->cond, locals);
|
||||
else {
|
||||
ins2(c, A_MOVQ, amem(D_SP, 8),
|
||||
areg(D_CX));
|
||||
ins2(c, A_MOVQ, amem(D_CX, 8),
|
||||
areg(D_AX));
|
||||
}
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_CX));
|
||||
ins2(c, A_SUBQ, areg(D_CX), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
char *rll = mklabel(c, "rdl_l");
|
||||
char *rle = mklabel(c, "rdl_e");
|
||||
label(c, rll);
|
||||
ins2(c, A_MOVQ, amem(D_SP, 16), areg(D_DX));
|
||||
ins2(c, A_MOVQ, amem(D_SP, 8), areg(D_CX));
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_AX));
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(D_AX));
|
||||
ins2(c, A_MOVQ, amem(D_DX, 8), areg(D_BX));
|
||||
ins2(c, A_CMPQ, areg(D_BX), areg(D_AX));
|
||||
ins1(c, A_JGE, abranch(rle));
|
||||
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));
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), 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_ADDQ, areg(D_BX), areg(D_CX));
|
||||
int rk = 0;
|
||||
for (; rk + 8 <= esz; rk += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_CX, rk),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BX, rk));
|
||||
}
|
||||
if (rk + 4 <= esz) {
|
||||
ins2(c, A_MOVL, amem(D_CX, rk),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVL, areg(D_AX),
|
||||
amem(D_BX, rk));
|
||||
rk += 4;
|
||||
}
|
||||
if (rk + 2 <= esz) {
|
||||
ins2(c, A_MOVW, amem(D_CX, rk),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVW, areg(D_AX),
|
||||
amem(D_BX, rk));
|
||||
rk += 2;
|
||||
}
|
||||
if (rk + 1 <= esz) {
|
||||
ins2(c, A_MOVB, amem(D_CX, rk),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVB, areg(D_AX),
|
||||
amem(D_BX, rk));
|
||||
rk += 1;
|
||||
}
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_SP, 8));
|
||||
ins1(c, A_JMP, abranch(rll));
|
||||
label(c, rle);
|
||||
ins2(c, A_MOVQ, amem(D_SP, 16), areg(D_DX));
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_AX));
|
||||
ins2(c, A_MOVQ, amem(D_DX, 8), areg(D_BX));
|
||||
ins2(c, A_SUBQ, areg(D_AX), areg(D_BX));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_DX, 8));
|
||||
ins2(c, A_ADDQ, aimm(24), areg(D_SP));
|
||||
break;
|
||||
}
|
||||
int hdr_lea = 0;
|
||||
int hdr_off = 0;
|
||||
int hdr_ok = 0;
|
||||
|
||||
@@ -1479,12 +1479,14 @@ 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() is the twin arm below). harec
|
||||
* ref/harec/src/check.c:1981-2027 also accepts the range
|
||||
* form delete(xs[i..j]) (EXPR_SLICE) — out of scope here
|
||||
* (regex fold-2b's consumers are all single-element);
|
||||
* loud-rejected below, the range form stays filed on #35. */
|
||||
/* delete(xs[i]) / delete(xs[lo:hi]) — slice removal, the
|
||||
* delete-half of #35 (insert() is the twin arm below).
|
||||
* harec ref/harec/src/check.c:1981-2027 accepts both an
|
||||
* indexing place (EXPR_ACCESS/ACCESS_INDEX) and a slicing
|
||||
* place (EXPR_SLICE — Hare spells it delete(xs[i..j]));
|
||||
* either way the OBJECT must be a slice. The range form
|
||||
* is the fold-5a prereq P2 (regex.ha:333
|
||||
* delete(jump_idxs[group_level][..])). */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT &&
|
||||
n->lhs->str && strcmp(n->lhs->str, "delete") == 0) {
|
||||
Node *d = n->list;
|
||||
@@ -1492,16 +1494,17 @@ cexpr(Checker *c, Node *n)
|
||||
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]");
|
||||
/* harec check.c:2016's reject; wording
|
||||
* adapted to ww's delete: prefix. */
|
||||
if (d->kind != N_INDEX && d->kind != N_SLICE)
|
||||
err(c, n->pos, "delete: operand must be an indexing or slicing expression");
|
||||
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. */
|
||||
* fixed-size [N]T base and a str
|
||||
* base land here. */
|
||||
if (bt == NULL || bt->kind != TY_SLICE)
|
||||
err(c, n->pos, "delete must operate on a slice");
|
||||
}
|
||||
|
||||
@@ -12939,13 +12939,15 @@ 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() is the twin arm below). Mirrors
|
||||
// cstage cmd/wcc/check.c's delete arm. harec
|
||||
// ref/harec/src/check.c:1981-2027 also accepts the
|
||||
// range form delete(xs[i..j]) (EXPR_SLICE) —
|
||||
// loud-rejected here, filed on #35 (regex fold-2b's
|
||||
// consumers are all single-element).
|
||||
// delete(xs[i]) / delete(xs[lo:hi]) — slice removal,
|
||||
// the delete-half of #35 (insert() is the twin arm
|
||||
// below). Mirrors cstage cmd/wcc/check.c's delete
|
||||
// arm. harec ref/harec/src/check.c:1981-2027 accepts
|
||||
// both an indexing place (EXPR_ACCESS/ACCESS_INDEX)
|
||||
// and a slicing place (EXPR_SLICE — Hare spells it
|
||||
// delete(xs[i..j])); either way the OBJECT must be a
|
||||
// slice. The range form is the fold-5a prereq P2
|
||||
// (regex.ha:333 delete(jump_idxs[group_level][..])).
|
||||
if (streq(callee.str, "delete")) {
|
||||
let d: *node = e.list;
|
||||
if (d == nil || d.next != nil) {
|
||||
@@ -12954,22 +12956,20 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
};
|
||||
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");
|
||||
// harec check.c:2016's reject; wording
|
||||
// adapted to ww's delete: prefix.
|
||||
if (d.kind != nkind.N_INDEX && d.kind != nkind.N_SLICE) {
|
||||
cerr("delete: operand must be an indexing or slicing expression\n");
|
||||
c.errs += 1;
|
||||
} else {
|
||||
if (d.kind != nkind.N_INDEX) {
|
||||
cerr("delete: operand must be an indexing expression xs[i]\n");
|
||||
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 and a str
|
||||
// base land here.
|
||||
if (u == nil || u.kind != nkind.N_TSLICE) {
|
||||
cerr("delete must operate on a slice\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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -25817,6 +25817,201 @@ fn cgdelete(c: *cgen, n: *node) void = {
|
||||
emitline("\tADDQ\t$16, SP\n");
|
||||
};
|
||||
|
||||
// cgdeleterange — Hare `delete(xs[lo..hi])` (ww spells the range
|
||||
// `xs[lo:hi]`): range slice removal, the fold-5a prereq P2
|
||||
// (ref/hare/regex/regex.ha:333 delete(jump_idxs[group_level][..]);
|
||||
// harec ref/harec/src/check.c:1994 EXPR_SLICE). Shift [hi..len) down
|
||||
// count = hi-lo strides, len -= count, cap unchanged; lo defaults 0,
|
||||
// hi defaults len. delete(xs[:]) never enters the copy loop (lo+count
|
||||
// == len at entry) and zeroes len. The per-element move is cgdelete's
|
||||
// same-slice whole-stride word copy with a DYNAMIC src offset
|
||||
// (count*esz via a src register) instead of the constant one-stride.
|
||||
// Ascending j keeps src >= dst, the safe memmove-down direction.
|
||||
// Bounds are implicit (no range check, matching cgdelete and the rest
|
||||
// of cgen). Mirrors cmd/w6c/cgen.c's N_CALL delete range arm
|
||||
// instruction-for-instruction (rule-10 byte-id).
|
||||
fn cgdeleterange(c: *cgen, n: *node) void = {
|
||||
let d: *node = n.list; // N_SLICE, 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;
|
||||
let hdr_idx: bool = false;
|
||||
let osz: i32 = 0;
|
||||
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)[lo:hi]: header behind a local ptr-to-slice — cgdelete'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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// xs[g][lo:hi]: the header IS element g of an outer local slice —
|
||||
// the fold-5a consumer shape (ref/hare/regex/regex.ha:333
|
||||
// delete(jump_idxs[group_level][..])). Outer stride = the inner
|
||||
// header type's own table size (sti).
|
||||
if (!hdr_ok && base.kind == nkind.N_INDEX) {
|
||||
if (base.lhs != nil) {
|
||||
if (base.lhs.kind == nkind.N_IDENT) {
|
||||
let oc: *local = localfindnode(c, base.lhs.str);
|
||||
if (oc != nil) {
|
||||
hdr_idx = true;
|
||||
hdr_off = oc.off;
|
||||
if (sti != nil) { osz = sti.size: i32; };
|
||||
if (osz <= 0) {
|
||||
let m35c: str = "#35: delete() outer element size unresolved (rule-7)\n";
|
||||
os.write(2, m35c.ptr, m35c.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
hdr_ok = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!hdr_ok) {
|
||||
let m35b: str = "#35: delete() range base shape unsupported (rule-7: local slice ident, deref-of-local, or indexed local slice only)\n";
|
||||
os.write(2, m35b.ptr, m35b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (hdr_idx) {
|
||||
cgexpr(c, base.rhs);
|
||||
if (osz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(osz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(hdr_off: i64);
|
||||
emitline("(BP), CX\n");
|
||||
emitline("\tADDQ\tCX, AX\n");
|
||||
} else {
|
||||
if (hdr_lea) {
|
||||
emitline("\tLEAQ\t");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
};
|
||||
emitoff(hdr_off: i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (d.rhs != nil) {
|
||||
cgexpr(c, d.rhs);
|
||||
} else {
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (d.cond != nil) {
|
||||
cgexpr(c, d.cond);
|
||||
} else {
|
||||
emitline("\tMOVQ\t8(SP), CX\n");
|
||||
emitline("\tMOVQ\t8(CX), AX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(SP), CX\n");
|
||||
emitline("\tSUBQ\tCX, AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
let rll: str = mklabel(c, "rdl_l");
|
||||
let rle: str = mklabel(c, "rdl_e");
|
||||
emitlabel(rll);
|
||||
emitline("\tMOVQ\t16(SP), DX\n");
|
||||
emitline("\tMOVQ\t8(SP), CX\n");
|
||||
emitline("\tMOVQ\t(SP), AX\n");
|
||||
emitline("\tADDQ\tCX, AX\n");
|
||||
emitline("\tMOVQ\t8(DX), BX\n");
|
||||
emitline("\tCMPQ\tBX, AX\n");
|
||||
emitline("\tJGE\t"); emitline(rle); 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");
|
||||
emitline("\tMOVQ\t(SP), CX\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tADDQ\tBX, CX\n");
|
||||
let rk: i32 = 0;
|
||||
for (rk + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 8;
|
||||
};
|
||||
if (rk + 4 <= esz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 4;
|
||||
};
|
||||
if (rk + 2 <= esz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 2;
|
||||
};
|
||||
if (rk + 1 <= esz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 1;
|
||||
};
|
||||
emitline("\tADDQ\t$1, 8(SP)\n");
|
||||
emitline("\tJMP\t"); emitline(rll); emitline("\n");
|
||||
emitlabel(rle);
|
||||
emitline("\tMOVQ\t16(SP), DX\n");
|
||||
emitline("\tMOVQ\t(SP), AX\n");
|
||||
emitline("\tMOVQ\t8(DX), BX\n");
|
||||
emitline("\tSUBQ\tAX, BX\n");
|
||||
emitline("\tMOVQ\tBX, 8(DX)\n");
|
||||
emitline("\tADDQ\t$24, 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
|
||||
@@ -26335,11 +26530,17 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// delete(xs[i]) — #35 delete-half; mirror of cstage
|
||||
// cgen.c's N_CALL delete arm.
|
||||
// delete(xs[i]) / delete(xs[lo:hi]) — #35
|
||||
// delete-half + fold-5a P2 range form; mirror of
|
||||
// cstage cgen.c's N_CALL delete arm (which branches
|
||||
// on d->kind == N_SLICE internally).
|
||||
if (streq(callee.str, "delete")) {
|
||||
if (n.list != nil) {
|
||||
if (n.list.next == nil) {
|
||||
if (n.list.kind == nkind.N_SLICE) {
|
||||
cgdeleterange(c, n);
|
||||
return;
|
||||
};
|
||||
cgdelete(c, n);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -5383,6 +5383,201 @@ fn cgdelete(c: *cgen, n: *node) void = {
|
||||
emitline("\tADDQ\t$16, SP\n");
|
||||
};
|
||||
|
||||
// cgdeleterange — Hare `delete(xs[lo..hi])` (ww spells the range
|
||||
// `xs[lo:hi]`): range slice removal, the fold-5a prereq P2
|
||||
// (ref/hare/regex/regex.ha:333 delete(jump_idxs[group_level][..]);
|
||||
// harec ref/harec/src/check.c:1994 EXPR_SLICE). Shift [hi..len) down
|
||||
// count = hi-lo strides, len -= count, cap unchanged; lo defaults 0,
|
||||
// hi defaults len. delete(xs[:]) never enters the copy loop (lo+count
|
||||
// == len at entry) and zeroes len. The per-element move is cgdelete's
|
||||
// same-slice whole-stride word copy with a DYNAMIC src offset
|
||||
// (count*esz via a src register) instead of the constant one-stride.
|
||||
// Ascending j keeps src >= dst, the safe memmove-down direction.
|
||||
// Bounds are implicit (no range check, matching cgdelete and the rest
|
||||
// of cgen). Mirrors cmd/w6c/cgen.c's N_CALL delete range arm
|
||||
// instruction-for-instruction (rule-10 byte-id).
|
||||
fn cgdeleterange(c: *cgen, n: *node) void = {
|
||||
let d: *node = n.list; // N_SLICE, 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;
|
||||
let hdr_idx: bool = false;
|
||||
let osz: i32 = 0;
|
||||
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)[lo:hi]: header behind a local ptr-to-slice — cgdelete'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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// xs[g][lo:hi]: the header IS element g of an outer local slice —
|
||||
// the fold-5a consumer shape (ref/hare/regex/regex.ha:333
|
||||
// delete(jump_idxs[group_level][..])). Outer stride = the inner
|
||||
// header type's own table size (sti).
|
||||
if (!hdr_ok && base.kind == nkind.N_INDEX) {
|
||||
if (base.lhs != nil) {
|
||||
if (base.lhs.kind == nkind.N_IDENT) {
|
||||
let oc: *local = localfindnode(c, base.lhs.str);
|
||||
if (oc != nil) {
|
||||
hdr_idx = true;
|
||||
hdr_off = oc.off;
|
||||
if (sti != nil) { osz = sti.size: i32; };
|
||||
if (osz <= 0) {
|
||||
let m35c: str = "#35: delete() outer element size unresolved (rule-7)\n";
|
||||
os.write(2, m35c.ptr, m35c.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
hdr_ok = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!hdr_ok) {
|
||||
let m35b: str = "#35: delete() range base shape unsupported (rule-7: local slice ident, deref-of-local, or indexed local slice only)\n";
|
||||
os.write(2, m35b.ptr, m35b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (hdr_idx) {
|
||||
cgexpr(c, base.rhs);
|
||||
if (osz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(osz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(hdr_off: i64);
|
||||
emitline("(BP), CX\n");
|
||||
emitline("\tADDQ\tCX, AX\n");
|
||||
} else {
|
||||
if (hdr_lea) {
|
||||
emitline("\tLEAQ\t");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
};
|
||||
emitoff(hdr_off: i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (d.rhs != nil) {
|
||||
cgexpr(c, d.rhs);
|
||||
} else {
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (d.cond != nil) {
|
||||
cgexpr(c, d.cond);
|
||||
} else {
|
||||
emitline("\tMOVQ\t8(SP), CX\n");
|
||||
emitline("\tMOVQ\t8(CX), AX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(SP), CX\n");
|
||||
emitline("\tSUBQ\tCX, AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
let rll: str = mklabel(c, "rdl_l");
|
||||
let rle: str = mklabel(c, "rdl_e");
|
||||
emitlabel(rll);
|
||||
emitline("\tMOVQ\t16(SP), DX\n");
|
||||
emitline("\tMOVQ\t8(SP), CX\n");
|
||||
emitline("\tMOVQ\t(SP), AX\n");
|
||||
emitline("\tADDQ\tCX, AX\n");
|
||||
emitline("\tMOVQ\t8(DX), BX\n");
|
||||
emitline("\tCMPQ\tBX, AX\n");
|
||||
emitline("\tJGE\t"); emitline(rle); 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");
|
||||
emitline("\tMOVQ\t(SP), CX\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tADDQ\tBX, CX\n");
|
||||
let rk: i32 = 0;
|
||||
for (rk + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 8;
|
||||
};
|
||||
if (rk + 4 <= esz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 4;
|
||||
};
|
||||
if (rk + 2 <= esz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 2;
|
||||
};
|
||||
if (rk + 1 <= esz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 1;
|
||||
};
|
||||
emitline("\tADDQ\t$1, 8(SP)\n");
|
||||
emitline("\tJMP\t"); emitline(rll); emitline("\n");
|
||||
emitlabel(rle);
|
||||
emitline("\tMOVQ\t16(SP), DX\n");
|
||||
emitline("\tMOVQ\t(SP), AX\n");
|
||||
emitline("\tMOVQ\t8(DX), BX\n");
|
||||
emitline("\tSUBQ\tAX, BX\n");
|
||||
emitline("\tMOVQ\tBX, 8(DX)\n");
|
||||
emitline("\tADDQ\t$24, 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
|
||||
@@ -5901,11 +6096,17 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// delete(xs[i]) — #35 delete-half; mirror of cstage
|
||||
// cgen.c's N_CALL delete arm.
|
||||
// delete(xs[i]) / delete(xs[lo:hi]) — #35
|
||||
// delete-half + fold-5a P2 range form; mirror of
|
||||
// cstage cgen.c's N_CALL delete arm (which branches
|
||||
// on d->kind == N_SLICE internally).
|
||||
if (streq(callee.str, "delete")) {
|
||||
if (n.list != nil) {
|
||||
if (n.list.next == nil) {
|
||||
if (n.list.kind == nkind.N_SLICE) {
|
||||
cgdeleterange(c, n);
|
||||
return;
|
||||
};
|
||||
cgdelete(c, n);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -2675,13 +2675,15 @@ 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() is the twin arm below). Mirrors
|
||||
// cstage cmd/wcc/check.c's delete arm. harec
|
||||
// ref/harec/src/check.c:1981-2027 also accepts the
|
||||
// range form delete(xs[i..j]) (EXPR_SLICE) —
|
||||
// loud-rejected here, filed on #35 (regex fold-2b's
|
||||
// consumers are all single-element).
|
||||
// delete(xs[i]) / delete(xs[lo:hi]) — slice removal,
|
||||
// the delete-half of #35 (insert() is the twin arm
|
||||
// below). Mirrors cstage cmd/wcc/check.c's delete
|
||||
// arm. harec ref/harec/src/check.c:1981-2027 accepts
|
||||
// both an indexing place (EXPR_ACCESS/ACCESS_INDEX)
|
||||
// and a slicing place (EXPR_SLICE — Hare spells it
|
||||
// delete(xs[i..j])); either way the OBJECT must be a
|
||||
// slice. The range form is the fold-5a prereq P2
|
||||
// (regex.ha:333 delete(jump_idxs[group_level][..])).
|
||||
if (streq(callee.str, "delete")) {
|
||||
let d: *node = e.list;
|
||||
if (d == nil || d.next != nil) {
|
||||
@@ -2690,22 +2692,20 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
};
|
||||
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");
|
||||
// harec check.c:2016's reject; wording
|
||||
// adapted to ww's delete: prefix.
|
||||
if (d.kind != nkind.N_INDEX && d.kind != nkind.N_SLICE) {
|
||||
cerr("delete: operand must be an indexing or slicing expression\n");
|
||||
c.errs += 1;
|
||||
} else {
|
||||
if (d.kind != nkind.N_INDEX) {
|
||||
cerr("delete: operand must be an indexing expression xs[i]\n");
|
||||
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 and a str
|
||||
// base land here.
|
||||
if (u == nil || u.kind != nkind.N_TSLICE) {
|
||||
cerr("delete must operate on a slice\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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -12939,13 +12939,15 @@ 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() is the twin arm below). Mirrors
|
||||
// cstage cmd/wcc/check.c's delete arm. harec
|
||||
// ref/harec/src/check.c:1981-2027 also accepts the
|
||||
// range form delete(xs[i..j]) (EXPR_SLICE) —
|
||||
// loud-rejected here, filed on #35 (regex fold-2b's
|
||||
// consumers are all single-element).
|
||||
// delete(xs[i]) / delete(xs[lo:hi]) — slice removal,
|
||||
// the delete-half of #35 (insert() is the twin arm
|
||||
// below). Mirrors cstage cmd/wcc/check.c's delete
|
||||
// arm. harec ref/harec/src/check.c:1981-2027 accepts
|
||||
// both an indexing place (EXPR_ACCESS/ACCESS_INDEX)
|
||||
// and a slicing place (EXPR_SLICE — Hare spells it
|
||||
// delete(xs[i..j])); either way the OBJECT must be a
|
||||
// slice. The range form is the fold-5a prereq P2
|
||||
// (regex.ha:333 delete(jump_idxs[group_level][..])).
|
||||
if (streq(callee.str, "delete")) {
|
||||
let d: *node = e.list;
|
||||
if (d == nil || d.next != nil) {
|
||||
@@ -12954,22 +12956,20 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
};
|
||||
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");
|
||||
// harec check.c:2016's reject; wording
|
||||
// adapted to ww's delete: prefix.
|
||||
if (d.kind != nkind.N_INDEX && d.kind != nkind.N_SLICE) {
|
||||
cerr("delete: operand must be an indexing or slicing expression\n");
|
||||
c.errs += 1;
|
||||
} else {
|
||||
if (d.kind != nkind.N_INDEX) {
|
||||
cerr("delete: operand must be an indexing expression xs[i]\n");
|
||||
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 and a str
|
||||
// base land here.
|
||||
if (u == nil || u.kind != nkind.N_TSLICE) {
|
||||
cerr("delete must operate on a slice\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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -25817,6 +25817,201 @@ fn cgdelete(c: *cgen, n: *node) void = {
|
||||
emitline("\tADDQ\t$16, SP\n");
|
||||
};
|
||||
|
||||
// cgdeleterange — Hare `delete(xs[lo..hi])` (ww spells the range
|
||||
// `xs[lo:hi]`): range slice removal, the fold-5a prereq P2
|
||||
// (ref/hare/regex/regex.ha:333 delete(jump_idxs[group_level][..]);
|
||||
// harec ref/harec/src/check.c:1994 EXPR_SLICE). Shift [hi..len) down
|
||||
// count = hi-lo strides, len -= count, cap unchanged; lo defaults 0,
|
||||
// hi defaults len. delete(xs[:]) never enters the copy loop (lo+count
|
||||
// == len at entry) and zeroes len. The per-element move is cgdelete's
|
||||
// same-slice whole-stride word copy with a DYNAMIC src offset
|
||||
// (count*esz via a src register) instead of the constant one-stride.
|
||||
// Ascending j keeps src >= dst, the safe memmove-down direction.
|
||||
// Bounds are implicit (no range check, matching cgdelete and the rest
|
||||
// of cgen). Mirrors cmd/w6c/cgen.c's N_CALL delete range arm
|
||||
// instruction-for-instruction (rule-10 byte-id).
|
||||
fn cgdeleterange(c: *cgen, n: *node) void = {
|
||||
let d: *node = n.list; // N_SLICE, 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;
|
||||
let hdr_idx: bool = false;
|
||||
let osz: i32 = 0;
|
||||
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)[lo:hi]: header behind a local ptr-to-slice — cgdelete'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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// xs[g][lo:hi]: the header IS element g of an outer local slice —
|
||||
// the fold-5a consumer shape (ref/hare/regex/regex.ha:333
|
||||
// delete(jump_idxs[group_level][..])). Outer stride = the inner
|
||||
// header type's own table size (sti).
|
||||
if (!hdr_ok && base.kind == nkind.N_INDEX) {
|
||||
if (base.lhs != nil) {
|
||||
if (base.lhs.kind == nkind.N_IDENT) {
|
||||
let oc: *local = localfindnode(c, base.lhs.str);
|
||||
if (oc != nil) {
|
||||
hdr_idx = true;
|
||||
hdr_off = oc.off;
|
||||
if (sti != nil) { osz = sti.size: i32; };
|
||||
if (osz <= 0) {
|
||||
let m35c: str = "#35: delete() outer element size unresolved (rule-7)\n";
|
||||
os.write(2, m35c.ptr, m35c.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
hdr_ok = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (!hdr_ok) {
|
||||
let m35b: str = "#35: delete() range base shape unsupported (rule-7: local slice ident, deref-of-local, or indexed local slice only)\n";
|
||||
os.write(2, m35b.ptr, m35b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (hdr_idx) {
|
||||
cgexpr(c, base.rhs);
|
||||
if (osz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(osz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(hdr_off: i64);
|
||||
emitline("(BP), CX\n");
|
||||
emitline("\tADDQ\tCX, AX\n");
|
||||
} else {
|
||||
if (hdr_lea) {
|
||||
emitline("\tLEAQ\t");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
};
|
||||
emitoff(hdr_off: i64);
|
||||
emitline("(BP), AX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (d.rhs != nil) {
|
||||
cgexpr(c, d.rhs);
|
||||
} else {
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (d.cond != nil) {
|
||||
cgexpr(c, d.cond);
|
||||
} else {
|
||||
emitline("\tMOVQ\t8(SP), CX\n");
|
||||
emitline("\tMOVQ\t8(CX), AX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(SP), CX\n");
|
||||
emitline("\tSUBQ\tCX, AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
let rll: str = mklabel(c, "rdl_l");
|
||||
let rle: str = mklabel(c, "rdl_e");
|
||||
emitlabel(rll);
|
||||
emitline("\tMOVQ\t16(SP), DX\n");
|
||||
emitline("\tMOVQ\t8(SP), CX\n");
|
||||
emitline("\tMOVQ\t(SP), AX\n");
|
||||
emitline("\tADDQ\tCX, AX\n");
|
||||
emitline("\tMOVQ\t8(DX), BX\n");
|
||||
emitline("\tCMPQ\tBX, AX\n");
|
||||
emitline("\tJGE\t"); emitline(rle); 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");
|
||||
emitline("\tMOVQ\t(SP), CX\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", AX\n");
|
||||
emitline("\tIMULQ\tAX, CX\n");
|
||||
};
|
||||
emitline("\tADDQ\tBX, CX\n");
|
||||
let rk: i32 = 0;
|
||||
for (rk + 8 <= esz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 8;
|
||||
};
|
||||
if (rk + 4 <= esz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 4;
|
||||
};
|
||||
if (rk + 2 <= esz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 2;
|
||||
};
|
||||
if (rk + 1 <= esz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitdispreg(rk: i64, "CX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitdispreg(rk: i64, "BX");
|
||||
emitline("\n");
|
||||
rk += 1;
|
||||
};
|
||||
emitline("\tADDQ\t$1, 8(SP)\n");
|
||||
emitline("\tJMP\t"); emitline(rll); emitline("\n");
|
||||
emitlabel(rle);
|
||||
emitline("\tMOVQ\t16(SP), DX\n");
|
||||
emitline("\tMOVQ\t(SP), AX\n");
|
||||
emitline("\tMOVQ\t8(DX), BX\n");
|
||||
emitline("\tSUBQ\tAX, BX\n");
|
||||
emitline("\tMOVQ\tBX, 8(DX)\n");
|
||||
emitline("\tADDQ\t$24, 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
|
||||
@@ -26335,11 +26530,17 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// delete(xs[i]) — #35 delete-half; mirror of cstage
|
||||
// cgen.c's N_CALL delete arm.
|
||||
// delete(xs[i]) / delete(xs[lo:hi]) — #35
|
||||
// delete-half + fold-5a P2 range form; mirror of
|
||||
// cstage cgen.c's N_CALL delete arm (which branches
|
||||
// on d->kind == N_SLICE internally).
|
||||
if (streq(callee.str, "delete")) {
|
||||
if (n.list != nil) {
|
||||
if (n.list.next == nil) {
|
||||
if (n.list.kind == nkind.N_SLICE) {
|
||||
cgdeleterange(c, n);
|
||||
return;
|
||||
};
|
||||
cgdelete(c, n);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
* 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.
|
||||
* (ref/harec/src/check.c:1981-2027 EXPR_SLICE) — implemented as the
|
||||
* fold-5a prereq P2; covered by 809_delete_range.
|
||||
*
|
||||
* row | shape | want
|
||||
* ----------------+----------------------------------------+------
|
||||
@@ -47,9 +47,8 @@
|
||||
* | iteration, base-4 order accumulation |
|
||||
* 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
|
||||
* | message must cite task #35 |
|
||||
* reject_nonindex | delete(xs) — operand must be an index- | BUILD_FAIL
|
||||
* | ing or slicing expression |
|
||||
* reject_arity | delete(xs[0], xs[1]) | BUILD_FAIL
|
||||
*
|
||||
* BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring,
|
||||
@@ -78,9 +77,9 @@ runwait(const char *cmd)
|
||||
|
||||
/* 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 range 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). */
|
||||
* included — 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 {
|
||||
@@ -290,18 +289,7 @@ static const struct row rows[] = {
|
||||
"\tdelete(xs);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "delete: 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"
|
||||
"\tdelete(xs[0:2]);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "range form delete(xs[i..j]) unimplemented (task #35)" },
|
||||
BUILD_FAIL, "delete: operand must be an indexing or slicing expression" },
|
||||
|
||||
{ "reject_arity",
|
||||
"package main;\n"
|
||||
|
||||
607
test/wcc/809_delete_range.c
Normal file
607
test/wcc/809_delete_range.c
Normal file
@@ -0,0 +1,607 @@
|
||||
/*
|
||||
* 809_delete_range — cstage and wwstage agree, byte-for-byte and at
|
||||
* runtime, that the range form `delete(xs[lo:hi])` removes elements
|
||||
* [lo, hi) from a slice: the tail [hi..len) shifts down count = hi-lo
|
||||
* strides, len -= count, cap unchanged; lo defaults 0, hi defaults
|
||||
* len (so delete(xs[:]) clears the slice, storage retained). The
|
||||
* range half of the delete() builtin — fold-5a prereq P2 for the
|
||||
* regex.ha:333 consumer `delete(jump_idxs[group_level][..])` (harec
|
||||
* ref/harec/src/check.c:1981-2027 accepts EXPR_SLICE alongside the
|
||||
* 804-covered ACCESS_INDEX form).
|
||||
*
|
||||
* Lowering (BOTH stages, converged byte-identical by construction):
|
||||
* push &hdr, lo and count = hi-lo, then an ascending word-copy loop
|
||||
* moves element j+count onto element j until j+count >= len, then
|
||||
* hdr.len -= count. The per-element move is 804's same-slice
|
||||
* whole-stride word copy with a DYNAMIC src offset (count*esz via a
|
||||
* src register) instead of the constant one-stride; the esz spread
|
||||
* below (1/2/4/8/16/24) re-proves every copy-tail arm against the
|
||||
* dynamic src. Base shapes: local slice ident, deref-of-local
|
||||
* ptr-to-slice, and — new for the range arm — an INDEXED local slice
|
||||
* base xs[g][lo:hi] (the regex fold-5a consumer shape). Bounds are
|
||||
* implicit (no range check), matching the 804 single-element arm and
|
||||
* the rest of cgen — so no out-of-bounds abort rows here; a
|
||||
* violating range is as undefined as a violating index.
|
||||
*
|
||||
* row | shape | want
|
||||
* -----------------+----------------------------------------+------
|
||||
* full_range | [7,11,13], delete(xs[:]) -> len 0 | 7
|
||||
* full_explicit | [7,11,13], delete(xs[0:3]) -> len 0 | 8
|
||||
* full_twice | delete(xs[:]) twice — second is a | 9
|
||||
* | no-op on the emptied slice (count 0) |
|
||||
* head | [1,2,3], delete(xs[:2]) — lo default 0 | 13
|
||||
* mid | [1..5], delete(xs[1:3]) | 148
|
||||
* tail_open | [1,2,3], delete(xs[1:]) — hi default | 11
|
||||
* | len, copy loop never entered |
|
||||
* empty_range | [1,2], delete(xs[1:1]) — count 0, | 23
|
||||
* | self-copy loop, len unchanged |
|
||||
* end_boundary | [1,2], delete(xs[len:len]) — lo==hi== | 212
|
||||
* | len, loop exits at entry, no deref |
|
||||
* | past the end |
|
||||
* empty_explicit | delete(xs[0:0]) on a never-appended | 31
|
||||
* | empty slice (ptr nil) — count 0 and |
|
||||
* | len 0, nothing dereferenced |
|
||||
* single_via_range | delete(xs[1:2]) == delete(ys[1]) | 42
|
||||
* | element-for-element |
|
||||
* cap_preserved | manual {ptr,len=3,cap=8} header, | 80
|
||||
* | delete(xs[:]); cap must still read 8 |
|
||||
* u8_narrow | []u8 esz=1 — MOVB tail, dynamic src | 215
|
||||
* u16_narrow | []u16 esz=2 — MOVW tail, dynamic src | 25
|
||||
* i32_narrow | []i32 esz=4 — MOVL tail, dynamic src | 50
|
||||
* struct16 | []pair esz=16 — 2 whole qwords, no | 216
|
||||
* | sub-word tail (804's struct esz twin) |
|
||||
* str_elem | []str esz=24 — 3-qword headers move | 235
|
||||
* | whole across a 2-stride shift |
|
||||
* regex_exact | [][]size, delete(jj[lvl][:]) — the | 10
|
||||
* | EXACT regex.ha:333 consumer shape |
|
||||
* | (indexed base, size-typed index) |
|
||||
* deref_shape | delete((*p)[:]) behind *[]u8 — the | 17
|
||||
* | 804 regex_shape twin |
|
||||
* order_of_eval | delete(xs[lof():hif()]) — lo then hi, | 16
|
||||
* | each CALLED exactly once (trace must |
|
||||
* | read 12; harec evaluates the slicing |
|
||||
* | operands left-to-right, once) |
|
||||
* alias_visible | delete(xs[poke(&xs):]) — lo's write | 91
|
||||
* | through &xs lands BEFORE the shift |
|
||||
* | (loop reads ptr/len/elems fresh from |
|
||||
* | the header, after operand eval) |
|
||||
* reject_array | delete(t[0:2]) on [3]i64 — "delete | BUILD_FAIL
|
||||
* | must operate on a slice" |
|
||||
* reject_str | delete(s[:]) on str — slicing a str | BUILD_FAIL
|
||||
* | yields str, not a slice |
|
||||
*
|
||||
* 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) and the rdl_l/rdl_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 — 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[] = {
|
||||
{ "full_range",
|
||||
"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[:]);\n"
|
||||
"\treturn len(xs): i32 + 7;\n"
|
||||
"};\n",
|
||||
7, NULL },
|
||||
|
||||
{ "full_explicit",
|
||||
"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:3]);\n"
|
||||
"\treturn len(xs): i32 + 8;\n"
|
||||
"};\n",
|
||||
8, NULL },
|
||||
|
||||
/* Second full-range delete sees len == 0: count = len - 0 = 0,
|
||||
* the copy loop exits at entry, len -= 0. Pins that an emptied
|
||||
* slice survives a re-clear (the regex `)` arm clears
|
||||
* jump_idxs[group_level] whether or not `|` populated it). */
|
||||
{ "full_twice",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 7);\n"
|
||||
"\tappend(xs, 11);\n"
|
||||
"\tdelete(xs[:]);\n"
|
||||
"\tdelete(xs[:]);\n"
|
||||
"\treturn len(xs): i32 + 9;\n"
|
||||
"};\n",
|
||||
9, NULL },
|
||||
|
||||
{ "head",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 1);\n"
|
||||
"\tappend(xs, 2);\n"
|
||||
"\tappend(xs, 3);\n"
|
||||
"\tdelete(xs[:2]);\n"
|
||||
"\treturn xs[0]: i32 + len(xs)*10;\n"
|
||||
"};\n",
|
||||
13, NULL },
|
||||
|
||||
{ "mid",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 1);\n"
|
||||
"\tappend(xs, 2);\n"
|
||||
"\tappend(xs, 3);\n"
|
||||
"\tappend(xs, 4);\n"
|
||||
"\tappend(xs, 5);\n"
|
||||
"\tdelete(xs[1:3]);\n"
|
||||
"\treturn (xs[0]*100 + xs[1]*10 + xs[2]): i32 + len(xs);\n"
|
||||
"};\n",
|
||||
148, NULL },
|
||||
|
||||
{ "tail_open",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 1);\n"
|
||||
"\tappend(xs, 2);\n"
|
||||
"\tappend(xs, 3);\n"
|
||||
"\tdelete(xs[1:]);\n"
|
||||
"\treturn xs[0]: i32 + len(xs)*10;\n"
|
||||
"};\n",
|
||||
11, NULL },
|
||||
|
||||
{ "empty_range",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 1);\n"
|
||||
"\tappend(xs, 2);\n"
|
||||
"\tdelete(xs[1:1]);\n"
|
||||
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
|
||||
"};\n",
|
||||
23, NULL },
|
||||
|
||||
/* lo == hi == len: the loop guard j+count >= len holds at entry
|
||||
* with j = len, so nothing past the end is ever addressed. */
|
||||
{ "end_boundary",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 1);\n"
|
||||
"\tappend(xs, 2);\n"
|
||||
"\tdelete(xs[len(xs):len(xs)]);\n"
|
||||
"\treturn (xs[0]*10 + xs[1]): i32 + len(xs)*100;\n"
|
||||
"};\n",
|
||||
212, NULL },
|
||||
|
||||
/* Never-appended empty slice: ptr is nil. count 0 and len 0 mean
|
||||
* the loop body (the only dereferencing code) never runs. */
|
||||
{ "empty_explicit",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tdelete(xs[0:0]);\n"
|
||||
"\treturn len(xs): i32 + 31;\n"
|
||||
"};\n",
|
||||
31, NULL },
|
||||
|
||||
{ "single_via_range",
|
||||
"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:2]);\n"
|
||||
"\tlet ys: []i64 = [];\n"
|
||||
"\tappend(ys, 7);\n"
|
||||
"\tappend(ys, 11);\n"
|
||||
"\tappend(ys, 13);\n"
|
||||
"\tdelete(ys[1]);\n"
|
||||
"\tif (len(xs) != len(ys)) { return 99; };\n"
|
||||
"\tif (xs[0] != ys[0]) { return 98; };\n"
|
||||
"\tif (xs[1] != ys[1]) { return 97; };\n"
|
||||
"\treturn 42;\n"
|
||||
"};\n",
|
||||
42, NULL },
|
||||
|
||||
{ "cap_preserved",
|
||||
"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[:]);\n"
|
||||
"\treturn (xs.cap*10 + xs.len): i32;\n"
|
||||
"};\n",
|
||||
80, NULL },
|
||||
|
||||
{ "u8_narrow",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []u8 = [];\n"
|
||||
"\tappend(xs, 1u8);\n"
|
||||
"\tappend(xs, 2u8);\n"
|
||||
"\tappend(xs, 3u8);\n"
|
||||
"\tappend(xs, 4u8);\n"
|
||||
"\tappend(xs, 5u8);\n"
|
||||
"\tdelete(xs[1:4]);\n"
|
||||
"\treturn (xs[0]*10u8 + xs[1]): i32 + len(xs)*100;\n"
|
||||
"};\n",
|
||||
215, NULL },
|
||||
|
||||
{ "u16_narrow",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []u16 = [];\n"
|
||||
"\tappend(xs, 1u16);\n"
|
||||
"\tappend(xs, 2u16);\n"
|
||||
"\tappend(xs, 3u16);\n"
|
||||
"\tappend(xs, 4u16);\n"
|
||||
"\tdelete(xs[1:3]);\n"
|
||||
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
|
||||
"};\n",
|
||||
25, NULL },
|
||||
|
||||
{ "i32_narrow",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i32 = [];\n"
|
||||
"\tappend(xs, 10i32);\n"
|
||||
"\tappend(xs, 20i32);\n"
|
||||
"\tappend(xs, 30i32);\n"
|
||||
"\tappend(xs, 40i32);\n"
|
||||
"\tdelete(xs[2:4]);\n"
|
||||
"\treturn xs[0] + xs[1] + len(xs)*10;\n"
|
||||
"};\n",
|
||||
50, NULL },
|
||||
|
||||
/* esz 16: exactly two whole qwords per element — the only spread
|
||||
* point with multiple MOVQs and NO sub-word tail. */
|
||||
{ "struct16",
|
||||
"package main;\n"
|
||||
"type pair = struct { a: i64, b: i64 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []pair = [];\n"
|
||||
"\tappend(xs, pair{ a = 1, b = 2 });\n"
|
||||
"\tappend(xs, pair{ a = 3, b = 4 });\n"
|
||||
"\tappend(xs, pair{ a = 5, b = 6 });\n"
|
||||
"\tdelete(xs[1:2]);\n"
|
||||
"\treturn (xs[0].a*10 + xs[1].b): i32 + len(xs)*100;\n"
|
||||
"};\n",
|
||||
216, NULL },
|
||||
|
||||
{ "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"
|
||||
"\tlet h: str = \"jklmn\";\n"
|
||||
"\tappend(xs, a);\n"
|
||||
"\tappend(xs, b);\n"
|
||||
"\tappend(xs, g);\n"
|
||||
"\tappend(xs, h);\n"
|
||||
"\tdelete(xs[1:3]);\n"
|
||||
"\treturn (xs[0].len*10 + xs[1].len): i32 + len(xs)*100;\n"
|
||||
"};\n",
|
||||
235, NULL },
|
||||
|
||||
/* The fold-5a consumer, shape-EXACT: indexed local-slice base,
|
||||
* size-typed outer index, defaulted full range (regex.ha:333
|
||||
* `delete(jump_idxs[group_level][..])`; ww spells `[..]` as
|
||||
* `[:]`, D7). The header lives at jj.ptr + g*size([]size) —
|
||||
* outer stride off the type table, never a literal. */
|
||||
{ "regex_exact",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet jump_idxs: [][]size = [];\n"
|
||||
"\tlet lvl0: []size = [];\n"
|
||||
"\tlet a: size = 3;\n"
|
||||
"\tlet b: size = 9;\n"
|
||||
"\tappend(lvl0, a);\n"
|
||||
"\tappend(lvl0, b);\n"
|
||||
"\tappend(jump_idxs, lvl0);\n"
|
||||
"\tlet group_level: size = 0;\n"
|
||||
"\tdelete(jump_idxs[group_level][:]);\n"
|
||||
"\treturn len(jump_idxs[0]): i32 + len(jump_idxs): i32 * 10;\n"
|
||||
"};\n",
|
||||
10, NULL },
|
||||
|
||||
{ "deref_shape",
|
||||
"package main;\n"
|
||||
"fn clear(p: *[]u8) void = {\n"
|
||||
"\tdelete((*p)[:]);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []u8 = [];\n"
|
||||
"\tappend(xs, 5u8);\n"
|
||||
"\tappend(xs, 6u8);\n"
|
||||
"\tclear(&xs);\n"
|
||||
"\treturn len(xs): i32 + 17;\n"
|
||||
"};\n",
|
||||
17, NULL },
|
||||
|
||||
/* lo and hi as CALLs: each side effect fires exactly once, lo
|
||||
* before hi (trace 12, never 21/121/122) — pins that neither
|
||||
* bound expression is re-evaluated by the count computation or
|
||||
* the copy loop. */
|
||||
{ "order_of_eval",
|
||||
"package main;\n"
|
||||
"let trace: i64 = 0;\n"
|
||||
"fn lof() size = {\n"
|
||||
"\ttrace = trace*10 + 1;\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n"
|
||||
"fn hif() size = {\n"
|
||||
"\ttrace = trace*10 + 2;\n"
|
||||
"\treturn 3;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 1);\n"
|
||||
"\tappend(xs, 2);\n"
|
||||
"\tappend(xs, 3);\n"
|
||||
"\tappend(xs, 4);\n"
|
||||
"\tdelete(xs[lof():hif()]);\n"
|
||||
"\tif (trace != 12) { return 90; };\n"
|
||||
"\treturn (xs[0]*10 + xs[1]): i32 + len(xs);\n"
|
||||
"};\n",
|
||||
16, NULL },
|
||||
|
||||
/* lo mutates the slice it is deleting from: only the header
|
||||
* ADDRESS is taken before operand eval; ptr/len/elements are read
|
||||
* after, so poke's write to xs[0] survives into the result. */
|
||||
{ "alias_visible",
|
||||
"package main;\n"
|
||||
"fn poke(p: *[]i64) size = {\n"
|
||||
"\t(*p)[0] = 9;\n"
|
||||
"\treturn 1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet xs: []i64 = [];\n"
|
||||
"\tappend(xs, 1);\n"
|
||||
"\tappend(xs, 2);\n"
|
||||
"\tappend(xs, 3);\n"
|
||||
"\tdelete(xs[poke(&xs):]);\n"
|
||||
"\treturn xs[0]: i32 * 10 + len(xs);\n"
|
||||
"};\n",
|
||||
91, NULL },
|
||||
|
||||
{ "reject_array",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: [3]i64 = [1, 2, 3];\n"
|
||||
"\tdelete(t[0:2]);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "delete must operate on a slice" },
|
||||
|
||||
{ "reject_str",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet s: str = \"abc\";\n"
|
||||
"\tdelete(s[:]);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "delete must operate on a slice" },
|
||||
};
|
||||
|
||||
/* 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/delr_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/delr_%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 — compile the row's source standalone with w6c and
|
||||
* w6c_ww and diff. Both range-delete lowerings are written fresh, so
|
||||
* this is the converged-by-construction gate: any drift in the shift
|
||||
* loop, the rdl_l/rdl_e label sequence, the dynamic-src copy, or the
|
||||
* indexed-base header address 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/delr_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs, sizeof cs, "/tmp/delr_asm_%d_%d_c.s", getpid(), i);
|
||||
snprintf(ws, sizeof ws, "/tmp/delr_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_range: 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_range[%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_range: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("delete_range: %d fixtures passed\n", total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user