// 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.
///|
// One original continuation owns each mutable ordered read set. Siblings borrow
// it only during their recursive visit; suffix/loop accumulation copies first.
priv struct SlCleanupReadSet {
reads : Array[Int32]
mut word : Int32
mut members_shared : Bool
mut bits : UInt64
mut members : Map[Int32, UInt32]?
}
///|
fn SlCleanupReadSet::new(reads : Array[Int32]) -> SlCleanupReadSet {
{ reads, word: 0, bits: 0UL, members: None, members_shared: false, }
}
///|
fn SlCleanupReadSet::copy(self : SlCleanupReadSet) -> SlCleanupReadSet {
let copied = SlCleanupReadSet::new(self.reads.copy())
copied.word = self.word
copied.bits = self.bits
// Keep initialized membership instead of rebuilding it from every read.
// Both owners detach before updating an overflow word. Read-only copies
// need no map allocation; the ordered rows and first-word bits stay owned.
if self.members is Some(_) {
self.members_shared = true
copied.members = self.members
copied.members_shared = true
}
copied
}
///|
fn SlCleanupReadSet::contains(
self : SlCleanupReadSet,
local_id : Int32,
membership_probes~ : Array[Int32]? = None,
) -> Bool {
// Zero bits mean no index: a nonempty indexed set never loses its first bit.
// Cache the first 64-local word without a map; only other words need entries.
if self.bits == 0UL && self.reads.length() > 8 {
self.word = self.reads[0] >> 6
for read in self.reads {
self.index_read(read)
}
}
if self.bits != 0UL {
match membership_probes {
Some(probes) => probes[0] += 1
None => ()
}
let word = local_id >> 6
let bit = 1UL << (local_id & 63)
if word == self.word {
return (self.bits & bit) != 0UL
}
return match self.members {
Some(members) => {
let bucket = local_id >> 5
let bit = 1U << (local_id & 31)
(members.get(bucket).unwrap_or(0U) & bit) != 0U
}
None => false
}
}
// Keep the ordinary tiny path identical to Array::contains. Optional work
// accounting must not put an extra branch in every production comparison.
match membership_probes {
None => self.reads.contains(local_id)
Some(probes) => {
for read in self.reads {
probes[0] += 1
if read == local_id {
return true
}
}
false
}
}
}
///|
fn SlCleanupReadSet::index_read(
self : SlCleanupReadSet,
local_id : Int32,
) -> Unit {
let word = local_id >> 6
let bit = 1UL << (local_id & 63)
if word == self.word {
self.bits = self.bits | bit
} else {
let members = match self.members {
Some(members) =>
if self.members_shared {
let owned = members.copy()
self.members = Some(owned)
self.members_shared = false
owned
} else {
members
}
None => {
let members : Map[Int32, UInt32] = Map([])
self.members = Some(members)
members
}
}
// UInt options use an unboxed native result; a UInt64 option allocates a
// Some box on every successful map lookup. Only overflow uses 32-bit words.
let bucket = local_id >> 5
let bit = 1U << (local_id & 31)
members[bucket] = members.get(bucket).unwrap_or(0U) | bit
}
}
///|
fn SlCleanupReadSet::insert(
self : SlCleanupReadSet,
local_id : Int32,
membership_probes~ : Array[Int32]? = None,
) -> Unit {
if !self.contains(local_id, membership_probes~) {
self.reads.push(local_id)
if self.bits != 0UL {
self.index_read(local_id)
}
}
}
///|
fn SlCleanupReadSet::collect(
self : SlCleanupReadSet,
instrs : Array[@lib.Instruction],
membership_probes~ : Array[Int32]? = None,
) -> Unit {
for instr in instrs {
self.collect_instr(instr, membership_probes~)
}
}
///|
fn SlCleanupReadSet::collect_instr(
self : SlCleanupReadSet,
instr : @lib.Instruction,
membership_probes~ : Array[Int32]? = None,
) -> Unit {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) =>
self.insert(raw.reinterpret_as_int(), membership_probes~)
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
self.collect(body, membership_probes~)
@lib.If(_, then_body, else_body) => {
self.collect(then_body, membership_probes~)
if else_body is Some(body) {
self.collect(body, membership_probes~)
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
self.collect(body, membership_probes~)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(body))
| @lib.LegacyCatchAll(@lib.Expr(body)) =>
self.collect(body, membership_probes~)
}
}
}
_ => ()
}
}