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::prepared_module::PreparedModuleArtifact;
13use crate::value::{ModuleFunctionRegistry, VmClosure, VmEnv, VmError, VmValue};
14
15use super::{ScopeSpan, Vm};
16
17static STDLIB_MODULE_ARTIFACT_CACHE: OnceLock<
18 Mutex<BTreeMap<String, Arc<PreparedModuleArtifact>>>,
19> = OnceLock::new();
20
21fn stdlib_module_artifact_cache() -> &'static Mutex<BTreeMap<String, Arc<PreparedModuleArtifact>>> {
22 STDLIB_MODULE_ARTIFACT_CACHE.get_or_init(|| Mutex::new(BTreeMap::new()))
23}
24
25fn verified_package_source(bytes: Vec<u8>, path: &Path) -> Result<String, VmError> {
26 String::from_utf8(bytes).map_err(|error| {
27 VmError::Runtime(format!(
28 "installed package source {} is not valid UTF-8: {error}",
29 path.display()
30 ))
31 })
32}
33
34fn exported_function_closures(
35 loaded: &LoadedModule,
36 display_path: &Path,
37) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
38 let mut exports = BTreeMap::new();
39 for name in loaded
40 .public_exports
41 .keys()
42 .filter(|name| loaded.functions.contains_key(*name))
43 {
44 let Some(closure) = loaded.functions.get(name) else {
45 return Err(VmError::Runtime(format!(
46 "Import error: exported function '{name}' is missing from {}",
47 display_path.display()
48 )));
49 };
50 exports.insert(name.clone(), Arc::clone(closure));
51 }
52 Ok(exports)
53}
54
55#[cfg(test)]
56fn reset_stdlib_module_artifact_cache() {
57 stdlib_module_artifact_cache().lock().unwrap().clear();
58}
59
60#[cfg(test)]
61fn stdlib_module_artifact_cache_ptr(module: &str, source: &str) -> Option<usize> {
62 let key = stdlib_artifact_cache_key(module, source);
63 stdlib_module_artifact_cache()
64 .lock()
65 .unwrap()
66 .get(&key)
67 .map(|artifact| Arc::as_ptr(artifact) as usize)
68}
69
70pub(crate) struct LoadedModule {
71 pub(crate) functions: BTreeMap<String, Arc<VmClosure>>,
72 pub(crate) public_exports: BTreeMap<String, DefKind>,
75 pub(crate) public_values: BTreeMap<String, VmValue>,
78 pub(crate) public_type_schemas: BTreeMap<String, VmValue>,
82 package_execution_guard: Option<Arc<harn_modules::package_execution::PackageExecutionGuard>>,
87 pub(crate) _module_functions: crate::value::ModuleFunctionRegistry,
88 pub(crate) _module_state: crate::value::ModuleState,
89}
90
91pub(crate) type ModuleCache = Arc<BTreeMap<PathBuf, Arc<LoadedModule>>>;
99
100#[derive(Clone, Debug)]
106pub(crate) struct DeferredCyclicImport {
107 pub(crate) importer: PathBuf,
109 pub(crate) target: PathBuf,
111 pub(crate) selected_names: Option<Vec<String>>,
113}
114
115#[derive(Clone, Copy)]
116enum ImportProjection<'a> {
117 BindCaller(Option<&'a [String]>),
118 MaterializeOnly,
119}
120
121impl ImportProjection<'_> {
122 fn package_rejection_kind(self) -> &'static str {
123 match self {
124 Self::BindCaller(_) => "import",
125 Self::MaterializeOnly => "execution",
126 }
127 }
128}
129
130fn module_import_names(
134 module_name: &str,
135 loaded: &LoadedModule,
136 selected_names: Option<&[String]>,
137) -> Result<Vec<String>, VmError> {
138 if let Some(names) = selected_names {
139 for name in names {
140 if !loaded.public_exports.contains_key(name) {
141 let hint = if loaded.functions.contains_key(name) {
142 " — it is defined there but not `pub`; mark it `pub` to export it"
143 } else {
144 ""
145 };
146 return Err(VmError::Runtime(format!(
147 "Import error: '{name}' is not exported by {module_name}{hint}"
148 )));
149 }
150 }
151 return Ok(names.to_vec());
152 }
153
154 Ok(loaded.public_exports.keys().cloned().collect())
155}
156
157pub fn resolve_module_import_path(base: &Path, path: &str) -> PathBuf {
158 let synthetic_current_file = base.join("__harn_import_base__.harn");
159 if let Some(resolved) = harn_modules::resolve_import_path(&synthetic_current_file, path) {
160 return resolved;
161 }
162
163 let mut file_path = base.join(path);
164
165 if !file_path.exists() && file_path.extension().is_none() {
166 file_path.set_extension("harn");
167 }
168
169 file_path
170}
171
172fn stdlib_artifact_cache_key(module: &str, source: &str) -> String {
173 let mut hasher = std::collections::hash_map::DefaultHasher::new();
174 module.hash(&mut hasher);
175 source.hash(&mut hasher);
176 format!("{module}:{:016x}", hasher.finish())
177}
178
179fn stdlib_module_artifact(
180 module: &str,
181 synthetic: &Path,
182 source: &'static str,
183 recorder: Option<&super::ModulePhaseRecorder>,
184) -> Result<Arc<PreparedModuleArtifact>, VmError> {
185 let key = stdlib_artifact_cache_key(module, source);
186 {
187 let cache = stdlib_module_artifact_cache().lock().unwrap();
188 if let Some(cached) = cache.get(&key) {
189 return Ok(Arc::clone(cached));
190 }
191 }
192
193 let lookup = {
198 let _load_span = recorder.map(super::ModulePhaseRecorder::load_span);
199 bytecode_cache::load_module(synthetic, source)
200 };
201 let artifact = if let Some(artifact) = lookup.artifact {
202 artifact
203 } else {
204 let mut compile_span = recorder.map(super::ModulePhaseRecorder::compile_span);
205 let compiled = compile_module_artifact_from_source(synthetic, source)?;
206 if let Some(span) = &mut compile_span {
207 span.mark_compile_succeeded();
208 }
209 drop(compile_span);
210 if let Err(err) = bytecode_cache::store_module(&lookup.key, &compiled) {
211 if std::env::var_os("HARN_BYTECODE_CACHE_DEBUG").is_some() {
212 eprintln!("[harn] stdlib module cache write skipped for {module}: {err}");
213 }
214 }
215 compiled
216 };
217
218 let compiled = {
219 let _load_span = recorder.map(super::ModulePhaseRecorder::load_span);
220 Arc::new(PreparedModuleArtifact::from_cached(artifact))
221 };
222 let mut cache = stdlib_module_artifact_cache().lock().unwrap();
223 if let Some(cached) = cache.get(&key) {
224 return Ok(Arc::clone(cached));
225 }
226 cache.insert(key, Arc::clone(&compiled));
227 Ok(compiled)
228}
229
230impl Vm {
231 fn resolve_module_import_path(&self, base: &Path, path: &str) -> Result<PathBuf, VmError> {
232 if let Some(guard) = &self.package_execution_guard {
233 let synthetic_current_file = base.join("__harn_import_base__.harn");
234 if let Some(resolved) =
235 harn_modules::resolve_import_path_with_guard(&synthetic_current_file, path, guard)
236 .map_err(|error| {
237 VmError::Runtime(format!("installed package import rejected: {error}"))
238 })?
239 {
240 return Ok(resolved);
241 }
242 let mut file_path = base.join(path);
243 if !file_path.exists() && file_path.extension().is_none() {
244 file_path.set_extension("harn");
245 }
246 return Ok(file_path);
247 }
248 Ok(resolve_module_import_path(base, path))
249 }
250
251 pub async fn resolve_callable(
255 &mut self,
256 callable: &crate::value::VmCallable,
257 ) -> Result<Arc<crate::value::VmClosure>, VmError> {
258 self.ensure_execution_available()?;
259 match callable {
260 crate::value::VmCallable::Eager(closure) => Ok(Arc::clone(closure)),
261 crate::value::VmCallable::Lazy(lazy) => {
262 let (cache_key, module_path) = self.lazy_callable_module_path(lazy);
263 let next_guard = lazy
264 .package_execution_guard_handle()
265 .or_else(|| self.package_execution_guard.clone());
266 if let Some(guard) = &next_guard {
267 guard.verify_entry_source(&module_path).map_err(|error| {
268 VmError::Runtime(format!("installed package execution rejected: {error}"))
269 })?;
270 }
271 let resolution = {
272 let mut modules = self.lazy_callable_modules.lock();
273 let slots = modules.entry(cache_key).or_default();
274 if let Some(slot) = slots.iter().find(|slot| slot.execution_guard == next_guard)
275 {
276 Arc::clone(&slot.resolution)
277 } else {
278 let resolution = Arc::new(tokio::sync::OnceCell::new());
279 slots.push(crate::vm::state::LazyCallableCacheSlot {
280 execution_guard: next_guard.clone(),
281 resolution: Arc::clone(&resolution),
282 });
283 resolution
284 }
285 };
286 let previous_package_execution_guard =
287 std::mem::replace(&mut self.package_execution_guard, next_guard);
288 let resolved = resolution
289 .get_or_try_init(|| async {
290 let exports = self.load_module_exports(&module_path).await?;
291 let exports = exports
292 .into_iter()
293 .map(|(name, closure)| (name, closure.retained_for_host_registry()))
294 .collect();
295 Ok::<_, VmError>(Arc::new(crate::vm::state::ResolvedLazyCallable {
300 exports,
301 retained_module_graph: Arc::clone(&self.module_cache),
302 }))
303 })
304 .await;
305 self.package_execution_guard = previous_package_execution_guard;
306 let resolved = resolved?;
307 resolved
308 .exports
309 .get(&lazy.function_name)
310 .cloned()
311 .ok_or_else(|| {
312 VmError::Runtime(format!(
313 "function '{}' is not exported by module '{}'",
314 lazy.function_name,
315 lazy.module_path.display()
316 ))
317 })
318 }
319 crate::value::VmCallable::Pipeline(_) => Err(VmError::TypeError(
320 "pipeline callable requires execute_callable".to_string(),
321 )),
322 }
323 }
324
325 pub async fn execute_callable(
326 &mut self,
327 callable: &crate::value::VmCallable,
328 args: &[crate::value::VmValue],
329 ) -> Result<crate::value::VmValue, VmError> {
330 let crate::value::VmCallable::Pipeline(pipeline) = callable else {
331 let closure = self.resolve_callable(callable).await?;
332 return self.call_closure_pub(&closure, args).await;
333 };
334
335 let (_, module_path) = self.lazy_module_path(&pipeline.module_path);
336 let next_guard = pipeline
337 .package_execution_guard_handle()
338 .or_else(|| self.package_execution_guard.clone());
339 let previous_package_execution_guard =
340 std::mem::replace(&mut self.package_execution_guard, next_guard);
341 let result = async {
342 let closure = self
343 .load_public_module_callable(&module_path, &pipeline.pipeline_name)
344 .await?;
345 self.call_closure_pub(&closure, args).await
346 }
347 .await;
348 self.package_execution_guard = previous_package_execution_guard;
349 result
350 }
351
352 fn lazy_callable_module_path(&self, lazy: &crate::value::LazyVmCallable) -> (PathBuf, PathBuf) {
353 self.lazy_module_path(&lazy.module_path)
354 }
355
356 fn lazy_module_path(&self, path: &std::path::Path) -> (PathBuf, PathBuf) {
357 let mut module_path = if path.is_absolute() {
358 path.to_path_buf()
359 } else {
360 self.source_dir
361 .clone()
362 .unwrap_or_else(|| PathBuf::from("."))
363 .join(path)
364 };
365 if !module_path.exists() && module_path.extension().is_none() {
366 module_path.set_extension("harn");
367 }
368 let cache_key = module_path
369 .canonicalize()
370 .unwrap_or_else(|_| module_path.clone());
371 (cache_key, module_path)
372 }
373
374 async fn load_module_from_source(
375 &mut self,
376 synthetic: PathBuf,
377 source: &str,
378 ) -> Result<Arc<LoadedModule>, VmError> {
379 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
380 return Ok(loaded);
381 }
382 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), source.to_string());
383
384 let mut compile_span = self.module_compile_span();
385 let compiled = compile_module_artifact_from_source(&synthetic, source)?;
386 if let Some(span) = &mut compile_span {
387 span.mark_compile_succeeded();
388 }
389 drop(compile_span);
390 let artifact = {
391 let _load_span = self.module_load_span();
392 PreparedModuleArtifact::from_cached(compiled)
393 };
394
395 self.imported_paths.push(synthetic.clone());
396 let loaded = Arc::new(self.instantiate_module(None, &artifact).await?);
397 self.imported_paths.pop();
398 {
399 let _load_span = self.module_load_span();
400 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
401 }
402 self.record_module_loaded();
403 Ok(loaded)
404 }
405
406 fn add_builtin_reexports(module: &str, loaded: &mut LoadedModule) {
415 for name in harn_stdlib::builtin_reexports(module) {
416 if loaded.public_exports.contains_key(*name) {
420 continue;
421 }
422 loaded
423 .public_exports
424 .insert((*name).to_string(), DefKind::Function);
425 loaded.public_values.insert(
426 (*name).to_string(),
427 VmValue::BuiltinRef(arcstr::ArcStr::from(*name)),
428 );
429 }
430 }
431
432 async fn load_stdlib_module_from_source(
433 &mut self,
434 module: &str,
435 synthetic: PathBuf,
436 source: &'static str,
437 ) -> Result<Arc<LoadedModule>, VmError> {
438 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
439 return Ok(loaded);
440 }
441 Arc::make_mut(&mut self.source_cache).insert(synthetic.clone(), source.to_string());
442
443 let artifact = stdlib_module_artifact(
444 module,
445 &synthetic,
446 source,
447 self.module_phase_recorder.as_ref(),
448 )?;
449 self.imported_paths.push(synthetic.clone());
450 let mut loaded = self.instantiate_stdlib_module(artifact.as_ref()).await?;
451 self.imported_paths.pop();
452 Self::add_builtin_reexports(module, &mut loaded);
453 let loaded = Arc::new(loaded);
454 {
455 let _load_span = self.module_load_span();
456 Arc::make_mut(&mut self.module_cache).insert(synthetic, Arc::clone(&loaded));
457 }
458 self.record_module_loaded();
459 Ok(loaded)
460 }
461
462 async fn instantiate_stdlib_module(
463 &mut self,
464 artifact: &PreparedModuleArtifact,
465 ) -> Result<LoadedModule, VmError> {
466 self.instantiate_module(None, artifact).await
467 }
468
469 async fn instantiate_module(
477 &mut self,
478 module_source_dir: Option<PathBuf>,
479 artifact: &PreparedModuleArtifact,
480 ) -> Result<LoadedModule, VmError> {
481 let caller_env = self.env.clone();
482 let old_source_dir = self.source_dir.clone();
483 self.env = VmEnv::new();
484 self.source_dir = module_source_dir.clone();
485
486 for import in &artifact.imports {
487 self.execute_import(&import.path, import.selected_names.as_deref())
488 .await?;
489 }
490
491 let _load_span = self.module_load_span();
494
495 let module_state: crate::value::ModuleState = {
496 let mut init_env = self.env.clone();
497 if artifact.type_schema_init_chunk.is_some() || artifact.init_chunk.is_some() {
498 let saved_env = std::mem::replace(&mut self.env, init_env);
499 let saved_frames = std::mem::take(&mut self.frames);
500 let saved_handlers = std::mem::take(&mut self.exception_handlers);
501 let saved_iterators = std::mem::take(&mut self.iterators);
502 let saved_deadlines = std::mem::take(&mut self.deadlines);
503 let active_context = crate::step_runtime::take_active_context();
514 let init_result: Result<(), VmError> = async {
515 if let Some(chunk) = &artifact.type_schema_init_chunk {
516 self.run_chunk(Arc::clone(chunk)).await?;
517 }
518 if let Some(chunk) = &artifact.init_chunk {
519 self.run_chunk(Arc::clone(chunk)).await?;
520 }
521 Ok(())
522 }
523 .await;
524 crate::step_runtime::restore_active_context(active_context);
525 init_env = std::mem::replace(&mut self.env, saved_env);
526 self.frames = saved_frames;
527 self.exception_handlers = saved_handlers;
528 self.iterators = saved_iterators;
529 self.deadlines = saved_deadlines;
530 init_result?;
531 }
532 Arc::new(crate::value::VmMutex::new(init_env))
533 };
534
535 let module_env = self.env.clone();
536 let registry: ModuleFunctionRegistry =
537 Arc::new(crate::value::VmMutex::new(BTreeMap::new()));
538 let mut functions: BTreeMap<String, Arc<VmClosure>> = BTreeMap::new();
539 let mut public_exports = artifact.public_exports.clone();
540 let mut public_values: BTreeMap<String, VmValue> = BTreeMap::new();
544 {
545 let state = module_state.lock();
546 for name in &artifact.public_value_names {
547 if let Some(value) = state.get(name) {
548 public_values.insert(name.clone(), value);
549 }
550 }
551 }
552 let public_type_names = artifact.public_type_names.clone();
553 let mut public_type_schemas: BTreeMap<String, VmValue> = {
554 let state = module_state.lock();
555 public_type_names
556 .iter()
557 .filter_map(|name| state.get(name).map(|schema| (name.clone(), schema)))
558 .collect()
559 };
560
561 for (name, compiled) in &artifact.functions {
562 let closure = Arc::new(VmClosure {
563 func: Arc::clone(compiled),
564 env: module_env.clone(),
565 source_dir: module_source_dir.clone(),
566 module_functions: Some(Arc::downgrade(®istry)),
567 module_state: Some(Arc::downgrade(&module_state)),
568 retained_module_scope: None,
569 });
570 registry.lock().insert(name.clone(), Arc::clone(&closure));
571 self.env
572 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
573 module_state
574 .lock()
575 .define(name, VmValue::Closure(Arc::clone(&closure)), false)?;
576 functions.insert(name.clone(), Arc::clone(&closure));
577 }
578
579 for import in artifact.imports.iter().filter(|import| import.is_pub) {
580 let cache_key = self.cache_key_for_import(&import.path)?;
581 let Some(loaded) = self.module_cache.get(&cache_key).cloned() else {
582 if self.imported_paths.contains(&cache_key) {
589 return Err(VmError::Runtime(format!(
590 "Re-export error: cannot `pub import` from '{}' because it forms an \
591 import cycle with this module (its public surface is still being \
592 built). Use a plain `import` here, or re-export from a module that is \
593 not part of the cycle.",
594 import.path
595 )));
596 }
597 return Err(VmError::Runtime(format!(
598 "Re-export error: imported module '{}' was not loaded",
599 import.path
600 )));
601 };
602 let names_to_reexport =
603 module_import_names(&import.path, &loaded, import.selected_names.as_deref())?;
604 for name in names_to_reexport {
605 let Some(kind) = loaded.public_exports.get(&name).copied() else {
606 return Err(VmError::Runtime(format!(
607 "Re-export error: '{name}' is not exported by '{}'",
608 import.path
609 )));
610 };
611 let Some(closure) = loaded.functions.get(&name) else {
612 if let Some(value) = loaded.public_values.get(&name) {
616 public_values.insert(name.clone(), value.clone());
617 public_exports.insert(name, kind);
618 continue;
619 }
620 if let Some(schema) = loaded.public_type_schemas.get(&name) {
623 public_type_schemas.insert(name.clone(), schema.clone());
624 }
625 public_exports.insert(name, kind);
626 continue;
627 };
628 if let Some(existing) = functions.get(&name) {
629 if !Arc::ptr_eq(existing, closure) {
630 return Err(VmError::Runtime(format!(
631 "Re-export collision: '{name}' is defined here and also \
632 re-exported from '{}'",
633 import.path
634 )));
635 }
636 }
637 functions.insert(name.clone(), Arc::clone(closure));
638 public_exports.insert(name, kind);
639 }
640 }
641
642 self.env = caller_env;
643 self.source_dir = old_source_dir;
644
645 Ok(LoadedModule {
646 functions,
647 public_exports,
648 public_values,
649 public_type_schemas,
650 package_execution_guard: module_source_dir
651 .as_ref()
652 .and(self.package_execution_guard.clone()),
653 _module_functions: registry,
654 _module_state: module_state,
655 })
656 }
657
658 fn export_loaded_module(
659 &mut self,
660 module_path: &Path,
661 loaded: &LoadedModule,
662 selected_names: Option<&[String]>,
663 ) -> Result<(), VmError> {
664 let module_name = module_path.display().to_string();
665 let export_names = module_import_names(&module_name, loaded, selected_names)?;
666
667 for name in export_names {
668 if let Some(value) = loaded.public_values.get(&name) {
670 if self.env.get(&name).is_some() {
671 return Err(VmError::Runtime(format!(
672 "Import collision: '{name}' is already defined when importing \
673 {module_name}. Use selective imports to disambiguate: \
674 import {{ {name} }} from \"...\""
675 )));
676 }
677 self.env.define(&name, value.clone(), false)?;
678 continue;
679 }
680 if let Some(schema) = loaded.public_type_schemas.get(&name) {
684 self.env.define(&name, schema.clone(), false)?;
685 continue;
686 }
687 if loaded
688 .public_exports
689 .get(&name)
690 .is_some_and(|kind| !kind.has_runtime_value())
691 {
692 continue;
693 }
694 let Some(closure) = loaded.functions.get(&name) else {
695 return Err(VmError::Runtime(format!(
696 "Import error: '{name}' is not defined in {module_name}"
697 )));
698 };
699 if let Some(VmValue::Closure(_)) = self.env.get(&name) {
700 return Err(VmError::Runtime(format!(
701 "Import collision: '{name}' is already defined when importing {module_name}. \
702 Use selective imports to disambiguate: import {{ {name} }} from \"...\""
703 )));
704 }
705 self.env
706 .define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
707 }
708 Ok(())
709 }
710
711 pub(super) fn execute_import<'a>(
713 &'a mut self,
714 path: &'a str,
715 selected_names: Option<&'a [String]>,
716 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
717 self.execute_import_with_projection(path, ImportProjection::BindCaller(selected_names))
718 }
719
720 fn materialize_import<'a>(
721 &'a mut self,
722 path: &'a str,
723 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
724 self.execute_import_with_projection(path, ImportProjection::MaterializeOnly)
725 }
726
727 fn execute_import_with_projection<'a>(
728 &'a mut self,
729 path: &'a str,
730 projection: ImportProjection<'a>,
731 ) -> Pin<Box<dyn Future<Output = Result<(), VmError>> + Send + 'a>> {
732 Box::pin(async move {
733 let _import_span = ScopeSpan::new(crate::tracing::SpanKind::Import, path.to_string());
734
735 let stdlib_module = path
736 .strip_prefix("std/")
737 .or_else(|| (path == "observability").then_some("observability"));
738 if let Some(module) = stdlib_module {
739 if let Some(source) = crate::stdlib_modules::get_stdlib_source(module) {
740 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
741 if self.imported_paths.contains(&synthetic) {
742 return Ok(());
743 }
744 if let Some(loaded) = self.module_cache.get(&synthetic).cloned() {
745 return match projection {
746 ImportProjection::BindCaller(selected_names) => {
747 self.export_loaded_module(&synthetic, &loaded, selected_names)
748 }
749 ImportProjection::MaterializeOnly => Ok(()),
750 };
751 }
752 let loaded = self
753 .load_stdlib_module_from_source(module, synthetic.clone(), source)
754 .await?;
755 if let ImportProjection::BindCaller(selected_names) = projection {
756 let _load_span = self.module_load_span();
757 self.export_loaded_module(&synthetic, &loaded, selected_names)?;
758 }
759 return Ok(());
760 }
761 return Err(VmError::Runtime(format!(
762 "Unknown stdlib module: std/{module}"
763 )));
764 }
765
766 let base = self
767 .source_dir
768 .clone()
769 .unwrap_or_else(|| PathBuf::from("."));
770 let file_path = self.resolve_module_import_path(&base, path)?;
771 let verified_source = if let Some(guard) = &self.package_execution_guard {
772 let bytes = guard.verify_entry_source(&file_path).map_err(|error| {
773 VmError::Runtime(format!(
774 "installed package {} rejected: {error}",
775 projection.package_rejection_kind()
776 ))
777 })?;
778 Some(verified_package_source(bytes, &file_path)?)
779 } else {
780 None
781 };
782
783 let canonical = file_path
784 .canonicalize()
785 .unwrap_or_else(|_| file_path.clone());
786 if self.imported_paths.contains(&canonical) {
787 if let ImportProjection::BindCaller(selected_names) = projection {
795 if let Some(importer) = self.imported_paths.last().cloned() {
796 if importer != canonical {
797 self.deferred_cyclic_imports.push(DeferredCyclicImport {
798 importer,
799 target: canonical.clone(),
800 selected_names: selected_names.map(<[String]>::to_vec),
801 });
802 }
803 }
804 }
805 return Ok(());
806 }
807 if let Some(loaded) = self.module_cache.get(&canonical).cloned() {
808 if let Some(source) = &verified_source {
809 let cached_source = self.source_cache.get(&canonical);
810 if cached_source != Some(source) {
811 return Err(VmError::Runtime(format!(
812 "installed package {} rejected: cached module {} was not compiled from the verified package bytes",
813 projection.package_rejection_kind(),
814 canonical.display()
815 )));
816 }
817 let active_guard = self
818 .package_execution_guard
819 .as_deref()
820 .expect("verified package source requires an active guard");
821 if loaded.package_execution_guard.as_deref() != Some(active_guard) {
822 return Err(VmError::Runtime(format!(
823 "installed package {} rejected: cached module {} was not instantiated under the active package execution guard",
824 projection.package_rejection_kind(),
825 canonical.display()
826 )));
827 }
828 }
829 return match projection {
830 ImportProjection::BindCaller(selected_names) => {
831 self.export_loaded_module(&canonical, &loaded, selected_names)
832 }
833 ImportProjection::MaterializeOnly => Ok(()),
834 };
835 }
836 self.imported_paths.push(canonical.clone());
837
838 let source = {
839 let _load_span = self.module_load_span();
840 match verified_source {
841 Some(source) => source,
842 None => std::fs::read_to_string(&file_path).map_err(|e| {
843 VmError::Runtime(format!(
848 "Import error: cannot read '{}' (resolved '{path}' relative to {}): {e}",
849 file_path.display(),
850 base.display()
851 ))
852 })?,
853 }
854 };
855 Arc::make_mut(&mut self.source_cache).insert(canonical.clone(), source.clone());
856 Arc::make_mut(&mut self.source_cache).insert(file_path.clone(), source.clone());
857
858 let prepared = {
859 let _load_span = self.module_load_span();
860 if bytecode_cache::cache_enabled() {
861 self.prepared_module_cache.get(&canonical, &source)
862 } else {
863 None
864 }
865 };
866 let artifact = if let Some(prepared) = prepared {
867 prepared
868 } else {
869 let lookup = {
873 let _load_span = self.module_load_span();
874 bytecode_cache::load_module(&file_path, &source)
875 };
876 let cached = if let Some(artifact) = lookup.artifact {
877 artifact
878 } else {
879 let mut compile_span = self.module_compile_span();
880 let compiled = compile_module_artifact_from_source(&file_path, &source)?;
881 if let Some(span) = &mut compile_span {
882 span.mark_compile_succeeded();
883 }
884 drop(compile_span);
885 if let Err(err) = bytecode_cache::store_module(&lookup.key, &compiled) {
886 if std::env::var_os("HARN_BYTECODE_CACHE_DEBUG").is_some() {
887 eprintln!(
888 "[harn] module cache write skipped for {}: {err}",
889 file_path.display()
890 );
891 }
892 }
893 compiled
894 };
895 let mut prepared = {
896 let _load_span = self.module_load_span();
897 Arc::new(PreparedModuleArtifact::from_cached(cached))
898 };
899 if bytecode_cache::cache_enabled() {
900 prepared =
901 self.prepared_module_cache
902 .insert(canonical.clone(), &source, prepared);
903 }
904 prepared
905 };
906
907 let module_source_dir = file_path.parent().map(|p| p.to_path_buf());
908 let loaded = Arc::new(
909 self.instantiate_module(module_source_dir, artifact.as_ref())
910 .await?,
911 );
912 self.imported_paths.pop();
913 {
914 let _load_span = self.module_load_span();
915 Arc::make_mut(&mut self.module_cache)
916 .insert(canonical.clone(), Arc::clone(&loaded));
917 }
918 self.record_module_loaded();
919 if let ImportProjection::BindCaller(selected_names) = projection {
920 let _load_span = self.module_load_span();
921 self.export_loaded_module(&canonical, &loaded, selected_names)?;
922 }
923
924 if self.imported_paths.is_empty() {
928 let _load_span = self.module_load_span();
929 self.flush_deferred_cyclic_imports()?;
930 }
931
932 Ok(())
933 })
934 }
935
936 fn flush_deferred_cyclic_imports(&mut self) -> Result<(), VmError> {
945 if self.deferred_cyclic_imports.is_empty() {
946 return Ok(());
947 }
948 let deferred = std::mem::take(&mut self.deferred_cyclic_imports);
949 let mut still_pending = Vec::new();
950 for import in deferred {
951 let (Some(importer), Some(target)) = (
952 self.module_cache.get(&import.importer).cloned(),
953 self.module_cache.get(&import.target).cloned(),
954 ) else {
955 still_pending.push(import);
959 continue;
960 };
961
962 let export_names = module_import_names(
963 &import.target.display().to_string(),
964 &target,
965 import.selected_names.as_deref(),
966 )?;
967
968 let mut module_state = importer._module_state.lock();
969 for name in export_names {
970 if module_state.get(&name).is_some() {
973 continue;
974 }
975 if let Some(closure) = target.functions.get(&name) {
976 module_state.define(&name, VmValue::Closure(Arc::clone(closure)), false)?;
977 } else if let Some(value) = target.public_values.get(&name) {
978 module_state.define(&name, value.clone(), false)?;
980 } else if target
981 .public_exports
982 .get(&name)
983 .is_some_and(|kind| !kind.has_runtime_value())
984 {
985 continue;
987 } else {
988 return Err(VmError::Runtime(format!(
989 "Import error: '{name}' is not defined in {}",
990 import.target.display()
991 )));
992 }
993 }
994 }
995 self.deferred_cyclic_imports = still_pending;
996 Ok(())
997 }
998
999 fn cache_key_for_import(&self, path: &str) -> Result<PathBuf, VmError> {
1004 if let Some(module) = path
1005 .strip_prefix("std/")
1006 .or_else(|| (path == "observability").then_some("observability"))
1007 {
1008 return Ok(PathBuf::from(format!("<stdlib>/{module}.harn")));
1009 }
1010 let base = self
1011 .source_dir
1012 .clone()
1013 .unwrap_or_else(|| PathBuf::from("."));
1014 let file_path = self.resolve_module_import_path(&base, path)?;
1015 Ok(file_path.canonicalize().unwrap_or(file_path))
1016 }
1017
1018 async fn loaded_module_for_path(
1019 &mut self,
1020 path: &Path,
1021 ) -> Result<(PathBuf, Arc<LoadedModule>), VmError> {
1022 self.ensure_execution_available()?;
1023 let path_str = path.to_string_lossy().into_owned();
1024 self.materialize_import(&path_str).await?;
1025
1026 let mut file_path = if path.is_absolute() {
1027 path.to_path_buf()
1028 } else {
1029 self.source_dir
1030 .clone()
1031 .unwrap_or_else(|| PathBuf::from("."))
1032 .join(path)
1033 };
1034 if !file_path.exists() && file_path.extension().is_none() {
1035 file_path.set_extension("harn");
1036 }
1037
1038 let canonical = file_path
1039 .canonicalize()
1040 .unwrap_or_else(|_| file_path.clone());
1041 let loaded = self.module_cache.get(&canonical).cloned().ok_or_else(|| {
1042 VmError::Runtime(format!(
1043 "Import error: failed to cache loaded module '{}'",
1044 canonical.display()
1045 ))
1046 })?;
1047 Ok((canonical, loaded))
1048 }
1049
1050 pub async fn load_public_module_callable(
1052 &mut self,
1053 path: &Path,
1054 name: &str,
1055 ) -> Result<Arc<VmClosure>, VmError> {
1056 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1057 if !loaded.public_exports.contains_key(name) {
1058 let hint = if loaded.functions.contains_key(name) {
1059 "; it is defined there but not `pub`"
1060 } else {
1061 ""
1062 };
1063 return Err(VmError::Runtime(format!(
1064 "callable '{name}' is not exported by module '{}'{hint}",
1065 canonical.display()
1066 )));
1067 }
1068 loaded.functions.get(name).cloned().ok_or_else(|| {
1069 VmError::Runtime(format!(
1070 "Import error: exported callable '{name}' is missing from {}",
1071 canonical.display()
1072 ))
1073 })
1074 }
1075
1076 pub async fn load_module_exports(
1079 &mut self,
1080 path: &Path,
1081 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1082 let (canonical, loaded) = self.loaded_module_for_path(path).await?;
1083 exported_function_closures(&loaded, &canonical)
1084 }
1085
1086 pub async fn load_module_exports_from_source(
1089 &mut self,
1090 source_key: impl Into<PathBuf>,
1091 source: &str,
1092 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1093 self.ensure_execution_available()?;
1094 let synthetic = source_key.into();
1095 let loaded = self
1096 .load_module_from_source(synthetic.clone(), source)
1097 .await?;
1098 exported_function_closures(&loaded, &synthetic)
1099 }
1100
1101 pub async fn load_module_callable_from_source(
1106 &mut self,
1107 source_key: impl Into<PathBuf>,
1108 source: &str,
1109 name: &str,
1110 ) -> Result<Option<Arc<VmClosure>>, VmError> {
1111 self.ensure_execution_available()?;
1112 let synthetic = source_key.into();
1113 let loaded = self.load_module_from_source(synthetic, source).await?;
1114 Ok(loaded.functions.get(name).cloned())
1115 }
1116
1117 pub async fn load_module_exports_from_import(
1121 &mut self,
1122 import_path: &str,
1123 ) -> Result<BTreeMap<String, Arc<VmClosure>>, VmError> {
1124 self.ensure_execution_available()?;
1125 self.materialize_import(import_path).await?;
1126
1127 if let Some(module) = import_path
1128 .strip_prefix("std/")
1129 .or_else(|| (import_path == "observability").then_some("observability"))
1130 {
1131 let synthetic = PathBuf::from(format!("<stdlib>/{module}.harn"));
1132 let loaded = self.module_cache.get(&synthetic).cloned().ok_or_else(|| {
1133 VmError::Runtime(format!(
1134 "Import error: failed to cache loaded module '{}'",
1135 synthetic.display()
1136 ))
1137 })?;
1138 return exported_function_closures(&loaded, &synthetic);
1139 }
1140
1141 let base = self
1142 .source_dir
1143 .clone()
1144 .unwrap_or_else(|| PathBuf::from("."));
1145 let file_path = self.resolve_module_import_path(&base, import_path)?;
1146 self.load_module_exports(&file_path).await
1147 }
1148}
1149
1150#[cfg(test)]
1151#[path = "modules_tests.rs"]
1152mod tests;