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