Skip to main content

shape_runtime/
module_bindings.rs

1//! Module Binding Registry - Single source of truth for module binding values
2//!
3//! This module provides a unified module binding registry that is shared between
4//! the interpreter, VM, and (future) JIT compiler. All module binding values
5//! (functions, constants, imported symbols) live here.
6//!
7//! Design goals:
8//! - Name → index mapping for fast compilation
9//! - Index → value for O(1) runtime access
10//! - Stable memory addresses for JIT compilation
11//! - Thread-safe access via RwLock
12
13use crate::Result;
14use shape_ast::error::ShapeError;
15// ADR-006 §2.7: GENERIC_CARRIER vector storage uses `Vec<KindedSlot>`.
16// `ModuleBindingRegistry` holds heterogeneous module bindings (functions,
17// constants, imports) — kind isn't statically determined per slot, so the
18// audit (Cluster A in `phase-1b-valueword-callers.md`) classifies this as
19// the GENERIC_CARRIER vector form. `KindedSlot` carries explicit
20// `Drop`/`Clone` for refcount discipline.
21use shape_value::KindedSlot;
22use std::collections::HashMap;
23
24/// Single source of truth for all module binding values.
25///
26/// Used by:
27/// - Interpreter: name-based lookup
28/// - VM: index-based lookup (after compilation resolves names)
29/// - JIT: stable pointers for inlined access
30#[derive(Debug)]
31pub struct ModuleBindingRegistry {
32    /// Name → index mapping (for compilation)
33    name_to_index: HashMap<String, u32>,
34
35    /// Index → name mapping (for debugging/errors)
36    index_to_name: Vec<String>,
37
38    /// The actual values - accessed by index for O(1) lookup. ADR-006 §2.7
39    /// GENERIC_CARRIER vector storage; `KindedSlot` pairs each slot with
40    /// its `NativeKind` so refcount discipline survives push/pop/clone.
41    values: Vec<KindedSlot>,
42
43    /// Track which module bindings are constants (functions, imports)
44    is_const: Vec<bool>,
45}
46
47impl Default for ModuleBindingRegistry {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl ModuleBindingRegistry {
54    /// Create a new empty module binding registry
55    pub fn new() -> Self {
56        Self {
57            name_to_index: HashMap::new(),
58            index_to_name: Vec::new(),
59            values: Vec::new(),
60            is_const: Vec::new(),
61        }
62    }
63
64    /// Create with pre-allocated capacity
65    pub fn with_capacity(capacity: usize) -> Self {
66        Self {
67            name_to_index: HashMap::with_capacity(capacity),
68            index_to_name: Vec::with_capacity(capacity),
69            values: Vec::with_capacity(capacity),
70            is_const: Vec::with_capacity(capacity),
71        }
72    }
73
74    /// Register or update a module binding, returns its stable index.
75    ///
76    /// If the module binding already exists:
77    /// - If it's const and we're re-registering with same constness, update value
78    /// - If it's const and we're trying to make it mutable, error
79    /// - If it's mutable, always update
80    ///
81    /// # Arguments
82    /// * `name` - The module binding's name
83    /// * `value` - The value to store as a `KindedSlot` (slot + NativeKind)
84    /// * `is_const` - Whether this module binding is constant (functions, imports)
85    ///
86    /// # Returns
87    /// The stable index for this module binding
88    pub fn register(&mut self, name: &str, value: KindedSlot, is_const: bool) -> Result<u32> {
89        self.register_nb(name, value, is_const)
90    }
91
92    /// Register or update a module binding with a `KindedSlot` value, returns its stable index.
93    pub fn register_nb(&mut self, name: &str, value: KindedSlot, is_const: bool) -> Result<u32> {
94        if let Some(&idx) = self.name_to_index.get(name) {
95            let idx_usize = idx as usize;
96
97            // Allow re-registration of const module bindings (e.g., during stdlib reload)
98            // but don't allow changing const to mutable
99            if self.is_const[idx_usize] && !is_const {
100                return Err(ShapeError::RuntimeError {
101                    message: format!("Cannot redeclare const '{}' as mutable", name),
102                    location: None,
103                });
104            }
105
106            self.values[idx_usize] = value;
107            self.is_const[idx_usize] = is_const;
108            Ok(idx)
109        } else {
110            // New module binding
111            let idx = self.values.len() as u32;
112            self.name_to_index.insert(name.to_string(), idx);
113            self.index_to_name.push(name.to_string());
114            self.values.push(value);
115            self.is_const.push(is_const);
116            Ok(idx)
117        }
118    }
119
120    /// Register a constant module binding (convenience method)
121    pub fn register_const(&mut self, name: &str, value: KindedSlot) -> Result<u32> {
122        self.register(name, value, true)
123    }
124
125    /// Register a mutable module binding (convenience method)
126    pub fn register_mut(&mut self, name: &str, value: KindedSlot) -> Result<u32> {
127        self.register_nb(name, value, false)
128    }
129
130    /// Check if a module binding exists
131    pub fn contains(&self, name: &str) -> bool {
132        self.name_to_index.contains_key(name)
133    }
134
135    /// Resolve name to index (compile-time)
136    pub fn resolve(&self, name: &str) -> Option<u32> {
137        self.name_to_index.get(name).copied()
138    }
139
140    /// Get name for an index (for error messages)
141    pub fn get_name(&self, idx: u32) -> Option<&str> {
142        self.index_to_name.get(idx as usize).map(|s| s.as_str())
143    }
144
145    /// Get by name as owned `KindedSlot` (interpreter, dynamic lookup).
146    /// `Clone` on `KindedSlot` retains the underlying refcount.
147    pub fn get_by_name(&self, name: &str) -> Option<KindedSlot> {
148        self.name_to_index
149            .get(name)
150            .map(|&idx| self.values[idx as usize].clone())
151    }
152
153    /// Get by index as `KindedSlot` reference (O(1))
154    #[inline]
155    pub fn get_by_index(&self, idx: u32) -> Option<&KindedSlot> {
156        self.values.get(idx as usize)
157    }
158
159    /// Set by index from `KindedSlot` (for VM assignment).
160    /// The previous value is dropped via its `Drop` impl, retiring its
161    /// refcount cleanly.
162    pub fn set_by_index(&mut self, idx: u32, value: KindedSlot) -> Result<()> {
163        let idx_usize = idx as usize;
164        if idx_usize >= self.values.len() {
165            return Err(ShapeError::RuntimeError {
166                message: format!("module binding index {} out of bounds", idx),
167                location: None,
168            });
169        }
170        if self.is_const[idx_usize] {
171            return Err(ShapeError::RuntimeError {
172                message: format!("Cannot assign to const '{}'", self.index_to_name[idx_usize]),
173                location: None,
174            });
175        }
176        self.values[idx_usize] = value;
177        Ok(())
178    }
179
180    /// Check if a module binding is const
181    pub fn is_const(&self, name: &str) -> Option<bool> {
182        self.name_to_index
183            .get(name)
184            .map(|&idx| self.is_const[idx as usize])
185    }
186
187    /// Check if a module binding at index is const
188    pub fn is_const_by_index(&self, idx: u32) -> Option<bool> {
189        self.is_const.get(idx as usize).copied()
190    }
191
192    /// Get the number of registered module bindings
193    pub fn len(&self) -> usize {
194        self.values.len()
195    }
196
197    /// Check if the registry is empty
198    pub fn is_empty(&self) -> bool {
199        self.values.is_empty()
200    }
201
202    /// Get all module binding names (for debugging/introspection)
203    pub fn names(&self) -> impl Iterator<Item = &str> {
204        self.index_to_name.iter().map(|s| s.as_str())
205    }
206
207    /// Get stable pointer for JIT (address won't change after registration)
208    ///
209    /// # Safety
210    /// The pointer is valid as long as no new module bindings are registered.
211    /// For JIT, call this after all module bindings are registered.
212    #[inline]
213    pub fn get_ptr(&self, idx: u32) -> Option<*const KindedSlot> {
214        self.values.get(idx as usize).map(|v| v as *const KindedSlot)
215    }
216
217    /// Snapshot constant module bindings for JIT constant folding.
218    /// Cloning each `KindedSlot` bumps its refcount via the explicit
219    /// `Clone` impl — no aliasing copies.
220    pub fn snapshot_constants(&self) -> Vec<(u32, KindedSlot)> {
221        self.values
222            .iter()
223            .enumerate()
224            .filter(|(i, _)| self.is_const[*i])
225            .map(|(i, v)| (i as u32, v.clone()))
226            .collect()
227    }
228
229    /// Clear all module bindings (for testing or reset)
230    pub fn clear(&mut self) {
231        self.name_to_index.clear();
232        self.index_to_name.clear();
233        self.values.clear();
234        self.is_const.clear();
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn test_register_and_resolve() {
244        let mut registry = ModuleBindingRegistry::new();
245
246        let idx = registry
247            .register_const("x", KindedSlot::from_number(42.0))
248            .unwrap();
249        assert_eq!(idx, 0);
250
251        let idx2 = registry
252            .register_const("y", KindedSlot::from_number(100.0))
253            .unwrap();
254        assert_eq!(idx2, 1);
255
256        assert_eq!(registry.resolve("x"), Some(0));
257        assert_eq!(registry.resolve("y"), Some(1));
258        assert_eq!(registry.resolve("z"), None);
259    }
260
261    #[test]
262    fn test_get_by_name() {
263        let mut registry = ModuleBindingRegistry::new();
264        registry
265            .register_const("pi", KindedSlot::from_number(3.14159))
266            .unwrap();
267
268        let val = registry.get_by_name("pi");
269        assert!(val.is_some());
270        assert!((val.unwrap().slot().as_f64() - 3.14159).abs() < 0.0001);
271
272        assert!(registry.get_by_name("unknown").is_none());
273    }
274
275    #[test]
276    fn test_get_by_index() {
277        let mut registry = ModuleBindingRegistry::new();
278        registry
279            .register_const("a", KindedSlot::from_number(1.0))
280            .unwrap();
281        registry
282            .register_const("b", KindedSlot::from_number(2.0))
283            .unwrap();
284
285        assert_eq!(registry.get_by_index(0).map(|ks| ks.slot().as_f64()), Some(1.0));
286        assert_eq!(registry.get_by_index(1).map(|ks| ks.slot().as_f64()), Some(2.0));
287        assert!(registry.get_by_index(99).is_none());
288    }
289
290    #[test]
291    fn test_const_protection() {
292        let mut registry = ModuleBindingRegistry::new();
293        registry
294            .register_const("CONST_VAL", KindedSlot::from_number(42.0))
295            .unwrap();
296
297        // Should fail to set const by index
298        let result = registry.set_by_index(0, KindedSlot::from_number(100.0));
299        assert!(result.is_err());
300
301        // Value should be unchanged
302        assert_eq!(registry.get_by_index(0).map(|ks| ks.slot().as_f64()), Some(42.0));
303    }
304
305    #[test]
306    fn test_mutable_module_binding() {
307        let mut registry = ModuleBindingRegistry::new();
308        registry
309            .register_mut("counter", KindedSlot::from_number(0.0))
310            .unwrap();
311
312        // Should succeed to set mutable by index
313        registry.set_by_index(0, KindedSlot::from_number(1.0)).unwrap();
314        assert_eq!(registry.get_by_index(0).map(|ks| ks.slot().as_f64()), Some(1.0));
315    }
316
317    #[test]
318    fn test_re_register_const() {
319        let mut registry = ModuleBindingRegistry::new();
320        registry
321            .register_const("func", KindedSlot::from_number(1.0))
322            .unwrap();
323
324        // Re-registering same const should update value
325        registry
326            .register_const("func", KindedSlot::from_number(2.0))
327            .unwrap();
328        assert_eq!(
329            registry.get_by_name("func").map(|ks| ks.slot().as_f64()),
330            Some(2.0)
331        );
332
333        // Index should remain the same
334        assert_eq!(registry.resolve("func"), Some(0));
335    }
336
337    #[test]
338    fn test_snapshot_constants() {
339        let mut registry = ModuleBindingRegistry::new();
340        registry
341            .register_const("a", KindedSlot::from_number(1.0))
342            .unwrap();
343        registry
344            .register_mut("b", KindedSlot::from_number(2.0))
345            .unwrap();
346        registry
347            .register_const("c", KindedSlot::from_number(3.0))
348            .unwrap();
349
350        let constants = registry.snapshot_constants();
351        assert_eq!(constants.len(), 2); // Only a and c are const
352
353        // Check indices
354        let indices: Vec<u32> = constants.iter().map(|(i, _)| *i).collect();
355        assert!(indices.contains(&0)); // a
356        assert!(indices.contains(&2)); // c
357    }
358
359    #[test]
360    fn test_contains() {
361        let mut registry = ModuleBindingRegistry::new();
362        registry
363            .register_const("exists", KindedSlot::from_number(1.0))
364            .unwrap();
365
366        assert!(registry.contains("exists"));
367        assert!(!registry.contains("not_exists"));
368    }
369
370    #[test]
371    fn test_is_const() {
372        let mut registry = ModuleBindingRegistry::new();
373        registry
374            .register_const("constant", KindedSlot::from_number(1.0))
375            .unwrap();
376        registry
377            .register_mut("mutable", KindedSlot::from_number(2.0))
378            .unwrap();
379
380        assert_eq!(registry.is_const("constant"), Some(true));
381        assert_eq!(registry.is_const("mutable"), Some(false));
382        assert_eq!(registry.is_const("unknown"), None);
383    }
384}