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