use std::collections::HashSet;
use crate::canon::{Layout, RefKind, Slot};
use crate::error::InternalError;
use crate::value::{Ref, Val};
use crate::Result;
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)]) -> Result<()> {
if !self.gc.is_collecting() {
return Ok(());
}
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 (extern_seen, exn_seen) = match self.trace_reachable(work) {
Ok(live) => live,
Err(error) => {
self.gc.clear_marks();
return Err(error);
}
};
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();
Ok(())
}
fn trace_reachable(&mut self, mut work: Vec<Reached>) -> Result<(HashSet<u32>, HashSet<u32>)> {
let mut extern_seen = HashSet::new();
let mut exn_seen = HashSet::new();
while let Some(r) = work.pop() {
match r {
Reached::Gc(i) => {
if self.gc.get(i).is_none() {
return Err(InternalError::GcMetadata("dangling GC root").into());
}
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)?;
}
}
}
}
Ok((extern_seen, exn_seen))
}
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()));
}
let roots = self
.gc_roots
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for &(handle, kind) in roots.iter() {
seed_handle(work, handle, kind);
}
}
fn trace_gc(&self, index: u32, work: &mut Vec<Reached>) -> Result<()> {
let obj = self
.gc
.get(index)
.ok_or(InternalError::GcMetadata("marked GC object is missing"))?;
match obj.header.kind {
ObjKind::Struct => {
let fields = self
.engine()
.struct_fields(obj.header.type_id)
.map_err(|_| InternalError::GcMetadata("struct type metadata is missing"))?;
for &slot in Layout::for_struct(&fields).fields() {
seed_slot(work, slot, &obj.data)?;
}
}
ObjKind::Array => {
let field = self
.engine()
.array_field(obj.header.type_id)
.map_err(|_| InternalError::GcMetadata("array type metadata is missing"))?;
let layout = Layout::for_array(&field);
if layout.stride() == 0 || obj.data.len() % layout.stride() != 0 {
return Err(InternalError::GcMetadata(
"array body does not match its element layout",
)
.into());
}
for i in 0..obj.array_len(layout.stride()) as usize {
seed_slot(work, layout.elem_at(i), &obj.data)?;
}
}
ObjKind::Extern => {
let idx = obj.extern_index().ok_or(InternalError::GcMetadata(
"extern wrapper body is truncated",
))?;
work.push(Reached::Extern(idx));
}
}
Ok(())
}
fn trace_extern(&self, index: u32, work: &mut Vec<Reached>) -> Result<()> {
let entry = self
.externrefs
.get(index)
.ok_or(InternalError::GcMetadata("dangling externref root"))?;
if let ExternEntry::Internal(handle) = entry {
seed_handle(work, *handle, RefKind::Any);
}
Ok(())
}
fn trace_exn(&self, index: u32, work: &mut Vec<Reached>) -> Result<()> {
let exn = self
.exns
.get(index)
.ok_or(InternalError::GcMetadata("dangling exception root"))?;
for v in &exn.args {
seed_val(work, v);
}
Ok(())
}
}
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]) -> Result<()> {
if let Slot::Ref { offset, kind } = slot {
let end = offset
.checked_add(4)
.ok_or(InternalError::GcMetadata("reference slot offset overflow"))?;
let bytes = data.get(offset..end).ok_or(InternalError::GcMetadata(
"reference slot exceeds object body",
))?;
seed_handle(work, le_u32(bytes), kind);
}
Ok(())
}