diff --git a/bend2/comp.ts b/bend2/comp.ts index 771db8e4d2..4bfe5666b2 100644 --- a/bend2/comp.ts +++ b/bend2/comp.ts @@ -1029,6 +1029,21 @@ function ctr_doms(ctr: Bend.Ctr, xs?: HTerm[]): HTerm[] { return ctr_live(ctr, xs).map(([, , A]) => A); } +// The slot term_drop moves last, so a node frees before its spine: the +// last box of the last field that holds the node's own datatype. +function ctr_spine(k: Name, n: number): number { + const ctr = FL.book.ctrs[k]; + if (!ctr || n > WIDE) { + return 255; + } + const fam = Bend.book_fam(FL.book, k); + const lays = lay_node(k).arms![k]; + const i = ctr_doms(ctr).findLastIndex((F) => + ty_holds(F, (u) => u?.$ === "ADT" && u.k === fam)); + return i < 0 ? 255 : lays.slice(0, i).reduce((at, lay) => + at + lay.ks.length, lays[i].ks.lastIndexOf("box")); +} + function ctr_flds(k: Name, xs: HTerm[]): HTerm[] { const ds = FL.book.ctrs[k] ? ctr_tail(FL.book.ctrs[k]) : []; return xs.filter((_, j) => !ds[j] || dom_live(ds[j])); @@ -2852,8 +2867,8 @@ export function compile_book(book: Bend.Book): string { `{ ${s.params.length}, ${s.frame === null ? 0 : s.params.length - s.frame.at.length}, ${Number(FL.bangs.has(s.def)) | Number(!forky.has(s.fid)) << 1} }`).join(", ")} };`, - `CONSTV u8 CID_T[][2] = { ${[...cids].map(([k, n]) => - `{ ${n}, ${Number(FL.hot.has(k))} }`).join(", ")} };`, + `CONSTV u8 CID_T[][3] = { ${[...cids].map(([k, n]) => + `{ ${n}, ${Number(FL.hot.has(k))}, ${ctr_spine(k, n)} }`).join(", ")} };`, `#define STAT_LEN ${FL.img.length}`, "", `#define WL_RESW ${resw}`, `#define BANGS ${FL.bangs.size}`, "", `#define WL_BANK Term ${ws.join(", ")};`, "", @@ -3918,6 +3933,26 @@ FAR void term_drop(Env e, Term t) { u32 cls = tag == TAG_ARR ? 64 | blk_cls(t) : n > ${WIDE} ? 64 | (n - 240) : cls_fit(tag == TAG_TSK ? n + 2 : n); + u32 s = tag == TAG_CTR ? CID_T[aux][2] : n; + if (s + 1 < n) { + Term f = H[loc + s]; + H[loc + s] = H[loc + n - 1]; + H[loc + n - 1] = f; + } + if (cls < 64) { + while (n > 1 && term_triv(H[loc + n - 1])) { + n -= 1; + } + } else { + u32 m = 1u << (cls & 63); + while (m > 1 && term_triv(H[loc + m - 1])) { + m -= 1; + } + if (m < 256) { + n = m; + cls &= 63; + } + } c0 = H[loc]; H[loc] = cur; cur = loc | ((u64)n << 48) | ((u64)cls << 56); @@ -3940,22 +3975,21 @@ FAR void term_drop(Env e, Term t) { j = (u32)H[loc + 1]; } } - if (j < n) { - Term c = j == 0 ? c0 : H[loc + j]; + Term c = j >= n ? 0 : j == 0 ? c0 : H[loc + j]; + if (j + 1 >= n) { + cur = H[loc]; + heap_free(e, cls, loc); + } else { if (arr && j > 0) { H[loc + 1] = j + 1; } if (!arr || i < 2) { cur += 1ull << 40; } - if (!term_triv(c)) { - t = c; - break; - } - } else { - u64 up = H[loc]; - heap_free(e, cls, loc); - cur = up; + } + if (!term_triv(c)) { + t = c; + break; } } } diff --git a/tests/rfc/drop_array_spine.bend b/tests/rfc/drop_array_spine.bend new file mode 100644 index 0000000000..194d061465 --- /dev/null +++ b/tests/rfc/drop_array_spine.bend @@ -0,0 +1,77 @@ +# drop order through an Array: term_drop frees an Array's frame once only +# trivial cells follow, so a chain whose spine runs through one cell frees +# each Array before it drops the next link. ACon moves its kids behind its +# item. A 4-cell Array with the child in cell 1 becomes a plain frame of 2; +# a 512-cell one stays an Array frame, with the child last (512) and in +# cell 255 (live end 256, one past a plain frame). Each round drops fresh +# chains whole, then sums fresh ones; a cell freed early would read back +# wrong from reused memory in a later round. Expect 3 rounds of 3 * 5050. +import Base + +type AT is Type: + ANil{} + ACon{kids: Array, item: List<&2, U32>} + +def first(xs: List<&2, U32>) -> U32: + match xs: + case []: + 0 + case x <> rest: + x + +def nils(+d: Nat) -> Array: + match d: + case 0n: + ALeaf{ANil{}} + case 1n+p: + ANode{nils(p), nils(p)} + +# the child in the last cell, or with mid in the last cell of the low half +def kids(+d: Nat, mid: Bool, t: AT) -> Array: + match d mid: + case 0n _: + ALeaf{t} + case 1n+p True{}: + ANode{kids(p, False{}, t), nils(p)} + case 1n+p False{}: + ANode{nils(p), kids(p, False{}, t)} + +def at(+d: Nat, +mid: Bool, +n: Nat) -> AT: + match n: + case 0n: + ANil{} + case 1n+m: + ACon{kids(d, mid, at(d, mid, m)), [U32.from_nat(n)]} + +def pick(a: Array, mid: Bool) -> AT: + match a mid: + case ALeaf{t} _: + t + case ANode{xs, ys} True{}: + pick(xs, False{}) + case ANode{xs, ys} False{}: + pick(ys, False{}) + +def at_sum(+n: Nat, +mid: Bool, t: AT) -> U32: + match n t: + case 0n _: + 0 + case _ ANil{}: + 0 + case 1n+m ACon{ks, item}: + U32.add(first(item), at_sum(m, mid, pick(ks, mid))) + +def waste(a: AT, b: AT, c: AT, x: U32) -> U32: + x + +def round(+n: Nat) -> U32: + a = waste(at(2n, True{}, n), at(9n, False{}, n), at(9n, True{}, n), 0) + b = U32.add(at_sum(n, True{}, at(2n, True{}, n)), at_sum(n, False{}, at(9n, False{}, n))) + U32.add(a, U32.add(b, at_sum(n, True{}, at(9n, True{}, n)))) + +def main() -> IO(Unit): + IO.print(Nat.show(U32.to_nat(round(100n))) + ++ " " ++ Nat.show(U32.to_nat(round(100n))) + ++ " " ++ Nat.show(U32.to_nat(round(100n)))) + +#|15150 15150 15150 diff --git a/tests/rfc/drop_spine.bend b/tests/rfc/drop_spine.bend new file mode 100644 index 0000000000..4a5815a14a --- /dev/null +++ b/tests/rfc/drop_spine.bend @@ -0,0 +1,108 @@ +# drop order: term_drop moves a constructor's recursive field to its +# last slot and frees a frame once only trivial slots follow, so the +# frame frees before the spine drops. RCon has the spine first, MCon +# before a word, Rose behind a List and OCon behind a flat Maybe's tag; +# each item is a boxed one-cell List. Each round drops a fresh owned copy +# whole, then shares one, sums it through one holder and drops it through +# the other; a misplaced field would read back wrong from reused memory +# in a later round. Expect 3 rounds of 4 * 5050 + 100 * 7. +import Base + +type RL is Data: + RNil{} + RCon{rest: RL, item: List<&2, U32>} + +type ML is Data: + MNil{} + MCon{item: List<&2, U32>, rest: ML, tag: U32} + +type OL is Data: + ONil{} + OCon{next: Maybe<&2, OL>, item: List<&2, U32>} + +type Rose is Data: + Rose{kids: List<&2, Rose>, item: List<&2, U32>} + +def rl(+n: Nat) -> RL: + match n: + case 0n: + RNil{} + case 1n+m: + RCon{rl(m), [U32.from_nat(n)]} + +def ml(+n: Nat) -> ML: + match n: + case 0n: + MNil{} + case 1n+m: + MCon{[U32.from_nat(n)], ml(m), 7} + +def ol(+n: Nat) -> OL: + match n: + case 0n: + ONil{} + case 1n+m: + OCon{Some{ol(m)}, [U32.from_nat(n)]} + +def rose(+n: Nat) -> Rose: + match n: + case 0n: + Rose{[], [0]} + case 1n+m: + Rose{[rose(m)], [U32.from_nat(n)]} + +def first(xs: List<&2, U32>) -> U32: + match xs: + case []: + 0 + case x <> rest: + x + +def rl_sum(xs: RL) -> U32: + match xs: + case RNil{}: + 0 + case RCon{rest, item}: + U32.add(first(item), rl_sum(rest)) + +def ml_sum(xs: ML) -> U32: + match xs: + case MNil{}: + 0 + case MCon{item, rest, tag}: + U32.add(U32.add(first(item), tag), ml_sum(rest)) + +def ol_sum(xs: OL) -> U32: + match xs: + case ONil{}: + 0 + case OCon{None{}, item}: + first(item) + case OCon{Some{rest}, item}: + U32.add(first(item), ol_sum(rest)) + +def rose_sum(r: Rose) -> U32: + match r: + case Rose{[], item}: + first(item) + case Rose{k <> rest, item}: + U32.add(first(item), rose_sum(k)) + +def waste(r: RL, m: ML, o: Rose, l: OL, x: U32) -> U32: + x + +def round(+n: Nat) -> U32: + a = waste(rl(n), ml(n), rose(n), ol(n), 0) + +r = rl(n) + +m = ml(n) + +o = rose(n) + +l = ol(n) + b = U32.add(U32.add(rl_sum(r), ml_sum(m)), U32.add(rose_sum(o), ol_sum(l))) + U32.add(a, waste(r, m, o, l, b)) + +def main() -> IO(Unit): + IO.print(Nat.show(U32.to_nat(round(100n))) + ++ " " ++ Nat.show(U32.to_nat(round(100n))) + ++ " " ++ Nat.show(U32.to_nat(round(100n)))) + +#|20900 20900 20900