1use std::collections::BTreeMap;
2use std::future::Future;
3use std::path::{Path, PathBuf};
4use std::pin::Pin;
5use std::sync::Arc;
6
7use harn_modules::DefKind;
8
9use crate::bytecode_cache;
10use crate::module_artifact::{
11 compile_embedded_stdlib_module_artifact_from_source, compile_module_artifact_from_source,
12 compile_trusted_host_dispatch_module_artifact_from_source, ModuleImportBinding,
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::stdlib_artifact::stdlib_module_artifact;
20use super::{ScopeSpan, Vm};
21#[cfg(test)]
23use super::stdlib_artifact::{
24 reset_stdlib_module_artifact_cache, stdlib_artifact_get_or_prepare,
25 stdlib_module_artifact_cache_ptr,
26};
27
28fn verified_package_source(bytes: Vec<u8>, path: &Path) -> Result<String, VmError> {
29 String::from_utf8(bytes).map_err(|error| {
30 VmError::Runtime(format!(
31 "installed package source {} is not valid UTF-8: {error}",
32 path.display()
33 ))
34 })
35}
36
37fn exported_function_closures(
38 loaded: &LoadedModule,
39 display_path: &Path,
40) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
41 let mut exports = BTreeMap::new();
42 for name in loaded
43 .public_exports
44 .keys()
45 .filter(|name| loaded.functions.contains_key(*name))
46 {
47 let Some(closure) = loaded.functions.get(name) else {
48 return Err(VmError::Runtime(format!(
49 "Import error: exported function '{name}' is missing from {}",
50 display_path.display()
51 )));
52 };
53 exports.insert(name.clone(), Arc::clone(closure));
54 }
55 Ok(exports)
56}
57
58pub(crate) struct LoadedModule {
59 pub(crate) functions: BTreeMap<String, Arc<VmClosure>>,
60 pub(crate) public_exports: BTreeMap<String, DefKind>,
63 pub(crate) public_values: BTreeMap<String, VmValue>,
66 pub(crate) public_type_schemas: BTreeMap<String, VmValue>,
70 package_execution_guard: Option<Arc<harn_modules::package_execution::PackageExecutionGuard>>,
75 pub(crate) _module_functions: crate::value::ModuleFunctionRegistry,
76 pub(crate) _module_state: crate::value::ModuleState,
77}
78
79pub(crate) type ModuleCache = Arc<BTreeMap<PathBuf, Arc<LoadedModule>>>;
87
88#[derive(Clone, Debug)]
94pub(crate) struct DeferredCyclicImport {
95 pub(crate) importer: PathBuf,
97 pub(crate) target: PathBuf,
99 pub(crate) selected_names: Option<Vec<String>>,
101 pub(crate) namespace_alias: Option<String>,
103 pub(crate) namespace_members: Option<Vec<String>>,
105}
106
107#[derive(Clone, Copy)]
108enum ImportProjection<'a> {
109 BindCaller(Option<&'a [String]>),
110 BindNamespace(&'a str, Option<&'a [String]>),
112 MaterializeOnly,
113}
114
115impl ImportProjection<'_> {
116 fn package_rejection_kind(self) -> &'static str {
117 match self {
118 Self::BindCaller(_) | Self::BindNamespace(..) => "import",
119 Self::MaterializeOnly => "execution",
120 }
121 }
122}
123
124#[derive(Clone, Copy)]
128enum ImportNameUse {
129 Binding,
130 Namespace,
131}
132
133fn module_import_names(
134 module_name: &str,
135 loaded: &LoadedModule,
136 selected_names: Option<&[String]>,
137 name_use: ImportNameUse,
138) -> Result<Vec<String>, VmError> {
139 if let Some(names) = selected_names {
140 for name in names {
141 if !loaded.public_exports.contains_key(name) {
142 let message = match name_use {
143 ImportNameUse::Binding => {
144 let hint = if loaded.functions.contains_key(name) {
145 " — it is defined there but not `pub`; mark it `pub` to export it"
146 } else {
147 ""
148 };
149 format!("Import error: '{name}' is not exported by {module_name}{hint}")
150 }
151 ImportNameUse::Namespace => {
152 format!("module `{module_name}` has no exported member `{name}`")
153 }
154 };
155 return Err(VmError::Runtime(message));
156 }
157 }
158 return Ok(names.to_vec());
159 }
160
161 Ok(loaded.public_exports.keys().cloned().collect())
162}
163
164fn build_namespace_dict(
170 module_path: &str,
171 loaded: &LoadedModule,
172 members: Option<&[String]>,
173) -> Result<VmValue, VmError> {
174 let mut map = BTreeMap::new();
175 map.insert(
176 "_namespace".to_string(),
177 VmValue::String(arcstr::ArcStr::from(module_path)),
178 );
179 let names = module_import_names(module_path, loaded, members, ImportNameUse::Namespace)?;
180 for name in names {
181 let kind = loaded
182 .public_exports
183 .get(&name)
184 .expect("module_import_names validates the public export contract");
185 if !kind.has_runtime_value() {
186 if let Some(schema) = loaded.public_type_schemas.get(&name) {
188 map.insert(name, schema.clone());
189 }
190 continue;
191 }
192 if let Some(value) = loaded.public_values.get(&name) {
193 map.insert(name, value.clone());
194 continue;
195 }
196 if let Some(schema) = loaded.public_type_schemas.get(&name) {
197 map.insert(name, schema.clone());
198 continue;
199 }
200 if let Some(closure) = loaded.functions.get(&name) {
201 map.insert(name, VmValue::Closure(Arc::clone(closure)));
202 }
203 }
204 Ok(VmValue::dict(map))
205}
206
207pub fn resolve_module_import_path(base: &Path, path: &str) -> PathBuf {
208 let synthetic_current_file = base.join("__harn_import_base__.harn");
209 if let Some(resolved) = harn_modules::resolve_import_path(&synthetic_current_file, path) {
210 return resolved;
211 }
212
213 let mut file_path = base.join(path);
214
215 if !file_path.exists() && file_path.extension().is_none() {
216 file_path.set_extension("harn");
217 }
218
219 file_path
220}
221
222impl Vm {
223 pub fn enable_trusted_host_dispatch(&mut self) -> Result<(), VmError> {
229 self.ensure_execution_available()?;
230 if self.module_provenance == ModuleProvenance::TrustedHostDispatch {
231 return Ok(());
232 }
233 if !self.module_cache.is_empty() || !self.imported_paths.is_empty() {
234 return Err(VmError::Runtime(
235 "trusted host dispatch must be enabled before loading modules".to_string(),
236 ));
237 }
238 self.module_provenance = ModuleProvenance::TrustedHostDispatch;
239 self.graph_link_table = None;
240 self.linked_program_repository = None;
241 Ok(())
242 }
243
244 fn resolve_module_import_path(&self, base: &Path, path: &str) -> Result<PathBuf, VmError> {
245 if let Some(guard) = &self.package_execution_guard {
246 let synthetic_current_file = base.join("__harn_import_base__.harn");
247 if let Some(resolved) =
248 harn_modules::resolve_import_path_with_guard(&synthetic_current_file, path, guard)
249 .map_err(|error| {
250 VmError::Runtime(format!("installed package import rejected: {error}"))
251 })?
252 {
253 return Ok(resolved);
254 }
255 let mut file_path = base.join(path);
256 if !file_path.exists() && file_path.extension().is_none() {
257 file_path.set_extension("harn");
258 }
259 return Ok(file_path);
260 }
261 Ok(resolve_module_import_path(base, path))
262 }
263
264 pub async fn resolve_callable(
268 &mut self,
269 callable: &crate::value::VmCallable,
270 ) -> Result<Arc<crate::value::VmClosure>, VmError> {
271 self.ensure_execution_available()?;
272 match callable {
273 crate::value::VmCallable::Eager(closure) => Ok(Arc::clone(closure)),
274 crate::value::VmCallable::Lazy(lazy) => {
275 lazy.prepare().await?;
276 let (cache_key, module_path) = self.lazy_callable_module_path(lazy);
277 let next_guard = lazy
278 .package_execution_guard_handle()
279 .or_else(|| self.package_execution_guard.clone());
280 if let Some(guard) = &next_guard {
281 guard.verify_entry_source(&module_path).map_err(|error| {
282 VmError::Runtime(format!("installed package execution rejected: {error}"))
283 })?;
284 }
285 let resolution = {
286 let mut modules = self.lazy_callable_modules.lock();
287 let slots = modules.entry(cache_key).or_default();
288 if let Some(slot) = slots.iter().find(|slot| slot.execution_guard == next_guard)
289 {
290 Arc::clone(&slot.resolution)
291 } else {
292 let resolution = Arc::new(tokio::sync::OnceCell::new());
293 slots.push(crate::vm::state::LazyCallableCacheSlot {
294 execution_guard: next_guard.clone(),
295 resolution: Arc::clone(&resolution),
296 });
297 resolution
298 }
299 };
300 let previous_package_execution_guard =
301 std::mem::replace(&mut self.package_execution_guard, next_guard);
302 let resolved = resolution
303 .get_or_try_init(|| async {
304 let exports = self.load_module_exports(&module_path).await?;
305 let exports = exports
306 .into_iter()
307 .map(|(name, closure)| (name, closure.retained_for_host_registry()))
308 .collect();
309 Ok::<_, VmError>(Arc::new(crate::vm::state::ResolvedLazyCallable {
314 exports,
315 retained_module_graph: Arc::clone(&self.module_cache),
316 }))
317 })
318 .await;
319 self.package_execution_guard = previous_package_execution_guard;
320 let resolved = resolved?;
321 resolved
322 .exports
323 .get(&lazy.function_name)
324 .cloned()
325 .ok_or_else(|| {
326 VmError::Runtime(format!(
327 "function '{}' is not exported by module '{}'",
328 lazy.function_name,
329 lazy.module_path.display()
330 ))
331 })
332 }
333 crate::value::VmCallable::Pipeline(_) => Err(VmError::TypeError(
334 "pipeline callable requires execute_callable".to_string(),
335 )),
336 }
337 }
338
339 pub async fn execute_callable(
340 &mut self,
341 callable: &crate::value::VmCallable,
342 args: &[crate::value::VmValue],
343 ) -> Result<crate::value::VmValue, VmError> {
344 let crate::value::VmCallable::Pipeline(pipeline) = callable else {
345 let closure = self.resolve_callable(callable).await?;
346 return self.call_closure_pub(&closure, args).await;
347 };
348
349 let (_, module_path) = self.lazy_module_path(&pipeline.module_path);
350 let next_guard = pipeline
351 .package_execution_guard_handle()
352 .or_else(|| self.package_execution_guard.clone());
353 let previous_package_execution_guard =
354 std::mem::replace(&mut self.package_execution_guard, next_guard);
355 let result = async {
356 let closure = self
357 .load_public_module_callable(&module_path, &pipeline.pipeline_name)
358 .await?;
359 self.call_closure_pub(&closure, args).await
360 }
361 .await;
362 self.package_execution_guard = previous_package_execution_guard;
363 result
364 }
365
366 fn lazy_callable_module_path(&self, lazy: &crate::value::LazyVmCallable) -> (PathBuf, PathBuf) {
367 self.lazy_module_path(&lazy.module_path)
368 }
369
370 fn lazy_module_path(&self, path: &std::path::Path) -> (PathBuf, PathBuf) {
371 let mut module_path = if path.is_absolute() {
372 path.to_path_buf()
373 } else {
374 self.source_dir
375 .clone()
376 .unwrap_or_else(|| PathBuf::from("."))
377 .join(path)
378 };
379 if !module_path.exists() && module_path.extension().is_none() {
380 module_path.set_extension("harn");
381 }
382 let cache_key = module_path
383 .canonicalize()
384 .unwrap_or_else(|_| module_path.clone());
385 (cache_key, module_path)
386 }
387
388 async fn load_module_from_source(
389 &mut self,
390 synthetic: PathBuf,
391 source: &str,
392 ) -> Result<Arc<LoadedModule>, VmError> {
393 self.load_module_from_source_with_preparation(synthetic, source, false)
394 .await
395 }
396
397 async fn load_module_from_source_with_preparation(
398 &mut self,
399 synthetic: PathBuf,
400 source: &str,
401 use_prepared_generation: bool,
402 ) -> Result<Arc<LoadedModule>, VmError> {
403 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
404 return Ok(loaded);
405 }
406 let source = Arc::new(ModuleSource::from_text(source));
407 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::clone(source.text()));
408 let artifact = if use_prepared_generation {
409 self.prepared_module_cache.prepare(
410 &synthetic,
411 &synthetic,
412 &source,
413 None,
414 self.module_phase_recorder.as_ref(),
415 self.module_provenance,
416 &self.prepared_module_validation,
417 )?
418 } else {
419 let mut compile_span = self.module_compile_span();
420 let compiled = match self.module_provenance {
421 ModuleProvenance::EmbeddedStdlib => {
422 compile_embedded_stdlib_module_artifact_from_source(
423 &synthetic,
424 source.as_str(),
425 )?
426 }
427 ModuleProvenance::TrustedHostDispatch => {
428 compile_trusted_host_dispatch_module_artifact_from_source(
429 &synthetic,
430 source.as_str(),
431 )?
432 }
433 ModuleProvenance::User | ModuleProvenance::PrivilegedWire => {
434 compile_module_artifact_from_source(&synthetic, source.as_str())?
435 }
436 };
437 if let Some(span) = &mut compile_span {
438 span.mark_compile_succeeded();
439 }
440 drop(compile_span);
441 let _load_span = self.module_load_span();
442 Arc::new(PreparedModuleArtifact::from_cached(compiled))
443 };
444
445 self.imported_paths.push(synthetic.clone());
446 let source_dir = use_prepared_generation
447 .then(|| synthetic.parent().map(Path::to_path_buf))
448 .flatten();
449 let loaded = Arc::new(self.instantiate_module(source_dir, &artifact).await?);
450 self.imported_paths.pop();
451 {
452 let _load_span = self.module_load_span();
453 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
454 }
455 self.record_module_loaded();
456 Ok(loaded)
457 }
458
459 fn add_builtin_reexports(module: &str, loaded: &mut LoadedModule) {
468 for name in harn_stdlib::builtin_reexports(module) {
469 if loaded.public_exports.contains_key(*name) {
473 continue;
474 }
475 loaded
476 .public_exports
477 .insert((*name).to_string(), DefKind::Function);
478 loaded.public_values.insert(
479 (*name).to_string(),
480 VmValue::BuiltinRef(arcstr::ArcStr::from(*name)),
481 );
482 }
483 }
484
485 async fn load_stdlib_module_from_source(
486 &mut self,
487 module: &str,
488 synthetic: PathBuf,
489 source: &'static str,
490 ) -> Result<Arc<LoadedModule>, VmError> {
491 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
492 return Ok(loaded);
493 }
494 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::from(source));
495
496 let artifact = stdlib_module_artifact(
497 module,
498 &synthetic,
499 source,
500 self.module_phase_recorder.as_ref(),
501 )?;
502 self.imported_paths.push(synthetic.clone());
503 let mut loaded = self.instantiate_stdlib_module(artifact.as_ref()).await?;
504 self.imported_paths.pop();
505 Self::add_builtin_reexports(module, &mut loaded);
506 let loaded = Arc::new(loaded);
507 {
508 let _load_span = self.module_load_span();
509 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
510 }
511 self.record_module_loaded();
512 Ok(loaded)
513 }
514
515 async fn instantiate_stdlib_module(
516 &mut self,
517 artifact: &PreparedModuleArtifact,
518 ) -> Result<LoadedModule, VmError> {
519 self.instantiate_module(None, artifact).await
520 }
521
522 async fn instantiate_module(
530 &mut self,
531 module_source_dir: Option<PathBuf>,
532 artifact: &PreparedModuleArtifact,
533 ) -> Result<LoadedModule, VmError> {
534 let caller_env = self.env.clone();
535 let old_source_dir = self.source_dir.clone();
536 self.env = VmEnv::new();
537 self.source_dir = module_source_dir.clone();
538
539 for import in &artifact.imports {
540 let projection = match &import.binding {
541 ModuleImportBinding::Wildcard => ImportProjection::BindCaller(None),
542 ModuleImportBinding::Selected(names) => ImportProjection::BindCaller(Some(names)),
543 ModuleImportBinding::Namespace { alias, demand } => {
544 let members = match demand {
545 harn_parser::NamespaceDemand::Whole => None,
546 harn_parser::NamespaceDemand::Members(members) => {
547 Some(members.iter().cloned().collect::<Vec<_>>())
548 }
549 };
550 self.execute_import_with_projection(
551 &import.path,
552 ImportProjection::BindNamespace(alias, members.as_deref()),
553 artifact.provenance,
554 )
555 .await?;
556 continue;
557 }
558 };
559 self.execute_import_with_projection(&import.path, projection, artifact.provenance)
560 .await?;
561 }
562
563 let _load_span = self.module_load_span();
566
567 let module_state: crate::value::ModuleState = {
568 let mut init_env = self.env.clone();
569 if !artifact.type_schema_init_chunks.is_empty() || artifact.init_chunk.is_some() {
570 let saved_env = std::mem::replace(&mut self.env, init_env);
571 let saved_frames = std::mem::take(&mut self.frames);
572 let saved_handlers = std::mem::take(&mut self.exception_handlers);
573 let saved_iterators = std::mem::take(&mut self.iterators);
574 let saved_deadlines = std::mem::take(&mut self.deadlines);
575 let active_context = crate::step_runtime::suspend_active_context();
586 let init_result: Result<(), VmError> = async {
587 for chunk in &artifact.type_schema_init_chunks {
588 self.run_chunk(Arc::clone(chunk)).await?;
589 }
590 if let Some(chunk) = &artifact.init_chunk {
591 self.run_chunk(Arc::clone(chunk)).await?;
592 }
593 Ok(())
594 }
595 .await;
596 drop(active_context);
597 init_env = std::mem::replace(&mut self.env, saved_env);
598 self.frames = saved_frames;
599 self.exception_handlers = saved_handlers;
600 self.iterators = saved_iterators;
601 self.deadlines = saved_deadlines;
602 init_result?;
603 }
604 Arc::new(crate::value::VmMutex::new(init_env))
605 };
606
607 let module_env = self.env.clone();
608 let registry: ModuleFunctionRegistry =
609 Arc::new(crate::value::VmMutex::new(BTreeMap::new()));
610 let mut functions: BTreeMap<String, Arc<VmClosure>> = BTreeMap::new();
611 let mut public_exports = artifact.public_exports.clone();
612 let mut public_values: BTreeMap<String, VmValue> = BTreeMap::new();
616 {
617 let state = module_state.lock();
618 for name in &artifact.public_value_names {
619 if let Some(value) = state.get(name) {
620 public_values.insert(name.clone(), value);
621 }
622 }
623 }
624 if artifact.provenance == crate::module_artifact::ModuleProvenance::PrivilegedWire {
625 for (name, value) in &public_values {
626 if !matches!(value, VmValue::Harness(_)) {
627 return Err(VmError::Runtime(format!(
628 "Privileged wire module export `{name}` produced {}; only a nominal Harness capability handle may cross the wire boundary",
629 value.type_name()
630 )));
631 }
632 }
633 }
634 let public_type_names = artifact.public_type_names.clone();
635 let mut public_type_schemas: BTreeMap<String, VmValue> = {
636 let state = module_state.lock();
637 public_type_names
638 .iter()
639 .filter_map(|name| state.get(name).map(|schema| (name.clone(), schema)))
640 .collect()
641 };
642
643 for (name, compiled) in &artifact.functions {
644 let closure = Arc::new(VmClosure {
645 func: Arc::clone(compiled),
646 env: module_env.clone(),
647 source_dir: module_source_dir.clone(),
648 module_functions: Some(Arc::downgrade(®istry)),
649 module_state: Some(Arc::downgrade(&module_state)),
650 retained_module_scope: None,
651 });
652 registry.lock().insert(name.clone(), Arc::clone(&closure));
653 self.env
654 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
655 module_state
656 .lock()
657 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
658 functions.insert(name.clone(), Arc::clone(&closure));
659 }
660
661 for import in artifact.imports.iter().filter(|import| import.is_pub) {
662 let cache_key = self.cache_key_for_import(&import.path)?;
663 let Some(loaded) = self.module_cache.get(&cache_key).cloned() else {
664 if self.imported_paths.contains(&cache_key) {
671 return Err(VmError::Runtime(format!(
672 "Re-export error: cannot `pub import` from '{}' because it forms an \
673 import cycle with this module (its public surface is still being \
674 built). Use a plain `import` here, or re-export from a module that is \
675 not part of the cycle.",
676 import.path
677 )));
678 }
679 return Err(VmError::Runtime(format!(
680 "Re-export error: imported module '{}' was not loaded",
681 import.path
682 )));
683 };
684 if let ModuleImportBinding::Namespace { alias, .. } = &import.binding {
687 if public_exports.contains_key(alias) || functions.contains_key(alias) {
688 return Err(VmError::Runtime(format!(
689 "Re-export collision: '{alias}' is defined here and also \
690 re-exported as a namespace from '{}'",
691 import.path
692 )));
693 }
694 let dict = build_namespace_dict(&import.path, &loaded, None)?;
697 public_values.insert(alias.clone(), dict);
698 public_exports.insert(alias.clone(), DefKind::Variable);
699 continue;
700 }
701 let selected_names = match &import.binding {
702 ModuleImportBinding::Selected(names) => Some(names.as_slice()),
703 ModuleImportBinding::Wildcard => None,
704 ModuleImportBinding::Namespace { .. } => unreachable!("handled above"),
705 };
706 let names_to_reexport = module_import_names(
707 &import.path,
708 &loaded,
709 selected_names,
710 ImportNameUse::Binding,
711 )?;
712 for name in names_to_reexport {
713 let Some(kind) = loaded.public_exports.get(&name).copied() else {
714 return Err(VmError::Runtime(format!(
715 "Re-export error: '{name}' is not exported by '{}'",
716 import.path
717 )));
718 };
719 let Some(closure) = loaded.functions.get(&name) else {
720 if let Some(value) = loaded.public_values.get(&name) {
724 public_values.insert(name.clone(), value.clone());
725 public_exports.insert(name, kind);
726 continue;
727 }
728 if let Some(schema) = loaded.public_type_schemas.get(&name) {
731 public_type_schemas.insert(name.clone(), schema.clone());
732 }
733 public_exports.insert(name, kind);
734 continue;
735 };
736 if let Some(existing) = functions.get(&name) {
737 if !Arc::ptr_eq(existing, closure) {
738 return Err(VmError::Runtime(format!(
739 "Re-export collision: '{name}' is defined here and also \
740 re-exported from '{}'",
741 import.path
742 )));
743 }
744 }
745 functions.insert(name.clone(), Arc::clone(closure));
746 public_exports.insert(name, kind);
747 }
748 }
749
750 self.env = caller_env;
751 self.source_dir = old_source_dir;
752
753 Ok(LoadedModule {
754 functions,
755 public_exports,
756 public_values,
757 public_type_schemas,
758 package_execution_guard: module_source_dir
759 .as_ref()
760 .and(self.package_execution_guard.clone()),
761 _module_functions: registry,
762 _module_state: module_state,
763 })
764 }
765
766 fn export_namespace_module(
767 &mut self,
768 module_path: &Path,
769 loaded: &LoadedModule,
770 alias: &str,
771 members: Option<&[String]>,
772 ) -> Result<(), VmError> {
773 let module_name = module_path.display().to_string();
774 if self.env.get(alias).is_some() {
775 return Err(VmError::Runtime(format!(
776 "Import collision: '{alias}' is already defined when importing {module_name}. \
777 Use a different namespace alias: import * as <name> from \"...\""
778 )));
779 }
780 let dict = build_namespace_dict(&module_name, loaded, members)?;
781 self.env.define(alias, dict, false)?;
782 Ok(())
783 }
784
785 fn export_loaded_module(
786 &mut self,
787 module_path: &Path,
788 loaded: &LoadedModule,
789 selected_names: Option<&[String]>,
790 ) -> Result<(), VmError> {
791 let module_name = module_path.display().to_string();
792 let export_names =
793 module_import_names(&module_name, loaded, selected_names, ImportNameUse::Binding)?;
794
795 for name in export_names {
796 if let Some(value) = loaded.public_values.get(&name) {
798 if self.env.get(&name).is_some() {
799 return Err(VmError::Runtime(format!(
800 "Import collision: '{name}' is already defined when importing \
801 {module_name}. Use selective imports to disambiguate: \
802 import {{ {name} }} from \"...\""
803 )));
804 }
805 self.env.define(&name, value.clone(), false)?;
806 continue;
807 }
808 if let Some(schema) = loaded.public_type_schemas.get(&name) {
812 self.env.define(&name, schema.clone(), false)?;
813 continue;
814 }
815 if loaded
816 .public_exports
817 .get(&name)
818 .is_some_and(|kind| !kind.has_runtime_value())
819 {
820 continue;
821 }
822 let Some(closure) = loaded.functions.get(&name) else {
823 return Err(VmError::Runtime(format!(
824 "Import error: '{name}' is not defined in {module_name}"
825 )));
826 };
827 if let Some(VmValue::Closure(_)) = self.env.get(&name) {
828 return Err(VmError::Runtime(format!(
829 "Import collision: '{name}' is already defined when importing {module_name}. \
830 Use selective imports to disambiguate: import {{ {name} }} from \"...\""
831 )));
832 }
833 self.env
834 .define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
835 }
836 Ok(())
837 }
838
839 pub(super) fn execute_import<'a>(
841 &'a mut self,
842 path: &'a str,
843 selected_names: Option<&'a [String]>,
844 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
845 self.execute_import_with_projection(
846 path,
847 ImportProjection::BindCaller(selected_names),
848 self.module_provenance,
849 )
850 }
851
852 pub(super) fn execute_namespace_import_bind<'a>(
854 &'a mut self,
855 path: &'a str,
856 alias: &'a str,
857 members: Option<&'a [String]>,
858 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
859 self.execute_import_with_projection(
860 path,
861 ImportProjection::BindNamespace(alias, members),
862 self.module_provenance,
863 )
864 }
865
866 fn materialize_import<'a>(
867 &'a mut self,
868 path: &'a str,
869 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
870 self.execute_import_with_projection(
871 path,
872 ImportProjection::MaterializeOnly,
873 self.module_provenance,
874 )
875 }
876
877 fn apply_import_projection(
878 &mut self,
879 module_path: &Path,
880 loaded: &LoadedModule,
881 projection: ImportProjection<'_>,
882 ) -> Result<(), VmError> {
883 match projection {
884 ImportProjection::BindCaller(selected_names) => {
885 self.export_loaded_module(module_path, loaded, selected_names)
886 }
887 ImportProjection::BindNamespace(alias, members) => {
888 self.export_namespace_module(module_path, loaded, alias, members)
889 }
890 ImportProjection::MaterializeOnly => Ok(()),
891 }
892 }
893
894 fn execute_import_with_projection<'a>(
895 &'a mut self,
896 path: &'a str,
897 projection: ImportProjection<'a>,
898 provenance: ModuleProvenance,
899 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
900 Box::pin(async move {
901 let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
902
903 let stdlib_module = path
904 .strip_prefix("std/")
905 .or_else(|| (path == "observability").then_some("observability"));
906 if let Some(module) = stdlib_module {
907 if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
908 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
909 if self.imported_paths.contains(&synthetic) {
910 return Ok(());
911 }
912 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
913 return self.apply_import_projection(&synthetic, &loaded, projection);
914 }
915 if let Some(repository) = &self.linked_program_repository {
916 let artifact = repository.get(&synthetic).ok_or_else(|| {
917 VmError::Runtime(format!(
918 "linked program is missing required module std/{module}"
919 ))
920 })?;
921 self.imported_paths.push(synthetic.clone());
922 let loaded = Arc::new(
923 self.instantiate_module(
924 synthetic.parent().map(Path::to_path_buf),
925 &artifact,
926 )
927 .await?,
928 );
929 self.imported_paths.pop();
930 Arc::make_mut(&mut self.module_cache)
931 .insert(synthetic.clone(), Arc::clone(&loaded));
932 self.record_module_loaded();
933 return self.apply_import_projection(&synthetic, &loaded, projection);
934 }
935 let loaded = self
936 .load_stdlib_module_from_source(module, synthetic.clone(), source)
937 .await?;
938 if !matches!(projection, ImportProjection::MaterializeOnly) {
939 let _load_span = self.module_load_span();
940 self.apply_import_projection(&synthetic, &loaded, projection)?;
941 }
942 return Ok(());
943 }
944 return Err(VmError::Runtime(format!(
945 "Unknown stdlib module: std/{module}"
946 )));
947 }
948
949 let base = self
950 .source_dir
951 .clone()
952 .unwrap_or_else(|| PathBuf::from("."));
953 let file_path = self.resolve_module_import_path(&base, path)?;
954 let verified_source = if let Some(guard) = &self.package_execution_guard {
955 let bytes = guard.verify_entry_source(&file_path).map_err(|error| {
956 VmError::Runtime(format!(
957 "installed package {} rejected: {error}",
958 projection.package_rejection_kind()
959 ))
960 })?;
961 Some(verified_package_source(bytes, &file_path)?)
962 } else {
963 None
964 };
965
966 let canonical = harn_modules::canonical_path(&file_path);
967 if self.imported_paths.contains(&canonical) {
968 match projection {
976 ImportProjection::BindCaller(selected_names) => {
977 if let Some(importer) = self.imported_paths.last().cloned() {
978 if importer != canonical {
979 self.deferred_cyclic_imports.push(DeferredCyclicImport {
980 importer,
981 target: canonical.clone(),
982 selected_names: selected_names.map(<[String]>::to_vec),
983 namespace_alias: None,
984 namespace_members: None,
985 });
986 }
987 }
988 }
989 ImportProjection::BindNamespace(alias, members) => {
990 if let Some(importer) = self.imported_paths.last().cloned() {
991 if importer != canonical {
992 self.deferred_cyclic_imports.push(DeferredCyclicImport {
993 importer,
994 target: canonical.clone(),
995 selected_names: None,
996 namespace_alias: Some(alias.to_string()),
997 namespace_members: members.map(<[String]>::to_vec),
998 });
999 }
1000 }
1001 }
1002 ImportProjection::MaterializeOnly => {}
1003 }
1004 return Ok(());
1005 }
1006 if let Some(loaded) = self.module_cache.get(&canonical).cloned() {
1007 if let Some(source) = &verified_source {
1008 let cached_source = self.source_cache.get(&canonical).map(Arc::as_ref);
1009 if cached_source != Some(source.as_str()) {
1010 return Err(VmError::Runtime(format!(
1011 "installed package {} rejected: cached module {} was not compiled from the verified package bytes",
1012 projection.package_rejection_kind(),
1013 canonical.display()
1014 )));
1015 }
1016 let active_guard = self
1017 .package_execution_guard
1018 .as_deref()
1019 .expect("verified package source requires an active guard");
1020 if loaded.package_execution_guard.as_deref() != Some(active_guard) {
1021 return Err(VmError::Runtime(format!(
1022 "installed package {} rejected: cached module {} was not instantiated under the active package execution guard",
1023 projection.package_rejection_kind(),
1024 canonical.display()
1025 )));
1026 }
1027 }
1028 return self.apply_import_projection(&canonical, &loaded, projection);
1029 }
1030 self.imported_paths.push(canonical.clone());
1031
1032 let closed = self
1038 .linked_program_repository
1039 .as_ref()
1040 .map(|repository| {
1041 repository
1042 .get(&canonical)
1043 .or_else(|| repository.get(&file_path))
1044 .ok_or_else(|| {
1045 VmError::Runtime(format!(
1046 "linked program is missing required module {}",
1047 file_path.display()
1048 ))
1049 })
1050 })
1051 .transpose()?;
1052
1053 let linked = (closed.is_none()
1054 && provenance == ModuleProvenance::User
1055 && verified_source.is_none())
1056 .then(|| {
1057 let (content_hash, compilation_context) = self
1058 .graph_link_table
1059 .as_ref()?
1060 .module_identity(canonical.as_path())?;
1061 let _load_span = self.module_load_span();
1062 self.linked_module_artifact(
1063 &file_path,
1064 &canonical,
1065 content_hash,
1066 &compilation_context,
1067 )
1068 })
1069 .flatten();
1070
1071 let artifact = if let Some(closed) = closed {
1072 closed
1073 } else if let Some(linked) = linked {
1074 linked
1075 } else {
1076 let source = {
1077 let _load_span = self.module_load_span();
1078 match verified_source {
1079 Some(source) => Arc::new(ModuleSource::from_text(source)),
1082 None => self
1083 .source_cache
1084 .get(&canonical)
1085 .cloned()
1086 .map(ModuleSource::from_text)
1087 .map(Arc::new)
1088 .map(Ok)
1089 .unwrap_or_else(|| module_source::read(&file_path))
1090 .map_err(|e| {
1091 VmError::Runtime(format!(
1096 "Import error: cannot read '{}' (resolved '{path}' relative to {}): {e}",
1097 file_path.display(),
1098 base.display()
1099 ))
1100 })?,
1101 }
1102 };
1103 {
1104 let source_is_cached = |path: &Path| {
1105 self.source_cache
1106 .get(path)
1107 .is_some_and(|cached| cached.as_ref() == source.as_str())
1108 };
1109 if !source_is_cached(&canonical) || !source_is_cached(&file_path) {
1110 let source_cache = Arc::make_mut(&mut self.source_cache);
1111 source_cache.insert(canonical.clone(), Arc::clone(source.text()));
1112 source_cache.insert(file_path.clone(), Arc::clone(source.text()));
1113 }
1114 }
1115
1116 match provenance {
1117 ModuleProvenance::TrustedHostDispatch => self.prepared_module_cache.prepare(
1118 &file_path,
1119 &canonical,
1120 &source,
1121 None,
1122 self.module_phase_recorder.as_ref(),
1123 ModuleProvenance::TrustedHostDispatch,
1124 &self.prepared_module_validation,
1125 )?,
1126 ModuleProvenance::User
1127 | ModuleProvenance::EmbeddedStdlib
1128 | ModuleProvenance::PrivilegedWire => self.prepared_module_cache.prepare(
1129 &file_path,
1130 &canonical,
1131 &source,
1132 None,
1133 self.module_phase_recorder.as_ref(),
1134 ModuleProvenance::User,
1135 &self.prepared_module_validation,
1136 )?,
1137 }
1138 };
1139
1140 let module_source_dir = file_path.parent().map(|p| p.to_path_buf());
1141 let loaded = Arc::new(
1142 self.instantiate_module(module_source_dir, artifact.as_ref())
1143 .await?,
1144 );
1145 self.imported_paths.pop();
1146 {
1147 let _load_span = self.module_load_span();
1148 Arc::make_mut(&mut self.module_cache)
1149 .insert(canonical.clone(), Arc::clone(&loaded));
1150 }
1151 self.record_module_loaded();
1152 if !matches!(projection, ImportProjection::MaterializeOnly) {
1153 let _load_span = self.module_load_span();
1154 self.apply_import_projection(&canonical, &loaded, projection)?;
1155 }
1156
1157 if self.imported_paths.is_empty() {
1161 let _load_span = self.module_load_span();
1162 self.flush_deferred_cyclic_imports()?;
1163 }
1164
1165 Ok(())
1166 })
1167 }
1168
1169 fn linked_module_artifact(
1181 &self,
1182 file_path: &Path,
1183 canonical: &Path,
1184 content_hash: [u8; 32],
1185 compilation_context: &crate::module_artifact::ModuleCompilationContext,
1186 ) -> Option<Arc<PreparedModuleArtifact>> {
1187 if !bytecode_cache::cache_enabled() {
1188 return None;
1189 }
1190 if let Some(prepared) = self.prepared_module_cache.get_with_context(
1191 canonical,
1192 content_hash,
1193 ModuleProvenance::User,
1194 compilation_context,
1195 ) {
1196 return Some(prepared);
1197 }
1198 let key = bytecode_cache::CacheKey::from_module_content_hash(
1202 content_hash,
1203 compilation_context,
1204 ModuleProvenance::User,
1205 );
1206 let artifact = bytecode_cache::load_module_for_key(file_path, key).artifact?;
1207 Some(self.prepared_module_cache.insert_with_context(
1208 canonical.to_path_buf(),
1209 content_hash,
1210 compilation_context,
1211 Arc::new(PreparedModuleArtifact::from_cached(artifact)),
1212 ))
1213 }
1214
1215 fn flush_deferred_cyclic_imports(&mut self) -> Result<(), VmError> {
1224 if self.deferred_cyclic_imports.is_empty() {
1225 return Ok(());
1226 }
1227 let deferred = std::mem::take(&mut self.deferred_cyclic_imports);
1228 let mut still_pending = Vec::new();
1229 for import in deferred {
1230 let (Some(importer), Some(target)) = (
1231 self.module_cache.get(&import.importer).cloned(),
1232 self.module_cache.get(&import.target).cloned(),
1233 ) else {
1234 still_pending.push(import);
1238 continue;
1239 };
1240
1241 let mut module_state = importer._module_state.lock();
1242 if let Some(alias) = &import.namespace_alias {
1243 if module_state.get(alias).is_none() {
1244 let dict = build_namespace_dict(
1245 &import.target.display().to_string(),
1246 &target,
1247 import.namespace_members.as_deref(),
1248 )?;
1249 module_state.define(alias, dict, false)?;
1250 }
1251 continue;
1252 }
1253
1254 let export_names = module_import_names(
1255 &import.target.display().to_string(),
1256 &target,
1257 import.selected_names.as_deref(),
1258 ImportNameUse::Binding,
1259 )?;
1260
1261 for name in export_names {
1262 if module_state.get(&name).is_some() {
1265 continue;
1266 }
1267 if let Some(closure) = target.functions.get(&name) {
1268 module_state.define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
1269 } else if let Some(value) = target.public_values.get(&name) {
1270 module_state.define(&name, value.clone(), false)?;
1272 } else if target
1273 .public_exports
1274 .get(&name)
1275 .is_some_and(|kind| !kind.has_runtime_value())
1276 {
1277 continue;
1279 } else {
1280 return Err(VmError::Runtime(format!(
1281 "Import error: '{name}' is not defined in {}",
1282 import.target.display()
1283 )));
1284 }
1285 }
1286 }
1287 self.deferred_cyclic_imports = still_pending;
1288 Ok(())
1289 }
1290
1291 fn cache_key_for_import(&self, path: &str) -> Result<PathBuf, VmError> {
1296 if let Some(module) = path
1297 .strip_prefix("std/")
1298 .or_else(|| (path == "observability").then_some("observability"))
1299 {
1300 return Ok(PathBuf::from(format!("<stdlib>/{module}.harn")));
1301 }
1302 let base = self
1303 .source_dir
1304 .clone()
1305 .unwrap_or_else(|| PathBuf::from("."));
1306 let file_path = self.resolve_module_import_path(&base, path)?;
1307 Ok(file_path.canonicalize().unwrap_or(file_path))
1308 }
1309
1310 async fn loaded_module_for_path(
1311 &mut self,
1312 path: &Path,
1313 ) -> Result<(PathBuf, Arc<LoadedModule>), VmError> {
1314 self.ensure_execution_available()?;
1315 let path_str = path.to_string_lossy().into_owned();
1316 self.materialize_import(&path_str).await?;
1317
1318 let mut file_path = if path.is_absolute() {
1319 path.to_path_buf()
1320 } else {
1321 self.source_dir
1322 .clone()
1323 .unwrap_or_else(|| PathBuf::from("."))
1324 .join(path)
1325 };
1326 if !file_path.exists() && file_path.extension().is_none() {
1327 file_path.set_extension("harn");
1328 }
1329
1330 let canonical = file_path
1331 .canonicalize()
1332 .unwrap_or_else(|_| file_path.clone());
1333 let loaded = self.module_cache.get(&canonical).cloned().ok_or_else(|| {
1334 VmError::Runtime(format!(
1335 "Import error: failed to cache loaded module '{}'",
1336 canonical.display()
1337 ))
1338 })?;
1339 Ok((canonical, loaded))
1340 }
1341
1342 pub async fn load_public_module_callable(
1344 &mut self,
1345 path: &Path,
1346 name: &str,
1347 ) -> Result<Arc<VmClosure>, VmError> {
1348 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1349 if !loaded.public_exports.contains_key(name) {
1350 let hint = if loaded.functions.contains_key(name) {
1351 "; it is defined there but not `pub`"
1352 } else {
1353 ""
1354 };
1355 return Err(VmError::Runtime(format!(
1356 "callable '{name}' is not exported by module '{}'{hint}",
1357 canonical.display()
1358 )));
1359 }
1360 loaded.functions.get(name).cloned().ok_or_else(|| {
1361 VmError::Runtime(format!(
1362 "Import error: exported callable '{name}' is missing from {}",
1363 canonical.display()
1364 ))
1365 })
1366 }
1367
1368 pub async fn load_module_exports(
1371 &mut self,
1372 path: &Path,
1373 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1374 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1375 exported_function_closures(&loaded, &canonical)
1376 }
1377
1378 pub async fn load_module_exports_from_source(
1381 &mut self,
1382 source_key: impl Into<PathBuf>,
1383 source: &str,
1384 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1385 self.ensure_execution_available()?;
1386 let synthetic = source_key.into();
1387 let loaded = self
1388 .load_module_from_source(synthetic.clone(), source)
1389 .await?;
1390 exported_function_closures(&loaded, &synthetic)
1391 }
1392
1393 pub async fn load_prepared_module_exports_from_source(
1398 &mut self,
1399 source_key: impl Into<PathBuf>,
1400 source: &str,
1401 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1402 self.ensure_execution_available()?;
1403 let synthetic = source_key.into();
1404 let loaded = self
1405 .load_module_from_source_with_preparation(synthetic.clone(), source, true)
1406 .await?;
1407 exported_function_closures(&loaded, &synthetic)
1408 }
1409
1410 pub async fn load_module_callable_from_source(
1415 &mut self,
1416 source_key: impl Into<PathBuf>,
1417 source: &str,
1418 name: &str,
1419 ) -> Result<Option<Arc<VmClosure>>, VmError> {
1420 self.ensure_execution_available()?;
1421 let synthetic = source_key.into();
1422 let loaded = self.load_module_from_source(synthetic, source).await?;
1423 Ok(loaded.functions.get(name).cloned())
1424 }
1425
1426 pub async fn load_module_exports_from_import(
1430 &mut self,
1431 import_path: &str,
1432 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1433 self.ensure_execution_available()?;
1434 self.materialize_import(import_path).await?;
1435
1436 if let Some(module) = import_path
1437 .strip_prefix("std/")
1438 .or_else(|| (import_path == "observability").then_some("observability"))
1439 {
1440 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
1441 let loaded = self.module_cache.get(&synthetic).cloned().ok_or_else(|| {
1442 VmError::Runtime(format!(
1443 "Import error: failed to cache loaded module '{}'",
1444 synthetic.display()
1445 ))
1446 })?;
1447 return exported_function_closures(&loaded, &synthetic);
1448 }
1449
1450 let base = self
1451 .source_dir
1452 .clone()
1453 .unwrap_or_else(|| PathBuf::from("."));
1454 let file_path = self.resolve_module_import_path(&base, import_path)?;
1455 self.load_module_exports(&file_path).await
1456 }
1457}
1458
1459#[cfg(test)]
1460#[path = "modules_tests.rs"]
1461mod tests;