use std::collections::HashSet;
use crate::canon::{Layout, RefKind, Slot};
use crate::value::{Ref, Val};
use super::entity::{ExnEntity, ExternEntry};
use super::gc::{decode_anyref_handle, AnyRefHandle};
use super::gc_codec::{le_u32, NULL_REF};
use super::{ObjKind, StoreInner};
enum Reached {
Gc(u32),
Extern(u32),
Exn(u32),
}
impl StoreInner {
pub(crate) fn collect(&mut self, stack_roots: &[(u32, RefKind)]) {
if !self.gc.is_collecting() {
return;
}
let mut work: Vec<Reached> = Vec::new();
for &(handle, kind) in stack_roots {
seed_handle(&mut work, handle, kind);
}
self.seed_entity_roots(&mut work);
let mut extern_seen: HashSet<u32> = HashSet::new();
let mut exn_seen: HashSet<u32> = HashSet::new();
while let Some(r) = work.pop() {
match r {
Reached::Gc(i) => {
if self.gc.mark(i) {
self.trace_gc(i, &mut work);
}
}
Reached::Extern(i) => {
if extern_seen.insert(i) {
self.trace_extern(i, &mut work);
}
}
Reached::Exn(i) => {
if exn_seen.insert(i) {
self.trace_exn(i, &mut work);
}
}
}
}
let freed = self.externrefs.sweep(&extern_seen, ExternEntry::byte_size);
self.gc.credit(freed);
let freed = self.exns.sweep(&exn_seen, ExnEntity::byte_size);
self.gc.credit(freed);
self.gc.sweep();
}
fn seed_entity_roots(&self, work: &mut Vec<Reached>) {
for g in self.globals.iter() {
seed_val(work, &g.value);
}
for t in self.tables.iter() {
for r in &t.elems {
seed_ref(work, r);
}
}
for inst in self.instances.iter() {
for seg in &inst.elems {
for r in seg {
seed_ref(work, r);
}
}
}
if let Some(e) = self.pending_exception {
work.push(Reached::Exn(e.raw()));
}
for &(handle, kind) in &self.gc_roots {
seed_handle(work, handle, kind);
}
}
fn trace_gc(&self, index: u32, work: &mut Vec<Reached>) {
let Some(obj) = self.gc.get(index) else {
return;
};
match obj.header.kind {
ObjKind::Struct => {
let fields = self.engine().struct_fields(obj.header.type_id);
if let Layout::Struct { fields: slots, .. } = Layout::for_struct(&fields) {
for &slot in &slots {
seed_slot(work, slot, &obj.data);
}
}
}
ObjKind::Array => {
let field = self.engine().array_field(obj.header.type_id);
let layout = Layout::for_array(&field);
for i in 0..obj.array_len(layout.stride()) as usize {
seed_slot(work, layout.elem_at(i), &obj.data);
}
}
ObjKind::Extern => {
if let Some(idx) = obj.extern_index() {
work.push(Reached::Extern(idx));
}
}
}
}
fn trace_extern(&self, index: u32, work: &mut Vec<Reached>) {
if let Some(ExternEntry::Internal(handle)) = self.externrefs.get(index) {
seed_handle(work, *handle, RefKind::Any);
}
}
fn trace_exn(&self, index: u32, work: &mut Vec<Reached>) {
if let Some(exn) = self.exns.get(index) {
for v in &exn.args {
seed_val(work, v);
}
}
}
}
fn seed_handle(work: &mut Vec<Reached>, handle: u32, kind: RefKind) {
if handle == NULL_REF {
return;
}
match kind {
RefKind::Func => {}
RefKind::Any => {
if let AnyRefHandle::Slot(i) = decode_anyref_handle(handle) {
work.push(Reached::Gc(i));
}
}
RefKind::Extern => work.push(Reached::Extern(handle)),
RefKind::Exn => work.push(Reached::Exn(handle)),
}
}
fn seed_val(work: &mut Vec<Reached>, v: &Val) {
match v {
Val::ExternRef(Some(r)) => seed_handle(work, r.raw(), RefKind::Extern),
Val::AnyRef(Some(r)) => seed_handle(work, r.raw(), RefKind::Any),
Val::ExnRef(Some(r)) => seed_handle(work, r.raw(), RefKind::Exn),
_ => {} }
}
fn seed_ref(work: &mut Vec<Reached>, r: &Ref) {
match r {
Ref::Extern(Some(rt)) => seed_handle(work, rt.raw(), RefKind::Extern),
Ref::Any(Some(rt)) => seed_handle(work, rt.raw(), RefKind::Any),
Ref::Exn(Some(rt)) => seed_handle(work, rt.raw(), RefKind::Exn),
_ => {} }
}
fn seed_slot(work: &mut Vec<Reached>, slot: Slot, data: &[u8]) {
if let Slot::Ref { offset, kind } = slot {
if let Some(bytes) = data.get(offset..offset + 4) {
seed_handle(work, le_u32(bytes), kind);
}
}
}