1use std::collections::BTreeMap;
2use std::future::Future;
3use std::hash::{Hash, Hasher};
4use std::path::{Path, PathBuf};
5use std::pin::Pin;
6use std::sync::{Arc, Mutex, OnceLock};
7
8use harn_modules::DefKind;
9
10use crate::bytecode_cache;
11use crate::module_artifact::{
12 compile_module_artifact_from_source, compile_trusted_host_dispatch_module_artifact_from_source,
13 ModuleProvenance,
14};
15use crate::module_source::{self, ModuleSource};
16use crate::prepared_module::PreparedModuleArtifact;
17use crate::value::{ModuleFunctionRegistry, VmClosure, VmEnv, VmError, VmValue};
18
19use super::{ScopeSpan, Vm};
20
21static STDLIB_MODULE_ARTIFACT_CACHE: OnceLock<
22 Mutex<BTreeMap<String, Arc<PreparedModuleArtifact>>>,
23> = OnceLock::new();
24
25fn stdlib_module_artifact_cache() -> &'static Mutex<BTreeMap<String, Arc<PreparedModuleArtifact>>> {
26 STDLIB_MODULE_ARTIFACT_CACHE.get_or_init(|| Mutex::new(BTreeMap::new()))
27}
28
29fn verified_package_source(bytes: Vec<u8>, path: &Path) -> Result<String, VmError> {
30 String::from_utf8(bytes).map_err(|error| {
31 VmError::Runtime(format!(
32 "installed package source {} is not valid UTF-8: {error}",
33 path.display()
34 ))
35 })
36}
37
38fn exported_function_closures(
39 loaded: &LoadedModule,
40 display_path: &Path,
41) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
42 let mut exports = BTreeMap::new();
43 for name in loaded
44 .public_exports
45 .keys()
46 .filter(|name| loaded.functions.contains_key(*name))
47 {
48 let Some(closure) = loaded.functions.get(name) else {
49 return Err(VmError::Runtime(format!(
50 "Import error: exported function '{name}' is missing from {}",
51 display_path.display()
52 )));
53 };
54 exports.insert(name.clone(), Arc::clone(closure));
55 }
56 Ok(exports)
57}
58
59#[cfg(test)]
60fn reset_stdlib_module_artifact_cache() {
61 stdlib_module_artifact_cache().lock().unwrap().clear();
62}
63
64#[cfg(test)]
65fn stdlib_module_artifact_cache_ptr(module: &str, source: &str) -> Option<usize> {
66 let key = stdlib_artifact_cache_key(module, source);
67 stdlib_module_artifact_cache()
68 .lock()
69 .unwrap()
70 .get(&key)
71 .map(|artifact| Arc::as_ptr(artifact) as usize)
72}
73
74pub(crate) struct LoadedModule {
75 pub(crate) functions: BTreeMap<String, Arc<VmClosure>>,
76 pub(crate) public_exports: BTreeMap<String, DefKind>,
79 pub(crate) public_values: BTreeMap<String, VmValue>,
82 pub(crate) public_type_schemas: BTreeMap<String, VmValue>,
86 package_execution_guard: Option<Arc<harn_modules::package_execution::PackageExecutionGuard>>,
91 pub(crate) _module_functions: crate::value::ModuleFunctionRegistry,
92 pub(crate) _module_state: crate::value::ModuleState,
93}
94
95pub(crate) type ModuleCache = Arc<BTreeMap<PathBuf, Arc<LoadedModule>>>;
103
104#[derive(Clone, Debug)]
110pub(crate) struct DeferredCyclicImport {
111 pub(crate) importer: PathBuf,
113 pub(crate) target: PathBuf,
115 pub(crate) selected_names: Option<Vec<String>>,
117 pub(crate) namespace_alias: Option<String>,
119}
120
121#[derive(Clone, Copy)]
122enum ImportProjection<'a> {
123 BindCaller(Option<&'a [String]>),
124 BindNamespace(&'a str),
126 MaterializeOnly,
127}
128
129impl ImportProjection<'_> {
130 fn package_rejection_kind(self) -> &'static str {
131 match self {
132 Self::BindCaller(_) | Self::BindNamespace(_) => "import",
133 Self::MaterializeOnly => "execution",
134 }
135 }
136}
137
138fn module_import_names(
142 module_name: &str,
143 loaded: &LoadedModule,
144 selected_names: Option<&[String]>,
145) -> Result<Vec<String>, VmError> {
146 if let Some(names) = selected_names {
147 for name in names {
148 if !loaded.public_exports.contains_key(name) {
149 let hint = if loaded.functions.contains_key(name) {
150 " — it is defined there but not `pub`; mark it `pub` to export it"
151 } else {
152 ""
153 };
154 return Err(VmError::Runtime(format!(
155 "Import error: '{name}' is not exported by {module_name}{hint}"
156 )));
157 }
158 }
159 return Ok(names.to_vec());
160 }
161
162 Ok(loaded.public_exports.keys().cloned().collect())
163}
164
165fn build_namespace_dict(module_path: &str, loaded: &LoadedModule) -> VmValue {
171 let mut map = BTreeMap::new();
172 map.insert(
173 "_namespace".to_string(),
174 VmValue::String(arcstr::ArcStr::from(module_path)),
175 );
176 for (name, kind) in &loaded.public_exports {
177 if !kind.has_runtime_value() {
178 if let Some(schema) = loaded.public_type_schemas.get(name) {
180 map.insert(name.clone(), schema.clone());
181 }
182 continue;
183 }
184 if let Some(value) = loaded.public_values.get(name) {
185 map.insert(name.clone(), value.clone());
186 continue;
187 }
188 if let Some(schema) = loaded.public_type_schemas.get(name) {
189 map.insert(name.clone(), schema.clone());
190 continue;
191 }
192 if let Some(closure) = loaded.functions.get(name) {
193 map.insert(name.clone(), VmValue::Closure(Arc::clone(closure)));
194 }
195 }
196 VmValue::dict(map)
197}
198
199pub fn resolve_module_import_path(base: &Path, path: &str) -> PathBuf {
200 let synthetic_current_file = base.join("__harn_import_base__.harn");
201 if let Some(resolved) = harn_modules::resolve_import_path(&synthetic_current_file, path) {
202 return resolved;
203 }
204
205 let mut file_path = base.join(path);
206
207 if !file_path.exists() && file_path.extension().is_none() {
208 file_path.set_extension("harn");
209 }
210
211 file_path
212}
213
214fn stdlib_artifact_cache_key(module: &str, source: &str) -> String {
215 let mut hasher = std::collections::hash_map::DefaultHasher::new();
216 module.hash(&mut hasher);
217 source.hash(&mut hasher);
218 format!("{module}:{:016x}", hasher.finish())
219}
220
221fn stdlib_module_artifact(
222 module: &str,
223 synthetic: &Path,
224 source: &'static str,
225 recorder: Option<&super::ModulePhaseRecorder>,
226) -> Result<Arc<PreparedModuleArtifact>, VmError> {
227 let key = stdlib_artifact_cache_key(module, source);
228 {
229 let cache = stdlib_module_artifact_cache().lock().unwrap();
230 if let Some(cached) = cache.get(&key) {
231 return Ok(Arc::clone(cached));
232 }
233 }
234
235 let embedded = ModuleSource::from_text(source);
240 let lookup = {
241 let _load_span = recorder.map(super::ModulePhaseRecorder::load_span);
242 bytecode_cache::load_module(synthetic, &embedded)
243 };
244 let artifact = if let Some(artifact) = lookup.artifact {
245 artifact
246 } else {
247 let mut compile_span = recorder.map(super::ModulePhaseRecorder::compile_span);
248 let compiled = compile_module_artifact_from_source(synthetic, source)?;
249 if let Some(span) = &mut compile_span {
250 span.mark_compile_succeeded();
251 }
252 drop(compile_span);
253 if let Err(err) = bytecode_cache::store_module(&lookup.key, &compiled) {
254 if std::env::var_os("HARN_BYTECODE_CACHE_DEBUG").is_some() {
255 eprintln!("[harn] stdlib module cache write skipped for {module}: {err}");
256 }
257 }
258 compiled
259 };
260
261 let compiled = {
262 let _load_span = recorder.map(super::ModulePhaseRecorder::load_span);
263 Arc::new(PreparedModuleArtifact::from_cached(artifact))
264 };
265 let mut cache = stdlib_module_artifact_cache().lock().unwrap();
266 if let Some(cached) = cache.get(&key) {
267 return Ok(Arc::clone(cached));
268 }
269 cache.insert(key, Arc::clone(&compiled));
270 Ok(compiled)
271}
272
273pub(crate) fn prepare_stdlib_module_artifact(
274 path: &Path,
275 recorder: Option<&super::ModulePhaseRecorder>,
276) -> Result<(), VmError> {
277 let Some(module) = path.to_str().and_then(|path| path.strip_prefix("<std>/")) else {
278 return Ok(());
279 };
280 let Some(source) = crate::stdlib_modules::get_stdlib_source(module) else {
281 return Ok(());
282 };
283 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
284 stdlib_module_artifact(module, &synthetic, source, recorder).map(|_| ())
285}
286
287impl Vm {
288 pub fn enable_trusted_host_dispatch(&mut self) -> Result<(), VmError> {
294 self.ensure_execution_available()?;
295 if self.module_provenance == ModuleProvenance::TrustedHostDispatch {
296 return Ok(());
297 }
298 if !self.module_cache.is_empty() || !self.imported_paths.is_empty() {
299 return Err(VmError::Runtime(
300 "trusted host dispatch must be enabled before loading modules".to_string(),
301 ));
302 }
303 self.module_provenance = ModuleProvenance::TrustedHostDispatch;
304 self.graph_link_table = None;
305 Ok(())
306 }
307
308 fn resolve_module_import_path(&self, base: &Path, path: &str) -> Result<PathBuf, VmError> {
309 if let Some(guard) = &self.package_execution_guard {
310 let synthetic_current_file = base.join("__harn_import_base__.harn");
311 if let Some(resolved) =
312 harn_modules::resolve_import_path_with_guard(&synthetic_current_file, path, guard)
313 .map_err(|error| {
314 VmError::Runtime(format!("installed package import rejected: {error}"))
315 })?
316 {
317 return Ok(resolved);
318 }
319 let mut file_path = base.join(path);
320 if !file_path.exists() && file_path.extension().is_none() {
321 file_path.set_extension("harn");
322 }
323 return Ok(file_path);
324 }
325 Ok(resolve_module_import_path(base, path))
326 }
327
328 pub async fn resolve_callable(
332 &mut self,
333 callable: &crate::value::VmCallable,
334 ) -> Result<Arc<crate::value::VmClosure>, VmError> {
335 self.ensure_execution_available()?;
336 match callable {
337 crate::value::VmCallable::Eager(closure) => Ok(Arc::clone(closure)),
338 crate::value::VmCallable::Lazy(lazy) => {
339 let (cache_key, module_path) = self.lazy_callable_module_path(lazy);
340 let next_guard = lazy
341 .package_execution_guard_handle()
342 .or_else(|| self.package_execution_guard.clone());
343 if let Some(guard) = &next_guard {
344 guard.verify_entry_source(&module_path).map_err(|error| {
345 VmError::Runtime(format!("installed package execution rejected: {error}"))
346 })?;
347 }
348 let resolution = {
349 let mut modules = self.lazy_callable_modules.lock();
350 let slots = modules.entry(cache_key).or_default();
351 if let Some(slot) = slots.iter().find(|slot| slot.execution_guard == next_guard)
352 {
353 Arc::clone(&slot.resolution)
354 } else {
355 let resolution = Arc::new(tokio::sync::OnceCell::new());
356 slots.push(crate::vm::state::LazyCallableCacheSlot {
357 execution_guard: next_guard.clone(),
358 resolution: Arc::clone(&resolution),
359 });
360 resolution
361 }
362 };
363 let previous_package_execution_guard =
364 std::mem::replace(&mut self.package_execution_guard, next_guard);
365 let resolved = resolution
366 .get_or_try_init(|| async {
367 let exports = self.load_module_exports(&module_path).await?;
368 let exports = exports
369 .into_iter()
370 .map(|(name, closure)| (name, closure.retained_for_host_registry()))
371 .collect();
372 Ok::<_, VmError>(Arc::new(crate::vm::state::ResolvedLazyCallable {
377 exports,
378 retained_module_graph: Arc::clone(&self.module_cache),
379 }))
380 })
381 .await;
382 self.package_execution_guard = previous_package_execution_guard;
383 let resolved = resolved?;
384 resolved
385 .exports
386 .get(&lazy.function_name)
387 .cloned()
388 .ok_or_else(|| {
389 VmError::Runtime(format!(
390 "function '{}' is not exported by module '{}'",
391 lazy.function_name,
392 lazy.module_path.display()
393 ))
394 })
395 }
396 crate::value::VmCallable::Pipeline(_) => Err(VmError::TypeError(
397 "pipeline callable requires execute_callable".to_string(),
398 )),
399 }
400 }
401
402 pub async fn execute_callable(
403 &mut self,
404 callable: &crate::value::VmCallable,
405 args: &[crate::value::VmValue],
406 ) -> Result<crate::value::VmValue, VmError> {
407 let crate::value::VmCallable::Pipeline(pipeline) = callable else {
408 let closure = self.resolve_callable(callable).await?;
409 return self.call_closure_pub(&closure, args).await;
410 };
411
412 let (_, module_path) = self.lazy_module_path(&pipeline.module_path);
413 let next_guard = pipeline
414 .package_execution_guard_handle()
415 .or_else(|| self.package_execution_guard.clone());
416 let previous_package_execution_guard =
417 std::mem::replace(&mut self.package_execution_guard, next_guard);
418 let result = async {
419 let closure = self
420 .load_public_module_callable(&module_path, &pipeline.pipeline_name)
421 .await?;
422 self.call_closure_pub(&closure, args).await
423 }
424 .await;
425 self.package_execution_guard = previous_package_execution_guard;
426 result
427 }
428
429 fn lazy_callable_module_path(&self, lazy: &crate::value::LazyVmCallable) -> (PathBuf, PathBuf) {
430 self.lazy_module_path(&lazy.module_path)
431 }
432
433 fn lazy_module_path(&self, path: &std::path::Path) -> (PathBuf, PathBuf) {
434 let mut module_path = if path.is_absolute() {
435 path.to_path_buf()
436 } else {
437 self.source_dir
438 .clone()
439 .unwrap_or_else(|| PathBuf::from("."))
440 .join(path)
441 };
442 if !module_path.exists() && module_path.extension().is_none() {
443 module_path.set_extension("harn");
444 }
445 let cache_key = module_path
446 .canonicalize()
447 .unwrap_or_else(|_| module_path.clone());
448 (cache_key, module_path)
449 }
450
451 async fn load_module_from_source(
452 &mut self,
453 synthetic: PathBuf,
454 source: &str,
455 ) -> Result<Arc<LoadedModule>, VmError> {
456 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
457 return Ok(loaded);
458 }
459 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::from(source));
460
461 let mut compile_span = self.module_compile_span();
462 let compiled = match self.module_provenance {
463 ModuleProvenance::TrustedHostDispatch => {
464 compile_trusted_host_dispatch_module_artifact_from_source(&synthetic, source)?
465 }
466 ModuleProvenance::User | ModuleProvenance::PrivilegedWire => {
467 compile_module_artifact_from_source(&synthetic, source)?
468 }
469 };
470 if let Some(span) = &mut compile_span {
471 span.mark_compile_succeeded();
472 }
473 drop(compile_span);
474 let artifact = {
475 let _load_span = self.module_load_span();
476 PreparedModuleArtifact::from_cached(compiled)
477 };
478
479 self.imported_paths.push(synthetic.clone());
480 let loaded = Arc::new(self.instantiate_module(None, &artifact).await?);
481 self.imported_paths.pop();
482 {
483 let _load_span = self.module_load_span();
484 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
485 }
486 self.record_module_loaded();
487 Ok(loaded)
488 }
489
490 fn add_builtin_reexports(module: &str, loaded: &mut LoadedModule) {
499 for name in harn_stdlib::builtin_reexports(module) {
500 if loaded.public_exports.contains_key(*name) {
504 continue;
505 }
506 loaded
507 .public_exports
508 .insert((*name).to_string(), DefKind::Function);
509 loaded.public_values.insert(
510 (*name).to_string(),
511 VmValue::BuiltinRef(arcstr::ArcStr::from(*name)),
512 );
513 }
514 }
515
516 async fn load_stdlib_module_from_source(
517 &mut self,
518 module: &str,
519 synthetic: PathBuf,
520 source: &'static str,
521 ) -> Result<Arc<LoadedModule>, VmError> {
522 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
523 return Ok(loaded);
524 }
525 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::from(source));
526
527 let artifact = stdlib_module_artifact(
528 module,
529 &synthetic,
530 source,
531 self.module_phase_recorder.as_ref(),
532 )?;
533 self.imported_paths.push(synthetic.clone());
534 let mut loaded = self.instantiate_stdlib_module(artifact.as_ref()).await?;
535 self.imported_paths.pop();
536 Self::add_builtin_reexports(module, &mut loaded);
537 let loaded = Arc::new(loaded);
538 {
539 let _load_span = self.module_load_span();
540 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
541 }
542 self.record_module_loaded();
543 Ok(loaded)
544 }
545
546 async fn instantiate_stdlib_module(
547 &mut self,
548 artifact: &PreparedModuleArtifact,
549 ) -> Result<LoadedModule, VmError> {
550 self.instantiate_module(None, artifact).await
551 }
552
553 async fn instantiate_module(
561 &mut self,
562 module_source_dir: Option<PathBuf>,
563 artifact: &PreparedModuleArtifact,
564 ) -> Result<LoadedModule, VmError> {
565 let caller_env = self.env.clone();
566 let old_source_dir = self.source_dir.clone();
567 self.env = VmEnv::new();
568 self.source_dir = module_source_dir.clone();
569
570 for import in &artifact.imports {
571 if let Some(alias) = &import.namespace_alias {
572 self.execute_import_with_projection(
573 &import.path,
574 ImportProjection::BindNamespace(alias),
575 artifact.provenance,
576 )
577 .await?;
578 } else {
579 self.execute_import_with_projection(
580 &import.path,
581 ImportProjection::BindCaller(import.selected_names.as_deref()),
582 artifact.provenance,
583 )
584 .await?;
585 }
586 }
587
588 let _load_span = self.module_load_span();
591
592 let module_state: crate::value::ModuleState = {
593 let mut init_env = self.env.clone();
594 if artifact.type_schema_init_chunk.is_some() || artifact.init_chunk.is_some() {
595 let saved_env = std::mem::replace(&mut self.env, init_env);
596 let saved_frames = std::mem::take(&mut self.frames);
597 let saved_handlers = std::mem::take(&mut self.exception_handlers);
598 let saved_iterators = std::mem::take(&mut self.iterators);
599 let saved_deadlines = std::mem::take(&mut self.deadlines);
600 let active_context = crate::step_runtime::suspend_active_context();
611 let init_result: Result<(), VmError> = async {
612 if let Some(chunk) = &artifact.type_schema_init_chunk {
613 self.run_chunk(Arc::clone(chunk)).await?;
614 }
615 if let Some(chunk) = &artifact.init_chunk {
616 self.run_chunk(Arc::clone(chunk)).await?;
617 }
618 Ok(())
619 }
620 .await;
621 drop(active_context);
622 init_env = std::mem::replace(&mut self.env, saved_env);
623 self.frames = saved_frames;
624 self.exception_handlers = saved_handlers;
625 self.iterators = saved_iterators;
626 self.deadlines = saved_deadlines;
627 init_result?;
628 }
629 Arc::new(crate::value::VmMutex::new(init_env))
630 };
631
632 let module_env = self.env.clone();
633 let registry: ModuleFunctionRegistry =
634 Arc::new(crate::value::VmMutex::new(BTreeMap::new()));
635 let mut functions: BTreeMap<String, Arc<VmClosure>> = BTreeMap::new();
636 let mut public_exports = artifact.public_exports.clone();
637 let mut public_values: BTreeMap<String, VmValue> = BTreeMap::new();
641 {
642 let state = module_state.lock();
643 for name in &artifact.public_value_names {
644 if let Some(value) = state.get(name) {
645 public_values.insert(name.clone(), value);
646 }
647 }
648 }
649 if artifact.provenance == crate::module_artifact::ModuleProvenance::PrivilegedWire {
650 for (name, value) in &public_values {
651 if !matches!(value, VmValue::Harness(_)) {
652 return Err(VmError::Runtime(format!(
653 "Privileged wire module export `{name}` produced {}; only a nominal Harness capability handle may cross the wire boundary",
654 value.type_name()
655 )));
656 }
657 }
658 }
659 let public_type_names = artifact.public_type_names.clone();
660 let mut public_type_schemas: BTreeMap<String, VmValue> = {
661 let state = module_state.lock();
662 public_type_names
663 .iter()
664 .filter_map(|name| state.get(name).map(|schema| (name.clone(), schema)))
665 .collect()
666 };
667
668 for (name, compiled) in &artifact.functions {
669 let closure = Arc::new(VmClosure {
670 func: Arc::clone(compiled),
671 env: module_env.clone(),
672 source_dir: module_source_dir.clone(),
673 module_functions: Some(Arc::downgrade(®istry)),
674 module_state: Some(Arc::downgrade(&module_state)),
675 retained_module_scope: None,
676 });
677 registry.lock().insert(name.clone(), Arc::clone(&closure));
678 self.env
679 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
680 module_state
681 .lock()
682 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
683 functions.insert(name.clone(), Arc::clone(&closure));
684 }
685
686 for import in artifact.imports.iter().filter(|import| import.is_pub) {
687 let cache_key = self.cache_key_for_import(&import.path)?;
688 let Some(loaded) = self.module_cache.get(&cache_key).cloned() else {
689 if self.imported_paths.contains(&cache_key) {
696 return Err(VmError::Runtime(format!(
697 "Re-export error: cannot `pub import` from '{}' because it forms an \
698 import cycle with this module (its public surface is still being \
699 built). Use a plain `import` here, or re-export from a module that is \
700 not part of the cycle.",
701 import.path
702 )));
703 }
704 return Err(VmError::Runtime(format!(
705 "Re-export error: imported module '{}' was not loaded",
706 import.path
707 )));
708 };
709 if let Some(alias) = &import.namespace_alias {
712 if public_exports.contains_key(alias) || functions.contains_key(alias) {
713 return Err(VmError::Runtime(format!(
714 "Re-export collision: '{alias}' is defined here and also \
715 re-exported as a namespace from '{}'",
716 import.path
717 )));
718 }
719 let dict = build_namespace_dict(&import.path, &loaded);
720 public_values.insert(alias.clone(), dict);
721 public_exports.insert(alias.clone(), DefKind::Variable);
722 continue;
723 }
724 let names_to_reexport =
725 module_import_names(&import.path, &loaded, import.selected_names.as_deref())?;
726 for name in names_to_reexport {
727 let Some(kind) = loaded.public_exports.get(&name).copied() else {
728 return Err(VmError::Runtime(format!(
729 "Re-export error: '{name}' is not exported by '{}'",
730 import.path
731 )));
732 };
733 let Some(closure) = loaded.functions.get(&name) else {
734 if let Some(value) = loaded.public_values.get(&name) {
738 public_values.insert(name.clone(), value.clone());
739 public_exports.insert(name, kind);
740 continue;
741 }
742 if let Some(schema) = loaded.public_type_schemas.get(&name) {
745 public_type_schemas.insert(name.clone(), schema.clone());
746 }
747 public_exports.insert(name, kind);
748 continue;
749 };
750 if let Some(existing) = functions.get(&name) {
751 if !Arc::ptr_eq(existing, closure) {
752 return Err(VmError::Runtime(format!(
753 "Re-export collision: '{name}' is defined here and also \
754 re-exported from '{}'",
755 import.path
756 )));
757 }
758 }
759 functions.insert(name.clone(), Arc::clone(closure));
760 public_exports.insert(name, kind);
761 }
762 }
763
764 self.env = caller_env;
765 self.source_dir = old_source_dir;
766
767 Ok(LoadedModule {
768 functions,
769 public_exports,
770 public_values,
771 public_type_schemas,
772 package_execution_guard: module_source_dir
773 .as_ref()
774 .and(self.package_execution_guard.clone()),
775 _module_functions: registry,
776 _module_state: module_state,
777 })
778 }
779
780 fn export_namespace_module(
781 &mut self,
782 module_path: &Path,
783 loaded: &LoadedModule,
784 alias: &str,
785 ) -> Result<(), VmError> {
786 let module_name = module_path.display().to_string();
787 if self.env.get(alias).is_some() {
788 return Err(VmError::Runtime(format!(
789 "Import collision: '{alias}' is already defined when importing {module_name}. \
790 Use a different namespace alias: import * as <name> from \"...\""
791 )));
792 }
793 let dict = build_namespace_dict(&module_name, loaded);
794 self.env.define(alias, dict, false)?;
795 Ok(())
796 }
797
798 fn export_loaded_module(
799 &mut self,
800 module_path: &Path,
801 loaded: &LoadedModule,
802 selected_names: Option<&[String]>,
803 ) -> Result<(), VmError> {
804 let module_name = module_path.display().to_string();
805 let export_names = module_import_names(&module_name, loaded, selected_names)?;
806
807 for name in export_names {
808 if let Some(value) = loaded.public_values.get(&name) {
810 if self.env.get(&name).is_some() {
811 return Err(VmError::Runtime(format!(
812 "Import collision: '{name}' is already defined when importing \
813 {module_name}. Use selective imports to disambiguate: \
814 import {{ {name} }} from \"...\""
815 )));
816 }
817 self.env.define(&name, value.clone(), false)?;
818 continue;
819 }
820 if let Some(schema) = loaded.public_type_schemas.get(&name) {
824 self.env.define(&name, schema.clone(), false)?;
825 continue;
826 }
827 if loaded
828 .public_exports
829 .get(&name)
830 .is_some_and(|kind| !kind.has_runtime_value())
831 {
832 continue;
833 }
834 let Some(closure) = loaded.functions.get(&name) else {
835 return Err(VmError::Runtime(format!(
836 "Import error: '{name}' is not defined in {module_name}"
837 )));
838 };
839 if let Some(VmValue::Closure(_)) = self.env.get(&name) {
840 return Err(VmError::Runtime(format!(
841 "Import collision: '{name}' is already defined when importing {module_name}. \
842 Use selective imports to disambiguate: import {{ {name} }} from \"...\""
843 )));
844 }
845 self.env
846 .define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
847 }
848 Ok(())
849 }
850
851 pub(super) fn execute_import<'a>(
853 &'a mut self,
854 path: &'a str,
855 selected_names: Option<&'a [String]>,
856 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
857 self.execute_import_with_projection(
858 path,
859 ImportProjection::BindCaller(selected_names),
860 self.module_provenance,
861 )
862 }
863
864 pub(super) fn execute_namespace_import_bind<'a>(
866 &'a mut self,
867 path: &'a str,
868 alias: &'a str,
869 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
870 self.execute_import_with_projection(
871 path,
872 ImportProjection::BindNamespace(alias),
873 self.module_provenance,
874 )
875 }
876
877 fn materialize_import<'a>(
878 &'a mut self,
879 path: &'a str,
880 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
881 self.execute_import_with_projection(
882 path,
883 ImportProjection::MaterializeOnly,
884 self.module_provenance,
885 )
886 }
887
888 fn apply_import_projection(
889 &mut self,
890 module_path: &Path,
891 loaded: &LoadedModule,
892 projection: ImportProjection<'_>,
893 ) -> Result<(), VmError> {
894 match projection {
895 ImportProjection::BindCaller(selected_names) => {
896 self.export_loaded_module(module_path, loaded, selected_names)
897 }
898 ImportProjection::BindNamespace(alias) => {
899 self.export_namespace_module(module_path, loaded, alias)
900 }
901 ImportProjection::MaterializeOnly => Ok(()),
902 }
903 }
904
905 fn execute_import_with_projection<'a>(
906 &'a mut self,
907 path: &'a str,
908 projection: ImportProjection<'a>,
909 provenance: ModuleProvenance,
910 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
911 Box::pin(async move {
912 let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
913
914 let stdlib_module = path
915 .strip_prefix("std/")
916 .or_else(|| (path == "observability").then_some("observability"));
917 if let Some(module) = stdlib_module {
918 if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
919 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
920 if self.imported_paths.contains(&synthetic) {
921 return Ok(());
922 }
923 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
924 return self.apply_import_projection(&synthetic, &loaded, projection);
925 }
926 let loaded = self
927 .load_stdlib_module_from_source(module, synthetic.clone(), source)
928 .await?;
929 if !matches!(projection, ImportProjection::MaterializeOnly) {
930 let _load_span = self.module_load_span();
931 self.apply_import_projection(&synthetic, &loaded, projection)?;
932 }
933 return Ok(());
934 }
935 return Err(VmError::Runtime(format!(
936 "Unknown stdlib module: std/{module}"
937 )));
938 }
939
940 let base = self
941 .source_dir
942 .clone()
943 .unwrap_or_else(|| PathBuf::from("."));
944 let file_path = self.resolve_module_import_path(&base, path)?;
945 let verified_source = if let Some(guard) = &self.package_execution_guard {
946 let bytes = guard.verify_entry_source(&file_path).map_err(|error| {
947 VmError::Runtime(format!(
948 "installed package {} rejected: {error}",
949 projection.package_rejection_kind()
950 ))
951 })?;
952 Some(verified_package_source(bytes, &file_path)?)
953 } else {
954 None
955 };
956
957 let canonical = file_path
958 .canonicalize()
959 .unwrap_or_else(|_| file_path.clone());
960 if self.imported_paths.contains(&canonical) {
961 match projection {
969 ImportProjection::BindCaller(selected_names) => {
970 if let Some(importer) = self.imported_paths.last().cloned() {
971 if importer != canonical {
972 self.deferred_cyclic_imports.push(DeferredCyclicImport {
973 importer,
974 target: canonical.clone(),
975 selected_names: selected_names.map(<[String]>::to_vec),
976 namespace_alias: None,
977 });
978 }
979 }
980 }
981 ImportProjection::BindNamespace(alias) => {
982 if let Some(importer) = self.imported_paths.last().cloned() {
983 if importer != canonical {
984 self.deferred_cyclic_imports.push(DeferredCyclicImport {
985 importer,
986 target: canonical.clone(),
987 selected_names: None,
988 namespace_alias: Some(alias.to_string()),
989 });
990 }
991 }
992 }
993 ImportProjection::MaterializeOnly => {}
994 }
995 return Ok(());
996 }
997 if let Some(loaded) = self.module_cache.get(&canonical).cloned() {
998 if let Some(source) = &verified_source {
999 let cached_source = self.source_cache.get(&canonical).map(Arc::as_ref);
1000 if cached_source != Some(source.as_str()) {
1001 return Err(VmError::Runtime(format!(
1002 "installed package {} rejected: cached module {} was not compiled from the verified package bytes",
1003 projection.package_rejection_kind(),
1004 canonical.display()
1005 )));
1006 }
1007 let active_guard = self
1008 .package_execution_guard
1009 .as_deref()
1010 .expect("verified package source requires an active guard");
1011 if loaded.package_execution_guard.as_deref() != Some(active_guard) {
1012 return Err(VmError::Runtime(format!(
1013 "installed package {} rejected: cached module {} was not instantiated under the active package execution guard",
1014 projection.package_rejection_kind(),
1015 canonical.display()
1016 )));
1017 }
1018 }
1019 return self.apply_import_projection(&canonical, &loaded, projection);
1020 }
1021 self.imported_paths.push(canonical.clone());
1022
1023 let linked = (provenance == ModuleProvenance::User && verified_source.is_none())
1029 .then(|| {
1030 let content_hash = self
1031 .graph_link_table
1032 .as_ref()?
1033 .content_hash(canonical.as_path())?;
1034 let _load_span = self.module_load_span();
1035 self.linked_module_artifact(&file_path, &canonical, content_hash)
1036 })
1037 .flatten();
1038
1039 let artifact = if let Some(linked) = linked {
1040 linked
1041 } else {
1042 let source = {
1043 let _load_span = self.module_load_span();
1044 match verified_source {
1045 Some(source) => Arc::new(ModuleSource::from_text(source)),
1048 None => module_source::read(&file_path).map_err(|e| {
1049 VmError::Runtime(format!(
1054 "Import error: cannot read '{}' (resolved '{path}' relative to {}): {e}",
1055 file_path.display(),
1056 base.display()
1057 ))
1058 })?,
1059 }
1060 };
1061 {
1062 let source_cache = Arc::make_mut(&mut self.source_cache);
1063 source_cache.insert(canonical.clone(), Arc::clone(source.text()));
1064 source_cache.insert(file_path.clone(), Arc::clone(source.text()));
1065 }
1066
1067 match provenance {
1068 ModuleProvenance::TrustedHostDispatch => {
1069 let mut compile_span = self.module_compile_span();
1070 let compiled = compile_trusted_host_dispatch_module_artifact_from_source(
1071 &file_path,
1072 source.as_str(),
1073 )?;
1074 if let Some(span) = &mut compile_span {
1075 span.mark_compile_succeeded();
1076 }
1077 Arc::new(PreparedModuleArtifact::from_cached(compiled))
1078 }
1079 ModuleProvenance::User | ModuleProvenance::PrivilegedWire => {
1080 self.prepared_module_cache.prepare(
1081 &file_path,
1082 &canonical,
1083 &source,
1084 None,
1085 self.module_phase_recorder.as_ref(),
1086 )?
1087 }
1088 }
1089 };
1090
1091 let module_source_dir = file_path.parent().map(|p| p.to_path_buf());
1092 let loaded = Arc::new(
1093 self.instantiate_module(module_source_dir, artifact.as_ref())
1094 .await?,
1095 );
1096 self.imported_paths.pop();
1097 {
1098 let _load_span = self.module_load_span();
1099 Arc::make_mut(&mut self.module_cache)
1100 .insert(canonical.clone(), Arc::clone(&loaded));
1101 }
1102 self.record_module_loaded();
1103 if !matches!(projection, ImportProjection::MaterializeOnly) {
1104 let _load_span = self.module_load_span();
1105 self.apply_import_projection(&canonical, &loaded, projection)?;
1106 }
1107
1108 if self.imported_paths.is_empty() {
1112 let _load_span = self.module_load_span();
1113 self.flush_deferred_cyclic_imports()?;
1114 }
1115
1116 Ok(())
1117 })
1118 }
1119
1120 fn linked_module_artifact(
1132 &self,
1133 file_path: &Path,
1134 canonical: &Path,
1135 content_hash: [u8; 32],
1136 ) -> Option<Arc<PreparedModuleArtifact>> {
1137 if !bytecode_cache::cache_enabled() {
1138 return None;
1139 }
1140 if let Some(prepared) = self.prepared_module_cache.get(canonical, content_hash) {
1141 return Some(prepared);
1142 }
1143 let key = bytecode_cache::CacheKey::from_module_content_hash(content_hash);
1144 let artifact = bytecode_cache::load_module_for_key(file_path, key).artifact?;
1145 Some(self.prepared_module_cache.insert(
1146 canonical.to_path_buf(),
1147 content_hash,
1148 Arc::new(PreparedModuleArtifact::from_cached(artifact)),
1149 ))
1150 }
1151
1152 fn flush_deferred_cyclic_imports(&mut self) -> Result<(), VmError> {
1161 if self.deferred_cyclic_imports.is_empty() {
1162 return Ok(());
1163 }
1164 let deferred = std::mem::take(&mut self.deferred_cyclic_imports);
1165 let mut still_pending = Vec::new();
1166 for import in deferred {
1167 let (Some(importer), Some(target)) = (
1168 self.module_cache.get(&import.importer).cloned(),
1169 self.module_cache.get(&import.target).cloned(),
1170 ) else {
1171 still_pending.push(import);
1175 continue;
1176 };
1177
1178 let mut module_state = importer._module_state.lock();
1179 if let Some(alias) = &import.namespace_alias {
1180 if module_state.get(alias).is_none() {
1181 let dict = build_namespace_dict(&import.target.display().to_string(), &target);
1182 module_state.define(alias, dict, false)?;
1183 }
1184 continue;
1185 }
1186
1187 let export_names = module_import_names(
1188 &import.target.display().to_string(),
1189 &target,
1190 import.selected_names.as_deref(),
1191 )?;
1192
1193 for name in export_names {
1194 if module_state.get(&name).is_some() {
1197 continue;
1198 }
1199 if let Some(closure) = target.functions.get(&name) {
1200 module_state.define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
1201 } else if let Some(value) = target.public_values.get(&name) {
1202 module_state.define(&name, value.clone(), false)?;
1204 } else if target
1205 .public_exports
1206 .get(&name)
1207 .is_some_and(|kind| !kind.has_runtime_value())
1208 {
1209 continue;
1211 } else {
1212 return Err(VmError::Runtime(format!(
1213 "Import error: '{name}' is not defined in {}",
1214 import.target.display()
1215 )));
1216 }
1217 }
1218 }
1219 self.deferred_cyclic_imports = still_pending;
1220 Ok(())
1221 }
1222
1223 fn cache_key_for_import(&self, path: &str) -> Result<PathBuf, VmError> {
1228 if let Some(module) = path
1229 .strip_prefix("std/")
1230 .or_else(|| (path == "observability").then_some("observability"))
1231 {
1232 return Ok(PathBuf::from(format!("<stdlib>/{module}.harn")));
1233 }
1234 let base = self
1235 .source_dir
1236 .clone()
1237 .unwrap_or_else(|| PathBuf::from("."));
1238 let file_path = self.resolve_module_import_path(&base, path)?;
1239 Ok(file_path.canonicalize().unwrap_or(file_path))
1240 }
1241
1242 async fn loaded_module_for_path(
1243 &mut self,
1244 path: &Path,
1245 ) -> Result<(PathBuf, Arc<LoadedModule>), VmError> {
1246 self.ensure_execution_available()?;
1247 let path_str = path.to_string_lossy().into_owned();
1248 self.materialize_import(&path_str).await?;
1249
1250 let mut file_path = if path.is_absolute() {
1251 path.to_path_buf()
1252 } else {
1253 self.source_dir
1254 .clone()
1255 .unwrap_or_else(|| PathBuf::from("."))
1256 .join(path)
1257 };
1258 if !file_path.exists() && file_path.extension().is_none() {
1259 file_path.set_extension("harn");
1260 }
1261
1262 let canonical = file_path
1263 .canonicalize()
1264 .unwrap_or_else(|_| file_path.clone());
1265 let loaded = self.module_cache.get(&canonical).cloned().ok_or_else(|| {
1266 VmError::Runtime(format!(
1267 "Import error: failed to cache loaded module '{}'",
1268 canonical.display()
1269 ))
1270 })?;
1271 Ok((canonical, loaded))
1272 }
1273
1274 pub async fn load_public_module_callable(
1276 &mut self,
1277 path: &Path,
1278 name: &str,
1279 ) -> Result<Arc<VmClosure>, VmError> {
1280 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1281 if !loaded.public_exports.contains_key(name) {
1282 let hint = if loaded.functions.contains_key(name) {
1283 "; it is defined there but not `pub`"
1284 } else {
1285 ""
1286 };
1287 return Err(VmError::Runtime(format!(
1288 "callable '{name}' is not exported by module '{}'{hint}",
1289 canonical.display()
1290 )));
1291 }
1292 loaded.functions.get(name).cloned().ok_or_else(|| {
1293 VmError::Runtime(format!(
1294 "Import error: exported callable '{name}' is missing from {}",
1295 canonical.display()
1296 ))
1297 })
1298 }
1299
1300 pub async fn load_module_exports(
1303 &mut self,
1304 path: &Path,
1305 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1306 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1307 exported_function_closures(&loaded, &canonical)
1308 }
1309
1310 pub async fn load_module_exports_from_source(
1313 &mut self,
1314 source_key: impl Into<PathBuf>,
1315 source: &str,
1316 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1317 self.ensure_execution_available()?;
1318 let synthetic = source_key.into();
1319 let loaded = self
1320 .load_module_from_source(synthetic.clone(), source)
1321 .await?;
1322 exported_function_closures(&loaded, &synthetic)
1323 }
1324
1325 pub async fn load_module_callable_from_source(
1330 &mut self,
1331 source_key: impl Into<PathBuf>,
1332 source: &str,
1333 name: &str,
1334 ) -> Result<Option<Arc<VmClosure>>, VmError> {
1335 self.ensure_execution_available()?;
1336 let synthetic = source_key.into();
1337 let loaded = self.load_module_from_source(synthetic, source).await?;
1338 Ok(loaded.functions.get(name).cloned())
1339 }
1340
1341 pub async fn load_module_exports_from_import(
1345 &mut self,
1346 import_path: &str,
1347 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1348 self.ensure_execution_available()?;
1349 self.materialize_import(import_path).await?;
1350
1351 if let Some(module) = import_path
1352 .strip_prefix("std/")
1353 .or_else(|| (import_path == "observability").then_some("observability"))
1354 {
1355 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
1356 let loaded = self.module_cache.get(&synthetic).cloned().ok_or_else(|| {
1357 VmError::Runtime(format!(
1358 "Import error: failed to cache loaded module '{}'",
1359 synthetic.display()
1360 ))
1361 })?;
1362 return exported_function_closures(&loaded, &synthetic);
1363 }
1364
1365 let base = self
1366 .source_dir
1367 .clone()
1368 .unwrap_or_else(|| PathBuf::from("."));
1369 let file_path = self.resolve_module_import_path(&base, import_path)?;
1370 self.load_module_exports(&file_path).await
1371 }
1372}
1373
1374#[cfg(test)]
1375#[path = "modules_tests.rs"]
1376mod tests;