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")))]
17struct SourceBytecodeCacheEntry {
18 namespace_form: String,
19 program: crate::direct_native::ValidatedProgram,
20}
21
22#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
23impl SourceBytecodeCache {
24 pub(crate) fn new(root: &std::path::Path, source_index_fingerprint: [u8; 32]) -> Self {
25 Self {
26 directory: root
27 .join("target/hara/test-bytecode/v2")
28 .join(hex_digest(&source_index_fingerprint)),
29 }
30 }
31
32 fn path_for(&self, namespace: &str, source: &str) -> std::path::PathBuf {
33 use sha2::{Digest, Sha256};
34
35 let mut digest = Sha256::new();
36 digest.update(b"hara-direct-native-source-v1\0");
37 digest.update(env!("CARGO_PKG_VERSION").as_bytes());
38 digest.update([0]);
39 digest.update(namespace.as_bytes());
40 digest.update([0]);
41 digest.update(source.as_bytes());
42 self.directory
43 .join(format!("{}.hbc", hex_digest(&digest.finalize())))
44 }
45
46 fn namespace_path_for(&self, namespace: &str, source: &str) -> std::path::PathBuf {
47 self.path_for(namespace, source).with_extension("ns")
48 }
49
50 fn load(
51 &self,
52 namespace: &str,
53 source: &str,
54 ) -> Option<SourceBytecodeCacheEntry> {
55 let path = self.path_for(namespace, source);
56 let namespace_form = std::fs::read_to_string(self.namespace_path_for(namespace, source)).ok()?;
57 let bytes = std::fs::read(path).ok()?;
58 let program = crate::vm::decode_program(&bytes).ok()?;
59 if program.namespace.as_deref() != Some(namespace) {
60 return None;
61 }
62 Some(SourceBytecodeCacheEntry {
63 namespace_form,
64 program: crate::direct_native::ValidatedProgram::from_artifact(Rc::new(program)),
65 })
66 }
67
68 fn store(
69 &self,
70 namespace: &str,
71 source: &str,
72 namespace_form: &str,
73 program: &crate::vm::Program,
74 ) {
75 let path = self.path_for(namespace, source);
76 let namespace_path = self.namespace_path_for(namespace, source);
77 if path.is_file() && namespace_path.is_file() {
78 return;
79 }
80 let Ok(bytes) = crate::vm::encode_program(program) else {
81 return;
82 };
83 if std::fs::create_dir_all(&self.directory).is_err() {
84 return;
85 }
86 let id = std::process::id();
87 let namespace_temporary = namespace_path.with_extension(format!("ns.tmp-{id}"));
88 let program_temporary = path.with_extension(format!("hbc.tmp-{id}"));
89 if std::fs::write(&namespace_temporary, namespace_form).is_err()
90 || std::fs::write(&program_temporary, bytes).is_err()
91 {
92 let _ = std::fs::remove_file(namespace_temporary);
93 let _ = std::fs::remove_file(program_temporary);
94 return;
95 }
96 let _ = std::fs::rename(namespace_temporary, namespace_path);
97 let _ = std::fs::rename(program_temporary, path);
98 }
99}
100
101#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
102fn hex_digest(bytes: &[u8]) -> String {
103 const HEX: &[u8; 16] = b"0123456789abcdef";
104 let mut output = String::with_capacity(bytes.len() * 2);
105 for byte in bytes {
106 output.push(HEX[(byte >> 4) as usize] as char);
107 output.push(HEX[(byte & 0x0f) as usize] as char);
108 }
109 output
110}
111
112#[cfg(feature = "bytecode-vm")]
113pub fn bytecode_namespace_registry() -> kernel::NamespaceRegistry<core::Value> {
114 core::minimal_namespace_registry()
115}
116
117#[cfg(feature = "bytecode-vm")]
118pub fn compile_bytecode(source: &str) -> Result<std::rc::Rc<vm::Program>, String> {
119 let registry = bytecode_namespace_registry();
120 vm::compile_source_with(source, ®istry)
121 .map(std::rc::Rc::new)
122 .map_err(|error| error.to_string())
123}
124
125#[cfg(feature = "bytecode-vm")]
127pub fn execute_bytecode(program: &std::rc::Rc<vm::Program>) -> Result<String, String> {
128 let registry = bytecode_namespace_registry();
129 vm::execute_program_with_globals(program.clone(), ®istry)
130 .map(|value| value.display())
131 .map_err(|error| error.to_string())
132}
133
134#[cfg(all(feature = "bytecode-vm", feature = "tracing-jit"))]
137pub fn bytecode_jit_telemetry(program: &std::rc::Rc<vm::Program>) -> jit::JitTelemetry {
138 vm::machine::cached_jit_telemetry(program)
139}
140
141#[cfg(feature = "bytecode-vm")]
143pub fn compile_bytecode_artifact(source: &str) -> Result<Vec<u8>, String> {
144 let program = compile_bytecode(source)?;
145 vm::encode_program(program.as_ref())
146}
147
148#[cfg(feature = "bytecode-vm")]
150pub fn execute_bytecode_artifact(bytes: &[u8]) -> Result<String, String> {
151 let program = std::rc::Rc::new(vm::decode_program(bytes)?);
152 execute_bytecode(&program)
153}
154
155#[cfg(feature = "bytecode-vm")]
157pub fn eval_bytecode_native(source: &str) -> Result<String, String> {
158 execute_bytecode(&compile_bytecode(source)?)
159}
160
161impl Runtime {
162 #[cfg(feature = "bytecode-vm")]
163 pub(crate) fn compile_bytecode_product(
164 &self,
165 source: &str,
166 ) -> Result<crate::compiled_product::CompiledProduct, String> {
167 let source_digest = crate::compiled_product::sha256_hex(source.as_bytes());
168 let compiler_id = format!("hara-runtime/{}", env!("CARGO_PKG_VERSION"));
169 let options = format!("target=HBC0;namespace={}", self.current_namespace());
170 let program = self.compile_bytecode(source)?;
171 let bytes = vm::encode_program(program.as_ref())?;
172 let module_digest = crate::compiled_product::sha256_hex(&bytes);
173 let key = crate::compiled_product::ProductCacheKey::with_module_digests(
174 crate::compiled_product::CompiledProductKind::HbcModule,
175 source_digest.clone(),
176 compiler_id.clone(),
177 "hbc0",
178 options.as_bytes(),
179 vec![module_digest],
180 );
181 if let Some(product) = self.product_cache.borrow().get(&key).cloned() {
182 return Ok(product);
183 }
184 let product = crate::compiled_product::CompiledProduct::new(
185 crate::compiled_product::CompiledProductKind::HbcModule,
186 source_digest,
187 vec![crate::compiled_product::sha256_hex(&bytes)],
188 compiler_id,
189 "hbc0",
190 options.as_bytes(),
191 bytes,
192 );
193 self.product_cache.borrow_mut().insert(product.clone())?;
194 Ok(product)
195 }
196
197 #[cfg(feature = "whole-wasm")]
198 pub(crate) fn compile_whole_wasm_product(
199 &self,
200 source: &str,
201 ) -> Result<crate::compiled_product::CompiledProduct, String> {
202 let source_digest = crate::compiled_product::sha256_hex(source.as_bytes());
203 let compiler_id = format!("hara-runtime/{}", env!("CARGO_PKG_VERSION"));
204 let options = format!("target=HNW0;namespace={}", self.current_namespace());
205 let abi_version = format!("hnw0/{}", crate::whole_wasm::HNW_ABI_VERSION);
206 let hbc_product = self.compile_bytecode_product(source)?;
207 let module_digest = hbc_product.manifest.artifact_digest.clone();
208 let key = crate::compiled_product::ProductCacheKey::with_module_digests(
209 crate::compiled_product::CompiledProductKind::WholeWasm,
210 source_digest.clone(),
211 compiler_id.clone(),
212 abi_version.clone(),
213 options.as_bytes(),
214 vec![module_digest],
215 );
216 if let Some(product) = self.product_cache.borrow().get(&key).cloned() {
217 return Ok(product);
218 }
219 let hbc = hbc_product.bytes;
220 let bytes = crate::whole_wasm::compile_artifact_from_hbc(&hbc)?;
221 let product = crate::compiled_product::CompiledProduct::new(
222 crate::compiled_product::CompiledProductKind::WholeWasm,
223 hbc_product.manifest.source_digest,
224 vec![hbc_product.manifest.artifact_digest],
225 compiler_id,
226 abi_version,
227 options.as_bytes(),
228 bytes,
229 );
230 self.product_cache.borrow_mut().insert(product.clone())?;
231 Ok(product)
232 }
233
234 #[cfg(not(target_arch = "wasm32"))]
236 pub fn install_native_kernel_provider(&mut self, provider: Rc<core::KernelProvider>) {
237 self.providers.install_kernel(provider);
238 }
239
240 #[cfg(not(target_arch = "wasm32"))]
244 pub fn install_native_host_handler(
245 &mut self,
246 handler: Rc<dyn Fn(String, String, Vec<core::Value>) -> Result<core::Value, String>>,
247 ) {
248 self.native_host_handler = Some(handler);
249 }
250
251 #[cfg(not(target_arch = "wasm32"))]
254 pub fn install_native_module(
255 &mut self,
256 module: std::sync::Arc<dyn hara_abi::NativeModule>,
257 ) -> Result<(), String> {
258 self.native_modules.install(module)?;
259 let registry = self.native_modules.clone();
260 self.native_host_handler = Some(Rc::new(move |service, operation, arguments| {
261 registry.invoke(service, operation, arguments)
262 }));
263 Ok(())
264 }
265
266 #[cfg(not(target_arch = "wasm32"))]
267 pub fn native_module_services(&self) -> Vec<String> {
268 self.native_modules.services()
269 }
270}
271
272#[cfg(feature = "bytecode-vm")]
273impl Runtime {
274 pub fn compile_bytecode(&self, source: &str) -> Result<std::rc::Rc<vm::Program>, String> {
279 self.compile_bytecode_with_policy(source, false)
280 }
281
282 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
283 fn compile_bytecode_for_direct_native(
284 &self,
285 source: &str,
286 ) -> Result<std::rc::Rc<vm::Program>, String> {
287 self.compile_bytecode_with_policy(source, true)
288 }
289
290 fn compile_spanned_forms_for_direct_native(
291 &self,
292 forms: &[kernel::SpannedForm],
293 ) -> Result<std::rc::Rc<vm::Program>, String> {
294 let namespace = self.current_namespace();
295 let config = self
296 .generated_configs
297 .get(&namespace)
298 .cloned()
299 .unwrap_or_else(kernel::GeneratedNamespaceConfig::defaults);
300 core::with_macros(self.macros.clone(), || {
301 vm::compile_spanned_forms_with_config_allow_unbound_globals(
302 forms,
303 &self.namespace_registry,
304 config,
305 )
306 .map(|mut program| {
307 program.namespace = Some(namespace.clone());
308 std::rc::Rc::new(program)
309 })
310 .map_err(|error| error.to_string())
311 })
312 }
313
314 fn compile_bytecode_with_policy(
315 &self,
316 source: &str,
317 allow_unbound_globals_for_direct_native: bool,
318 ) -> Result<std::rc::Rc<vm::Program>, String> {
319 core::with_macros(self.macros.clone(), || {
320 let forms = kernel::read_forms(source).map_err(|error| error.to_string())?;
321 let has_namespace_form = forms.iter().any(|form| {
322 matches!(
323 crate::core::form_without_metadata(&form.form),
324 crate::kernel::Form::List(items)
325 if matches!(items.first(), Some(crate::kernel::Form::Symbol(operator)) if operator == "ns" || operator == "ns+")
326 )
327 });
328 let config = if has_namespace_form {
329 vm::source_namespace_config(&forms).map_err(|error| error.to_string())?
330 } else {
331 self.generated_configs
332 .get(&self.current_namespace())
333 .cloned()
334 .unwrap_or_else(kernel::GeneratedNamespaceConfig::defaults)
335 };
336 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
337 let allow_unbound_globals = allow_unbound_globals_for_direct_native
338 || (self.execution_backend == "direct-native"
339 && vm::source_uses_dynamic_evaluation(source).unwrap_or(false));
340 #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
341 let allow_unbound_globals = false;
342 let compiled = if allow_unbound_globals {
343 vm::compile_source_with_config_allow_unbound_globals(
344 source,
345 &self.namespace_registry,
346 config,
347 )
348 } else {
349 vm::compile_source_with_config(source, &self.namespace_registry, config)
350 };
351 compiled
352 .map(|mut program| {
353 program.namespace =
354 Some(self.namespace_registry.current().name().as_str().to_owned());
355 program
356 })
357 .map(std::rc::Rc::new)
358 .map_err(|error| error.to_string())
359 })
360 }
361
362 pub fn execute_compiled_bytecode(
366 &mut self,
367 program: std::rc::Rc<vm::Program>,
368 ) -> Result<String, String> {
369 self.execute_compiled_bytecode_value(program)
370 .map(|value| value.display())
371 }
372
373 pub fn execute_compiled_bytecode_value(
377 &mut self,
378 program: std::rc::Rc<vm::Program>,
379 ) -> Result<core::Value, String> {
380 let result = self.execute_compiled_bytecode_registry_value(program);
381 let current = self.namespace_registry.current().name().as_str().to_owned();
382 core::select_namespace_environment(
383 &self.namespace_registry,
384 self.execution.environment_mut(),
385 ¤t,
386 );
387 result
388 }
389
390 pub fn execute_compiled_bytecode_registry_value(
393 &mut self,
394 program: std::rc::Rc<vm::Program>,
395 ) -> Result<core::Value, String> {
396 let mut declaration_environment = HashMap::new();
397 let namespace_source = self.namespace_source();
398 core::with_macros(self.macros.clone(), || {
399 core::with_namespace_source(namespace_source, || {
400 core::with_protocols(&self.protocols, || {
401 core::with_namespace_registry(&self.namespace_registry, || {
402 core::with_declaration_transaction(&mut declaration_environment, |_| {
403 vm::execute_program_with_globals(program, &self.namespace_registry)
404 .map_err(|error| error.to_string())
405 })
406 })
407 })
408 })
409 })
410 }
411
412 pub fn eval_bytecode_native(&mut self, source: &str) -> Result<String, String> {
417 let program = self.compile_bytecode(source)?;
418 self.execute_compiled_bytecode(program)
419 }
420
421 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
426 pub fn execute_compiled_direct_native(
427 &mut self,
428 program: std::rc::Rc<vm::Program>,
429 ) -> Result<crate::direct_native::NativeExecutionReport, String> {
430 let program = crate::direct_native::ValidatedProgram::validate(program)?;
431 self.execute_compiled_direct_native_validated(program)
432 }
433
434 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
435 fn execute_compiled_direct_native_validated(
436 &mut self,
437 program: crate::direct_native::ValidatedProgram,
438 ) -> Result<crate::direct_native::NativeExecutionReport, String> {
439 let program_image = program.program();
440 if let Some(namespace) = &program_image.namespace {
441 self.namespace_registry.set_current(namespace);
442 }
443 let mut declaration_environment = HashMap::new();
444 let namespace_source = self.namespace_source();
445 let execute = || {
446 core::with_test_runner(&self.test_runner, || {
447 core::with_capability_providers(
448 self.providers.file(),
449 self.providers.socket(),
450 self.providers.process(),
451 self.providers.kernel(),
452 || {
453 core::with_package_catalog(&self.package_catalog, || {
454 core::with_promise_provider(self.providers.promise(), || {
455 core::with_macros(self.macros.clone(), || {
456 core::with_namespace_registry(&self.namespace_registry, || {
457 core::with_namespace_source(namespace_source, || {
458 core::with_protocols(&self.protocols, || {
459 let loader = Self::direct_native_namespace_loader(
460 self.direct_native.clone(),
461 self.direct_native_multimethods.clone(),
462 self.direct_native_source_cache.clone(),
463 );
464 core::with_direct_native_namespace_loader(
465 loader,
466 || {
467 core::with_declaration_transaction(
468 &mut declaration_environment,
469 |_| {
470 self.direct_native
471 .execute_blocking_validated_with_multimethods(
472 program,
473 self.direct_native_multimethods
474 .clone(),
475 )
476 },
477 )
478 },
479 )
480 })
481 })
482 })
483 })
484 })
485 })
486 },
487 )
488 })
489 };
490 #[cfg(not(target_arch = "wasm32"))]
491 let result = if let Some(handler) = self.native_host_handler.clone() {
492 core::with_host_calls(handler, execute)
493 } else {
494 execute()
495 };
496 if result.is_ok() {
497 self.save_namespace();
498 self.refresh_qualified_bindings();
499 }
500 result
501 }
502
503 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
509 pub(crate) fn direct_native_namespace_loader(
510 engine: crate::direct_native::NativeEngine,
511 multimethods: core::MultiMethodRegistry,
512 source_cache: Option<SourceBytecodeCache>,
513 ) -> Rc<
514 dyn Fn(
515 &str,
516 core::NamespaceResource,
517 &mut HashMap<String, core::Value>,
518 ) -> Result<(), String>,
519 > {
520 Rc::new(move |name, resource, environment| {
521 load_direct_native_namespace(
522 &engine,
523 &multimethods,
524 source_cache.as_ref(),
525 name,
526 resource,
527 environment,
528 )
529 })
530 }
531
532 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
537 pub fn eval_direct_native(&mut self, source: &str) -> Result<String, String> {
538 let program = self.compile_bytecode_for_direct_native(source)?;
539 self.execute_compiled_direct_native_validated(
540 crate::direct_native::ValidatedProgram::from_compiler(program),
541 )
542 .map(|report| report.value.display())
543 }
544
545 pub fn compile_bytecode_artifact(&self, source: &str) -> Result<Vec<u8>, String> {
548 let program = self.compile_bytecode(source)?;
549 vm::encode_program(program.as_ref())
550 }
551
552 pub(crate) fn compile_package_bytecode_artifact(
557 &self,
558 source: &str,
559 ) -> Result<Vec<u8>, String> {
560 let program = self.compile_bytecode_with_policy(source, true)?;
561 vm::encode_program(program.as_ref())
562 }
563
564 pub fn compile_halc_bytecode_artifact(&mut self, bytes: &[u8]) -> Result<Vec<u8>, String> {
568 let module = kernel::halc::decode_halc(bytes)?;
569 if let Some(namespace_form) = module.forms.iter().find(|form| {
575 matches!(
576 core::form_without_metadata(form),
577 Form::List(items)
578 if matches!(items.first(), Some(Form::Symbol(operator)) if operator == "ns")
579 )
580 }) {
581 self.eval_forms(vec![synthetic_spanned_form(namespace_form.clone())], false)?;
582 } else {
583 self.use_namespace(&module.namespace);
584 }
585 let program = vm::compile_halc_module(&module, &self.namespace_registry)
586 .map_err(|error| error.to_string())?;
587 vm::encode_program(&program)
588 }
589
590 pub fn eval_bytecode_artifact(&mut self, bytes: &[u8]) -> Result<String, String> {
592 let program = std::rc::Rc::new(vm::decode_program(bytes)?);
593 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
594 if self.execution_backend == "direct-native" {
595 let schema_types = program.schema_types.clone();
596 let function_types = program.function_types.clone();
597 let inferred_function_types = program.inferred_function_types.clone();
598 let result = self
599 .execute_compiled_direct_native_validated(
600 crate::direct_native::ValidatedProgram::from_artifact(program),
601 )
602 .map(|report| report.value.display());
603 if result.is_ok() {
604 self.halc_schema_types.extend(schema_types);
605 self.halc_function_types.extend(function_types);
606 self.halc_inferred_function_types
607 .extend(inferred_function_types);
608 }
609 return result;
610 }
611 if let Some(namespace) = &program.namespace {
612 self.namespace_registry.set_current(namespace);
613 }
614 let schema_types = program.schema_types.clone();
615 let function_types = program.function_types.clone();
616 let inferred_function_types = program.inferred_function_types.clone();
617 let mut declaration_environment = HashMap::new();
618 let namespace_source = self.namespace_source();
619 let result = core::with_macros(self.macros.clone(), || {
620 core::with_namespace_source(namespace_source, || {
621 core::with_protocols(&self.protocols, || {
622 core::with_namespace_registry(&self.namespace_registry, || {
623 core::with_declaration_transaction(&mut declaration_environment, |_| {
624 vm::execute_program_with_globals(program, &self.namespace_registry)
625 .map(|value| value.display())
626 .map_err(|error| error.to_string())
627 })
628 })
629 })
630 })
631 });
632 if result.is_ok() {
633 self.halc_schema_types.extend(schema_types);
634 self.halc_function_types.extend(function_types);
635 self.halc_inferred_function_types
636 .extend(inferred_function_types);
637 }
638 let current = self.namespace_registry.current().name().as_str().to_owned();
639 core::select_namespace_environment(
640 &self.namespace_registry,
641 self.execution.environment_mut(),
642 ¤t,
643 );
644 result
645 }
646}
647
648#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
649fn load_direct_native_namespace(
650 engine: &crate::direct_native::NativeEngine,
651 multimethods: &core::MultiMethodRegistry,
652 source_cache: Option<&SourceBytecodeCache>,
653 name: &str,
654 resource: core::NamespaceResource,
655 environment: &mut HashMap<String, core::Value>,
656) -> Result<(), String> {
657 let profile = std::env::var_os("HARA_NATIVE_PROFILE_NAMESPACE_LOADS").is_some();
658 let started = std::time::Instant::now();
659 let program = match &resource {
660 core::NamespaceResource::Source(_) => {
661 compile_direct_native_source_namespace(name, &resource, environment, source_cache)?
662 }
663 #[cfg(not(target_arch = "wasm32"))]
664 core::NamespaceResource::SourcePath(_) => {
665 compile_direct_native_source_namespace(name, &resource, environment, source_cache)?
666 }
667 core::NamespaceResource::Bytecode {
668 namespace_form,
669 artifact,
670 } => {
671 for (index, form) in kernel::parse_forms(&namespace_form)?
672 .into_iter()
673 .enumerate()
674 {
675 let namespace_value = core::form_to_value(&form)?;
676 core::eval_bytecode_management_in(&namespace_value, environment)
677 .map_err(|error| format!("{name}: namespace form {}: {error}", index + 1))?;
678 }
679 let registry = core::namespace_registry()?;
680 registry.set_current(name);
681 let mut program = vm::decode_program(&artifact)
682 .map_err(|error| format!("{name}: direct-native artifact: {error}"))?;
683 program.namespace = Some(name.to_owned());
684 crate::direct_native::ValidatedProgram::from_artifact(Rc::new(program))
685 }
686 };
687 let result = engine
688 .execute_blocking_validated_with_multimethods(program, multimethods.clone())
689 .map(|_| ())
690 .map_err(|error| format!("{name}: direct-native execution: {error}"));
691 if profile {
692 eprintln!(
693 "PROFILE namespace {}={}ms {}",
694 name,
695 started.elapsed().as_millis(),
696 if result.is_ok() { "ok" } else { "error" }
697 );
698 }
699 result
700}
701
702#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
703fn compile_direct_native_source_namespace(
704 name: &str,
705 resource: &core::NamespaceResource,
706 environment: &mut HashMap<String, core::Value>,
707 source_cache: Option<&SourceBytecodeCache>,
708) -> Result<crate::direct_native::ValidatedProgram, String> {
709 let source = core::read_source_resource(resource, name)?;
710 if let Some(entry) = source_cache.and_then(|cache| cache.load(name, &source)) {
711 if std::env::var_os("HARA_NATIVE_PROFILE_NAMESPACE_LOADS").is_some() {
712 eprintln!("PROFILE source-cache {name}=hit");
713 }
714 let forms = kernel::read_forms(&entry.namespace_form).map_err(|error| error.to_string())?;
715 let namespace = forms
716 .first()
717 .filter(|form| {
718 matches!(
719 core::form_without_metadata(&form.form),
720 kernel::Form::List(items)
721 if matches!(items.first(), Some(kernel::Form::Symbol(operator)) if operator == "ns" || operator == "ns+")
722 )
723 })
724 .ok_or_else(|| format!("{name}: cached namespace declaration is invalid"))?;
725 let namespace_value = core::form_to_value(&namespace.form)?;
726 core::eval_bytecode_management_in(&namespace_value, environment)
727 .map_err(|error| format!("{name}: namespace declaration: {error}"))?;
728 let registry = core::namespace_registry()?;
729 registry.set_current(name);
730 return Ok(entry.program);
731 }
732 if std::env::var_os("HARA_NATIVE_PROFILE_NAMESPACE_LOADS").is_some() {
733 eprintln!("PROFILE source-cache {name}=miss");
734 }
735 let forms = kernel::read_forms(&source).map_err(|error| error.to_string())?;
736 let mut body_offset = 0;
737 let mut namespace_form = None;
738 if forms.first().is_some_and(|form| {
739 matches!(
740 core::form_without_metadata(&form.form),
741 kernel::Form::List(items)
742 if matches!(items.first(), Some(kernel::Form::Symbol(operator)) if operator == "ns" || operator == "ns+")
743 )
744 }) {
745 let namespace_value = core::form_to_value(&forms[0].form)?;
746 core::eval_bytecode_management_in(&namespace_value, environment)
747 .map_err(|error| format!("{name}: namespace declaration: {error}"))?;
748 body_offset = forms[0].span.end.offset;
749 namespace_form = source
750 .get(forms[0].span.start.offset..body_offset)
751 .map(str::to_owned);
752 }
753 let config = vm::source_namespace_config(&forms)
754 .map_err(|error| format!("{name}: namespace configuration: {error}"))?;
755 let registry = core::namespace_registry()?;
756 registry.set_current(name);
757 let body = source
758 .get(body_offset..)
759 .ok_or_else(|| format!("{name}: namespace form offset is invalid"))?;
760 let compile = || vm::compile_source_with_config_allow_unbound_globals(body, ®istry, config);
766 let mut program = core::without_direct_native_execution(compile)
767 .map_err(|error| format!("{name}: direct-native compilation: {error}"))?;
768 program.namespace = Some(name.to_owned());
769 if let (Some(cache), Some(namespace_form)) = (source_cache, namespace_form.as_deref()) {
770 cache.store(name, &source, namespace_form, &program);
771 }
772 Ok(crate::direct_native::ValidatedProgram::from_compiler(
773 Rc::new(program),
774 ))
775}
776
777#[cfg(all(
778 test,
779 feature = "bytecode-vm",
780 feature = "direct-native",
781 not(target_arch = "wasm32")
782))]
783mod source_cache_tests {
784 use super::SourceBytecodeCache;
785 use std::fs;
786 use std::path::PathBuf;
787 use std::sync::atomic::{AtomicU64, Ordering};
788
789 struct TempRoot(PathBuf);
790
791 impl Drop for TempRoot {
792 fn drop(&mut self) {
793 let _ = fs::remove_dir_all(&self.0);
794 }
795 }
796
797 fn temp_root() -> TempRoot {
798 static NEXT: AtomicU64 = AtomicU64::new(0);
799 let suffix = NEXT.fetch_add(1, Ordering::Relaxed);
800 let path = std::env::temp_dir().join(format!(
801 "hara-source-bytecode-cache-{}-{suffix}",
802 std::process::id()
803 ));
804 fs::create_dir(&path).expect("cache test temporary root must be new");
805 TempRoot(path)
806 }
807
808 #[test]
809 fn caches_only_the_matching_namespace_and_source() {
810 let root = temp_root();
811 let namespace = "example.cache";
812 let source = "(+ 1 2)";
813 let mut program = crate::vm::compile_source(source).expect("source must compile");
814 program.namespace = Some(namespace.to_owned());
815 let cache = SourceBytecodeCache::new(&root.0, [7; 32]);
816
817 assert!(cache.load(namespace, source).is_none());
818 cache.store(namespace, source, "(ns example.cache)", &program);
819
820 let loaded = cache
821 .load(namespace, source)
822 .expect("stored source must be readable");
823 assert_eq!(loaded.program.program().namespace.as_deref(), Some(namespace));
824 assert_eq!(loaded.namespace_form, "(ns example.cache)");
825 assert!(cache.load(namespace, "(+ 1 3)").is_none());
826 assert!(cache.load("example.other", source).is_none());
827 }
828}