Skip to main content

hara_native/runtime/
bytecode.rs

1/// Experimental bytecode VM entry points (issue #195), gated behind the
2/// non-default `bytecode-vm` feature. These accept only closed,
3/// namespace-independent forms in the supported synchronous subset;
4/// anything else fails as a typed compile error. There is no fallback to
5/// the default evaluator, and `Runtime::eval_native` is unaffected.
6///
7/// Programs are returned inside `Rc` because compiled closures share the
8/// program with their executing machines; `Rc::clone` is the cheap way to
9/// pass one around.
10#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
11#[derive(Clone)]
12pub(crate) struct SourceBytecodeCache {
13    directory: std::path::PathBuf,
14}
15
16#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
17struct SourceBytecodeCacheEntry {
18    namespace_form: String,
19    program: crate::direct_native::ValidatedProgram,
20}
21
22#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
23impl SourceBytecodeCache {
24    pub(crate) fn new(root: &std::path::Path, source_index_fingerprint: [u8; 32]) -> Self {
25        Self {
26            directory: root
27                .join("target/hara/test-bytecode/v2")
28                .join(hex_digest(&source_index_fingerprint)),
29        }
30    }
31
32    fn path_for(&self, namespace: &str, source: &str) -> std::path::PathBuf {
33        use sha2::{Digest, Sha256};
34
35        let mut digest = Sha256::new();
36        digest.update(b"hara-direct-native-source-v1\0");
37        digest.update(env!("CARGO_PKG_VERSION").as_bytes());
38        digest.update([0]);
39        digest.update(namespace.as_bytes());
40        digest.update([0]);
41        digest.update(source.as_bytes());
42        self.directory
43            .join(format!("{}.hbc", hex_digest(&digest.finalize())))
44    }
45
46    fn namespace_path_for(&self, namespace: &str, source: &str) -> std::path::PathBuf {
47        self.path_for(namespace, source).with_extension("ns")
48    }
49
50    fn load(
51        &self,
52        namespace: &str,
53        source: &str,
54    ) -> Option<SourceBytecodeCacheEntry> {
55        let path = self.path_for(namespace, source);
56        let namespace_form = std::fs::read_to_string(self.namespace_path_for(namespace, source)).ok()?;
57        let bytes = std::fs::read(path).ok()?;
58        let program = crate::vm::decode_program(&bytes).ok()?;
59        if program.namespace.as_deref() != Some(namespace) {
60            return None;
61        }
62        Some(SourceBytecodeCacheEntry {
63            namespace_form,
64            program: crate::direct_native::ValidatedProgram::from_artifact(Rc::new(program)),
65        })
66    }
67
68    fn store(
69        &self,
70        namespace: &str,
71        source: &str,
72        namespace_form: &str,
73        program: &crate::vm::Program,
74    ) {
75        let path = self.path_for(namespace, source);
76        let namespace_path = self.namespace_path_for(namespace, source);
77        if path.is_file() && namespace_path.is_file() {
78            return;
79        }
80        let Ok(bytes) = crate::vm::encode_program(program) else {
81            return;
82        };
83        if std::fs::create_dir_all(&self.directory).is_err() {
84            return;
85        }
86        let id = std::process::id();
87        let namespace_temporary = namespace_path.with_extension(format!("ns.tmp-{id}"));
88        let program_temporary = path.with_extension(format!("hbc.tmp-{id}"));
89        if std::fs::write(&namespace_temporary, namespace_form).is_err()
90            || std::fs::write(&program_temporary, bytes).is_err()
91        {
92            let _ = std::fs::remove_file(namespace_temporary);
93            let _ = std::fs::remove_file(program_temporary);
94            return;
95        }
96        let _ = std::fs::rename(namespace_temporary, namespace_path);
97        let _ = std::fs::rename(program_temporary, path);
98    }
99}
100
101#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
102fn hex_digest(bytes: &[u8]) -> String {
103    const HEX: &[u8; 16] = b"0123456789abcdef";
104    let mut output = String::with_capacity(bytes.len() * 2);
105    for byte in bytes {
106        output.push(HEX[(byte >> 4) as usize] as char);
107        output.push(HEX[(byte & 0x0f) as usize] as char);
108    }
109    output
110}
111
112#[cfg(feature = "bytecode-vm")]
113pub fn bytecode_namespace_registry() -> kernel::NamespaceRegistry<core::Value> {
114    core::minimal_namespace_registry()
115}
116
117#[cfg(feature = "bytecode-vm")]
118pub fn compile_bytecode(source: &str) -> Result<std::rc::Rc<vm::Program>, String> {
119    let registry = bytecode_namespace_registry();
120    vm::compile_source_with(source, &registry)
121        .map(std::rc::Rc::new)
122        .map_err(|error| error.to_string())
123}
124
125/// Executes a previously compiled and validated program.
126#[cfg(feature = "bytecode-vm")]
127pub fn execute_bytecode(program: &std::rc::Rc<vm::Program>) -> Result<String, String> {
128    let registry = bytecode_namespace_registry();
129    vm::execute_program_with_globals(program.clone(), &registry)
130        .map(|value| value.display())
131        .map_err(|error| error.to_string())
132}
133
134/// Returns tracing-JIT counters retained for a compiled bytecode program.
135/// `None` means this build has no tracing-JIT feature enabled.
136#[cfg(all(feature = "bytecode-vm", feature = "tracing-jit"))]
137pub fn bytecode_jit_telemetry(program: &std::rc::Rc<vm::Program>) -> jit::JitTelemetry {
138    vm::machine::cached_jit_telemetry(program)
139}
140
141/// Compiles source into a checksummed, versioned bytecode artifact.
142#[cfg(feature = "bytecode-vm")]
143pub fn compile_bytecode_artifact(source: &str) -> Result<Vec<u8>, String> {
144    let program = compile_bytecode(source)?;
145    vm::encode_program(program.as_ref())
146}
147
148/// Decodes, validates, and executes a bytecode artifact.
149#[cfg(feature = "bytecode-vm")]
150pub fn execute_bytecode_artifact(bytes: &[u8]) -> Result<String, String> {
151    let program = std::rc::Rc::new(vm::decode_program(bytes)?);
152    execute_bytecode(&program)
153}
154
155/// Compiles and executes a source string through the experimental VM.
156#[cfg(feature = "bytecode-vm")]
157pub fn eval_bytecode_native(source: &str) -> Result<String, String> {
158    execute_bytecode(&compile_bytecode(source)?)
159}
160
161impl Runtime {
162    #[cfg(feature = "bytecode-vm")]
163    pub(crate) fn compile_bytecode_product(
164        &self,
165        source: &str,
166    ) -> Result<crate::compiled_product::CompiledProduct, String> {
167        let source_digest = crate::compiled_product::sha256_hex(source.as_bytes());
168        let compiler_id = format!("hara-runtime/{}", env!("CARGO_PKG_VERSION"));
169        let options = format!("target=HBC0;namespace={}", self.current_namespace());
170        let program = self.compile_bytecode(source)?;
171        let bytes = vm::encode_program(program.as_ref())?;
172        let module_digest = crate::compiled_product::sha256_hex(&bytes);
173        let key = crate::compiled_product::ProductCacheKey::with_module_digests(
174            crate::compiled_product::CompiledProductKind::HbcModule,
175            source_digest.clone(),
176            compiler_id.clone(),
177            "hbc0",
178            options.as_bytes(),
179            vec![module_digest],
180        );
181        if let Some(product) = self.product_cache.borrow().get(&key).cloned() {
182            return Ok(product);
183        }
184        let product = crate::compiled_product::CompiledProduct::new(
185            crate::compiled_product::CompiledProductKind::HbcModule,
186            source_digest,
187            vec![crate::compiled_product::sha256_hex(&bytes)],
188            compiler_id,
189            "hbc0",
190            options.as_bytes(),
191            bytes,
192        );
193        self.product_cache.borrow_mut().insert(product.clone())?;
194        Ok(product)
195    }
196
197    #[cfg(feature = "whole-wasm")]
198    pub(crate) fn compile_whole_wasm_product(
199        &self,
200        source: &str,
201    ) -> Result<crate::compiled_product::CompiledProduct, String> {
202        let source_digest = crate::compiled_product::sha256_hex(source.as_bytes());
203        let compiler_id = format!("hara-runtime/{}", env!("CARGO_PKG_VERSION"));
204        let options = format!("target=HNW0;namespace={}", self.current_namespace());
205        let abi_version = format!("hnw0/{}", crate::whole_wasm::HNW_ABI_VERSION);
206        let hbc_product = self.compile_bytecode_product(source)?;
207        let module_digest = hbc_product.manifest.artifact_digest.clone();
208        let key = crate::compiled_product::ProductCacheKey::with_module_digests(
209            crate::compiled_product::CompiledProductKind::WholeWasm,
210            source_digest.clone(),
211            compiler_id.clone(),
212            abi_version.clone(),
213            options.as_bytes(),
214            vec![module_digest],
215        );
216        if let Some(product) = self.product_cache.borrow().get(&key).cloned() {
217            return Ok(product);
218        }
219        let hbc = hbc_product.bytes;
220        let bytes = crate::whole_wasm::compile_artifact_from_hbc(&hbc)?;
221        let product = crate::compiled_product::CompiledProduct::new(
222            crate::compiled_product::CompiledProductKind::WholeWasm,
223            hbc_product.manifest.source_digest,
224            vec![hbc_product.manifest.artifact_digest],
225            compiler_id,
226            abi_version,
227            options.as_bytes(),
228            bytes,
229        );
230        self.product_cache.borrow_mut().insert(product.clone())?;
231        Ok(product)
232    }
233
234    /// Installs the typed native driver behind `std.native.Kernel/*`.
235    #[cfg(not(target_arch = "wasm32"))]
236    pub fn install_native_kernel_provider(&mut self, provider: Rc<core::KernelProvider>) {
237        self.providers.install_kernel(provider);
238    }
239
240    /// Installs the native host service handler used by `std.native.Host/call`.
241    /// Embedders can expose process-local services without converting values
242    /// through JavaScript or textual serialization.
243    #[cfg(not(target_arch = "wasm32"))]
244    pub fn install_native_host_handler(
245        &mut self,
246        handler: Rc<dyn Fn(String, String, Vec<core::Value>) -> Result<core::Value, String>>,
247    ) {
248        self.native_host_handler = Some(handler);
249    }
250
251    /// Installs a publication-linked native ABI module and exposes it through
252    /// the same promise-returning Host/call boundary used by browser embedders.
253    #[cfg(not(target_arch = "wasm32"))]
254    pub fn install_native_module(
255        &mut self,
256        module: std::sync::Arc<dyn hara_abi::NativeModule>,
257    ) -> Result<(), String> {
258        self.native_modules.install(module)?;
259        let registry = self.native_modules.clone();
260        self.native_host_handler = Some(Rc::new(move |service, operation, arguments| {
261            registry.invoke(service, operation, arguments)
262        }));
263        Ok(())
264    }
265
266    #[cfg(not(target_arch = "wasm32"))]
267    pub fn native_module_services(&self) -> Vec<String> {
268        self.native_modules.services()
269    }
270}
271
272#[cfg(feature = "bytecode-vm")]
273impl Runtime {
274    /// Compiles source against this runtime's namespace registry:
275    /// std.foundation vars and anything already interned are visible to
276    /// the compiler's two-phase global check (issue #223). The program
277    /// is validated but not executed; globals intern only at execution.
278    pub fn compile_bytecode(&self, source: &str) -> Result<std::rc::Rc<vm::Program>, String> {
279        self.compile_bytecode_with_policy(source, false)
280    }
281
282    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
283    fn compile_bytecode_for_direct_native(
284        &self,
285        source: &str,
286    ) -> Result<std::rc::Rc<vm::Program>, String> {
287        self.compile_bytecode_with_policy(source, true)
288    }
289
290    fn compile_spanned_forms_for_direct_native(
291        &self,
292        forms: &[kernel::SpannedForm],
293    ) -> Result<std::rc::Rc<vm::Program>, String> {
294        let namespace = self.current_namespace();
295        let config = self
296            .generated_configs
297            .get(&namespace)
298            .cloned()
299            .unwrap_or_else(kernel::GeneratedNamespaceConfig::defaults);
300        core::with_macros(self.macros.clone(), || {
301            vm::compile_spanned_forms_with_config_allow_unbound_globals(
302                forms,
303                &self.namespace_registry,
304                config,
305            )
306            .map(|mut program| {
307                program.namespace = Some(namespace.clone());
308                std::rc::Rc::new(program)
309            })
310            .map_err(|error| error.to_string())
311        })
312    }
313
314    fn compile_bytecode_with_policy(
315        &self,
316        source: &str,
317        allow_unbound_globals_for_direct_native: bool,
318    ) -> Result<std::rc::Rc<vm::Program>, String> {
319        core::with_macros(self.macros.clone(), || {
320            let forms = kernel::read_forms(source).map_err(|error| error.to_string())?;
321            let has_namespace_form = forms.iter().any(|form| {
322                matches!(
323                    crate::core::form_without_metadata(&form.form),
324                    crate::kernel::Form::List(items)
325                        if matches!(items.first(), Some(crate::kernel::Form::Symbol(operator)) if operator == "ns" || operator == "ns+")
326                )
327            });
328            let config = if has_namespace_form {
329                vm::source_namespace_config(&forms).map_err(|error| error.to_string())?
330            } else {
331                self.generated_configs
332                    .get(&self.current_namespace())
333                    .cloned()
334                    .unwrap_or_else(kernel::GeneratedNamespaceConfig::defaults)
335            };
336            #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
337            let allow_unbound_globals = allow_unbound_globals_for_direct_native
338                || (self.execution_backend == "direct-native"
339                    && vm::source_uses_dynamic_evaluation(source).unwrap_or(false));
340            #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
341            let allow_unbound_globals = false;
342            let compiled = if allow_unbound_globals {
343                vm::compile_source_with_config_allow_unbound_globals(
344                    source,
345                    &self.namespace_registry,
346                    config,
347                )
348            } else {
349                vm::compile_source_with_config(source, &self.namespace_registry, config)
350            };
351            compiled
352                .map(|mut program| {
353                    program.namespace =
354                        Some(self.namespace_registry.current().name().as_str().to_owned());
355                    program
356                })
357                .map(std::rc::Rc::new)
358                .map_err(|error| error.to_string())
359        })
360    }
361
362    /// Executes an already compiled program against this runtime's namespace
363    /// registry. Embedding hosts use this for prepare-once/call-many paths
364    /// without decoding an artifact or rebuilding the program on every call.
365    pub fn execute_compiled_bytecode(
366        &mut self,
367        program: std::rc::Rc<vm::Program>,
368    ) -> Result<String, String> {
369        self.execute_compiled_bytecode_value(program)
370            .map(|value| value.display())
371    }
372
373    /// Executes an already compiled program and returns its immutable runtime
374    /// value directly. This avoids display serialization and lets native hosts
375    /// inspect persistent results through their shared representation.
376    pub fn execute_compiled_bytecode_value(
377        &mut self,
378        program: std::rc::Rc<vm::Program>,
379    ) -> Result<core::Value, String> {
380        let result = self.execute_compiled_bytecode_registry_value(program);
381        let current = self.namespace_registry.current().name().as_str().to_owned();
382        core::select_namespace_environment(
383            &self.namespace_registry,
384            self.execution.environment_mut(),
385            &current,
386        );
387        result
388    }
389
390    /// Executes a prepared program directly against the namespace registry,
391    /// without copying bindings into the compatibility environment per call.
392    pub fn execute_compiled_bytecode_registry_value(
393        &mut self,
394        program: std::rc::Rc<vm::Program>,
395    ) -> Result<core::Value, String> {
396        let mut declaration_environment = HashMap::new();
397        let namespace_source = self.namespace_source();
398        core::with_macros(self.macros.clone(), || {
399            core::with_namespace_source(namespace_source, || {
400                core::with_protocols(&self.protocols, || {
401                    core::with_namespace_registry(&self.namespace_registry, || {
402                        core::with_declaration_transaction(&mut declaration_environment, |_| {
403                            vm::execute_program_with_globals(program, &self.namespace_registry)
404                                .map_err(|error| error.to_string())
405                        })
406                    })
407                })
408            })
409        })
410    }
411
412    /// Compiles and executes through the experimental VM against this
413    /// runtime's registry, then syncs the flat env so later `eval_native`
414    /// calls see the vars the program interned. No fallback: unsupported
415    /// forms fail as compile errors. `eval_native` is unaffected.
416    pub fn eval_bytecode_native(&mut self, source: &str) -> Result<String, String> {
417        let program = self.compile_bytecode(source)?;
418        self.execute_compiled_bytecode(program)
419    }
420
421    /// Executes a validated program through the opt-in bytecode VM plus
422    /// native-substrate boundary. Ordinary Hara functions remain VM-owned;
423    /// only the closed native/protocol/evaluator target inventory crosses into
424    /// Rust callouts.
425    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
426    pub fn execute_compiled_direct_native(
427        &mut self,
428        program: std::rc::Rc<vm::Program>,
429    ) -> Result<crate::direct_native::NativeExecutionReport, String> {
430        let program = crate::direct_native::ValidatedProgram::validate(program)?;
431        self.execute_compiled_direct_native_validated(program)
432    }
433
434    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
435    fn execute_compiled_direct_native_validated(
436        &mut self,
437        program: crate::direct_native::ValidatedProgram,
438    ) -> Result<crate::direct_native::NativeExecutionReport, String> {
439        let program_image = program.program();
440        if let Some(namespace) = &program_image.namespace {
441            self.namespace_registry.set_current(namespace);
442        }
443        let mut declaration_environment = HashMap::new();
444        let namespace_source = self.namespace_source();
445        let execute = || {
446            core::with_test_runner(&self.test_runner, || {
447                core::with_capability_providers(
448                    self.providers.file(),
449                    self.providers.socket(),
450                    self.providers.process(),
451                    self.providers.kernel(),
452                    || {
453                        core::with_package_catalog(&self.package_catalog, || {
454                            core::with_promise_provider(self.providers.promise(), || {
455                                core::with_macros(self.macros.clone(), || {
456                                    core::with_namespace_registry(&self.namespace_registry, || {
457                                        core::with_namespace_source(namespace_source, || {
458                                            core::with_protocols(&self.protocols, || {
459                                                let loader = Self::direct_native_namespace_loader(
460                                                    self.direct_native.clone(),
461                                                    self.direct_native_multimethods.clone(),
462                                                    self.direct_native_source_cache.clone(),
463                                                );
464                                                core::with_direct_native_namespace_loader(
465                                                    loader,
466                                                    || {
467                                                        core::with_declaration_transaction(
468                                                            &mut declaration_environment,
469                                                            |_| {
470                                                                self.direct_native
471                                                                    .execute_blocking_validated_with_multimethods(
472                                                                        program,
473                                                                        self.direct_native_multimethods
474                                                                            .clone(),
475                                                                    )
476                                                            },
477                                                        )
478                                                    },
479                                                )
480                                            })
481                                        })
482                                    })
483                                })
484                            })
485                        })
486                    },
487                )
488            })
489        };
490        #[cfg(not(target_arch = "wasm32"))]
491        let result = if let Some(handler) = self.native_host_handler.clone() {
492            core::with_host_calls(handler, execute)
493        } else {
494            execute()
495        };
496        if result.is_ok() {
497            self.save_namespace();
498            self.refresh_qualified_bindings();
499        }
500        result
501    }
502
503    /// Builds the resource hook used by the shared namespace transaction when
504    /// direct-native execution is selected. The hook compiles source-backed
505    /// namespaces after their namespace declaration has been prepared and
506    /// executes both source and artifact-backed namespaces through the same
507    /// bytecode VM/native-substrate engine.
508    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
509    pub(crate) fn direct_native_namespace_loader(
510        engine: crate::direct_native::NativeEngine,
511        multimethods: core::MultiMethodRegistry,
512        source_cache: Option<SourceBytecodeCache>,
513    ) -> Rc<
514        dyn Fn(
515            &str,
516            core::NamespaceResource,
517            &mut HashMap<String, core::Value>,
518        ) -> Result<(), String>,
519    > {
520        Rc::new(move |name, resource, environment| {
521            load_direct_native_namespace(
522                &engine,
523                &multimethods,
524                source_cache.as_ref(),
525                name,
526                resource,
527                environment,
528            )
529        })
530    }
531
532    /// Compiles and executes source through the bytecode VM/native-substrate
533    /// backend. Compilation-time namespace preparation and macro expansion
534    /// retain their existing evaluator seam; no evaluator call is permitted
535    /// once the validated program enters the native backend.
536    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
537    pub fn eval_direct_native(&mut self, source: &str) -> Result<String, String> {
538        let program = self.compile_bytecode_for_direct_native(source)?;
539        self.execute_compiled_direct_native_validated(
540            crate::direct_native::ValidatedProgram::from_compiler(program),
541        )
542        .map(|report| report.value.display())
543    }
544
545    /// Compiles against this runtime's namespaces and persists the validated
546    /// program for later native or browser execution.
547    pub fn compile_bytecode_artifact(&self, source: &str) -> Result<Vec<u8>, String> {
548        let program = self.compile_bytecode(source)?;
549        vm::encode_program(program.as_ref())
550    }
551
552    /// Compiles package source whose namespace forms may refer to Vars that
553    /// materialize in a later top-level form or a dependency package. The
554    /// resulting global read stays late-bound, so an absent Var still fails at
555    /// its runtime call site.
556    pub(crate) fn compile_package_bytecode_artifact(
557        &self,
558        source: &str,
559    ) -> Result<Vec<u8>, String> {
560        let program = self.compile_bytecode_with_policy(source, true)?;
561        vm::encode_program(program.as_ref())
562    }
563
564    /// Lowers a HALC module directly to persistent bytecode. No source text is
565    /// reconstructed, and the module's normalized schema graph is embedded in
566    /// the HBC artifact for later inference and specialization tiers.
567    pub fn compile_halc_bytecode_artifact(&mut self, bytes: &[u8]) -> Result<Vec<u8>, String> {
568        let module = kernel::halc::decode_halc(bytes)?;
569        // HALC retains the source namespace declaration as structured data.
570        // Apply it through the ordinary module loader before lowering so
571        // aliases, refers, intrinsics, and required resources are identical
572        // to interpreted HALC. Only the declaration is evaluated here; the
573        // remaining forms go directly to the bytecode compiler below.
574        if let Some(namespace_form) = module.forms.iter().find(|form| {
575            matches!(
576                core::form_without_metadata(form),
577                Form::List(items)
578                    if matches!(items.first(), Some(Form::Symbol(operator)) if operator == "ns")
579            )
580        }) {
581            self.eval_forms(vec![synthetic_spanned_form(namespace_form.clone())], false)?;
582        } else {
583            self.use_namespace(&module.namespace);
584        }
585        let program = vm::compile_halc_module(&module, &self.namespace_registry)
586            .map_err(|error| error.to_string())?;
587        vm::encode_program(&program)
588    }
589
590    /// Executes a persisted artifact against this runtime's namespaces.
591    pub fn eval_bytecode_artifact(&mut self, bytes: &[u8]) -> Result<String, String> {
592        let program = std::rc::Rc::new(vm::decode_program(bytes)?);
593        #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
594        if self.execution_backend == "direct-native" {
595            let schema_types = program.schema_types.clone();
596            let function_types = program.function_types.clone();
597            let inferred_function_types = program.inferred_function_types.clone();
598            let result = self
599                .execute_compiled_direct_native_validated(
600                    crate::direct_native::ValidatedProgram::from_artifact(program),
601                )
602                .map(|report| report.value.display());
603            if result.is_ok() {
604                self.halc_schema_types.extend(schema_types);
605                self.halc_function_types.extend(function_types);
606                self.halc_inferred_function_types
607                    .extend(inferred_function_types);
608            }
609            return result;
610        }
611        if let Some(namespace) = &program.namespace {
612            self.namespace_registry.set_current(namespace);
613        }
614        let schema_types = program.schema_types.clone();
615        let function_types = program.function_types.clone();
616        let inferred_function_types = program.inferred_function_types.clone();
617        let mut declaration_environment = HashMap::new();
618        let namespace_source = self.namespace_source();
619        let result = core::with_macros(self.macros.clone(), || {
620            core::with_namespace_source(namespace_source, || {
621                core::with_protocols(&self.protocols, || {
622                    core::with_namespace_registry(&self.namespace_registry, || {
623                        core::with_declaration_transaction(&mut declaration_environment, |_| {
624                            vm::execute_program_with_globals(program, &self.namespace_registry)
625                                .map(|value| value.display())
626                                .map_err(|error| error.to_string())
627                        })
628                    })
629                })
630            })
631        });
632        if result.is_ok() {
633            self.halc_schema_types.extend(schema_types);
634            self.halc_function_types.extend(function_types);
635            self.halc_inferred_function_types
636                .extend(inferred_function_types);
637        }
638        let current = self.namespace_registry.current().name().as_str().to_owned();
639        core::select_namespace_environment(
640            &self.namespace_registry,
641            self.execution.environment_mut(),
642            &current,
643        );
644        result
645    }
646}
647
648#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
649fn load_direct_native_namespace(
650    engine: &crate::direct_native::NativeEngine,
651    multimethods: &core::MultiMethodRegistry,
652    source_cache: Option<&SourceBytecodeCache>,
653    name: &str,
654    resource: core::NamespaceResource,
655    environment: &mut HashMap<String, core::Value>,
656) -> Result<(), String> {
657    let profile = std::env::var_os("HARA_NATIVE_PROFILE_NAMESPACE_LOADS").is_some();
658    let started = std::time::Instant::now();
659    let program = match &resource {
660        core::NamespaceResource::Source(_) => {
661            compile_direct_native_source_namespace(name, &resource, environment, source_cache)?
662        }
663        #[cfg(not(target_arch = "wasm32"))]
664        core::NamespaceResource::SourcePath(_) => {
665            compile_direct_native_source_namespace(name, &resource, environment, source_cache)?
666        }
667        core::NamespaceResource::Bytecode {
668            namespace_form,
669            artifact,
670        } => {
671            for (index, form) in kernel::parse_forms(&namespace_form)?
672                .into_iter()
673                .enumerate()
674            {
675                let namespace_value = core::form_to_value(&form)?;
676                core::eval_bytecode_management_in(&namespace_value, environment)
677                    .map_err(|error| format!("{name}: namespace form {}: {error}", index + 1))?;
678            }
679            let registry = core::namespace_registry()?;
680            registry.set_current(name);
681            let mut program = vm::decode_program(&artifact)
682                .map_err(|error| format!("{name}: direct-native artifact: {error}"))?;
683            program.namespace = Some(name.to_owned());
684            crate::direct_native::ValidatedProgram::from_artifact(Rc::new(program))
685        }
686    };
687    let result = engine
688        .execute_blocking_validated_with_multimethods(program, multimethods.clone())
689        .map(|_| ())
690        .map_err(|error| format!("{name}: direct-native execution: {error}"));
691    if profile {
692        eprintln!(
693            "PROFILE namespace {}={}ms {}",
694            name,
695            started.elapsed().as_millis(),
696            if result.is_ok() { "ok" } else { "error" }
697        );
698    }
699    result
700}
701
702#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
703fn compile_direct_native_source_namespace(
704    name: &str,
705    resource: &core::NamespaceResource,
706    environment: &mut HashMap<String, core::Value>,
707    source_cache: Option<&SourceBytecodeCache>,
708) -> Result<crate::direct_native::ValidatedProgram, String> {
709    let source = core::read_source_resource(resource, name)?;
710    if let Some(entry) = source_cache.and_then(|cache| cache.load(name, &source)) {
711        if std::env::var_os("HARA_NATIVE_PROFILE_NAMESPACE_LOADS").is_some() {
712            eprintln!("PROFILE source-cache {name}=hit");
713        }
714        let forms = kernel::read_forms(&entry.namespace_form).map_err(|error| error.to_string())?;
715        let namespace = forms
716            .first()
717            .filter(|form| {
718                matches!(
719                    core::form_without_metadata(&form.form),
720                    kernel::Form::List(items)
721                        if matches!(items.first(), Some(kernel::Form::Symbol(operator)) if operator == "ns" || operator == "ns+")
722                )
723            })
724            .ok_or_else(|| format!("{name}: cached namespace declaration is invalid"))?;
725        let namespace_value = core::form_to_value(&namespace.form)?;
726        core::eval_bytecode_management_in(&namespace_value, environment)
727            .map_err(|error| format!("{name}: namespace declaration: {error}"))?;
728        let registry = core::namespace_registry()?;
729        registry.set_current(name);
730        return Ok(entry.program);
731    }
732    if std::env::var_os("HARA_NATIVE_PROFILE_NAMESPACE_LOADS").is_some() {
733        eprintln!("PROFILE source-cache {name}=miss");
734    }
735    let forms = kernel::read_forms(&source).map_err(|error| error.to_string())?;
736    let mut body_offset = 0;
737    let mut namespace_form = None;
738    if forms.first().is_some_and(|form| {
739        matches!(
740            core::form_without_metadata(&form.form),
741            kernel::Form::List(items)
742                if matches!(items.first(), Some(kernel::Form::Symbol(operator)) if operator == "ns" || operator == "ns+")
743        )
744    }) {
745        let namespace_value = core::form_to_value(&forms[0].form)?;
746        core::eval_bytecode_management_in(&namespace_value, environment)
747            .map_err(|error| format!("{name}: namespace declaration: {error}"))?;
748        body_offset = forms[0].span.end.offset;
749        namespace_form = source
750            .get(forms[0].span.start.offset..body_offset)
751            .map(str::to_owned);
752    }
753    let config = vm::source_namespace_config(&forms)
754        .map_err(|error| format!("{name}: namespace configuration: {error}"))?;
755    let registry = core::namespace_registry()?;
756    registry.set_current(name);
757    let body = source
758        .get(body_offset..)
759        .ok_or_else(|| format!("{name}: namespace form offset is invalid"))?;
760    // A namespace is compiled as one source unit, while registrations such as
761    // `defstruct` publish constructors as earlier forms execute. Keep those
762    // references late-bound so the body can use its own generated Vars (and
763    // Vars from a dependency still being loaded) without falling back to the
764    // tree evaluator. Missing Vars still fail at their native call site.
765    let compile = || vm::compile_source_with_config_allow_unbound_globals(body, &registry, config);
766    let mut program = core::without_direct_native_execution(compile)
767        .map_err(|error| format!("{name}: direct-native compilation: {error}"))?;
768    program.namespace = Some(name.to_owned());
769    if let (Some(cache), Some(namespace_form)) = (source_cache, namespace_form.as_deref()) {
770        cache.store(name, &source, namespace_form, &program);
771    }
772    Ok(crate::direct_native::ValidatedProgram::from_compiler(
773        Rc::new(program),
774    ))
775}
776
777#[cfg(all(
778    test,
779    feature = "bytecode-vm",
780    feature = "direct-native",
781    not(target_arch = "wasm32")
782))]
783mod source_cache_tests {
784    use super::SourceBytecodeCache;
785    use std::fs;
786    use std::path::PathBuf;
787    use std::sync::atomic::{AtomicU64, Ordering};
788
789    struct TempRoot(PathBuf);
790
791    impl Drop for TempRoot {
792        fn drop(&mut self) {
793            let _ = fs::remove_dir_all(&self.0);
794        }
795    }
796
797    fn temp_root() -> TempRoot {
798        static NEXT: AtomicU64 = AtomicU64::new(0);
799        let suffix = NEXT.fetch_add(1, Ordering::Relaxed);
800        let path = std::env::temp_dir().join(format!(
801            "hara-source-bytecode-cache-{}-{suffix}",
802            std::process::id()
803        ));
804        fs::create_dir(&path).expect("cache test temporary root must be new");
805        TempRoot(path)
806    }
807
808    #[test]
809    fn caches_only_the_matching_namespace_and_source() {
810        let root = temp_root();
811        let namespace = "example.cache";
812        let source = "(+ 1 2)";
813        let mut program = crate::vm::compile_source(source).expect("source must compile");
814        program.namespace = Some(namespace.to_owned());
815        let cache = SourceBytecodeCache::new(&root.0, [7; 32]);
816
817        assert!(cache.load(namespace, source).is_none());
818        cache.store(namespace, source, "(ns example.cache)", &program);
819
820        let loaded = cache
821            .load(namespace, source)
822            .expect("stored source must be readable");
823        assert_eq!(loaded.program.program().namespace.as_deref(), Some(namespace));
824        assert_eq!(loaded.namespace_form, "(ns example.cache)");
825        assert!(cache.load(namespace, "(+ 1 3)").is_none());
826        assert!(cache.load("example.other", source).is_none());
827    }
828}