1#[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#[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#[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#[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#[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#[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 #[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 #[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 #[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 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 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 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 ¤t,
357 );
358 result
359 }
360
361 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 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 #[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 #[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 #[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 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 pub fn compile_halc_bytecode_artifact(&mut self, bytes: &[u8]) -> Result<Vec<u8>, String> {
527 let module = kernel::halc::decode_halc(bytes)?;
528 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 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 ¤t,
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, ®istry, config)
686 } else {
687 vm::compile_source_with_config(body, ®istry, 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}