///|
/// An embedding value with Store provenance and an explicit GC root lifetime.
/// On native, MoonBit RC retires the root when this value is dropped. On wasm,
/// explicitly close it (normally with defer). Close invalidates all aliases;
/// Store.close also retires outstanding roots.
/// Primitive values can be passed between Stores.
pub struct RootedValue {
priv mut state : RootedValueState?
priv mut declared_type : (ModuleInstance, @types.ValueType)?
}
///|
priv enum RootedValueState {
Primitive(@types.Value)
Reference(Store, GcRootLease, @jit_ffi.CleanupOwner, @types.Value)
}
///|
pub fn RootedValue::i32(value : Int32) -> RootedValue {
{ declared_type: None, state: Some(Primitive(I32(value))), }
}
///|
pub fn RootedValue::i64(value : Int64) -> RootedValue {
{ declared_type: None, state: Some(Primitive(I64(value))), }
}
///|
pub fn RootedValue::f32(value : Float) -> RootedValue {
{ declared_type: None, state: Some(Primitive(F32(value))), }
}
///|
pub fn RootedValue::f64(value : Double) -> RootedValue {
{ declared_type: None, state: Some(Primitive(F64(value))), }
}
///|
pub fn RootedValue::null() -> RootedValue {
{ declared_type: None, state: Some(Primitive(Null)), }
}
///|
/// Read a scalar without exposing Store-local reference indices.
pub fn RootedValue::scalar(
self : RootedValue,
) -> @types.Value raise RuntimeError {
match self.state {
Some(Primitive(value)) => value
Some(Reference(_, _, _, _)) => raise TypeMismatch
None => raise InvalidHandle
}
}
///|
pub fn RootedValue::close(self : RootedValue) -> Unit {
if self.state is Some(Reference(_, _roots, cleanup, _)) {
cleanup.close()
}
self.state = None
self.declared_type = None
}
///|
/// Low-level bridge from Store-local raw values. Raw reference indices must
/// originate in this Store; portable clients should use executor.Function results.
pub fn Store::root_raw_value(
self : Store,
value : @types.Value,
) -> RootedValue raise RuntimeError {
self.ensure_open()
match value {
I32(_) | I64(_) | F32(_) | F64(_) | V128(_) | I31(_) | Null =>
{ declared_type: None, state: Some(Primitive(value)), }
_ => {
self.check_raw_reference(value)
let roots = self.pin_gc_roots([value])
self.on_instantiation_rollback(fn() { roots.release() })
let cleanup = @jit_ffi.CleanupOwner::new(fn() { roots.release() })
{
declared_type: None,
state: Some(Reference(self, roots, cleanup, value)),
}
}
}
}
///|
/// Low-level execution bridge. Checks provenance and lease before revealing an index.
pub fn Store::borrow_rooted_value(
self : Store,
value : RootedValue,
) -> @types.Value raise RuntimeError {
self.ensure_open()
match value.state {
Some(Primitive(value)) => value
Some(Reference(owner, roots, _cleanup, value)) => {
guard physical_equal(owner, self) else { raise ForeignStore }
guard roots.active.val else { raise InvalidHandle }
self.check_raw_reference(value)
value
}
None => raise InvalidHandle
}
}
///|
fn Store::check_raw_reference(
self : Store,
value : @types.Value,
) -> Unit raise RuntimeError {
match value {
FuncRef(address) => self.get_func_inst(address) |> ignore
StructRef(index) | ArrayRef(index) =>
if !self.is_gc_valid(index) {
raise InvalidHandle
}
ExnRef(index) => self.get_exn(index) |> ignore
ExternRef(index) => if index < 0 { self.unwrap_extern(value) |> ignore }
ContRef(index) =>
if !self.has_guest_continuation(index) {
raise InvalidHandle
}
_ => ()
}
}
///|
pub fn RootedValue::v128(bytes : Bytes) -> RootedValue raise RuntimeError {
guard bytes.length() == 16 else { raise TypeMismatch }
{ declared_type: None, state: Some(Primitive(V128(bytes))), }
}
///|
/// Low-level result bridge: the caller guarantees the declared result contract.
pub fn Store::root_result_value(
self : Store,
instance : ModuleInstance,
value : @types.Value,
type_ : @types.ValueType,
) -> RootedValue raise RuntimeError {
self.check_instance(instance)
let rooted = self.root_raw_value(value)
if value is ContRef(_) {
rooted.declared_type = Some((instance, type_))
}
rooted
}
///|
/// Validate typed continuation transfer as well as Store/root identity.
pub fn Store::borrow_typed_rooted_value(
self : Store,
value : RootedValue,
instance : ModuleInstance,
expected : @types.ValueType,
) -> @types.Value raise RuntimeError {
self.check_instance(instance)
let raw = self.borrow_rooted_value(value)
if raw is ContRef(_) {
guard value.declared_type is Some((source, actual)) else {
raise TypeMismatch
}
self.check_instance(source)
guard @types.SubtypingContext::cross_module(
source.types,
instance.types,
rec_groups1=source.type_rec_groups,
rec_groups2=instance.type_rec_groups,
).value_type_subtype(actual, expected) else {
raise TypeMismatch
}
}
raw
}
///|
#cfg(target="native")
pub fn Store::has_guest_continuation(self : Store, handle : Int64) -> Bool {
if self.closed.val {
return false
}
if handle >= 0L {
self.guest_continuations.contains(handle)
} else {
self.native_execution.continuations().is_live((handle << 1) | 1L)
}
}