Skip to main content

shape_vm/executor/vm_impl/
modules.rs

1use super::super::*;
2
3impl VirtualMachine {
4    /// Register a built-in stdlib module into the VM's module registry.
5    /// Delegates to `register_extension` — this is a semantic alias to
6    /// distinguish VM-native stdlib modules from user-installed extension plugins.
7    pub fn register_stdlib_module(&mut self, module: shape_runtime::module_exports::ModuleExports) {
8        self.register_extension(module);
9    }
10
11    /// Register an external/user extension module (e.g. loaded from a .so plugin)
12    /// into the VM's module registry.
13    /// Also merges any method intrinsics for fast Object dispatch.
14    pub fn register_extension(&mut self, module: shape_runtime::module_exports::ModuleExports) {
15        // Merge method intrinsics
16        for (type_name, methods) in &module.method_intrinsics {
17            let entry = self.extension_methods.entry(type_name.clone()).or_default();
18            for (method_name, func) in methods {
19                entry.insert(method_name.clone(), func.clone());
20            }
21        }
22        // Expose module exports as methods on the module object type so
23        // `module.fn(...)` dispatches via CallMethod without UFCS rewrites.
24        let module_type_name = format!("__mod_{}", module.name);
25        let module_entry = self.extension_methods.entry(module_type_name).or_default();
26        for (export_name, func) in &module.exports {
27            module_entry.insert(export_name.clone(), func.clone());
28        }
29        for (export_name, async_fn) in &module.async_exports {
30            let async_fn = async_fn.clone();
31            let wrapped: shape_runtime::module_exports::ModuleFn = Arc::new(
32                move |args: &[ValueWord], _ctx: &shape_runtime::module_exports::ModuleContext| {
33                    let future = async_fn(args);
34                    tokio::task::block_in_place(|| {
35                        tokio::runtime::Handle::current().block_on(future)
36                    })
37                },
38            );
39            module_entry.insert(export_name.clone(), wrapped);
40        }
41        self.module_registry.register(module);
42    }
43
44    /// Register a ModuleFn in the table and return its ID (for ValueWord::ModuleFunction).
45    pub fn register_module_fn(&mut self, f: shape_runtime::module_exports::ModuleFn) -> usize {
46        let id = self.module_fn_table.len();
47        self.module_fn_table.push(f);
48        id
49    }
50
51    /// Invoke a registered module function with a scoped `ModuleContext`.
52    ///
53    /// The context provides access to the type schema registry, a callable
54    /// invoker closure, and a raw invoker that extensions can capture in
55    /// long-lived structs (e.g., CFFI callback userdata).
56    pub(crate) fn invoke_module_fn(
57        &mut self,
58        module_fn: &shape_runtime::module_exports::ModuleFn,
59        args: &[ValueWord],
60    ) -> Result<ValueWord, VMError> {
61        // SAFETY: The module function is called synchronously and the VM pointer
62        // remains valid for the duration of the call.  We use a raw pointer so
63        // that: (a) the callable invoker can re-enter the VM, and (b) we can
64        // simultaneously borrow the schema registry.
65        unsafe {
66            let vm_ptr = self as *mut VirtualMachine;
67
68            let invoker =
69                |callable: &ValueWord, call_args: &[ValueWord]| -> Result<ValueWord, String> {
70                    (*vm_ptr)
71                        .call_value_immediate_nb(callable, call_args, None)
72                        .map_err(|e| e.to_string())
73                };
74
75            unsafe fn vm_callable_invoker(
76                ctx: *mut std::ffi::c_void,
77                callable: &ValueWord,
78                args: &[ValueWord],
79            ) -> Result<ValueWord, String> {
80                let vm = unsafe { &mut *(ctx as *mut VirtualMachine) };
81                vm.call_value_immediate_nb(callable, args, None)
82                    .map_err(|err| err.to_string())
83            }
84
85            // Capture a read-only snapshot of VM state before dispatching.
86            // The snapshot lives on the stack and is referenced by ModuleContext
87            // for the duration of this synchronous call.
88            let vm_snapshot = (*vm_ptr).capture_vm_state();
89
90            let ctx = shape_runtime::module_exports::ModuleContext {
91                schemas: &(*vm_ptr).program.type_schema_registry,
92                invoke_callable: Some(&invoker),
93                raw_invoker: Some(shape_runtime::module_exports::RawCallableInvoker {
94                    ctx: vm_ptr as *mut std::ffi::c_void,
95                    invoke: vm_callable_invoker,
96                }),
97                function_hashes: if (*vm_ptr).function_hash_raw.is_empty() {
98                    None
99                } else {
100                    Some(&(*vm_ptr).function_hash_raw)
101                },
102                vm_state: Some(&vm_snapshot),
103                granted_permissions: None,
104                scope_constraints: None,
105                set_pending_resume: Some(&|snapshot| {
106                    // vm_ptr is valid for the duration of the module function call
107                    // (outer unsafe block covers this).
108                    (*vm_ptr).pending_resume = Some(snapshot);
109                }),
110                set_pending_frame_resume: Some(&|ip_offset, locals| {
111                    // vm_ptr is valid for the duration of the module function call
112                    // (outer unsafe block covers this).
113                    (*vm_ptr).pending_frame_resume = Some(FrameResumeData { ip_offset, locals });
114                }),
115            };
116
117            // Set thread-local program reference so remote.__call() can access it.
118            crate::executor::builtins::remote_builtins::set_current_program(&(*vm_ptr).program);
119            let result = module_fn(args, &ctx).map_err(VMError::RuntimeError);
120            crate::executor::builtins::remote_builtins::clear_current_program();
121
122            // Check if the module function requested a VM state resume.
123            // If so, return a special error that the dispatch loop intercepts.
124            if (*vm_ptr).pending_resume.is_some() {
125                return Err(VMError::ResumeRequested);
126            }
127
128            result
129        }
130    }
131
132    /// Populate extension module objects as module_bindings (json, duckdb, etc.).
133    /// These are used by extension Shape code (e.g., `duckdb.query(...)`).
134    /// Call this after load_program().
135    pub fn populate_module_objects(&mut self) {
136        // Collect module data first to avoid borrow conflicts
137        let module_data: Vec<(
138            String,
139            Vec<(String, shape_runtime::module_exports::ModuleFn)>,
140            Vec<(String, shape_runtime::module_exports::AsyncModuleFn)>,
141            Vec<String>,
142        )> = self
143            .module_registry
144            .module_names()
145            .iter()
146            .filter_map(|name| {
147                let module = self.module_registry.get(name)?;
148                let sync_exports: Vec<_> = module
149                    .exports
150                    .iter()
151                    .map(|(k, v)| (k.clone(), v.clone()))
152                    .collect();
153                let async_exports: Vec<_> = module
154                    .async_exports
155                    .iter()
156                    .map(|(k, v)| (k.clone(), v.clone()))
157                    .collect();
158                let mut source_exports = Vec::new();
159                for artifact in &module.module_artifacts {
160                    if artifact.module_path != *name {
161                        continue;
162                    }
163                    let Some(source) = artifact.source.as_deref() else {
164                        continue;
165                    };
166                    if let Ok(exports) =
167                        shape_runtime::module_loader::collect_exported_function_names_from_source(
168                            &artifact.module_path,
169                            source,
170                        )
171                    {
172                        source_exports.extend(exports);
173                    }
174                }
175                source_exports.sort();
176                source_exports.dedup();
177                Some((
178                    name.to_string(),
179                    sync_exports,
180                    async_exports,
181                    source_exports,
182                ))
183            })
184            .collect();
185
186        for (module_name, sync_exports, async_exports, source_exports) in module_data {
187            // Find the module_binding index for this module name
188            let binding_idx = self
189                .program
190                .module_binding_names
191                .iter()
192                .position(|n| n == &module_name);
193
194            if let Some(idx) = binding_idx {
195                let mut obj = HashMap::new();
196
197                // Register sync exports directly
198                for (export_name, module_fn) in sync_exports {
199                    let fn_id = self.register_module_fn(module_fn);
200                    obj.insert(export_name, ValueWord::from_module_function(fn_id as u32));
201                }
202
203                // Wrap async exports: block_in_place + block_on at call time
204                for (export_name, async_fn) in async_exports {
205                    let wrapped: shape_runtime::module_exports::ModuleFn =
206                        Arc::new(move |args: &[ValueWord], _ctx: &shape_runtime::module_exports::ModuleContext| {
207                            let future = async_fn(args);
208                            tokio::task::block_in_place(|| {
209                                tokio::runtime::Handle::current().block_on(future)
210                            })
211                        });
212                    let fn_id = self.register_module_fn(wrapped);
213                    obj.insert(export_name, ValueWord::from_module_function(fn_id as u32));
214                }
215
216                // Add Shape-source exported functions (compiled into bytecode).
217                // These are regular VM functions, not host module functions.
218                for export_name in source_exports {
219                    if obj.contains_key(&export_name) {
220                        continue;
221                    }
222                    if let Some(&func_id) = self.function_name_index.get(&export_name) {
223                        obj.insert(export_name, ValueWord::from_function(func_id));
224                    }
225                }
226
227                // Module object schemas must be predeclared at compile time.
228                let cache_name = format!("__mod_{}", module_name);
229                let schema_id = if let Some(schema) = self.lookup_schema_by_name(&cache_name) {
230                    schema.id
231                } else {
232                    // Keep execution predictable: no runtime schema synthesis.
233                    // Missing module schema means compiler/loader setup is incomplete.
234                    continue;
235                };
236
237                // Look up schema to get field ordering
238                let Some(schema) = self.lookup_schema(schema_id) else {
239                    continue;
240                };
241                let field_order: Vec<String> =
242                    schema.fields.iter().map(|f| f.name.clone()).collect();
243
244                let mut slots = Vec::with_capacity(field_order.len());
245                let mut heap_mask: u64 = 0;
246                for (i, field_name) in field_order.iter().enumerate() {
247                    let nb_val = obj.get(field_name).cloned().unwrap_or_else(ValueWord::none);
248                    let (slot, is_heap) =
249                        crate::executor::objects::object_creation::nb_to_slot_with_field_type(
250                            &nb_val, None,
251                        );
252                    slots.push(slot);
253                    if is_heap {
254                        heap_mask |= 1u64 << i;
255                    }
256                }
257
258                let typed_nb = ValueWord::from_heap_value(HeapValue::TypedObject {
259                    schema_id: schema_id as u64,
260                    slots: slots.into_boxed_slice(),
261                    heap_mask,
262                });
263                if idx >= self.module_bindings.len() {
264                    self.module_bindings.resize_with(idx + 1, ValueWord::none);
265                }
266                // BARRIER: heap write site — overwrites module binding during typed object initialization
267                self.module_bindings[idx] = typed_nb;
268            }
269        }
270    }
271}