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