Skip to main content

monkey_asm/
runtime_backend.rs

1//! Storage backends behind the runtime semantics (design §8.1).
2//!
3//! `runtime_core` only talks to a [`ValueStore`]; the native runtime uses
4//! [`PointerStore`] (validated tagged real pointers retained for the store's
5//! lifetime)
6//! while the wasm simulator and host tests use [`HandleStore`]
7//! (arena indices). Code addresses are carried as an opaque [`CodeHandle`]:
8//! the native runtime interprets it as a function address, a simulator as a
9//! label / instruction index.
10
11#[cfg(not(target_family = "wasm"))]
12use std::collections::HashMap;
13
14use crate::runtime_core::{HeapObject, Value, HEAP_TAG, PTR_TAG_MASK};
15
16/// Opaque code reference stored inside closures. Only the execution adapter
17/// that created it may interpret it (function pointer vs simulated PC).
18pub type CodeHandle = u64;
19
20pub trait ValueStore {
21    /// Moves `object` into the store and returns its tagged heap `Value`.
22    fn alloc(&mut self, object: HeapObject) -> Value;
23    /// Resolves a tagged heap value. `None` when `value` does not carry the
24    /// heap tag or does not name a live object of this store.
25    fn try_get(&self, value: Value) -> Option<&HeapObject>;
26    fn try_get_mut(&mut self, value: Value) -> Option<&mut HeapObject>;
27}
28
29/// Arena-backed store: `((index << 3) | 0b001)`. Used by the wasm simulator
30/// and by host-side tests; never hands out host pointers.
31#[derive(Default)]
32pub struct HandleStore {
33    arena: Vec<HeapObject>,
34}
35
36impl HandleStore {
37    pub fn new() -> HandleStore {
38        HandleStore::default()
39    }
40
41    fn index_of(value: Value) -> Option<usize> {
42        if value & PTR_TAG_MASK != HEAP_TAG {
43            return None;
44        }
45        Some((value >> 3) as usize)
46    }
47}
48
49impl ValueStore for HandleStore {
50    fn alloc(&mut self, object: HeapObject) -> Value {
51        self.arena.push(object);
52        (((self.arena.len() - 1) as u64) << 3) | HEAP_TAG
53    }
54
55    fn try_get(&self, value: Value) -> Option<&HeapObject> {
56        self.arena.get(Self::index_of(value)?)
57    }
58
59    fn try_get_mut(&mut self, value: Value) -> Option<&mut HeapObject> {
60        let index = Self::index_of(value)?;
61        self.arena.get_mut(index)
62    }
63}
64
65/// Native store: heap objects live in owned, 8-byte-aligned cells and the
66/// tagged value is the stable cell address with the low bits `001` (design
67/// §5.2).
68///
69/// The address map is part of the safety boundary: an arbitrary tagged
70/// integer is never dereferenced. A value resolves only when this store owns
71/// the exact cell, and references remain tied to the corresponding store
72/// borrow. The native runtime keeps one process-wide store behind a mutex so
73/// values remain live across FFI entries without creating independent aliasing
74/// tokens.
75#[cfg(not(target_family = "wasm"))]
76#[repr(align(8))]
77struct HeapCell(HeapObject);
78
79#[cfg(not(target_family = "wasm"))]
80#[derive(Default)]
81pub struct PointerStore {
82    cells: HashMap<Value, Box<HeapCell>>,
83}
84
85#[cfg(not(target_family = "wasm"))]
86impl PointerStore {
87    pub fn new() -> PointerStore {
88        PointerStore::default()
89    }
90}
91
92#[cfg(not(target_family = "wasm"))]
93impl ValueStore for PointerStore {
94    fn alloc(&mut self, object: HeapObject) -> Value {
95        let cell = Box::new(HeapCell(object));
96        let address = cell.as_ref() as *const HeapCell as u64;
97        debug_assert_eq!(address & PTR_TAG_MASK, 0, "heap cells must be 8-byte aligned");
98        let value = address | HEAP_TAG;
99        let replaced = self.cells.insert(value, cell);
100        debug_assert!(replaced.is_none(), "live heap addresses must be unique");
101        value
102    }
103
104    fn try_get(&self, value: Value) -> Option<&HeapObject> {
105        if value & PTR_TAG_MASK != HEAP_TAG {
106            return None;
107        }
108        self.cells.get(&value).map(|cell| &cell.0)
109    }
110
111    fn try_get_mut(&mut self, value: Value) -> Option<&mut HeapObject> {
112        if value & PTR_TAG_MASK != HEAP_TAG {
113            return None;
114        }
115        self.cells.get_mut(&value).map(|cell| &mut cell.0)
116    }
117}