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
270impl Vm {
271 fn resolve_module_import_path(&self, base: &Path, path: &str) -> Result<PathBuf, VmError> {
272 if let Some(guard) = &self.package_execution_guard {
273 let synthetic_current_file = base.join("__harn_import_base__.harn");
274 if let Some(resolved) =
275 harn_modules::resolve_import_path_with_guard(&synthetic_current_file, path, guard)
276 .map_err(|error| {
277 VmError::Runtime(format!("installed package import rejected: {error}"))
278 })?
279 {
280 return Ok(resolved);
281 }
282 let mut file_path = base.join(path);
283 if !file_path.exists() && file_path.extension().is_none() {
284 file_path.set_extension("harn");
285 }
286 return Ok(file_path);
287 }
288 Ok(resolve_module_import_path(base, path))
289 }
290
291 pub async fn resolve_callable(
295 &mut self,
296 callable: &crate::value::VmCallable,
297 ) -> Result<Arc<crate::value::VmClosure>, VmError> {
298 self.ensure_execution_available()?;
299 match callable {
300 crate::value::VmCallable::Eager(closure) => Ok(Arc::clone(closure)),
301 crate::value::VmCallable::Lazy(lazy) => {
302 let (cache_key, module_path) = self.lazy_callable_module_path(lazy);
303 let next_guard = lazy
304 .package_execution_guard_handle()
305 .or_else(|| self.package_execution_guard.clone());
306 if let Some(guard) = &next_guard {
307 guard.verify_entry_source(&module_path).map_err(|error| {
308 VmError::Runtime(format!("installed package execution rejected: {error}"))
309 })?;
310 }
311 let resolution = {
312 let mut modules = self.lazy_callable_modules.lock();
313 let slots = modules.entry(cache_key).or_default();
314 if let Some(slot) = slots.iter().find(|slot| slot.execution_guard == next_guard)
315 {
316 Arc::clone(&slot.resolution)
317 } else {
318 let resolution = Arc::new(tokio::sync::OnceCell::new());
319 slots.push(crate::vm::state::LazyCallableCacheSlot {
320 execution_guard: next_guard.clone(),
321 resolution: Arc::clone(&resolution),
322 });
323 resolution
324 }
325 };
326 let previous_package_execution_guard =
327 std::mem::replace(&mut self.package_execution_guard, next_guard);
328 let resolved = resolution
329 .get_or_try_init(|| async {
330 let exports = self.load_module_exports(&module_path).await?;
331 let exports = exports
332 .into_iter()
333 .map(|(name, closure)| (name, closure.retained_for_host_registry()))
334 .collect();
335 Ok::<_, VmError>(Arc::new(crate::vm::state::ResolvedLazyCallable {
340 exports,
341 retained_module_graph: Arc::clone(&self.module_cache),
342 }))
343 })
344 .await;
345 self.package_execution_guard = previous_package_execution_guard;
346 let resolved = resolved?;
347 resolved
348 .exports
349 .get(&lazy.function_name)
350 .cloned()
351 .ok_or_else(|| {
352 VmError::Runtime(format!(
353 "function '{}' is not exported by module '{}'",
354 lazy.function_name,
355 lazy.module_path.display()
356 ))
357 })
358 }
359 crate::value::VmCallable::Pipeline(_) => Err(VmError::TypeError(
360 "pipeline callable requires execute_callable".to_string(),
361 )),
362 }
363 }
364
365 pub async fn execute_callable(
366 &mut self,
367 callable: &crate::value::VmCallable,
368 args: &[crate::value::VmValue],
369 ) -> Result<crate::value::VmValue, VmError> {
370 let crate::value::VmCallable::Pipeline(pipeline) = callable else {
371 let closure = self.resolve_callable(callable).await?;
372 return self.call_closure_pub(&closure, args).await;
373 };
374
375 let (_, module_path) = self.lazy_module_path(&pipeline.module_path);
376 let next_guard = pipeline
377 .package_execution_guard_handle()
378 .or_else(|| self.package_execution_guard.clone());
379 let previous_package_execution_guard =
380 std::mem::replace(&mut self.package_execution_guard, next_guard);
381 let result = async {
382 let closure = self
383 .load_public_module_callable(&module_path, &pipeline.pipeline_name)
384 .await?;
385 self.call_closure_pub(&closure, args).await
386 }
387 .await;
388 self.package_execution_guard = previous_package_execution_guard;
389 result
390 }
391
392 fn lazy_callable_module_path(&self, lazy: &crate::value::LazyVmCallable) -> (PathBuf, PathBuf) {
393 self.lazy_module_path(&lazy.module_path)
394 }
395
396 fn lazy_module_path(&self, path: &std::path::Path) -> (PathBuf, PathBuf) {
397 let mut module_path = if path.is_absolute() {
398 path.to_path_buf()
399 } else {
400 self.source_dir
401 .clone()
402 .unwrap_or_else(|| PathBuf::from("."))
403 .join(path)
404 };
405 if !module_path.exists() && module_path.extension().is_none() {
406 module_path.set_extension("harn");
407 }
408 let cache_key = module_path
409 .canonicalize()
410 .unwrap_or_else(|_| module_path.clone());
411 (cache_key, module_path)
412 }
413
414 async fn load_module_from_source(
415 &mut self,
416 synthetic: PathBuf,
417 source: &str,
418 ) -> Result<Arc<LoadedModule>, VmError> {
419 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
420 return Ok(loaded);
421 }
422 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::from(source));
423
424 let mut compile_span = self.module_compile_span();
425 let compiled = compile_module_artifact_from_source(&synthetic, source)?;
426 if let Some(span) = &mut compile_span {
427 span.mark_compile_succeeded();
428 }
429 drop(compile_span);
430 let artifact = {
431 let _load_span = self.module_load_span();
432 PreparedModuleArtifact::from_cached(compiled)
433 };
434
435 self.imported_paths.push(synthetic.clone());
436 let loaded = Arc::new(self.instantiate_module(None, &artifact).await?);
437 self.imported_paths.pop();
438 {
439 let _load_span = self.module_load_span();
440 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
441 }
442 self.record_module_loaded();
443 Ok(loaded)
444 }
445
446 fn add_builtin_reexports(module: &str, loaded: &mut LoadedModule) {
455 for name in harn_stdlib::builtin_reexports(module) {
456 if loaded.public_exports.contains_key(*name) {
460 continue;
461 }
462 loaded
463 .public_exports
464 .insert((*name).to_string(), DefKind::Function);
465 loaded.public_values.insert(
466 (*name).to_string(),
467 VmValue::BuiltinRef(arcstr::ArcStr::from(*name)),
468 );
469 }
470 }
471
472 async fn load_stdlib_module_from_source(
473 &mut self,
474 module: &str,
475 synthetic: PathBuf,
476 source: &'static str,
477 ) -> Result<Arc<LoadedModule>, VmError> {
478 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
479 return Ok(loaded);
480 }
481 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), Arc::from(source));
482
483 let artifact = stdlib_module_artifact(
484 module,
485 &synthetic,
486 source,
487 self.module_phase_recorder.as_ref(),
488 )?;
489 self.imported_paths.push(synthetic.clone());
490 let mut loaded = self.instantiate_stdlib_module(artifact.as_ref()).await?;
491 self.imported_paths.pop();
492 Self::add_builtin_reexports(module, &mut loaded);
493 let loaded = Arc::new(loaded);
494 {
495 let _load_span = self.module_load_span();
496 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
497 }
498 self.record_module_loaded();
499 Ok(loaded)
500 }
501
502 async fn instantiate_stdlib_module(
503 &mut self,
504 artifact: &PreparedModuleArtifact,
505 ) -> Result<LoadedModule, VmError> {
506 self.instantiate_module(None, artifact).await
507 }
508
509 async fn instantiate_module(
517 &mut self,
518 module_source_dir: Option<PathBuf>,
519 artifact: &PreparedModuleArtifact,
520 ) -> Result<LoadedModule, VmError> {
521 let caller_env = self.env.clone();
522 let old_source_dir = self.source_dir.clone();
523 self.env = VmEnv::new();
524 self.source_dir = module_source_dir.clone();
525
526 for import in &artifact.imports {
527 if let Some(alias) = &import.namespace_alias {
528 self.execute_namespace_import_bind(&import.path, alias)
529 .await?;
530 } else {
531 self.execute_import(&import.path, import.selected_names.as_deref())
532 .await?;
533 }
534 }
535
536 let _load_span = self.module_load_span();
539
540 let module_state: crate::value::ModuleState = {
541 let mut init_env = self.env.clone();
542 if artifact.type_schema_init_chunk.is_some() || artifact.init_chunk.is_some() {
543 let saved_env = std::mem::replace(&mut self.env, init_env);
544 let saved_frames = std::mem::take(&mut self.frames);
545 let saved_handlers = std::mem::take(&mut self.exception_handlers);
546 let saved_iterators = std::mem::take(&mut self.iterators);
547 let saved_deadlines = std::mem::take(&mut self.deadlines);
548 let active_context = crate::step_runtime::suspend_active_context();
559 let init_result: Result<(), VmError> = async {
560 if let Some(chunk) = &artifact.type_schema_init_chunk {
561 self.run_chunk(Arc::clone(chunk)).await?;
562 }
563 if let Some(chunk) = &artifact.init_chunk {
564 self.run_chunk(Arc::clone(chunk)).await?;
565 }
566 Ok(())
567 }
568 .await;
569 drop(active_context);
570 init_env = std::mem::replace(&mut self.env, saved_env);
571 self.frames = saved_frames;
572 self.exception_handlers = saved_handlers;
573 self.iterators = saved_iterators;
574 self.deadlines = saved_deadlines;
575 init_result?;
576 }
577 Arc::new(crate::value::VmMutex::new(init_env))
578 };
579
580 let module_env = self.env.clone();
581 let registry: ModuleFunctionRegistry =
582 Arc::new(crate::value::VmMutex::new(BTreeMap::new()));
583 let mut functions: BTreeMap<String, Arc<VmClosure>> = BTreeMap::new();
584 let mut public_exports = artifact.public_exports.clone();
585 let mut public_values: BTreeMap<String, VmValue> = BTreeMap::new();
589 {
590 let state = module_state.lock();
591 for name in &artifact.public_value_names {
592 if let Some(value) = state.get(name) {
593 public_values.insert(name.clone(), value);
594 }
595 }
596 }
597 let public_type_names = artifact.public_type_names.clone();
598 let mut public_type_schemas: BTreeMap<String, VmValue> = {
599 let state = module_state.lock();
600 public_type_names
601 .iter()
602 .filter_map(|name| state.get(name).map(|schema| (name.clone(), schema)))
603 .collect()
604 };
605
606 for (name, compiled) in &artifact.functions {
607 let closure = Arc::new(VmClosure {
608 func: Arc::clone(compiled),
609 env: module_env.clone(),
610 source_dir: module_source_dir.clone(),
611 module_functions: Some(Arc::downgrade(®istry)),
612 module_state: Some(Arc::downgrade(&module_state)),
613 retained_module_scope: None,
614 });
615 registry.lock().insert(name.clone(), Arc::clone(&closure));
616 self.env
617 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
618 module_state
619 .lock()
620 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
621 functions.insert(name.clone(), Arc::clone(&closure));
622 }
623
624 for import in artifact.imports.iter().filter(|import| import.is_pub) {
625 let cache_key = self.cache_key_for_import(&import.path)?;
626 let Some(loaded) = self.module_cache.get(&cache_key).cloned() else {
627 if self.imported_paths.contains(&cache_key) {
634 return Err(VmError::Runtime(format!(
635 "Re-export error: cannot `pub import` from '{}' because it forms an \
636 import cycle with this module (its public surface is still being \
637 built). Use a plain `import` here, or re-export from a module that is \
638 not part of the cycle.",
639 import.path
640 )));
641 }
642 return Err(VmError::Runtime(format!(
643 "Re-export error: imported module '{}' was not loaded",
644 import.path
645 )));
646 };
647 if let Some(alias) = &import.namespace_alias {
650 if public_exports.contains_key(alias) || functions.contains_key(alias) {
651 return Err(VmError::Runtime(format!(
652 "Re-export collision: '{alias}' is defined here and also \
653 re-exported as a namespace from '{}'",
654 import.path
655 )));
656 }
657 let dict = build_namespace_dict(&import.path, &loaded);
658 public_values.insert(alias.clone(), dict);
659 public_exports.insert(alias.clone(), DefKind::Variable);
660 continue;
661 }
662 let names_to_reexport =
663 module_import_names(&import.path, &loaded, import.selected_names.as_deref())?;
664 for name in names_to_reexport {
665 let Some(kind) = loaded.public_exports.get(&name).copied() else {
666 return Err(VmError::Runtime(format!(
667 "Re-export error: '{name}' is not exported by '{}'",
668 import.path
669 )));
670 };
671 let Some(closure) = loaded.functions.get(&name) else {
672 if let Some(value) = loaded.public_values.get(&name) {
676 public_values.insert(name.clone(), value.clone());
677 public_exports.insert(name, kind);
678 continue;
679 }
680 if let Some(schema) = loaded.public_type_schemas.get(&name) {
683 public_type_schemas.insert(name.clone(), schema.clone());
684 }
685 public_exports.insert(name, kind);
686 continue;
687 };
688 if let Some(existing) = functions.get(&name) {
689 if !Arc::ptr_eq(existing, closure) {
690 return Err(VmError::Runtime(format!(
691 "Re-export collision: '{name}' is defined here and also \
692 re-exported from '{}'",
693 import.path
694 )));
695 }
696 }
697 functions.insert(name.clone(), Arc::clone(closure));
698 public_exports.insert(name, kind);
699 }
700 }
701
702 self.env = caller_env;
703 self.source_dir = old_source_dir;
704
705 Ok(LoadedModule {
706 functions,
707 public_exports,
708 public_values,
709 public_type_schemas,
710 package_execution_guard: module_source_dir
711 .as_ref()
712 .and(self.package_execution_guard.clone()),
713 _module_functions: registry,
714 _module_state: module_state,
715 })
716 }
717
718 fn export_namespace_module(
719 &mut self,
720 module_path: &Path,
721 loaded: &LoadedModule,
722 alias: &str,
723 ) -> Result<(), VmError> {
724 let module_name = module_path.display().to_string();
725 if self.env.get(alias).is_some() {
726 return Err(VmError::Runtime(format!(
727 "Import collision: '{alias}' is already defined when importing {module_name}. \
728 Use a different namespace alias: import * as <name> from \"...\""
729 )));
730 }
731 let dict = build_namespace_dict(&module_name, loaded);
732 self.env.define(alias, dict, false)?;
733 Ok(())
734 }
735
736 fn export_loaded_module(
737 &mut self,
738 module_path: &Path,
739 loaded: &LoadedModule,
740 selected_names: Option<&[String]>,
741 ) -> Result<(), VmError> {
742 let module_name = module_path.display().to_string();
743 let export_names = module_import_names(&module_name, loaded, selected_names)?;
744
745 for name in export_names {
746 if let Some(value) = loaded.public_values.get(&name) {
748 if self.env.get(&name).is_some() {
749 return Err(VmError::Runtime(format!(
750 "Import collision: '{name}' is already defined when importing \
751 {module_name}. Use selective imports to disambiguate: \
752 import {{ {name} }} from \"...\""
753 )));
754 }
755 self.env.define(&name, value.clone(), false)?;
756 continue;
757 }
758 if let Some(schema) = loaded.public_type_schemas.get(&name) {
762 self.env.define(&name, schema.clone(), false)?;
763 continue;
764 }
765 if loaded
766 .public_exports
767 .get(&name)
768 .is_some_and(|kind| !kind.has_runtime_value())
769 {
770 continue;
771 }
772 let Some(closure) = loaded.functions.get(&name) else {
773 return Err(VmError::Runtime(format!(
774 "Import error: '{name}' is not defined in {module_name}"
775 )));
776 };
777 if let Some(VmValue::Closure(_)) = self.env.get(&name) {
778 return Err(VmError::Runtime(format!(
779 "Import collision: '{name}' is already defined when importing {module_name}. \
780 Use selective imports to disambiguate: import {{ {name} }} from \"...\""
781 )));
782 }
783 self.env
784 .define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
785 }
786 Ok(())
787 }
788
789 pub(super) fn execute_import<'a>(
791 &'a mut self,
792 path: &'a str,
793 selected_names: Option<&'a [String]>,
794 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
795 self.execute_import_with_projection(path, ImportProjection::BindCaller(selected_names))
796 }
797
798 pub(super) fn execute_namespace_import_bind<'a>(
800 &'a mut self,
801 path: &'a str,
802 alias: &'a str,
803 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
804 self.execute_import_with_projection(path, ImportProjection::BindNamespace(alias))
805 }
806
807 fn materialize_import<'a>(
808 &'a mut self,
809 path: &'a str,
810 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
811 self.execute_import_with_projection(path, ImportProjection::MaterializeOnly)
812 }
813
814 fn apply_import_projection(
815 &mut self,
816 module_path: &Path,
817 loaded: &LoadedModule,
818 projection: ImportProjection<'_>,
819 ) -> Result<(), VmError> {
820 match projection {
821 ImportProjection::BindCaller(selected_names) => {
822 self.export_loaded_module(module_path, loaded, selected_names)
823 }
824 ImportProjection::BindNamespace(alias) => {
825 self.export_namespace_module(module_path, loaded, alias)
826 }
827 ImportProjection::MaterializeOnly => Ok(()),
828 }
829 }
830
831 fn execute_import_with_projection<'a>(
832 &'a mut self,
833 path: &'a str,
834 projection: ImportProjection<'a>,
835 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
836 Box::pin(async move {
837 let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
838
839 let stdlib_module = path
840 .strip_prefix("std/")
841 .or_else(|| (path == "observability").then_some("observability"));
842 if let Some(module) = stdlib_module {
843 if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
844 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
845 if self.imported_paths.contains(&synthetic) {
846 return Ok(());
847 }
848 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
849 return self.apply_import_projection(&synthetic, &loaded, projection);
850 }
851 let loaded = self
852 .load_stdlib_module_from_source(module, synthetic.clone(), source)
853 .await?;
854 if !matches!(projection, ImportProjection::MaterializeOnly) {
855 let _load_span = self.module_load_span();
856 self.apply_import_projection(&synthetic, &loaded, projection)?;
857 }
858 return Ok(());
859 }
860 return Err(VmError::Runtime(format!(
861 "Unknown stdlib module: std/{module}"
862 )));
863 }
864
865 let base = self
866 .source_dir
867 .clone()
868 .unwrap_or_else(|| PathBuf::from("."));
869 let file_path = self.resolve_module_import_path(&base, path)?;
870 let verified_source = if let Some(guard) = &self.package_execution_guard {
871 let bytes = guard.verify_entry_source(&file_path).map_err(|error| {
872 VmError::Runtime(format!(
873 "installed package {} rejected: {error}",
874 projection.package_rejection_kind()
875 ))
876 })?;
877 Some(verified_package_source(bytes, &file_path)?)
878 } else {
879 None
880 };
881
882 let canonical = file_path
883 .canonicalize()
884 .unwrap_or_else(|_| file_path.clone());
885 if self.imported_paths.contains(&canonical) {
886 match projection {
894 ImportProjection::BindCaller(selected_names) => {
895 if let Some(importer) = self.imported_paths.last().cloned() {
896 if importer != canonical {
897 self.deferred_cyclic_imports.push(DeferredCyclicImport {
898 importer,
899 target: canonical.clone(),
900 selected_names: selected_names.map(<[String]>::to_vec),
901 namespace_alias: None,
902 });
903 }
904 }
905 }
906 ImportProjection::BindNamespace(alias) => {
907 if let Some(importer) = self.imported_paths.last().cloned() {
908 if importer != canonical {
909 self.deferred_cyclic_imports.push(DeferredCyclicImport {
910 importer,
911 target: canonical.clone(),
912 selected_names: None,
913 namespace_alias: Some(alias.to_string()),
914 });
915 }
916 }
917 }
918 ImportProjection::MaterializeOnly => {}
919 }
920 return Ok(());
921 }
922 if let Some(loaded) = self.module_cache.get(&canonical).cloned() {
923 if let Some(source) = &verified_source {
924 let cached_source = self.source_cache.get(&canonical).map(Arc::as_ref);
925 if cached_source != Some(source.as_str()) {
926 return Err(VmError::Runtime(format!(
927 "installed package {} rejected: cached module {} was not compiled from the verified package bytes",
928 projection.package_rejection_kind(),
929 canonical.display()
930 )));
931 }
932 let active_guard = self
933 .package_execution_guard
934 .as_deref()
935 .expect("verified package source requires an active guard");
936 if loaded.package_execution_guard.as_deref() != Some(active_guard) {
937 return Err(VmError::Runtime(format!(
938 "installed package {} rejected: cached module {} was not instantiated under the active package execution guard",
939 projection.package_rejection_kind(),
940 canonical.display()
941 )));
942 }
943 }
944 return self.apply_import_projection(&canonical, &loaded, projection);
945 }
946 self.imported_paths.push(canonical.clone());
947
948 let linked = verified_source
954 .is_none()
955 .then(|| {
956 let content_hash = self
957 .graph_link_table
958 .as_ref()?
959 .content_hash(canonical.as_path())?;
960 let _load_span = self.module_load_span();
961 self.linked_module_artifact(&file_path, &canonical, content_hash)
962 })
963 .flatten();
964
965 let artifact = if let Some(linked) = linked {
966 linked
967 } else {
968 let source = {
969 let _load_span = self.module_load_span();
970 match verified_source {
971 Some(source) => Arc::new(ModuleSource::from_text(source)),
974 None => module_source::read(&file_path).map_err(|e| {
975 VmError::Runtime(format!(
980 "Import error: cannot read '{}' (resolved '{path}' relative to {}): {e}",
981 file_path.display(),
982 base.display()
983 ))
984 })?,
985 }
986 };
987 {
988 let source_cache = Arc::make_mut(&mut self.source_cache);
989 source_cache.insert(canonical.clone(), Arc::clone(source.text()));
990 source_cache.insert(file_path.clone(), Arc::clone(source.text()));
991 }
992
993 let prepared = {
994 let _load_span = self.module_load_span();
995 if bytecode_cache::cache_enabled() {
996 self.prepared_module_cache.get(&canonical, source.sha256())
997 } else {
998 None
999 }
1000 };
1001 if let Some(prepared) = prepared {
1002 prepared
1003 } else {
1004 let lookup = {
1008 let _load_span = self.module_load_span();
1009 bytecode_cache::load_module(&file_path, &source)
1010 };
1011 let cached = if let Some(artifact) = lookup.artifact {
1012 artifact
1013 } else {
1014 let mut compile_span = self.module_compile_span();
1015 let compiled =
1016 compile_module_artifact_from_source(&file_path, source.as_str())?;
1017 if let Some(span) = &mut compile_span {
1018 span.mark_compile_succeeded();
1019 }
1020 drop(compile_span);
1021 if let Err(err) = bytecode_cache::store_module(&lookup.key, &compiled) {
1022 if std::env::var_os("HARN_BYTECODE_CACHE_DEBUG").is_some() {
1023 eprintln!(
1024 "[harn] module cache write skipped for {}: {err}",
1025 file_path.display()
1026 );
1027 }
1028 }
1029 compiled
1030 };
1031 let mut prepared = {
1032 let _load_span = self.module_load_span();
1033 Arc::new(PreparedModuleArtifact::from_cached(cached))
1034 };
1035 if bytecode_cache::cache_enabled() {
1036 prepared = self.prepared_module_cache.insert(
1037 canonical.clone(),
1038 source.sha256(),
1039 prepared,
1040 );
1041 }
1042 prepared
1043 }
1044 };
1045
1046 let module_source_dir = file_path.parent().map(|p| p.to_path_buf());
1047 let loaded = Arc::new(
1048 self.instantiate_module(module_source_dir, artifact.as_ref())
1049 .await?,
1050 );
1051 self.imported_paths.pop();
1052 {
1053 let _load_span = self.module_load_span();
1054 Arc::make_mut(&mut self.module_cache)
1055 .insert(canonical.clone(), Arc::clone(&loaded));
1056 }
1057 self.record_module_loaded();
1058 if !matches!(projection, ImportProjection::MaterializeOnly) {
1059 let _load_span = self.module_load_span();
1060 self.apply_import_projection(&canonical, &loaded, projection)?;
1061 }
1062
1063 if self.imported_paths.is_empty() {
1067 let _load_span = self.module_load_span();
1068 self.flush_deferred_cyclic_imports()?;
1069 }
1070
1071 Ok(())
1072 })
1073 }
1074
1075 fn linked_module_artifact(
1087 &self,
1088 file_path: &Path,
1089 canonical: &Path,
1090 content_hash: [u8; 32],
1091 ) -> Option<Arc<PreparedModuleArtifact>> {
1092 if !bytecode_cache::cache_enabled() {
1093 return None;
1094 }
1095 if let Some(prepared) = self.prepared_module_cache.get(canonical, content_hash) {
1096 return Some(prepared);
1097 }
1098 let key = bytecode_cache::CacheKey::from_module_content_hash(content_hash);
1099 let artifact = bytecode_cache::load_module_for_key(file_path, key).artifact?;
1100 Some(self.prepared_module_cache.insert(
1101 canonical.to_path_buf(),
1102 content_hash,
1103 Arc::new(PreparedModuleArtifact::from_cached(artifact)),
1104 ))
1105 }
1106
1107 fn flush_deferred_cyclic_imports(&mut self) -> Result<(), VmError> {
1116 if self.deferred_cyclic_imports.is_empty() {
1117 return Ok(());
1118 }
1119 let deferred = std::mem::take(&mut self.deferred_cyclic_imports);
1120 let mut still_pending = Vec::new();
1121 for import in deferred {
1122 let (Some(importer), Some(target)) = (
1123 self.module_cache.get(&import.importer).cloned(),
1124 self.module_cache.get(&import.target).cloned(),
1125 ) else {
1126 still_pending.push(import);
1130 continue;
1131 };
1132
1133 let mut module_state = importer._module_state.lock();
1134 if let Some(alias) = &import.namespace_alias {
1135 if module_state.get(alias).is_none() {
1136 let dict = build_namespace_dict(&import.target.display().to_string(), &target);
1137 module_state.define(alias, dict, false)?;
1138 }
1139 continue;
1140 }
1141
1142 let export_names = module_import_names(
1143 &import.target.display().to_string(),
1144 &target,
1145 import.selected_names.as_deref(),
1146 )?;
1147
1148 for name in export_names {
1149 if module_state.get(&name).is_some() {
1152 continue;
1153 }
1154 if let Some(closure) = target.functions.get(&name) {
1155 module_state.define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
1156 } else if let Some(value) = target.public_values.get(&name) {
1157 module_state.define(&name, value.clone(), false)?;
1159 } else if target
1160 .public_exports
1161 .get(&name)
1162 .is_some_and(|kind| !kind.has_runtime_value())
1163 {
1164 continue;
1166 } else {
1167 return Err(VmError::Runtime(format!(
1168 "Import error: '{name}' is not defined in {}",
1169 import.target.display()
1170 )));
1171 }
1172 }
1173 }
1174 self.deferred_cyclic_imports = still_pending;
1175 Ok(())
1176 }
1177
1178 fn cache_key_for_import(&self, path: &str) -> Result<PathBuf, VmError> {
1183 if let Some(module) = path
1184 .strip_prefix("std/")
1185 .or_else(|| (path == "observability").then_some("observability"))
1186 {
1187 return Ok(PathBuf::from(format!("<stdlib>/{module}.harn")));
1188 }
1189 let base = self
1190 .source_dir
1191 .clone()
1192 .unwrap_or_else(|| PathBuf::from("."));
1193 let file_path = self.resolve_module_import_path(&base, path)?;
1194 Ok(file_path.canonicalize().unwrap_or(file_path))
1195 }
1196
1197 async fn loaded_module_for_path(
1198 &mut self,
1199 path: &Path,
1200 ) -> Result<(PathBuf, Arc<LoadedModule>), VmError> {
1201 self.ensure_execution_available()?;
1202 let path_str = path.to_string_lossy().into_owned();
1203 self.materialize_import(&path_str).await?;
1204
1205 let mut file_path = if path.is_absolute() {
1206 path.to_path_buf()
1207 } else {
1208 self.source_dir
1209 .clone()
1210 .unwrap_or_else(|| PathBuf::from("."))
1211 .join(path)
1212 };
1213 if !file_path.exists() && file_path.extension().is_none() {
1214 file_path.set_extension("harn");
1215 }
1216
1217 let canonical = file_path
1218 .canonicalize()
1219 .unwrap_or_else(|_| file_path.clone());
1220 let loaded = self.module_cache.get(&canonical).cloned().ok_or_else(|| {
1221 VmError::Runtime(format!(
1222 "Import error: failed to cache loaded module '{}'",
1223 canonical.display()
1224 ))
1225 })?;
1226 Ok((canonical, loaded))
1227 }
1228
1229 pub async fn load_public_module_callable(
1231 &mut self,
1232 path: &Path,
1233 name: &str,
1234 ) -> Result<Arc<VmClosure>, VmError> {
1235 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1236 if !loaded.public_exports.contains_key(name) {
1237 let hint = if loaded.functions.contains_key(name) {
1238 "; it is defined there but not `pub`"
1239 } else {
1240 ""
1241 };
1242 return Err(VmError::Runtime(format!(
1243 "callable '{name}' is not exported by module '{}'{hint}",
1244 canonical.display()
1245 )));
1246 }
1247 loaded.functions.get(name).cloned().ok_or_else(|| {
1248 VmError::Runtime(format!(
1249 "Import error: exported callable '{name}' is missing from {}",
1250 canonical.display()
1251 ))
1252 })
1253 }
1254
1255 pub async fn load_module_exports(
1258 &mut self,
1259 path: &Path,
1260 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1261 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1262 exported_function_closures(&loaded, &canonical)
1263 }
1264
1265 pub async fn load_module_exports_from_source(
1268 &mut self,
1269 source_key: impl Into<PathBuf>,
1270 source: &str,
1271 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1272 self.ensure_execution_available()?;
1273 let synthetic = source_key.into();
1274 let loaded = self
1275 .load_module_from_source(synthetic.clone(), source)
1276 .await?;
1277 exported_function_closures(&loaded, &synthetic)
1278 }
1279
1280 pub async fn load_module_callable_from_source(
1285 &mut self,
1286 source_key: impl Into<PathBuf>,
1287 source: &str,
1288 name: &str,
1289 ) -> Result<Option<Arc<VmClosure>>, VmError> {
1290 self.ensure_execution_available()?;
1291 let synthetic = source_key.into();
1292 let loaded = self.load_module_from_source(synthetic, source).await?;
1293 Ok(loaded.functions.get(name).cloned())
1294 }
1295
1296 pub async fn load_module_exports_from_import(
1300 &mut self,
1301 import_path: &str,
1302 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1303 self.ensure_execution_available()?;
1304 self.materialize_import(import_path).await?;
1305
1306 if let Some(module) = import_path
1307 .strip_prefix("std/")
1308 .or_else(|| (import_path == "observability").then_some("observability"))
1309 {
1310 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
1311 let loaded = self.module_cache.get(&synthetic).cloned().ok_or_else(|| {
1312 VmError::Runtime(format!(
1313 "Import error: failed to cache loaded module '{}'",
1314 synthetic.display()
1315 ))
1316 })?;
1317 return exported_function_closures(&loaded, &synthetic);
1318 }
1319
1320 let base = self
1321 .source_dir
1322 .clone()
1323 .unwrap_or_else(|| PathBuf::from("."));
1324 let file_path = self.resolve_module_import_path(&base, import_path)?;
1325 self.load_module_exports(&file_path).await
1326 }
1327}
1328
1329#[cfg(test)]
1330#[path = "modules_tests.rs"]
1331mod tests;