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 let registry = bytecode_namespace_registry();
93 vm::compile_source_with(source, ®istry)
94 .map(std::rc::Rc::new)
95 .map_err(|error| error.to_string())
96}
97
98#[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(), ®istry)
103 .map(|value| value.display())
104 .map_err(|error| error.to_string())
105}
106
107#[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#[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#[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#[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 #[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 #[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 #[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 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 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 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 ¤t,
359 );
360 result
361 }
362
363 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 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 #[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 #[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 #[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 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 pub fn compile_halc_bytecode_artifact(&mut self, bytes: &[u8]) -> Result<Vec<u8>, String> {
529 let module = kernel::halc::decode_halc(bytes)?;
530 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 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 ¤t,
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, ®istry, config)
688 } else {
689 vm::compile_source_with_config(body, ®istry, 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}