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