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 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
394 return Ok(loaded);
395 }
396 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::from(source));
397
398 let mut compile_span = self.module_compile_span();
399 let compiled = match self.module_provenance {
400 ModuleProvenance::EmbeddedStdlib => {
401 compile_embedded_stdlib_module_artifact_from_source(&synthetic, source)?
402 }
403 ModuleProvenance::TrustedHostDispatch => {
404 compile_trusted_host_dispatch_module_artifact_from_source(&synthetic, source)?
405 }
406 ModuleProvenance::User | ModuleProvenance::PrivilegedWire => {
407 compile_module_artifact_from_source(&synthetic, source)?
408 }
409 };
410 if let Some(span) = &mut compile_span {
411 span.mark_compile_succeeded();
412 }
413 drop(compile_span);
414 let artifact = {
415 let _load_span = self.module_load_span();
416 PreparedModuleArtifact::from_cached(compiled)
417 };
418
419 self.imported_paths.push(synthetic.clone());
420 let loaded = Arc::new(self.instantiate_module(None, &artifact).await?);
421 self.imported_paths.pop();
422 {
423 let _load_span = self.module_load_span();
424 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
425 }
426 self.record_module_loaded();
427 Ok(loaded)
428 }
429
430 fn add_builtin_reexports(module: &str, loaded: &mut LoadedModule) {
439 for name in harn_stdlib::builtin_reexports(module) {
440 if loaded.public_exports.contains_key(*name) {
444 continue;
445 }
446 loaded
447 .public_exports
448 .insert((*name).to_string(), DefKind::Function);
449 loaded.public_values.insert(
450 (*name).to_string(),
451 VmValue::BuiltinRef(arcstr::ArcStr::from(*name)),
452 );
453 }
454 }
455
456 async fn load_stdlib_module_from_source(
457 &mut self,
458 module: &str,
459 synthetic: PathBuf,
460 source: &'static str,
461 ) -> Result<Arc<LoadedModule>, VmError> {
462 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
463 return Ok(loaded);
464 }
465 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::from(source));
466
467 let artifact = stdlib_module_artifact(
468 module,
469 &synthetic,
470 source,
471 self.module_phase_recorder.as_ref(),
472 )?;
473 self.imported_paths.push(synthetic.clone());
474 let mut loaded = self.instantiate_stdlib_module(artifact.as_ref()).await?;
475 self.imported_paths.pop();
476 Self::add_builtin_reexports(module, &mut loaded);
477 let loaded = Arc::new(loaded);
478 {
479 let _load_span = self.module_load_span();
480 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
481 }
482 self.record_module_loaded();
483 Ok(loaded)
484 }
485
486 async fn instantiate_stdlib_module(
487 &mut self,
488 artifact: &PreparedModuleArtifact,
489 ) -> Result<LoadedModule, VmError> {
490 self.instantiate_module(None, artifact).await
491 }
492
493 async fn instantiate_module(
501 &mut self,
502 module_source_dir: Option<PathBuf>,
503 artifact: &PreparedModuleArtifact,
504 ) -> Result<LoadedModule, VmError> {
505 let caller_env = self.env.clone();
506 let old_source_dir = self.source_dir.clone();
507 self.env = VmEnv::new();
508 self.source_dir = module_source_dir.clone();
509
510 for import in &artifact.imports {
511 let projection = match &import.binding {
512 ModuleImportBinding::Wildcard => ImportProjection::BindCaller(None),
513 ModuleImportBinding::Selected(names) => ImportProjection::BindCaller(Some(names)),
514 ModuleImportBinding::Namespace { alias, demand } => {
515 let members = match demand {
516 harn_parser::NamespaceDemand::Whole => None,
517 harn_parser::NamespaceDemand::Members(members) => {
518 Some(members.iter().cloned().collect::<Vec<_>>())
519 }
520 };
521 self.execute_import_with_projection(
522 &import.path,
523 ImportProjection::BindNamespace(alias, members.as_deref()),
524 artifact.provenance,
525 )
526 .await?;
527 continue;
528 }
529 };
530 self.execute_import_with_projection(&import.path, projection, artifact.provenance)
531 .await?;
532 }
533
534 let _load_span = self.module_load_span();
537
538 let module_state: crate::value::ModuleState = {
539 let mut init_env = self.env.clone();
540 if !artifact.type_schema_init_chunks.is_empty() || artifact.init_chunk.is_some() {
541 let saved_env = std::mem::replace(&mut self.env, init_env);
542 let saved_frames = std::mem::take(&mut self.frames);
543 let saved_handlers = std::mem::take(&mut self.exception_handlers);
544 let saved_iterators = std::mem::take(&mut self.iterators);
545 let saved_deadlines = std::mem::take(&mut self.deadlines);
546 let active_context = crate::step_runtime::suspend_active_context();
557 let init_result: Result<(), VmError> = async {
558 for chunk in &artifact.type_schema_init_chunks {
559 self.run_chunk(Arc::clone(chunk)).await?;
560 }
561 if let Some(chunk) = &artifact.init_chunk {
562 self.run_chunk(Arc::clone(chunk)).await?;
563 }
564 Ok(())
565 }
566 .await;
567 drop(active_context);
568 init_env = std::mem::replace(&mut self.env, saved_env);
569 self.frames = saved_frames;
570 self.exception_handlers = saved_handlers;
571 self.iterators = saved_iterators;
572 self.deadlines = saved_deadlines;
573 init_result?;
574 }
575 Arc::new(crate::value::VmMutex::new(init_env))
576 };
577
578 let module_env = self.env.clone();
579 let registry: ModuleFunctionRegistry =
580 Arc::new(crate::value::VmMutex::new(BTreeMap::new()));
581 let mut functions: BTreeMap<String, Arc<VmClosure>> = BTreeMap::new();
582 let mut public_exports = artifact.public_exports.clone();
583 let mut public_values: BTreeMap<String, VmValue> = BTreeMap::new();
587 {
588 let state = module_state.lock();
589 for name in &artifact.public_value_names {
590 if let Some(value) = state.get(name) {
591 public_values.insert(name.clone(), value);
592 }
593 }
594 }
595 if artifact.provenance == crate::module_artifact::ModuleProvenance::PrivilegedWire {
596 for (name, value) in &public_values {
597 if !matches!(value, VmValue::Harness(_)) {
598 return Err(VmError::Runtime(format!(
599 "Privileged wire module export `{name}` produced {}; only a nominal Harness capability handle may cross the wire boundary",
600 value.type_name()
601 )));
602 }
603 }
604 }
605 let public_type_names = artifact.public_type_names.clone();
606 let mut public_type_schemas: BTreeMap<String, VmValue> = {
607 let state = module_state.lock();
608 public_type_names
609 .iter()
610 .filter_map(|name| state.get(name).map(|schema| (name.clone(), schema)))
611 .collect()
612 };
613
614 for (name, compiled) in &artifact.functions {
615 let closure = Arc::new(VmClosure {
616 func: Arc::clone(compiled),
617 env: module_env.clone(),
618 source_dir: module_source_dir.clone(),
619 module_functions: Some(Arc::downgrade(®istry)),
620 module_state: Some(Arc::downgrade(&module_state)),
621 retained_module_scope: None,
622 });
623 registry.lock().insert(name.clone(), Arc::clone(&closure));
624 self.env
625 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
626 module_state
627 .lock()
628 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
629 functions.insert(name.clone(), Arc::clone(&closure));
630 }
631
632 for import in artifact.imports.iter().filter(|import| import.is_pub) {
633 let cache_key = self.cache_key_for_import(&import.path)?;
634 let Some(loaded) = self.module_cache.get(&cache_key).cloned() else {
635 if self.imported_paths.contains(&cache_key) {
642 return Err(VmError::Runtime(format!(
643 "Re-export error: cannot `pub import` from '{}' because it forms an \
644 import cycle with this module (its public surface is still being \
645 built). Use a plain `import` here, or re-export from a module that is \
646 not part of the cycle.",
647 import.path
648 )));
649 }
650 return Err(VmError::Runtime(format!(
651 "Re-export error: imported module '{}' was not loaded",
652 import.path
653 )));
654 };
655 if let ModuleImportBinding::Namespace { alias, .. } = &import.binding {
658 if public_exports.contains_key(alias) || functions.contains_key(alias) {
659 return Err(VmError::Runtime(format!(
660 "Re-export collision: '{alias}' is defined here and also \
661 re-exported as a namespace from '{}'",
662 import.path
663 )));
664 }
665 let dict = build_namespace_dict(&import.path, &loaded, None)?;
668 public_values.insert(alias.clone(), dict);
669 public_exports.insert(alias.clone(), DefKind::Variable);
670 continue;
671 }
672 let selected_names = match &import.binding {
673 ModuleImportBinding::Selected(names) => Some(names.as_slice()),
674 ModuleImportBinding::Wildcard => None,
675 ModuleImportBinding::Namespace { .. } => unreachable!("handled above"),
676 };
677 let names_to_reexport = module_import_names(
678 &import.path,
679 &loaded,
680 selected_names,
681 ImportNameUse::Binding,
682 )?;
683 for name in names_to_reexport {
684 let Some(kind) = loaded.public_exports.get(&name).copied() else {
685 return Err(VmError::Runtime(format!(
686 "Re-export error: '{name}' is not exported by '{}'",
687 import.path
688 )));
689 };
690 let Some(closure) = loaded.functions.get(&name) else {
691 if let Some(value) = loaded.public_values.get(&name) {
695 public_values.insert(name.clone(), value.clone());
696 public_exports.insert(name, kind);
697 continue;
698 }
699 if let Some(schema) = loaded.public_type_schemas.get(&name) {
702 public_type_schemas.insert(name.clone(), schema.clone());
703 }
704 public_exports.insert(name, kind);
705 continue;
706 };
707 if let Some(existing) = functions.get(&name) {
708 if !Arc::ptr_eq(existing, closure) {
709 return Err(VmError::Runtime(format!(
710 "Re-export collision: '{name}' is defined here and also \
711 re-exported from '{}'",
712 import.path
713 )));
714 }
715 }
716 functions.insert(name.clone(), Arc::clone(closure));
717 public_exports.insert(name, kind);
718 }
719 }
720
721 self.env = caller_env;
722 self.source_dir = old_source_dir;
723
724 Ok(LoadedModule {
725 functions,
726 public_exports,
727 public_values,
728 public_type_schemas,
729 package_execution_guard: module_source_dir
730 .as_ref()
731 .and(self.package_execution_guard.clone()),
732 _module_functions: registry,
733 _module_state: module_state,
734 })
735 }
736
737 fn export_namespace_module(
738 &mut self,
739 module_path: &Path,
740 loaded: &LoadedModule,
741 alias: &str,
742 members: Option<&[String]>,
743 ) -> Result<(), VmError> {
744 let module_name = module_path.display().to_string();
745 if self.env.get(alias).is_some() {
746 return Err(VmError::Runtime(format!(
747 "Import collision: '{alias}' is already defined when importing {module_name}. \
748 Use a different namespace alias: import * as <name> from \"...\""
749 )));
750 }
751 let dict = build_namespace_dict(&module_name, loaded, members)?;
752 self.env.define(alias, dict, false)?;
753 Ok(())
754 }
755
756 fn export_loaded_module(
757 &mut self,
758 module_path: &Path,
759 loaded: &LoadedModule,
760 selected_names: Option<&[String]>,
761 ) -> Result<(), VmError> {
762 let module_name = module_path.display().to_string();
763 let export_names =
764 module_import_names(&module_name, loaded, selected_names, ImportNameUse::Binding)?;
765
766 for name in export_names {
767 if let Some(value) = loaded.public_values.get(&name) {
769 if self.env.get(&name).is_some() {
770 return Err(VmError::Runtime(format!(
771 "Import collision: '{name}' is already defined when importing \
772 {module_name}. Use selective imports to disambiguate: \
773 import {{ {name} }} from \"...\""
774 )));
775 }
776 self.env.define(&name, value.clone(), false)?;
777 continue;
778 }
779 if let Some(schema) = loaded.public_type_schemas.get(&name) {
783 self.env.define(&name, schema.clone(), false)?;
784 continue;
785 }
786 if loaded
787 .public_exports
788 .get(&name)
789 .is_some_and(|kind| !kind.has_runtime_value())
790 {
791 continue;
792 }
793 let Some(closure) = loaded.functions.get(&name) else {
794 return Err(VmError::Runtime(format!(
795 "Import error: '{name}' is not defined in {module_name}"
796 )));
797 };
798 if let Some(VmValue::Closure(_)) = self.env.get(&name) {
799 return Err(VmError::Runtime(format!(
800 "Import collision: '{name}' is already defined when importing {module_name}. \
801 Use selective imports to disambiguate: import {{ {name} }} from \"...\""
802 )));
803 }
804 self.env
805 .define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
806 }
807 Ok(())
808 }
809
810 pub(super) fn execute_import<'a>(
812 &'a mut self,
813 path: &'a str,
814 selected_names: Option<&'a [String]>,
815 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
816 self.execute_import_with_projection(
817 path,
818 ImportProjection::BindCaller(selected_names),
819 self.module_provenance,
820 )
821 }
822
823 pub(super) fn execute_namespace_import_bind<'a>(
825 &'a mut self,
826 path: &'a str,
827 alias: &'a str,
828 members: Option<&'a [String]>,
829 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
830 self.execute_import_with_projection(
831 path,
832 ImportProjection::BindNamespace(alias, members),
833 self.module_provenance,
834 )
835 }
836
837 fn materialize_import<'a>(
838 &'a mut self,
839 path: &'a str,
840 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
841 self.execute_import_with_projection(
842 path,
843 ImportProjection::MaterializeOnly,
844 self.module_provenance,
845 )
846 }
847
848 fn apply_import_projection(
849 &mut self,
850 module_path: &Path,
851 loaded: &LoadedModule,
852 projection: ImportProjection<'_>,
853 ) -> Result<(), VmError> {
854 match projection {
855 ImportProjection::BindCaller(selected_names) => {
856 self.export_loaded_module(module_path, loaded, selected_names)
857 }
858 ImportProjection::BindNamespace(alias, members) => {
859 self.export_namespace_module(module_path, loaded, alias, members)
860 }
861 ImportProjection::MaterializeOnly => Ok(()),
862 }
863 }
864
865 fn execute_import_with_projection<'a>(
866 &'a mut self,
867 path: &'a str,
868 projection: ImportProjection<'a>,
869 provenance: ModuleProvenance,
870 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
871 Box::pin(async move {
872 let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
873
874 let stdlib_module = path
875 .strip_prefix("std/")
876 .or_else(|| (path == "observability").then_some("observability"));
877 if let Some(module) = stdlib_module {
878 if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
879 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
880 if self.imported_paths.contains(&synthetic) {
881 return Ok(());
882 }
883 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
884 return self.apply_import_projection(&synthetic, &loaded, projection);
885 }
886 if let Some(repository) = &self.linked_program_repository {
887 let artifact = repository.get(&synthetic).ok_or_else(|| {
888 VmError::Runtime(format!(
889 "linked program is missing required module std/{module}"
890 ))
891 })?;
892 self.imported_paths.push(synthetic.clone());
893 let loaded = Arc::new(
894 self.instantiate_module(
895 synthetic.parent().map(Path::to_path_buf),
896 &artifact,
897 )
898 .await?,
899 );
900 self.imported_paths.pop();
901 Arc::make_mut(&mut self.module_cache)
902 .insert(synthetic.clone(), Arc::clone(&loaded));
903 self.record_module_loaded();
904 return self.apply_import_projection(&synthetic, &loaded, projection);
905 }
906 let loaded = self
907 .load_stdlib_module_from_source(module, synthetic.clone(), source)
908 .await?;
909 if !matches!(projection, ImportProjection::MaterializeOnly) {
910 let _load_span = self.module_load_span();
911 self.apply_import_projection(&synthetic, &loaded, projection)?;
912 }
913 return Ok(());
914 }
915 return Err(VmError::Runtime(format!(
916 "Unknown stdlib module: std/{module}"
917 )));
918 }
919
920 let base = self
921 .source_dir
922 .clone()
923 .unwrap_or_else(|| PathBuf::from("."));
924 let file_path = self.resolve_module_import_path(&base, path)?;
925 let verified_source = if let Some(guard) = &self.package_execution_guard {
926 let bytes = guard.verify_entry_source(&file_path).map_err(|error| {
927 VmError::Runtime(format!(
928 "installed package {} rejected: {error}",
929 projection.package_rejection_kind()
930 ))
931 })?;
932 Some(verified_package_source(bytes, &file_path)?)
933 } else {
934 None
935 };
936
937 let canonical = file_path
938 .canonicalize()
939 .unwrap_or_else(|_| file_path.clone());
940 if self.imported_paths.contains(&canonical) {
941 match projection {
949 ImportProjection::BindCaller(selected_names) => {
950 if let Some(importer) = self.imported_paths.last().cloned() {
951 if importer != canonical {
952 self.deferred_cyclic_imports.push(DeferredCyclicImport {
953 importer,
954 target: canonical.clone(),
955 selected_names: selected_names.map(<[String]>::to_vec),
956 namespace_alias: None,
957 namespace_members: None,
958 });
959 }
960 }
961 }
962 ImportProjection::BindNamespace(alias, members) => {
963 if let Some(importer) = self.imported_paths.last().cloned() {
964 if importer != canonical {
965 self.deferred_cyclic_imports.push(DeferredCyclicImport {
966 importer,
967 target: canonical.clone(),
968 selected_names: None,
969 namespace_alias: Some(alias.to_string()),
970 namespace_members: members.map(<[String]>::to_vec),
971 });
972 }
973 }
974 }
975 ImportProjection::MaterializeOnly => {}
976 }
977 return Ok(());
978 }
979 if let Some(loaded) = self.module_cache.get(&canonical).cloned() {
980 if let Some(source) = &verified_source {
981 let cached_source = self.source_cache.get(&canonical).map(Arc::as_ref);
982 if cached_source != Some(source.as_str()) {
983 return Err(VmError::Runtime(format!(
984 "installed package {} rejected: cached module {} was not compiled from the verified package bytes",
985 projection.package_rejection_kind(),
986 canonical.display()
987 )));
988 }
989 let active_guard = self
990 .package_execution_guard
991 .as_deref()
992 .expect("verified package source requires an active guard");
993 if loaded.package_execution_guard.as_deref() != Some(active_guard) {
994 return Err(VmError::Runtime(format!(
995 "installed package {} rejected: cached module {} was not instantiated under the active package execution guard",
996 projection.package_rejection_kind(),
997 canonical.display()
998 )));
999 }
1000 }
1001 return self.apply_import_projection(&canonical, &loaded, projection);
1002 }
1003 self.imported_paths.push(canonical.clone());
1004
1005 let closed = self
1011 .linked_program_repository
1012 .as_ref()
1013 .map(|repository| {
1014 repository
1015 .get(&canonical)
1016 .or_else(|| repository.get(&file_path))
1017 .ok_or_else(|| {
1018 VmError::Runtime(format!(
1019 "linked program is missing required module {}",
1020 file_path.display()
1021 ))
1022 })
1023 })
1024 .transpose()?;
1025
1026 let linked = (closed.is_none()
1027 && provenance == ModuleProvenance::User
1028 && verified_source.is_none())
1029 .then(|| {
1030 let (content_hash, compilation_context) = self
1031 .graph_link_table
1032 .as_ref()?
1033 .module_identity(canonical.as_path())?;
1034 let _load_span = self.module_load_span();
1035 self.linked_module_artifact(
1036 &file_path,
1037 &canonical,
1038 content_hash,
1039 &compilation_context,
1040 )
1041 })
1042 .flatten();
1043
1044 let artifact = if let Some(closed) = closed {
1045 closed
1046 } else if let Some(linked) = linked {
1047 linked
1048 } else {
1049 let source = {
1050 let _load_span = self.module_load_span();
1051 match verified_source {
1052 Some(source) => Arc::new(ModuleSource::from_text(source)),
1055 None => module_source::read(&file_path).map_err(|e| {
1056 VmError::Runtime(format!(
1061 "Import error: cannot read '{}' (resolved '{path}' relative to {}): {e}",
1062 file_path.display(),
1063 base.display()
1064 ))
1065 })?,
1066 }
1067 };
1068 {
1069 let source_cache = Arc::make_mut(&mut self.source_cache);
1070 source_cache.insert(canonical.clone(), Arc::clone(source.text()));
1071 source_cache.insert(file_path.clone(), Arc::clone(source.text()));
1072 }
1073
1074 match provenance {
1075 ModuleProvenance::TrustedHostDispatch => self.prepared_module_cache.prepare(
1076 &file_path,
1077 &canonical,
1078 &source,
1079 None,
1080 self.module_phase_recorder.as_ref(),
1081 ModuleProvenance::TrustedHostDispatch,
1082 &self.prepared_module_validation,
1083 )?,
1084 ModuleProvenance::User
1085 | ModuleProvenance::EmbeddedStdlib
1086 | ModuleProvenance::PrivilegedWire => self.prepared_module_cache.prepare(
1087 &file_path,
1088 &canonical,
1089 &source,
1090 None,
1091 self.module_phase_recorder.as_ref(),
1092 ModuleProvenance::User,
1093 &self.prepared_module_validation,
1094 )?,
1095 }
1096 };
1097
1098 let module_source_dir = file_path.parent().map(|p| p.to_path_buf());
1099 let loaded = Arc::new(
1100 self.instantiate_module(module_source_dir, artifact.as_ref())
1101 .await?,
1102 );
1103 self.imported_paths.pop();
1104 {
1105 let _load_span = self.module_load_span();
1106 Arc::make_mut(&mut self.module_cache)
1107 .insert(canonical.clone(), Arc::clone(&loaded));
1108 }
1109 self.record_module_loaded();
1110 if !matches!(projection, ImportProjection::MaterializeOnly) {
1111 let _load_span = self.module_load_span();
1112 self.apply_import_projection(&canonical, &loaded, projection)?;
1113 }
1114
1115 if self.imported_paths.is_empty() {
1119 let _load_span = self.module_load_span();
1120 self.flush_deferred_cyclic_imports()?;
1121 }
1122
1123 Ok(())
1124 })
1125 }
1126
1127 fn linked_module_artifact(
1139 &self,
1140 file_path: &Path,
1141 canonical: &Path,
1142 content_hash: [u8; 32],
1143 compilation_context: &crate::module_artifact::ModuleCompilationContext,
1144 ) -> Option<Arc<PreparedModuleArtifact>> {
1145 if !bytecode_cache::cache_enabled() {
1146 return None;
1147 }
1148 if let Some(prepared) = self.prepared_module_cache.get_with_context(
1149 canonical,
1150 content_hash,
1151 ModuleProvenance::User,
1152 compilation_context,
1153 ) {
1154 return Some(prepared);
1155 }
1156 let key = bytecode_cache::CacheKey::from_module_content_hash(
1160 content_hash,
1161 compilation_context,
1162 ModuleProvenance::User,
1163 );
1164 let artifact = bytecode_cache::load_module_for_key(file_path, key).artifact?;
1165 Some(self.prepared_module_cache.insert_with_context(
1166 canonical.to_path_buf(),
1167 content_hash,
1168 compilation_context,
1169 Arc::new(PreparedModuleArtifact::from_cached(artifact)),
1170 ))
1171 }
1172
1173 fn flush_deferred_cyclic_imports(&mut self) -> Result<(), VmError> {
1182 if self.deferred_cyclic_imports.is_empty() {
1183 return Ok(());
1184 }
1185 let deferred = std::mem::take(&mut self.deferred_cyclic_imports);
1186 let mut still_pending = Vec::new();
1187 for import in deferred {
1188 let (Some(importer), Some(target)) = (
1189 self.module_cache.get(&import.importer).cloned(),
1190 self.module_cache.get(&import.target).cloned(),
1191 ) else {
1192 still_pending.push(import);
1196 continue;
1197 };
1198
1199 let mut module_state = importer._module_state.lock();
1200 if let Some(alias) = &import.namespace_alias {
1201 if module_state.get(alias).is_none() {
1202 let dict = build_namespace_dict(
1203 &import.target.display().to_string(),
1204 &target,
1205 import.namespace_members.as_deref(),
1206 )?;
1207 module_state.define(alias, dict, false)?;
1208 }
1209 continue;
1210 }
1211
1212 let export_names = module_import_names(
1213 &import.target.display().to_string(),
1214 &target,
1215 import.selected_names.as_deref(),
1216 ImportNameUse::Binding,
1217 )?;
1218
1219 for name in export_names {
1220 if module_state.get(&name).is_some() {
1223 continue;
1224 }
1225 if let Some(closure) = target.functions.get(&name) {
1226 module_state.define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
1227 } else if let Some(value) = target.public_values.get(&name) {
1228 module_state.define(&name, value.clone(), false)?;
1230 } else if target
1231 .public_exports
1232 .get(&name)
1233 .is_some_and(|kind| !kind.has_runtime_value())
1234 {
1235 continue;
1237 } else {
1238 return Err(VmError::Runtime(format!(
1239 "Import error: '{name}' is not defined in {}",
1240 import.target.display()
1241 )));
1242 }
1243 }
1244 }
1245 self.deferred_cyclic_imports = still_pending;
1246 Ok(())
1247 }
1248
1249 fn cache_key_for_import(&self, path: &str) -> Result<PathBuf, VmError> {
1254 if let Some(module) = path
1255 .strip_prefix("std/")
1256 .or_else(|| (path == "observability").then_some("observability"))
1257 {
1258 return Ok(PathBuf::from(format!("<stdlib>/{module}.harn")));
1259 }
1260 let base = self
1261 .source_dir
1262 .clone()
1263 .unwrap_or_else(|| PathBuf::from("."));
1264 let file_path = self.resolve_module_import_path(&base, path)?;
1265 Ok(file_path.canonicalize().unwrap_or(file_path))
1266 }
1267
1268 async fn loaded_module_for_path(
1269 &mut self,
1270 path: &Path,
1271 ) -> Result<(PathBuf, Arc<LoadedModule>), VmError> {
1272 self.ensure_execution_available()?;
1273 let path_str = path.to_string_lossy().into_owned();
1274 self.materialize_import(&path_str).await?;
1275
1276 let mut file_path = if path.is_absolute() {
1277 path.to_path_buf()
1278 } else {
1279 self.source_dir
1280 .clone()
1281 .unwrap_or_else(|| PathBuf::from("."))
1282 .join(path)
1283 };
1284 if !file_path.exists() && file_path.extension().is_none() {
1285 file_path.set_extension("harn");
1286 }
1287
1288 let canonical = file_path
1289 .canonicalize()
1290 .unwrap_or_else(|_| file_path.clone());
1291 let loaded = self.module_cache.get(&canonical).cloned().ok_or_else(|| {
1292 VmError::Runtime(format!(
1293 "Import error: failed to cache loaded module '{}'",
1294 canonical.display()
1295 ))
1296 })?;
1297 Ok((canonical, loaded))
1298 }
1299
1300 pub async fn load_public_module_callable(
1302 &mut self,
1303 path: &Path,
1304 name: &str,
1305 ) -> Result<Arc<VmClosure>, VmError> {
1306 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1307 if !loaded.public_exports.contains_key(name) {
1308 let hint = if loaded.functions.contains_key(name) {
1309 "; it is defined there but not `pub`"
1310 } else {
1311 ""
1312 };
1313 return Err(VmError::Runtime(format!(
1314 "callable '{name}' is not exported by module '{}'{hint}",
1315 canonical.display()
1316 )));
1317 }
1318 loaded.functions.get(name).cloned().ok_or_else(|| {
1319 VmError::Runtime(format!(
1320 "Import error: exported callable '{name}' is missing from {}",
1321 canonical.display()
1322 ))
1323 })
1324 }
1325
1326 pub async fn load_module_exports(
1329 &mut self,
1330 path: &Path,
1331 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1332 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1333 exported_function_closures(&loaded, &canonical)
1334 }
1335
1336 pub async fn load_module_exports_from_source(
1339 &mut self,
1340 source_key: impl Into<PathBuf>,
1341 source: &str,
1342 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1343 self.ensure_execution_available()?;
1344 let synthetic = source_key.into();
1345 let loaded = self
1346 .load_module_from_source(synthetic.clone(), source)
1347 .await?;
1348 exported_function_closures(&loaded, &synthetic)
1349 }
1350
1351 pub async fn load_module_callable_from_source(
1356 &mut self,
1357 source_key: impl Into<PathBuf>,
1358 source: &str,
1359 name: &str,
1360 ) -> Result<Option<Arc<VmClosure>>, VmError> {
1361 self.ensure_execution_available()?;
1362 let synthetic = source_key.into();
1363 let loaded = self.load_module_from_source(synthetic, source).await?;
1364 Ok(loaded.functions.get(name).cloned())
1365 }
1366
1367 pub async fn load_module_exports_from_import(
1371 &mut self,
1372 import_path: &str,
1373 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1374 self.ensure_execution_available()?;
1375 self.materialize_import(import_path).await?;
1376
1377 if let Some(module) = import_path
1378 .strip_prefix("std/")
1379 .or_else(|| (import_path == "observability").then_some("observability"))
1380 {
1381 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
1382 let loaded = self.module_cache.get(&synthetic).cloned().ok_or_else(|| {
1383 VmError::Runtime(format!(
1384 "Import error: failed to cache loaded module '{}'",
1385 synthetic.display()
1386 ))
1387 })?;
1388 return exported_function_closures(&loaded, &synthetic);
1389 }
1390
1391 let base = self
1392 .source_dir
1393 .clone()
1394 .unwrap_or_else(|| PathBuf::from("."));
1395 let file_path = self.resolve_module_import_path(&base, import_path)?;
1396 self.load_module_exports(&file_path).await
1397 }
1398}
1399
1400#[cfg(test)]
1401#[path = "modules_tests.rs"]
1402mod tests;