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 = allow_unbound_globals_for_direct_native
311 || (self.execution_backend == "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(crate) fn compile_package_bytecode_artifact(
530 &self,
531 source: &str,
532 ) -> Result<Vec<u8>, String> {
533 let program = self.compile_bytecode_with_policy(source, true)?;
534 vm::encode_program(program.as_ref())
535 }
536
537 pub fn compile_halc_bytecode_artifact(&mut self, bytes: &[u8]) -> Result<Vec<u8>, String> {
541 let module = kernel::halc::decode_halc(bytes)?;
542 if let Some(namespace_form) = module.forms.iter().find(|form| {
548 matches!(
549 core::form_without_metadata(form),
550 Form::List(items)
551 if matches!(items.first(), Some(Form::Symbol(operator)) if operator == "ns")
552 )
553 }) {
554 self.eval_forms(vec![synthetic_spanned_form(namespace_form.clone())], false)?;
555 } else {
556 self.use_namespace(&module.namespace);
557 }
558 let program = vm::compile_halc_module(&module, &self.namespace_registry)
559 .map_err(|error| error.to_string())?;
560 vm::encode_program(&program)
561 }
562
563 pub fn eval_bytecode_artifact(&mut self, bytes: &[u8]) -> Result<String, String> {
565 let program = std::rc::Rc::new(vm::decode_program(bytes)?);
566 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
567 if self.execution_backend == "direct-native" {
568 let schema_types = program.schema_types.clone();
569 let function_types = program.function_types.clone();
570 let inferred_function_types = program.inferred_function_types.clone();
571 let result = self
572 .execute_compiled_direct_native_validated(
573 crate::direct_native::ValidatedProgram::from_artifact(program),
574 )
575 .map(|report| report.value.display());
576 if result.is_ok() {
577 self.halc_schema_types.extend(schema_types);
578 self.halc_function_types.extend(function_types);
579 self.halc_inferred_function_types
580 .extend(inferred_function_types);
581 }
582 return result;
583 }
584 if let Some(namespace) = &program.namespace {
585 self.namespace_registry.set_current(namespace);
586 }
587 let schema_types = program.schema_types.clone();
588 let function_types = program.function_types.clone();
589 let inferred_function_types = program.inferred_function_types.clone();
590 let mut declaration_environment = HashMap::new();
591 let namespace_source = self.namespace_source();
592 let result = core::with_macros(self.macros.clone(), || {
593 core::with_namespace_source(namespace_source, || {
594 core::with_protocols(&self.protocols, || {
595 core::with_namespace_registry(&self.namespace_registry, || {
596 core::with_declaration_transaction(&mut declaration_environment, |_| {
597 vm::execute_program_with_globals(program, &self.namespace_registry)
598 .map(|value| value.display())
599 .map_err(|error| error.to_string())
600 })
601 })
602 })
603 })
604 });
605 if result.is_ok() {
606 self.halc_schema_types.extend(schema_types);
607 self.halc_function_types.extend(function_types);
608 self.halc_inferred_function_types
609 .extend(inferred_function_types);
610 }
611 let current = self.namespace_registry.current().name().as_str().to_owned();
612 core::select_namespace_environment(
613 &self.namespace_registry,
614 self.execution.environment_mut(),
615 ¤t,
616 );
617 result
618 }
619}
620
621#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
622fn load_direct_native_namespace(
623 engine: &crate::direct_native::NativeEngine,
624 multimethods: &core::MultiMethodRegistry,
625 source_cache: Option<&SourceBytecodeCache>,
626 name: &str,
627 resource: core::NamespaceResource,
628 environment: &mut HashMap<String, core::Value>,
629) -> Result<(), String> {
630 let program = match &resource {
631 core::NamespaceResource::Source(_) => {
632 compile_direct_native_source_namespace(name, &resource, environment, source_cache)?
633 }
634 #[cfg(not(target_arch = "wasm32"))]
635 core::NamespaceResource::SourcePath(_) => {
636 compile_direct_native_source_namespace(name, &resource, environment, source_cache)?
637 }
638 core::NamespaceResource::Bytecode {
639 namespace_form,
640 artifact,
641 } => {
642 for (index, form) in kernel::parse_forms(&namespace_form)?
643 .into_iter()
644 .enumerate()
645 {
646 let namespace_value = core::form_to_value(&form)?;
647 core::eval_bytecode_management_in(&namespace_value, environment)
648 .map_err(|error| format!("{name}: namespace form {}: {error}", index + 1))?;
649 }
650 let registry = core::namespace_registry()?;
651 registry.set_current(name);
652 let mut program = vm::decode_program(&artifact)
653 .map_err(|error| format!("{name}: direct-native artifact: {error}"))?;
654 program.namespace = Some(name.to_owned());
655 crate::direct_native::ValidatedProgram::from_artifact(Rc::new(program))
656 }
657 };
658 engine
659 .execute_blocking_validated_with_multimethods(program, multimethods.clone())
660 .map(|_| ())
661 .map_err(|error| format!("{name}: direct-native execution: {error}"))
662}
663
664#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
665fn compile_direct_native_source_namespace(
666 name: &str,
667 resource: &core::NamespaceResource,
668 environment: &mut HashMap<String, core::Value>,
669 source_cache: Option<&SourceBytecodeCache>,
670) -> Result<crate::direct_native::ValidatedProgram, String> {
671 let source = core::read_source_resource(resource, name)?;
672 let forms = kernel::read_forms(&source).map_err(|error| error.to_string())?;
673 let mut body_offset = 0;
674 if forms.first().is_some_and(|form| {
675 matches!(
676 core::form_without_metadata(&form.form),
677 kernel::Form::List(items)
678 if matches!(items.first(), Some(kernel::Form::Symbol(operator)) if operator == "ns" || operator == "ns+")
679 )
680 }) {
681 let namespace_value = core::form_to_value(&forms[0].form)?;
682 core::eval_bytecode_management_in(&namespace_value, environment)
683 .map_err(|error| format!("{name}: namespace declaration: {error}"))?;
684 body_offset = forms[0].span.end.offset;
685 }
686 if let Some(program) = source_cache.and_then(|cache| cache.load(name, &source)) {
687 return Ok(program);
688 }
689 let config = vm::source_namespace_config(&forms)
690 .map_err(|error| format!("{name}: namespace configuration: {error}"))?;
691 let registry = core::namespace_registry()?;
692 registry.set_current(name);
693 let body = source
694 .get(body_offset..)
695 .ok_or_else(|| format!("{name}: namespace form offset is invalid"))?;
696 let allow_unbound_globals = vm::source_uses_dynamic_evaluation(body).unwrap_or(false);
697 let compile = || {
698 if allow_unbound_globals {
699 vm::compile_source_with_config_allow_unbound_globals(body, ®istry, config)
700 } else {
701 vm::compile_source_with_config(body, ®istry, config)
702 }
703 };
704 let mut program = core::without_direct_native_execution(compile)
705 .map_err(|error| format!("{name}: direct-native compilation: {error}"))?;
706 program.namespace = Some(name.to_owned());
707 if let Some(cache) = source_cache {
708 cache.store(name, &source, &program);
709 }
710 Ok(crate::direct_native::ValidatedProgram::from_compiler(
711 Rc::new(program),
712 ))
713}
714
715#[cfg(all(
716 test,
717 feature = "bytecode-vm",
718 feature = "direct-native",
719 not(target_arch = "wasm32")
720))]
721mod source_cache_tests {
722 use super::SourceBytecodeCache;
723 use std::fs;
724 use std::path::PathBuf;
725 use std::sync::atomic::{AtomicU64, Ordering};
726
727 struct TempRoot(PathBuf);
728
729 impl Drop for TempRoot {
730 fn drop(&mut self) {
731 let _ = fs::remove_dir_all(&self.0);
732 }
733 }
734
735 fn temp_root() -> TempRoot {
736 static NEXT: AtomicU64 = AtomicU64::new(0);
737 let suffix = NEXT.fetch_add(1, Ordering::Relaxed);
738 let path = std::env::temp_dir().join(format!(
739 "hara-source-bytecode-cache-{}-{suffix}",
740 std::process::id()
741 ));
742 fs::create_dir(&path).expect("cache test temporary root must be new");
743 TempRoot(path)
744 }
745
746 #[test]
747 fn caches_only_the_matching_namespace_and_source() {
748 let root = temp_root();
749 let namespace = "example.cache";
750 let source = "(+ 1 2)";
751 let mut program = crate::vm::compile_source(source).expect("source must compile");
752 program.namespace = Some(namespace.to_owned());
753 let cache = SourceBytecodeCache::new(&root.0, [7; 32]);
754
755 assert!(cache.load(namespace, source).is_none());
756 cache.store(namespace, source, &program);
757
758 let loaded = cache
759 .load(namespace, source)
760 .expect("stored source must be readable");
761 assert_eq!(loaded.program().namespace.as_deref(), Some(namespace));
762 assert!(cache.load(namespace, "(+ 1 3)").is_none());
763 assert!(cache.load("example.other", source).is_none());
764 }
765}