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