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:
@@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user