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