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::compile_module_artifact_from_source;
12use crate::module_source::{self, ModuleSource};
13use crate::prepared_module::PreparedModuleArtifact;
14use crate::value::{ModuleFunctionRegistry, VmClosure, VmEnv, VmError, VmValue};
15
16use super::{ScopeSpan, Vm};
17
18static STDLIB_MODULE_ARTIFACT_CACHE: OnceLock<
19 Mutex<BTreeMap<String, Arc<PreparedModuleArtifact>>>,
20> = OnceLock::new();
21
22fn stdlib_module_artifact_cache() -> &'static Mutex<BTreeMap<String, Arc<PreparedModuleArtifact>>> {
23 STDLIB_MODULE_ARTIFACT_CACHE.get_or_init(|| Mutex::new(BTreeMap::new()))
24}
25
26fn verified_package_source(bytes: Vec<u8>, path: &Path) -> Result<String, VmError> {
27 String::from_utf8(bytes).map_err(|error| {
28 VmError::Runtime(format!(
29 "installed package source {} is not valid UTF-8: {error}",
30 path.display()
31 ))
32 })
33}
34
35fn exported_function_closures(
36 loaded: &LoadedModule,
37 display_path: &Path,
38) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
39 let mut exports = BTreeMap::new();
40 for name in loaded
41 .public_exports
42 .keys()
43 .filter(|name| loaded.functions.contains_key(*name))
44 {
45 let Some(closure) = loaded.functions.get(name) else {
46 return Err(VmError::Runtime(format!(
47 "Import error: exported function '{name}' is missing from {}",
48 display_path.display()
49 )));
50 };
51 exports.insert(name.clone(), Arc::clone(closure));
52 }
53 Ok(exports)
54}
55
56#[cfg(test)]
57fn reset_stdlib_module_artifact_cache() {
58 stdlib_module_artifact_cache().lock().unwrap().clear();
59}
60
61#[cfg(test)]
62fn stdlib_module_artifact_cache_ptr(module: &str, source: &str) -> Option<usize> {
63 let key = stdlib_artifact_cache_key(module, source);
64 stdlib_module_artifact_cache()
65 .lock()
66 .unwrap()
67 .get(&key)
68 .map(|artifact| Arc::as_ptr(artifact) as usize)
69}
70
71pub(crate) struct LoadedModule {
72 pub(crate) functions: BTreeMap<String, Arc<VmClosure>>,
73 pub(crate) public_exports: BTreeMap<String, DefKind>,
76 pub(crate) public_values: BTreeMap<String, VmValue>,
79 pub(crate) public_type_schemas: BTreeMap<String, VmValue>,
83 package_execution_guard: Option<Arc<harn_modules::package_execution::PackageExecutionGuard>>,
88 pub(crate) _module_functions: crate::value::ModuleFunctionRegistry,
89 pub(crate) _module_state: crate::value::ModuleState,
90}
91
92pub(crate) type ModuleCache = Arc<BTreeMap<PathBuf, Arc<LoadedModule>>>;
100
101#[derive(Clone, Debug)]
107pub(crate) struct DeferredCyclicImport {
108 pub(crate) importer: PathBuf,
110 pub(crate) target: PathBuf,
112 pub(crate) selected_names: Option<Vec<String>>,
114 pub(crate) namespace_alias: Option<String>,
116}
117
118#[derive(Clone, Copy)]
119enum ImportProjection<'a> {
120 BindCaller(Option<&'a [String]>),
121 BindNamespace(&'a str),
123 MaterializeOnly,
124}
125
126impl ImportProjection<'_> {
127 fn package_rejection_kind(self) -> &'static str {
128 match self {
129 Self::BindCaller(_) | Self::BindNamespace(_) => "import",
130 Self::MaterializeOnly => "execution",
131 }
132 }
133}
134
135fn module_import_names(
139 module_name: &str,
140 loaded: &LoadedModule,
141 selected_names: Option<&[String]>,
142) -> Result<Vec<String>, VmError> {
143 if let Some(names) = selected_names {
144 for name in names {
145 if !loaded.public_exports.contains_key(name) {
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 return Err(VmError::Runtime(format!(
152 "Import error: '{name}' is not exported by {module_name}{hint}"
153 )));
154 }
155 }
156 return Ok(names.to_vec());
157 }
158
159 Ok(loaded.public_exports.keys().cloned().collect())
160}
161
162fn build_namespace_dict(module_path: &str, loaded: &LoadedModule) -> VmValue {
168 let mut map = BTreeMap::new();
169 map.insert(
170 "_namespace".to_string(),
171 VmValue::String(arcstr::ArcStr::from(module_path)),
172 );
173 for (name, kind) in &loaded.public_exports {
174 if !kind.has_runtime_value() {
175 if let Some(schema) = loaded.public_type_schemas.get(name) {
177 map.insert(name.clone(), schema.clone());
178 }
179 continue;
180 }
181 if let Some(value) = loaded.public_values.get(name) {
182 map.insert(name.clone(), value.clone());
183 continue;
184 }
185 if let Some(schema) = loaded.public_type_schemas.get(name) {
186 map.insert(name.clone(), schema.clone());
187 continue;
188 }
189 if let Some(closure) = loaded.functions.get(name) {
190 map.insert(name.clone(), VmValue::Closure(Arc::clone(closure)));
191 }
192 }
193 VmValue::dict(map)
194}
195
196pub fn resolve_module_import_path(base: &Path, path: &str) -> PathBuf {
197 let synthetic_current_file = base.join("__harn_import_base__.harn");
198 if let Some(resolved) = harn_modules::resolve_import_path(&synthetic_current_file, path) {
199 return resolved;
200 }
201
202 let mut file_path = base.join(path);
203
204 if !file_path.exists() && file_path.extension().is_none() {
205 file_path.set_extension("harn");
206 }
207
208 file_path
209}
210
211fn stdlib_artifact_cache_key(module: &str, source: &str) -> String {
212 let mut hasher = std::collections::hash_map::DefaultHasher::new();
213 module.hash(&mut hasher);
214 source.hash(&mut hasher);
215 format!("{module}:{:016x}", hasher.finish())
216}
217
218fn stdlib_module_artifact(
219 module: &str,
220 synthetic: &Path,
221 source: &'static str,
222 recorder: Option<&super::ModulePhaseRecorder>,
223) -> Result<Arc<PreparedModuleArtifact>, VmError> {
224 let key = stdlib_artifact_cache_key(module, source);
225 {
226 let cache = stdlib_module_artifact_cache().lock().unwrap();
227 if let Some(cached) = cache.get(&key) {
228 return Ok(Arc::clone(cached));
229 }
230 }
231
232 let embedded = ModuleSource::from_text(source);
237 let lookup = {
238 let _load_span = recorder.map(super::ModulePhaseRecorder::load_span);
239 bytecode_cache::load_module(synthetic, &embedded)
240 };
241 let artifact = if let Some(artifact) = lookup.artifact {
242 artifact
243 } else {
244 let mut compile_span = recorder.map(super::ModulePhaseRecorder::compile_span);
245 let compiled = compile_module_artifact_from_source(synthetic, source)?;
246 if let Some(span) = &mut compile_span {
247 span.mark_compile_succeeded();
248 }
249 drop(compile_span);
250 if let Err(err) = bytecode_cache::store_module(&lookup.key, &compiled) {
251 if std::env::var_os("HARN_BYTECODE_CACHE_DEBUG").is_some() {
252 eprintln!("[harn] stdlib module cache write skipped for {module}: {err}");
253 }
254 }
255 compiled
256 };
257
258 let compiled = {
259 let _load_span = recorder.map(super::ModulePhaseRecorder::load_span);
260 Arc::new(PreparedModuleArtifact::from_cached(artifact))
261 };
262 let mut cache = stdlib_module_artifact_cache().lock().unwrap();
263 if let Some(cached) = cache.get(&key) {
264 return Ok(Arc::clone(cached));
265 }
266 cache.insert(key, Arc::clone(&compiled));
267 Ok(compiled)
268}
269
270pub(crate) fn prepare_stdlib_module_artifact(
271 path: &Path,
272 recorder: Option<&super::ModulePhaseRecorder>,
273) -> Result<(), VmError> {
274 let Some(module) = path.to_str().and_then(|path| path.strip_prefix("<std>/")) else {
275 return Ok(());
276 };
277 let Some(source) = crate::stdlib_modules::get_stdlib_source(module) else {
278 return Ok(());
279 };
280 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
281 stdlib_module_artifact(module, &synthetic, source, recorder).map(|_| ())
282}
283
284impl Vm {
285 fn resolve_module_import_path(&self, base: &Path, path: &str) -> Result<PathBuf, VmError> {
286 if let Some(guard) = &self.package_execution_guard {
287 let synthetic_current_file = base.join("__harn_import_base__.harn");
288 if let Some(resolved) =
289 harn_modules::resolve_import_path_with_guard(&synthetic_current_file, path, guard)
290 .map_err(|error| {
291 VmError::Runtime(format!("installed package import rejected: {error}"))
292 })?
293 {
294 return Ok(resolved);
295 }
296 let mut file_path = base.join(path);
297 if !file_path.exists() && file_path.extension().is_none() {
298 file_path.set_extension("harn");
299 }
300 return Ok(file_path);
301 }
302 Ok(resolve_module_import_path(base, path))
303 }
304
305 pub async fn resolve_callable(
309 &mut self,
310 callable: &crate::value::VmCallable,
311 ) -> Result<Arc<crate::value::VmClosure>, VmError> {
312 self.ensure_execution_available()?;
313 match callable {
314 crate::value::VmCallable::Eager(closure) => Ok(Arc::clone(closure)),
315 crate::value::VmCallable::Lazy(lazy) => {
316 let (cache_key, module_path) = self.lazy_callable_module_path(lazy);
317 let next_guard = lazy
318 .package_execution_guard_handle()
319 .or_else(|| self.package_execution_guard.clone());
320 if let Some(guard) = &next_guard {
321 guard.verify_entry_source(&module_path).map_err(|error| {
322 VmError::Runtime(format!("installed package execution rejected: {error}"))
323 })?;
324 }
325 let resolution = {
326 let mut modules = self.lazy_callable_modules.lock();
327 let slots = modules.entry(cache_key).or_default();
328 if let Some(slot) = slots.iter().find(|slot| slot.execution_guard == next_guard)
329 {
330 Arc::clone(&slot.resolution)
331 } else {
332 let resolution = Arc::new(tokio::sync::OnceCell::new());
333 slots.push(crate::vm::state::LazyCallableCacheSlot {
334 execution_guard: next_guard.clone(),
335 resolution: Arc::clone(&resolution),
336 });
337 resolution
338 }
339 };
340 let previous_package_execution_guard =
341 std::mem::replace(&mut self.package_execution_guard, next_guard);
342 let resolved = resolution
343 .get_or_try_init(|| async {
344 let exports = self.load_module_exports(&module_path).await?;
345 let exports = exports
346 .into_iter()
347 .map(|(name, closure)| (name, closure.retained_for_host_registry()))
348 .collect();
349 Ok::<_, VmError>(Arc::new(crate::vm::state::ResolvedLazyCallable {
354 exports,
355 retained_module_graph: Arc::clone(&self.module_cache),
356 }))
357 })
358 .await;
359 self.package_execution_guard = previous_package_execution_guard;
360 let resolved = resolved?;
361 resolved
362 .exports
363 .get(&lazy.function_name)
364 .cloned()
365 .ok_or_else(|| {
366 VmError::Runtime(format!(
367 "function '{}' is not exported by module '{}'",
368 lazy.function_name,
369 lazy.module_path.display()
370 ))
371 })
372 }
373 crate::value::VmCallable::Pipeline(_) => Err(VmError::TypeError(
374 "pipeline callable requires execute_callable".to_string(),
375 )),
376 }
377 }
378
379 pub async fn execute_callable(
380 &mut self,
381 callable: &crate::value::VmCallable,
382 args: &[crate::value::VmValue],
383 ) -> Result<crate::value::VmValue, VmError> {
384 let crate::value::VmCallable::Pipeline(pipeline) = callable else {
385 let closure = self.resolve_callable(callable).await?;
386 return self.call_closure_pub(&closure, args).await;
387 };
388
389 let (_, module_path) = self.lazy_module_path(&pipeline.module_path);
390 let next_guard = pipeline
391 .package_execution_guard_handle()
392 .or_else(|| self.package_execution_guard.clone());
393 let previous_package_execution_guard =
394 std::mem::replace(&mut self.package_execution_guard, next_guard);
395 let result = async {
396 let closure = self
397 .load_public_module_callable(&module_path, &pipeline.pipeline_name)
398 .await?;
399 self.call_closure_pub(&closure, args).await
400 }
401 .await;
402 self.package_execution_guard = previous_package_execution_guard;
403 result
404 }
405
406 fn lazy_callable_module_path(&self, lazy: &crate::value::LazyVmCallable) -> (PathBuf, PathBuf) {
407 self.lazy_module_path(&lazy.module_path)
408 }
409
410 fn lazy_module_path(&self, path: &std::path::Path) -> (PathBuf, PathBuf) {
411 let mut module_path = if path.is_absolute() {
412 path.to_path_buf()
413 } else {
414 self.source_dir
415 .clone()
416 .unwrap_or_else(|| PathBuf::from("."))
417 .join(path)
418 };
419 if !module_path.exists() && module_path.extension().is_none() {
420 module_path.set_extension("harn");
421 }
422 let cache_key = module_path
423 .canonicalize()
424 .unwrap_or_else(|_| module_path.clone());
425 (cache_key, module_path)
426 }
427
428 async fn load_module_from_source(
429 &mut self,
430 synthetic: PathBuf,
431 source: &str,
432 ) -> Result<Arc<LoadedModule>, VmError> {
433 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
434 return Ok(loaded);
435 }
436 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::from(source));
437
438 let mut compile_span = self.module_compile_span();
439 let compiled = compile_module_artifact_from_source(&synthetic, source)?;
440 if let Some(span) = &mut compile_span {
441 span.mark_compile_succeeded();
442 }
443 drop(compile_span);
444 let artifact = {
445 let _load_span = self.module_load_span();
446 PreparedModuleArtifact::from_cached(compiled)
447 };
448
449 self.imported_paths.push(synthetic.clone());
450 let loaded = Arc::new(self.instantiate_module(None, &artifact).await?);
451 self.imported_paths.pop();
452 {
453 let _load_span = self.module_load_span();
454 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
455 }
456 self.record_module_loaded();
457 Ok(loaded)
458 }
459
460 fn add_builtin_reexports(module: &str, loaded: &mut LoadedModule) {
469 for name in harn_stdlib::builtin_reexports(module) {
470 if loaded.public_exports.contains_key(*name) {
474 continue;
475 }
476 loaded
477 .public_exports
478 .insert((*name).to_string(), DefKind::Function);
479 loaded.public_values.insert(
480 (*name).to_string(),
481 VmValue::BuiltinRef(arcstr::ArcStr::from(*name)),
482 );
483 }
484 }
485
486 async fn load_stdlib_module_from_source(
487 &mut self,
488 module: &str,
489 synthetic: PathBuf,
490 source: &'static str,
491 ) -> Result<Arc<LoadedModule>, VmError> {
492 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
493 return Ok(loaded);
494 }
495 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::from(source));
496
497 let artifact = stdlib_module_artifact(
498 module,
499 &synthetic,
500 source,
501 self.module_phase_recorder.as_ref(),
502 )?;
503 self.imported_paths.push(synthetic.clone());
504 let mut loaded = self.instantiate_stdlib_module(artifact.as_ref()).await?;
505 self.imported_paths.pop();
506 Self::add_builtin_reexports(module, &mut loaded);
507 let loaded = Arc::new(loaded);
508 {
509 let _load_span = self.module_load_span();
510 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
511 }
512 self.record_module_loaded();
513 Ok(loaded)
514 }
515
516 async fn instantiate_stdlib_module(
517 &mut self,
518 artifact: &PreparedModuleArtifact,
519 ) -> Result<LoadedModule, VmError> {
520 self.instantiate_module(None, artifact).await
521 }
522
523 async fn instantiate_module(
531 &mut self,
532 module_source_dir: Option<PathBuf>,
533 artifact: &PreparedModuleArtifact,
534 ) -> Result<LoadedModule, VmError> {
535 let caller_env = self.env.clone();
536 let old_source_dir = self.source_dir.clone();
537 self.env = VmEnv::new();
538 self.source_dir = module_source_dir.clone();
539
540 for import in &artifact.imports {
541 if let Some(alias) = &import.namespace_alias {
542 self.execute_namespace_import_bind(&import.path, alias)
543 .await?;
544 } else {
545 self.execute_import(&import.path, import.selected_names.as_deref())
546 .await?;
547 }
548 }
549
550 let _load_span = self.module_load_span();
553
554 let module_state: crate::value::ModuleState = {
555 let mut init_env = self.env.clone();
556 if artifact.type_schema_init_chunk.is_some() || artifact.init_chunk.is_some() {
557 let saved_env = std::mem::replace(&mut self.env, init_env);
558 let saved_frames = std::mem::take(&mut self.frames);
559 let saved_handlers = std::mem::take(&mut self.exception_handlers);
560 let saved_iterators = std::mem::take(&mut self.iterators);
561 let saved_deadlines = std::mem::take(&mut self.deadlines);
562 let active_context = crate::step_runtime::suspend_active_context();
573 let init_result: Result<(), VmError> = async {
574 if let Some(chunk) = &artifact.type_schema_init_chunk {
575 self.run_chunk(Arc::clone(chunk)).await?;
576 }
577 if let Some(chunk) = &artifact.init_chunk {
578 self.run_chunk(Arc::clone(chunk)).await?;
579 }
580 Ok(())
581 }
582 .await;
583 drop(active_context);
584 init_env = std::mem::replace(&mut self.env, saved_env);
585 self.frames = saved_frames;
586 self.exception_handlers = saved_handlers;
587 self.iterators = saved_iterators;
588 self.deadlines = saved_deadlines;
589 init_result?;
590 }
591 Arc::new(crate::value::VmMutex::new(init_env))
592 };
593
594 let module_env = self.env.clone();
595 let registry: ModuleFunctionRegistry =
596 Arc::new(crate::value::VmMutex::new(BTreeMap::new()));
597 let mut functions: BTreeMap<String, Arc<VmClosure>> = BTreeMap::new();
598 let mut public_exports = artifact.public_exports.clone();
599 let mut public_values: BTreeMap<String, VmValue> = BTreeMap::new();
603 {
604 let state = module_state.lock();
605 for name in &artifact.public_value_names {
606 if let Some(value) = state.get(name) {
607 public_values.insert(name.clone(), value);
608 }
609 }
610 }
611 if artifact.provenance == crate::module_artifact::ModuleProvenance::PrivilegedWire {
612 for (name, value) in &public_values {
613 if !matches!(value, VmValue::Harness(_)) {
614 return Err(VmError::Runtime(format!(
615 "Privileged wire module export `{name}` produced {}; only a nominal Harness capability handle may cross the wire boundary",
616 value.type_name()
617 )));
618 }
619 }
620 }
621 let public_type_names = artifact.public_type_names.clone();
622 let mut public_type_schemas: BTreeMap<String, VmValue> = {
623 let state = module_state.lock();
624 public_type_names
625 .iter()
626 .filter_map(|name| state.get(name).map(|schema| (name.clone(), schema)))
627 .collect()
628 };
629
630 for (name, compiled) in &artifact.functions {
631 let closure = Arc::new(VmClosure {
632 func: Arc::clone(compiled),
633 env: module_env.clone(),
634 source_dir: module_source_dir.clone(),
635 module_functions: Some(Arc::downgrade(®istry)),
636 module_state: Some(Arc::downgrade(&module_state)),
637 retained_module_scope: None,
638 });
639 registry.lock().insert(name.clone(), Arc::clone(&closure));
640 self.env
641 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
642 module_state
643 .lock()
644 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
645 functions.insert(name.clone(), Arc::clone(&closure));
646 }
647
648 for import in artifact.imports.iter().filter(|import| import.is_pub) {
649 let cache_key = self.cache_key_for_import(&import.path)?;
650 let Some(loaded) = self.module_cache.get(&cache_key).cloned() else {
651 if self.imported_paths.contains(&cache_key) {
658 return Err(VmError::Runtime(format!(
659 "Re-export error: cannot `pub import` from '{}' because it forms an \
660 import cycle with this module (its public surface is still being \
661 built). Use a plain `import` here, or re-export from a module that is \
662 not part of the cycle.",
663 import.path
664 )));
665 }
666 return Err(VmError::Runtime(format!(
667 "Re-export error: imported module '{}' was not loaded",
668 import.path
669 )));
670 };
671 if let Some(alias) = &import.namespace_alias {
674 if public_exports.contains_key(alias) || functions.contains_key(alias) {
675 return Err(VmError::Runtime(format!(
676 "Re-export collision: '{alias}' is defined here and also \
677 re-exported as a namespace from '{}'",
678 import.path
679 )));
680 }
681 let dict = build_namespace_dict(&import.path, &loaded);
682 public_values.insert(alias.clone(), dict);
683 public_exports.insert(alias.clone(), DefKind::Variable);
684 continue;
685 }
686 let names_to_reexport =
687 module_import_names(&import.path, &loaded, import.selected_names.as_deref())?;
688 for name in names_to_reexport {
689 let Some(kind) = loaded.public_exports.get(&name).copied() else {
690 return Err(VmError::Runtime(format!(
691 "Re-export error: '{name}' is not exported by '{}'",
692 import.path
693 )));
694 };
695 let Some(closure) = loaded.functions.get(&name) else {
696 if let Some(value) = loaded.public_values.get(&name) {
700 public_values.insert(name.clone(), value.clone());
701 public_exports.insert(name, kind);
702 continue;
703 }
704 if let Some(schema) = loaded.public_type_schemas.get(&name) {
707 public_type_schemas.insert(name.clone(), schema.clone());
708 }
709 public_exports.insert(name, kind);
710 continue;
711 };
712 if let Some(existing) = functions.get(&name) {
713 if !Arc::ptr_eq(existing, closure) {
714 return Err(VmError::Runtime(format!(
715 "Re-export collision: '{name}' is defined here and also \
716 re-exported from '{}'",
717 import.path
718 )));
719 }
720 }
721 functions.insert(name.clone(), Arc::clone(closure));
722 public_exports.insert(name, kind);
723 }
724 }
725
726 self.env = caller_env;
727 self.source_dir = old_source_dir;
728
729 Ok(LoadedModule {
730 functions,
731 public_exports,
732 public_values,
733 public_type_schemas,
734 package_execution_guard: module_source_dir
735 .as_ref()
736 .and(self.package_execution_guard.clone()),
737 _module_functions: registry,
738 _module_state: module_state,
739 })
740 }
741
742 fn export_namespace_module(
743 &mut self,
744 module_path: &Path,
745 loaded: &LoadedModule,
746 alias: &str,
747 ) -> Result<(), VmError> {
748 let module_name = module_path.display().to_string();
749 if self.env.get(alias).is_some() {
750 return Err(VmError::Runtime(format!(
751 "Import collision: '{alias}' is already defined when importing {module_name}. \
752 Use a different namespace alias: import * as <name> from \"...\""
753 )));
754 }
755 let dict = build_namespace_dict(&module_name, loaded);
756 self.env.define(alias, dict, false)?;
757 Ok(())
758 }
759
760 fn export_loaded_module(
761 &mut self,
762 module_path: &Path,
763 loaded: &LoadedModule,
764 selected_names: Option<&[String]>,
765 ) -> Result<(), VmError> {
766 let module_name = module_path.display().to_string();
767 let export_names = module_import_names(&module_name, loaded, selected_names)?;
768
769 for name in export_names {
770 if let Some(value) = loaded.public_values.get(&name) {
772 if self.env.get(&name).is_some() {
773 return Err(VmError::Runtime(format!(
774 "Import collision: '{name}' is already defined when importing \
775 {module_name}. Use selective imports to disambiguate: \
776 import {{ {name} }} from \"...\""
777 )));
778 }
779 self.env.define(&name, value.clone(), false)?;
780 continue;
781 }
782 if let Some(schema) = loaded.public_type_schemas.get(&name) {
786 self.env.define(&name, schema.clone(), false)?;
787 continue;
788 }
789 if loaded
790 .public_exports
791 .get(&name)
792 .is_some_and(|kind| !kind.has_runtime_value())
793 {
794 continue;
795 }
796 let Some(closure) = loaded.functions.get(&name) else {
797 return Err(VmError::Runtime(format!(
798 "Import error: '{name}' is not defined in {module_name}"
799 )));
800 };
801 if let Some(VmValue::Closure(_)) = self.env.get(&name) {
802 return Err(VmError::Runtime(format!(
803 "Import collision: '{name}' is already defined when importing {module_name}. \
804 Use selective imports to disambiguate: import {{ {name} }} from \"...\""
805 )));
806 }
807 self.env
808 .define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
809 }
810 Ok(())
811 }
812
813 pub(super) fn execute_import<'a>(
815 &'a mut self,
816 path: &'a str,
817 selected_names: Option<&'a [String]>,
818 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
819 self.execute_import_with_projection(path, ImportProjection::BindCaller(selected_names))
820 }
821
822 pub(super) fn execute_namespace_import_bind<'a>(
824 &'a mut self,
825 path: &'a str,
826 alias: &'a str,
827 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
828 self.execute_import_with_projection(path, ImportProjection::BindNamespace(alias))
829 }
830
831 fn materialize_import<'a>(
832 &'a mut self,
833 path: &'a str,
834 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
835 self.execute_import_with_projection(path, ImportProjection::MaterializeOnly)
836 }
837
838 fn apply_import_projection(
839 &mut self,
840 module_path: &Path,
841 loaded: &LoadedModule,
842 projection: ImportProjection<'_>,
843 ) -> Result<(), VmError> {
844 match projection {
845 ImportProjection::BindCaller(selected_names) => {
846 self.export_loaded_module(module_path, loaded, selected_names)
847 }
848 ImportProjection::BindNamespace(alias) => {
849 self.export_namespace_module(module_path, loaded, alias)
850 }
851 ImportProjection::MaterializeOnly => Ok(()),
852 }
853 }
854
855 fn execute_import_with_projection<'a>(
856 &'a mut self,
857 path: &'a str,
858 projection: ImportProjection<'a>,
859 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
860 Box::pin(async move {
861 let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
862
863 let stdlib_module = path
864 .strip_prefix("std/")
865 .or_else(|| (path == "observability").then_some("observability"));
866 if let Some(module) = stdlib_module {
867 if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
868 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
869 if self.imported_paths.contains(&synthetic) {
870 return Ok(());
871 }
872 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
873 return self.apply_import_projection(&synthetic, &loaded, projection);
874 }
875 let loaded = self
876 .load_stdlib_module_from_source(module, synthetic.clone(), source)
877 .await?;
878 if !matches!(projection, ImportProjection::MaterializeOnly) {
879 let _load_span = self.module_load_span();
880 self.apply_import_projection(&synthetic, &loaded, projection)?;
881 }
882 return Ok(());
883 }
884 return Err(VmError::Runtime(format!(
885 "Unknown stdlib module: std/{module}"
886 )));
887 }
888
889 let base = self
890 .source_dir
891 .clone()
892 .unwrap_or_else(|| PathBuf::from("."));
893 let file_path = self.resolve_module_import_path(&base, path)?;
894 let verified_source = if let Some(guard) = &self.package_execution_guard {
895 let bytes = guard.verify_entry_source(&file_path).map_err(|error| {
896 VmError::Runtime(format!(
897 "installed package {} rejected: {error}",
898 projection.package_rejection_kind()
899 ))
900 })?;
901 Some(verified_package_source(bytes, &file_path)?)
902 } else {
903 None
904 };
905
906 let canonical = file_path
907 .canonicalize()
908 .unwrap_or_else(|_| file_path.clone());
909 if self.imported_paths.contains(&canonical) {
910 match projection {
918 ImportProjection::BindCaller(selected_names) => {
919 if let Some(importer) = self.imported_paths.last().cloned() {
920 if importer != canonical {
921 self.deferred_cyclic_imports.push(DeferredCyclicImport {
922 importer,
923 target: canonical.clone(),
924 selected_names: selected_names.map(<[String]>::to_vec),
925 namespace_alias: None,
926 });
927 }
928 }
929 }
930 ImportProjection::BindNamespace(alias) => {
931 if let Some(importer) = self.imported_paths.last().cloned() {
932 if importer != canonical {
933 self.deferred_cyclic_imports.push(DeferredCyclicImport {
934 importer,
935 target: canonical.clone(),
936 selected_names: None,
937 namespace_alias: Some(alias.to_string()),
938 });
939 }
940 }
941 }
942 ImportProjection::MaterializeOnly => {}
943 }
944 return Ok(());
945 }
946 if let Some(loaded) = self.module_cache.get(&canonical).cloned() {
947 if let Some(source) = &verified_source {
948 let cached_source = self.source_cache.get(&canonical).map(Arc::as_ref);
949 if cached_source != Some(source.as_str()) {
950 return Err(VmError::Runtime(format!(
951 "installed package {} rejected: cached module {} was not compiled from the verified package bytes",
952 projection.package_rejection_kind(),
953 canonical.display()
954 )));
955 }
956 let active_guard = self
957 .package_execution_guard
958 .as_deref()
959 .expect("verified package source requires an active guard");
960 if loaded.package_execution_guard.as_deref() != Some(active_guard) {
961 return Err(VmError::Runtime(format!(
962 "installed package {} rejected: cached module {} was not instantiated under the active package execution guard",
963 projection.package_rejection_kind(),
964 canonical.display()
965 )));
966 }
967 }
968 return self.apply_import_projection(&canonical, &loaded, projection);
969 }
970 self.imported_paths.push(canonical.clone());
971
972 let linked = verified_source
978 .is_none()
979 .then(|| {
980 let content_hash = self
981 .graph_link_table
982 .as_ref()?
983 .content_hash(canonical.as_path())?;
984 let _load_span = self.module_load_span();
985 self.linked_module_artifact(&file_path, &canonical, content_hash)
986 })
987 .flatten();
988
989 let artifact = if let Some(linked) = linked {
990 linked
991 } else {
992 let source = {
993 let _load_span = self.module_load_span();
994 match verified_source {
995 Some(source) => Arc::new(ModuleSource::from_text(source)),
998 None => module_source::read(&file_path).map_err(|e| {
999 VmError::Runtime(format!(
1004 "Import error: cannot read '{}' (resolved '{path}' relative to {}): {e}",
1005 file_path.display(),
1006 base.display()
1007 ))
1008 })?,
1009 }
1010 };
1011 {
1012 let source_cache = Arc::make_mut(&mut self.source_cache);
1013 source_cache.insert(canonical.clone(), Arc::clone(source.text()));
1014 source_cache.insert(file_path.clone(), Arc::clone(source.text()));
1015 }
1016
1017 self.prepared_module_cache.prepare(
1018 &file_path,
1019 &canonical,
1020 &source,
1021 None,
1022 self.module_phase_recorder.as_ref(),
1023 )?
1024 };
1025
1026 let module_source_dir = file_path.parent().map(|p| p.to_path_buf());
1027 let loaded = Arc::new(
1028 self.instantiate_module(module_source_dir, artifact.as_ref())
1029 .await?,
1030 );
1031 self.imported_paths.pop();
1032 {
1033 let _load_span = self.module_load_span();
1034 Arc::make_mut(&mut self.module_cache)
1035 .insert(canonical.clone(), Arc::clone(&loaded));
1036 }
1037 self.record_module_loaded();
1038 if !matches!(projection, ImportProjection::MaterializeOnly) {
1039 let _load_span = self.module_load_span();
1040 self.apply_import_projection(&canonical, &loaded, projection)?;
1041 }
1042
1043 if self.imported_paths.is_empty() {
1047 let _load_span = self.module_load_span();
1048 self.flush_deferred_cyclic_imports()?;
1049 }
1050
1051 Ok(())
1052 })
1053 }
1054
1055 fn linked_module_artifact(
1067 &self,
1068 file_path: &Path,
1069 canonical: &Path,
1070 content_hash: [u8; 32],
1071 ) -> Option<Arc<PreparedModuleArtifact>> {
1072 if !bytecode_cache::cache_enabled() {
1073 return None;
1074 }
1075 if let Some(prepared) = self.prepared_module_cache.get(canonical, content_hash) {
1076 return Some(prepared);
1077 }
1078 let key = bytecode_cache::CacheKey::from_module_content_hash(content_hash);
1079 let artifact = bytecode_cache::load_module_for_key(file_path, key).artifact?;
1080 Some(self.prepared_module_cache.insert(
1081 canonical.to_path_buf(),
1082 content_hash,
1083 Arc::new(PreparedModuleArtifact::from_cached(artifact)),
1084 ))
1085 }
1086
1087 fn flush_deferred_cyclic_imports(&mut self) -> Result<(), VmError> {
1096 if self.deferred_cyclic_imports.is_empty() {
1097 return Ok(());
1098 }
1099 let deferred = std::mem::take(&mut self.deferred_cyclic_imports);
1100 let mut still_pending = Vec::new();
1101 for import in deferred {
1102 let (Some(importer), Some(target)) = (
1103 self.module_cache.get(&import.importer).cloned(),
1104 self.module_cache.get(&import.target).cloned(),
1105 ) else {
1106 still_pending.push(import);
1110 continue;
1111 };
1112
1113 let mut module_state = importer._module_state.lock();
1114 if let Some(alias) = &import.namespace_alias {
1115 if module_state.get(alias).is_none() {
1116 let dict = build_namespace_dict(&import.target.display().to_string(), &target);
1117 module_state.define(alias, dict, false)?;
1118 }
1119 continue;
1120 }
1121
1122 let export_names = module_import_names(
1123 &import.target.display().to_string(),
1124 &target,
1125 import.selected_names.as_deref(),
1126 )?;
1127
1128 for name in export_names {
1129 if module_state.get(&name).is_some() {
1132 continue;
1133 }
1134 if let Some(closure) = target.functions.get(&name) {
1135 module_state.define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
1136 } else if let Some(value) = target.public_values.get(&name) {
1137 module_state.define(&name, value.clone(), false)?;
1139 } else if target
1140 .public_exports
1141 .get(&name)
1142 .is_some_and(|kind| !kind.has_runtime_value())
1143 {
1144 continue;
1146 } else {
1147 return Err(VmError::Runtime(format!(
1148 "Import error: '{name}' is not defined in {}",
1149 import.target.display()
1150 )));
1151 }
1152 }
1153 }
1154 self.deferred_cyclic_imports = still_pending;
1155 Ok(())
1156 }
1157
1158 fn cache_key_for_import(&self, path: &str) -> Result<PathBuf, VmError> {
1163 if let Some(module) = path
1164 .strip_prefix("std/")
1165 .or_else(|| (path == "observability").then_some("observability"))
1166 {
1167 return Ok(PathBuf::from(format!("<stdlib>/{module}.harn")));
1168 }
1169 let base = self
1170 .source_dir
1171 .clone()
1172 .unwrap_or_else(|| PathBuf::from("."));
1173 let file_path = self.resolve_module_import_path(&base, path)?;
1174 Ok(file_path.canonicalize().unwrap_or(file_path))
1175 }
1176
1177 async fn loaded_module_for_path(
1178 &mut self,
1179 path: &Path,
1180 ) -> Result<(PathBuf, Arc<LoadedModule>), VmError> {
1181 self.ensure_execution_available()?;
1182 let path_str = path.to_string_lossy().into_owned();
1183 self.materialize_import(&path_str).await?;
1184
1185 let mut file_path = if path.is_absolute() {
1186 path.to_path_buf()
1187 } else {
1188 self.source_dir
1189 .clone()
1190 .unwrap_or_else(|| PathBuf::from("."))
1191 .join(path)
1192 };
1193 if !file_path.exists() && file_path.extension().is_none() {
1194 file_path.set_extension("harn");
1195 }
1196
1197 let canonical = file_path
1198 .canonicalize()
1199 .unwrap_or_else(|_| file_path.clone());
1200 let loaded = self.module_cache.get(&canonical).cloned().ok_or_else(|| {
1201 VmError::Runtime(format!(
1202 "Import error: failed to cache loaded module '{}'",
1203 canonical.display()
1204 ))
1205 })?;
1206 Ok((canonical, loaded))
1207 }
1208
1209 pub async fn load_public_module_callable(
1211 &mut self,
1212 path: &Path,
1213 name: &str,
1214 ) -> Result<Arc<VmClosure>, VmError> {
1215 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1216 if !loaded.public_exports.contains_key(name) {
1217 let hint = if loaded.functions.contains_key(name) {
1218 "; it is defined there but not `pub`"
1219 } else {
1220 ""
1221 };
1222 return Err(VmError::Runtime(format!(
1223 "callable '{name}' is not exported by module '{}'{hint}",
1224 canonical.display()
1225 )));
1226 }
1227 loaded.functions.get(name).cloned().ok_or_else(|| {
1228 VmError::Runtime(format!(
1229 "Import error: exported callable '{name}' is missing from {}",
1230 canonical.display()
1231 ))
1232 })
1233 }
1234
1235 pub async fn load_module_exports(
1238 &mut self,
1239 path: &Path,
1240 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1241 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1242 exported_function_closures(&loaded, &canonical)
1243 }
1244
1245 pub async fn load_module_exports_from_source(
1248 &mut self,
1249 source_key: impl Into<PathBuf>,
1250 source: &str,
1251 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1252 self.ensure_execution_available()?;
1253 let synthetic = source_key.into();
1254 let loaded = self
1255 .load_module_from_source(synthetic.clone(), source)
1256 .await?;
1257 exported_function_closures(&loaded, &synthetic)
1258 }
1259
1260 pub async fn load_module_callable_from_source(
1265 &mut self,
1266 source_key: impl Into<PathBuf>,
1267 source: &str,
1268 name: &str,
1269 ) -> Result<Option<Arc<VmClosure>>, VmError> {
1270 self.ensure_execution_available()?;
1271 let synthetic = source_key.into();
1272 let loaded = self.load_module_from_source(synthetic, source).await?;
1273 Ok(loaded.functions.get(name).cloned())
1274 }
1275
1276 pub async fn load_module_exports_from_import(
1280 &mut self,
1281 import_path: &str,
1282 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1283 self.ensure_execution_available()?;
1284 self.materialize_import(import_path).await?;
1285
1286 if let Some(module) = import_path
1287 .strip_prefix("std/")
1288 .or_else(|| (import_path == "observability").then_some("observability"))
1289 {
1290 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
1291 let loaded = self.module_cache.get(&synthetic).cloned().ok_or_else(|| {
1292 VmError::Runtime(format!(
1293 "Import error: failed to cache loaded module '{}'",
1294 synthetic.display()
1295 ))
1296 })?;
1297 return exported_function_closures(&loaded, &synthetic);
1298 }
1299
1300 let base = self
1301 .source_dir
1302 .clone()
1303 .unwrap_or_else(|| PathBuf::from("."));
1304 let file_path = self.resolve_module_import_path(&base, import_path)?;
1305 self.load_module_exports(&file_path).await
1306 }
1307}
1308
1309#[cfg(test)]
1310#[path = "modules_tests.rs"]
1311mod tests;