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