Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 46 additions & 12 deletions bend2/comp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]));
Expand Down Expand Up @@ -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(", ")};`, "",
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
}
}
Expand Down
77 changes: 77 additions & 0 deletions tests/rfc/drop_array_spine.bend
Original file line number Diff line number Diff line change
@@ -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<AT>, item: List<&2, U32>}

def first(xs: List<&2, U32>) -> U32:
match xs:
case []:
0
case x <> rest:
x

def nils(+d: Nat) -> Array<AT>:
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<AT>:
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<AT>, 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
108 changes: 108 additions & 0 deletions tests/rfc/drop_spine.bend
Original file line number Diff line number Diff line change
@@ -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