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:
2026-06-04 23:18:38 +09:00
parent 60e61315bc
commit 1bcf2726cf
9 changed files with 1505 additions and 98 deletions

View File

@@ -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;
};