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;
15use shape_value::ValueWord;
16use std::collections::HashMap;
17
18/// Single source of truth for all module binding values.
19///
20/// Used by:
21/// - Interpreter: name-based lookup
22/// - VM: index-based lookup (after compilation resolves names)
23/// - JIT: stable pointers for inlined access
24#[derive(Debug)]
25pub struct ModuleBindingRegistry {
26    /// Name → index mapping (for compilation)
27    name_to_index: HashMap<String, u32>,
28
29    /// Index → name mapping (for debugging/errors)
30    index_to_name: Vec<String>,
31
32    /// The actual values (NaN-boxed) - accessed by index for O(1) lookup
33    values: Vec<ValueWord>,
34
35    /// Track which module bindings are constants (functions, imports)
36    is_const: Vec<bool>,
37}
38
39impl Default for ModuleBindingRegistry {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44
45impl ModuleBindingRegistry {
46    /// Create a new empty module binding registry
47    pub fn new() -> Self {
48        Self {
49            name_to_index: HashMap::new(),
50            index_to_name: Vec::new(),
51            values: Vec::new(),
52            is_const: Vec::new(),
53        }
54    }
55
56    /// Create with pre-allocated capacity
57    pub fn with_capacity(capacity: usize) -> Self {
58        Self {
59            name_to_index: HashMap::with_capacity(capacity),
60            index_to_name: Vec::with_capacity(capacity),
61            values: Vec::with_capacity(capacity),
62            is_const: Vec::with_capacity(capacity),
63        }
64    }
65
66    /// Register or update a module binding, returns its stable index.
67    ///
68    /// If the module binding already exists:
69    /// - If it's const and we're re-registering with same constness, update value
70    /// - If it's const and we're trying to make it mutable, error
71    /// - If it's mutable, always update
72    ///
73    /// # Arguments
74    /// * `name` - The module binding's name
75    /// * `value` - The value to store (converted to ValueWord internally)
76    /// * `is_const` - Whether this module binding is constant (functions, imports)
77    ///
78    /// # Returns
79    /// The stable index for this module binding
80    pub fn register(&mut self, name: &str, value: ValueWord, is_const: bool) -> Result<u32> {
81        self.register_nb(name, value, is_const)
82    }
83
84    /// Register or update a module binding with a ValueWord value, returns its stable index.
85    pub fn register_nb(&mut self, name: &str, value: ValueWord, is_const: bool) -> Result<u32> {
86        if let Some(&idx) = self.name_to_index.get(name) {
87            let idx_usize = idx as usize;
88
89            // Allow re-registration of const module bindings (e.g., during stdlib reload)
90            // but don't allow changing const to mutable
91            if self.is_const[idx_usize] && !is_const {
92                return Err(ShapeError::RuntimeError {
93                    message: format!("Cannot redeclare const '{}' as mutable", name),
94                    location: None,
95                });
96            }
97
98            self.values[idx_usize] = value;
99            self.is_const[idx_usize] = is_const;
100            Ok(idx)
101        } else {
102            // New module binding
103            let idx = self.values.len() as u32;
104            self.name_to_index.insert(name.to_string(), idx);
105            self.index_to_name.push(name.to_string());
106            self.values.push(value);
107            self.is_const.push(is_const);
108            Ok(idx)
109        }
110    }
111
112    /// Register a constant module binding (convenience method)
113    pub fn register_const(&mut self, name: &str, value: ValueWord) -> Result<u32> {
114        self.register(name, value, true)
115    }
116
117    /// Register a mutable module binding (convenience method)
118    pub fn register_mut(&mut self, name: &str, value: ValueWord) -> Result<u32> {
119        self.register_nb(name, value, false)
120    }
121
122    /// Check if a module binding exists
123    pub fn contains(&self, name: &str) -> bool {
124        self.name_to_index.contains_key(name)
125    }
126
127    /// Resolve name to index (compile-time)
128    pub fn resolve(&self, name: &str) -> Option<u32> {
129        self.name_to_index.get(name).copied()
130    }
131
132    /// Get name for an index (for error messages)
133    pub fn get_name(&self, idx: u32) -> Option<&str> {
134        self.index_to_name.get(idx as usize).map(|s| s.as_str())
135    }
136
137    /// Get by name as owned ValueWord (interpreter, dynamic lookup)
138    pub fn get_by_name(&self, name: &str) -> Option<ValueWord> {
139        self.name_to_index
140            .get(name)
141            .map(|&idx| self.values[idx as usize].clone())
142    }
143
144    /// Get by index as ValueWord reference (O(1))
145    #[inline]
146    pub fn get_by_index(&self, idx: u32) -> Option<&ValueWord> {
147        self.values.get(idx as usize)
148    }
149
150    /// Set by index from ValueWord (for VM assignment)
151    pub fn set_by_index(&mut self, idx: u32, value: ValueWord) -> Result<()> {
152        let idx_usize = idx as usize;
153        if idx_usize >= self.values.len() {
154            return Err(ShapeError::RuntimeError {
155                message: format!("module binding index {} out of bounds", idx),
156                location: None,
157            });
158        }
159        if self.is_const[idx_usize] {
160            return Err(ShapeError::RuntimeError {
161                message: format!("Cannot assign to const '{}'", self.index_to_name[idx_usize]),
162                location: None,
163            });
164        }
165        self.values[idx_usize] = value;
166        Ok(())
167    }
168
169    /// Check if a module binding is const
170    pub fn is_const(&self, name: &str) -> Option<bool> {
171        self.name_to_index
172            .get(name)
173            .map(|&idx| self.is_const[idx as usize])
174    }
175
176    /// Check if a module binding at index is const
177    pub fn is_const_by_index(&self, idx: u32) -> Option<bool> {
178        self.is_const.get(idx as usize).copied()
179    }
180
181    /// Get the number of registered module bindings
182    pub fn len(&self) -> usize {
183        self.values.len()
184    }
185
186    /// Check if the registry is empty
187    pub fn is_empty(&self) -> bool {
188        self.values.is_empty()
189    }
190
191    /// Get all module binding names (for debugging/introspection)
192    pub fn names(&self) -> impl Iterator<Item = &str> {
193        self.index_to_name.iter().map(|s| s.as_str())
194    }
195
196    /// Get stable pointer for JIT (address won't change after registration)
197    ///
198    /// # Safety
199    /// The pointer is valid as long as no new module bindings are registered.
200    /// For JIT, call this after all module bindings are registered.
201    #[inline]
202    pub fn get_ptr(&self, idx: u32) -> Option<*const ValueWord> {
203        self.values.get(idx as usize).map(|v| v as *const ValueWord)
204    }
205
206    /// Snapshot constant module bindings for JIT constant folding
207    pub fn snapshot_constants(&self) -> Vec<(u32, ValueWord)> {
208        self.values
209            .iter()
210            .enumerate()
211            .filter(|(i, _)| self.is_const[*i])
212            .map(|(i, v)| (i as u32, v.clone()))
213            .collect()
214    }
215
216    /// Clear all module bindings (for testing or reset)
217    pub fn clear(&mut self) {
218        self.name_to_index.clear();
219        self.index_to_name.clear();
220        self.values.clear();
221        self.is_const.clear();
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use shape_value::heap_value::HeapValue;
229
230    #[test]
231    fn test_register_and_resolve() {
232        let mut registry = ModuleBindingRegistry::new();
233
234        let idx = registry
235            .register_const("x", ValueWord::from_f64(42.0))
236            .unwrap();
237        assert_eq!(idx, 0);
238
239        let idx2 = registry
240            .register_const("y", ValueWord::from_f64(100.0))
241            .unwrap();
242        assert_eq!(idx2, 1);
243
244        assert_eq!(registry.resolve("x"), Some(0));
245        assert_eq!(registry.resolve("y"), Some(1));
246        assert_eq!(registry.resolve("z"), None);
247    }
248
249    #[test]
250    fn test_get_by_name() {
251        let mut registry = ModuleBindingRegistry::new();
252        registry
253            .register_const("pi", ValueWord::from_f64(3.14159))
254            .unwrap();
255
256        let val = registry.get_by_name("pi");
257        assert!(val.is_some());
258        assert!((val.unwrap().as_f64().unwrap() - 3.14159).abs() < 0.0001);
259
260        assert!(registry.get_by_name("unknown").is_none());
261    }
262
263    #[test]
264    fn test_get_by_index() {
265        let mut registry = ModuleBindingRegistry::new();
266        registry
267            .register_const("a", ValueWord::from_f64(1.0))
268            .unwrap();
269        registry
270            .register_const("b", ValueWord::from_f64(2.0))
271            .unwrap();
272
273        assert_eq!(
274            registry.get_by_index(0).and_then(|nb| nb.as_f64()),
275            Some(1.0)
276        );
277        assert_eq!(
278            registry.get_by_index(1).and_then(|nb| nb.as_f64()),
279            Some(2.0)
280        );
281        assert!(registry.get_by_index(99).is_none());
282    }
283
284    #[test]
285    fn test_const_protection() {
286        let mut registry = ModuleBindingRegistry::new();
287        registry
288            .register_const("CONST_VAL", ValueWord::from_f64(42.0))
289            .unwrap();
290
291        // Should fail to set const by index
292        let result = registry.set_by_index(0, ValueWord::from_f64(100.0));
293        assert!(result.is_err());
294
295        // Value should be unchanged
296        assert_eq!(
297            registry.get_by_index(0).and_then(|nb| nb.as_f64()),
298            Some(42.0)
299        );
300    }
301
302    #[test]
303    fn test_mutable_module_binding() {
304        let mut registry = ModuleBindingRegistry::new();
305        registry
306            .register_mut("counter", ValueWord::from_f64(0.0))
307            .unwrap();
308
309        // Should succeed to set mutable by index
310        registry.set_by_index(0, ValueWord::from_f64(1.0)).unwrap();
311        assert_eq!(
312            registry.get_by_index(0).and_then(|nb| nb.as_f64()),
313            Some(1.0)
314        );
315    }
316
317    #[test]
318    fn test_re_register_const() {
319        let mut registry = ModuleBindingRegistry::new();
320        registry
321            .register_const("func", ValueWord::from_f64(1.0))
322            .unwrap();
323
324        // Re-registering same const should update value
325        registry
326            .register_const("func", ValueWord::from_f64(2.0))
327            .unwrap();
328        assert_eq!(
329            registry.get_by_name("func").and_then(|nb| nb.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", ValueWord::from_f64(1.0))
342            .unwrap();
343        registry
344            .register_mut("b", ValueWord::from_f64(2.0))
345            .unwrap();
346        registry
347            .register_const("c", ValueWord::from_f64(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_closure_registration() {
361        let mut registry = ModuleBindingRegistry::new();
362
363        let closure_val = ValueWord::from_heap_value(HeapValue::Closure {
364            function_id: 0,
365            upvalues: vec![],
366        });
367
368        let idx = registry.register_const("test_func", closure_val).unwrap();
369        assert_eq!(idx, 0);
370
371        // Should be retrievable
372        let val = registry.get_by_name("test_func");
373        assert!(
374            matches!(val, Some(nb) if nb.as_heap_ref().is_some_and(|h| matches!(h, HeapValue::Closure { .. })))
375        );
376    }
377
378    #[test]
379    fn test_contains() {
380        let mut registry = ModuleBindingRegistry::new();
381        registry
382            .register_const("exists", ValueWord::from_f64(1.0))
383            .unwrap();
384
385        assert!(registry.contains("exists"));
386        assert!(!registry.contains("not_exists"));
387    }
388
389    #[test]
390    fn test_is_const() {
391        let mut registry = ModuleBindingRegistry::new();
392        registry
393            .register_const("constant", ValueWord::from_f64(1.0))
394            .unwrap();
395        registry
396            .register_mut("mutable", ValueWord::from_f64(2.0))
397            .unwrap();
398
399        assert_eq!(registry.is_const("constant"), Some(true));
400        assert_eq!(registry.is_const("mutable"), Some(false));
401        assert_eq!(registry.is_const("unknown"), None);
402    }
403}