///|
pub fn Store::decode_native_exception(
self : Store,
reference : Int64,
) -> @types.Value raise RuntimeError {
self.references.decode_native_exception(reference)
}
///|
pub fn Store::encode_native_exception(
self : Store,
index : Int32,
) -> Int64 raise RuntimeError {
self.references.encode_native_exception(index)
}
///|
/// Translate an immutable native snapshot once, preserving Store identity at
/// every subsequent interpreter/native boundary.
fn StoreReferences::decode_native_exception(
self : StoreReferences,
reference : Int64,
) -> @types.Value raise RuntimeError {
if reference == 0L {
return Null
}
if self.native.native_exception_to_store.get(reference) is Some(index) {
return ExnRef(index)
}
let pending = [reference]
let created : Array[(Int64, Int32)] = []
errdefer (for (reference, index) in created {
self.native.native_exception_to_store.remove(reference)
self.native.store_exception_to_native.remove(index)
self.exns.remove(index)
})
while !pending.is_empty() {
let reference = pending.pop().unwrap()
guard self.native.native_execution.exceptions().snapshot(reference)
is Some((tag, words)) else {
raise UndefinedElement
}
let index = match self.native.native_exception_to_store.get(reference) {
Some(index) => index
None => {
let index = self.alloc_exn(tag, [])
self.native.native_exception_to_store.set(reference, index)
self.native.store_exception_to_native.set(index, reference)
created.push((reference, index))
index
}
}
let signature = self.get_tag_type(tag)
let values : Array[@types.Value] = []
let mut slot = 0
for ty in signature.params {
let width = if ty == V128 { 2 } else { 1 }
if slot + width > words.length() {
raise TypeMismatch
}
let raw = words[slot]
let value : @types.Value = match ty {
ExnRef | NullExnRef =>
if raw == 0L {
Null
} else {
let child = match self.native.native_exception_to_store.get(raw) {
Some(index) => index
None => {
guard self.native.native_execution.exceptions().snapshot(raw)
is Some((tag, _)) else {
raise UndefinedElement
}
let index = self.alloc_exn(tag, [])
self.native.native_exception_to_store.set(raw, index)
self.native.store_exception_to_native.set(index, raw)
created.push((raw, index))
pending.push(raw)
index
}
}
ExnRef(child)
}
FuncRef
| RefFunc
| RefFuncTyped(_)
| RefNullFuncTyped(_)
| NullFuncRef =>
if raw == 0L {
Null
} else if @native_bridge.decode_store_funcref(raw) is Some(address) {
FuncRef(address)
} else {
guard self.funcref_registry.lookup_store_addr(
@native_bridge.untag_funcref_ptr(raw),
)
is Some(address) else {
raise UndefinedElement
}
FuncRef(address)
}
ExternRef | RefExtern | NullExternRef =>
if raw == 0L {
Null
} else if @native_bridge.decode_externref(raw) is Some(index) {
ExternRef(index)
} else {
self.wrap_as_extern(self.decode_exception_gc_word(raw))
}
AnyRef | RefAny | NullRef | RefEq | RefNullEq =>
self.decode_exception_gc_word(raw)
V128 => V128(@types.int64_pair_to_v128_le(raw, words[slot + 1]))
_ => @native_bridge.i64_to_value(raw, ty)
}
values.push(value)
slot = slot + width
}
if slot != words.length() {
raise TypeMismatch
}
for value in values {
self.exns[index].values.push(value)
}
}
ExnRef(self.native.native_exception_to_store.get(reference).unwrap())
}
///|
fn StoreReferences::decode_exception_gc_word(
self : StoreReferences,
raw : Int64,
) -> @types.Value {
if raw == 0L {
Null
} else if (raw & 1L) != 0L {
I31((raw >> 1).to_int())
} else {
let index = @native_bridge.decode_heap_ref(raw)
match self.native.c_heap {
Some(heap) if heap.is_array(index) => ArrayRef(index)
_ => StructRef(index)
}
}
}
///|
fn StoreReferences::encode_native_exception(
self : StoreReferences,
index : Int32,
created~ : Array[Int32] = [],
) -> Int64 raise RuntimeError {
self.ensure_open()
if self.native.store_exception_to_native.get(index) is Some(reference) {
return reference
}
let checkpoint = created.length()
errdefer (while created.length() > checkpoint {
self.release_native_exception(created.pop().unwrap())
})
let pending = [(index, false)]
let visiting : Map[Int32, Bool] = Map([])
while pending.pop() is Some((current, ready)) {
if self.native.store_exception_to_native.contains(current) {
continue
}
let exception = self.get_exn(current)
if ready {
self.encode_exception_snapshot(current) |> ignore
created.push(current)
visiting.remove(current)
} else {
// Wasm snapshots are immutable and acyclic. Reject a forged host cycle
// rather than recursing indefinitely or publishing an incomplete token.
if visiting.contains(current) {
raise TypeMismatch
}
visiting.set(current, true)
pending.push((current, true))
for value in exception.values {
if value is ExnRef(child) &&
!self.native.store_exception_to_native.contains(child) {
pending.push((child, false))
}
}
}
}
self.native.store_exception_to_native.get(index).unwrap()
}
///|
fn StoreReferences::encode_exception_snapshot(
self : StoreReferences,
index : Int32,
) -> Int64 raise RuntimeError {
let exception = self.get_exn(index)
if !self.native.native_execution
.exceptions()
.define_tag(
exception.tag_addr,
self.get_tag_type(exception.tag_addr).params,
) {
raise OutOfMemory
}
let words : Array[Int64] = []
for value in exception.values {
match value {
ExnRef(index) =>
words.push(self.native.store_exception_to_native.get(index).unwrap())
FuncRef(address) => {
guard address >= 0 else { raise UndefinedElement }
words.push(@native_bridge.encode_store_funcref(address))
}
ExternRef(_) => {
let inner = self.unwrap_extern(value)
words.push(@native_bridge.value_to_i64(inner))
}
V128(bytes) => {
words.push(@types.bytes_to_int64_le(bytes, 0))
words.push(@types.bytes_to_int64_le(bytes, 8))
}
_ => words.push(@native_bridge.value_to_i64(value))
}
}
let heap = match self.native.c_heap {
Some(heap) => heap.get_ptr()
None => @native_bridge.GCHeapHandle::null()
}
let reference = self.native.native_execution
.exceptions()
.insert(heap, exception.tag_addr, words)
if reference == 0L {
match self.native.native_execution.exceptions().quota_denied() {
25 => raise ResourceLimitExceeded(GcBytes)
26 => raise ResourceLimitExceeded(GcObjects)
_ => raise OutOfMemory
}
}
self.native.native_exception_to_store.set(reference, index)
self.native.store_exception_to_native.set(index, reference)
if self.native.exception_allocations is Some(journal) {
journal.snapshots.push((index, reference))
}
reference
}
///|
/// CHeap's low-level Value codec carries native exception indices; Store APIs
/// always expose interpreter indices and translate at this boundary.
fn Store::encode_heap_value(
self : Store,
value : @types.Value,
) -> @types.Value raise RuntimeError {
match value {
ExnRef(index) => ExnRef((self.encode_native_exception(index) >> 1).to_int())
ExternRef(index) if index < 0 => self.unwrap_extern(value)
_ => value
}
}
///|
fn Store::decode_heap_value(
self : Store,
ty : @types.ValueType,
value : @types.Value,
) -> @types.Value raise RuntimeError {
let value = match value {
ExnRef(index) =>
self.decode_native_exception(@native_bridge.encode_exception_ref(index))
StructRef(index) =>
match self.references.native.c_heap {
Some(heap) if heap.is_array(index) => ArrayRef(index)
_ => value
}
_ => value
}
match (ty, value) {
(ExternRef | RefExtern | NullExternRef, StructRef(_) | ArrayRef(_) | I31(_)) =>
self.wrap_as_extern(value)
_ => value
}
}
///|
fn StoreReferences::release_native_exception(
self : StoreReferences,
index : Int32,
) -> Unit {
if self.native.store_exception_to_native.get(index) is Some(reference) {
self.native.native_execution.exceptions().release_snapshot(reference)
self.native.store_exception_to_native.remove(index)
self.native.native_exception_to_store.remove(reference)
}
}
///|
/// Run after resource rollback and before heap rollback. Discover the complete
/// live graph before releasing either payload records or native snapshots.
/// Managed Values alone need no snapshot; native consumers retain both.
fn Store::register_native_exception_rollback(
self : Store,
registrar : RollbackRegistrar,
) -> Unit {
let journal : ExceptionAllocationJournal = { instances: [], snapshots: [], }
self.references.native.exception_allocations = Some(journal)
registrar.on_finish(fn() {
self.references.native.exception_allocations = None
})
registrar.on_rollback(fn() {
if self.closed.val ||
(journal.instances.is_empty() && journal.snapshots.is_empty()) {
return
}
let native_roots = match self.references.native.c_heap {
Some(heap) =>
self.decode_gc_root_words(heap.get_ptr().execution_roots()) catch {
_ => return
}
None => []
}
let trace = self.trace_gc_roots([], native_roots~)
// Incomplete root discovery cannot justify reclaiming a live token.
if trace.failed {
return
}
let pending = trace.native_exceptions.keys().to_array()
while pending.pop() is Some(index) {
guard self.references.exns.get(index) is Some(exception) else {
// A missing payload is incomplete discovery, not an empty payload.
return
}
for value in exception.values {
if value is ExnRef(child) && !trace.native_exceptions.contains(child) {
trace.native_exceptions.set(child, true)
pending.push(child)
}
}
}
for (index, token) in journal.snapshots {
if !trace.native_exceptions.contains(index) &&
self.references.native.store_exception_to_native.get(index) ==
Some(token) {
self.references.release_native_exception(index)
}
}
// Payloads remain available through transitive discovery. Escaped handles
// keep their identity and quota; only unpublished instances are rolled back.
for index in journal.instances {
if !trace.exceptions.contains(index) {
self.references.exns.remove(index)
self.references.release_auxiliary_handle(1, index.to_int64())
}
}
})
}