// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.
///|
#valtype
priv struct SlCleanupFutureReadProbe {
outcome : Int32
visited : Int32
}
///|
fn SlCleanupFutureReadProbe::new(
outcome : Int32,
visited : Int32,
) -> SlCleanupFutureReadProbe {
{ outcome, visited, }
}
///|
// 0 means no read, 1 means a read, -1 means exhausted. Primitive recursion
// credits and value results avoid a mutable heap owner on cheap repeated reads.
fn sl_cleanup_probe_future_read(
body : Array[@lib.Instruction],
start : Int32,
local : Int32,
limit : Int32,
visits : Array[Int32]?,
) -> SlCleanupFutureReadProbe {
let mut visited = 0
for idx = start; idx < body.length(); idx = idx + 1 {
if visited == limit {
return SlCleanupFutureReadProbe::new(-1, visited)
}
visited += 1
if visits is Some(visits) {
visits[0] += 1
}
let instr = body[idx]
if run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
instr,
) {
return SlCleanupFutureReadProbe::new(0, visited)
}
let found = match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
SlCleanupFutureReadProbe::new(
if raw.reinterpret_as_int() == local {
1
} else {
0
},
0,
)
@lib.Block(_, @lib.Expr(nested))
| @lib.Loop(_, @lib.Expr(nested))
| @lib.TryTable(_, _, @lib.Expr(nested)) =>
sl_cleanup_probe_future_read(nested, 0, local, limit - visited, visits)
@lib.If(_, yes, no) => {
let yes = sl_cleanup_probe_future_read(
yes,
0,
local,
limit - visited,
visits,
)
if yes.outcome != 0 {
return SlCleanupFutureReadProbe::new(
yes.outcome,
visited + yes.visited,
)
}
match no {
Some(no) => {
let no = sl_cleanup_probe_future_read(
no,
0,
local,
limit - visited - yes.visited,
visits,
)
SlCleanupFutureReadProbe::new(no.outcome, yes.visited + no.visited)
}
None => yes
}
}
@lib.Try(_, @lib.Expr(nested), catches, _) => {
let body = sl_cleanup_probe_future_read(
nested,
0,
local,
limit - visited,
visits,
)
visited += body.visited
if body.outcome != 0 {
return SlCleanupFutureReadProbe::new(body.outcome, visited)
}
for catch_ in catches {
let @lib.Expr(handler) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
let found = sl_cleanup_probe_future_read(
handler,
0,
local,
limit - visited,
visits,
)
visited += found.visited
if found.outcome != 0 {
return SlCleanupFutureReadProbe::new(found.outcome, visited)
}
}
SlCleanupFutureReadProbe::new(0, 0)
}
_ => SlCleanupFutureReadProbe::new(0, 0)
}
visited += found.visited
if found.outcome != 0 {
return SlCleanupFutureReadProbe::new(found.outcome, visited)
}
}
SlCleanupFutureReadProbe::new(0, visited)
}
///|
#inline
fn sl_cleanup_future_read_with_visits(
body : Array[@lib.Instruction],
start : Int32,
local : Int32,
visits : Array[Int32]?,
) -> Bool {
match visits {
None =>
run_hot_pipeline_raw_instrs_have_later_local_read(body, start, local)
Some(_) =>
sl_cleanup_probe_future_read(body, start, local, 2147483647, visits).outcome ==
1
}
}
///|
// Reachable-prefix union, matching the existing future-read query rather than
// the intentionally broader original-continuation collector. A terminator in
// one body/arm/handler does not stop its sibling or enclosing instruction list.
fn sl_cleanup_collect_reachable_read_instr(
reads : SlCleanupReadSet,
instr : @lib.Instruction,
visits : Array[Int32]?,
) -> Unit {
if visits is Some(visits) {
visits[0] += 1
}
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => reads.insert(raw.reinterpret_as_int())
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
sl_cleanup_collect_reachable_reads(reads, body, visits)
@lib.If(_, yes, no) => {
sl_cleanup_collect_reachable_reads(reads, yes, visits)
if no is Some(no) {
sl_cleanup_collect_reachable_reads(reads, no, visits)
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
sl_cleanup_collect_reachable_reads(reads, body, visits)
for catch_ in catches {
let @lib.Expr(handler) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
sl_cleanup_collect_reachable_reads(reads, handler, visits)
}
}
_ => ()
}
}
///|
fn sl_cleanup_collect_reachable_reads(
reads : SlCleanupReadSet,
body : Array[@lib.Instruction],
visits : Array[Int32]?,
) -> Unit {
for instr in body {
if run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
instr,
) {
if visits is Some(visits) {
visits[0] += 1
}
break
}
sl_cleanup_collect_reachable_read_instr(reads, instr, visits)
}
}
///|
fn sl_cleanup_build_future_read_mask(
body : Array[@lib.Instruction],
visits : Array[Int32]?,
) -> Array[Bool] {
let mask = Array::make(body.length(), false)
let mut reads = SlCleanupReadSet::new([])
for idx = body.length() - 1; idx >= 0; idx = idx - 1 {
if visits is Some(visits) {
visits[0] += 1
}
let instr = body[idx]
if run_hot_pipeline_raw_simplify_locals_instr_is_unconditional_terminator(
instr,
) {
reads = SlCleanupReadSet::new([])
continue
}
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) =>
mask[idx] = reads.contains(raw.reinterpret_as_int())
_ => ()
}
// Publish before inserting this read: facts describe its strict suffix.
// A queried tee is followed by drop, which cannot add a local read.
sl_cleanup_collect_reachable_read_instr(reads, instr, visits)
}
mask
}
///|
// The read is a root get or a dropped tee; its strict suffix cannot gain a
// local read from the skipped drop. Borrow published facts without a budget
// owner, value-result record or per-query recursion accounting in production.
#inline
fn sl_cleanup_future_read_query(
body : Array[@lib.Instruction],
read : Int32,
start : Int32,
local : Int32,
mask : Array[Bool]?,
visits : Array[Int32]?,
) -> Bool {
match mask {
Some(mask) => mask[read]
None => sl_cleanup_future_read_with_visits(body, start, local, visits)
}
}