1use sha2::{Digest, Sha256};
4
5use crate::{
6 core, kernel, Runtime, EAGER_HAL_RESOURCES, EMBEDDED_CLI_RESOURCES, EMBEDDED_HAL_RESOURCES,
7};
8
9#[path = "bundle/order.rs"]
10mod order;
11use order::order_module_sources;
12
13const MAGIC: &[u8; 4] = b"HBX0";
14
15#[derive(Clone, Copy)]
16pub struct ModuleSource<'a> {
17 pub resource: &'a str,
18 pub source: &'a str,
19}
20
21#[derive(Clone)]
26pub struct BytecodeBundleModule {
27 pub resource: String,
28 pub namespace_form: String,
29 pub source_digest: [u8; 32],
30 pub dependencies: Vec<String>,
31 pub eager: bool,
32 pub artifact: Vec<u8>,
33}
34
35pub fn embedded_foundation_bootstrap_sources() -> Vec<ModuleSource<'static>> {
36 if EMBEDDED_HAL_RESOURCES.is_empty() {
37 return Vec::new();
38 }
39 let ordered = std::iter::once("std.foundation")
40 .chain(EAGER_HAL_RESOURCES.iter().copied())
41 .chain(
42 EMBEDDED_HAL_RESOURCES
43 .iter()
44 .map(|(namespace, _, _)| *namespace)
45 .filter(|namespace| {
46 standard_library_namespace(namespace)
47 && *namespace != "std.foundation"
48 && !EAGER_HAL_RESOURCES.contains(namespace)
49 }),
50 );
51 let sources = ordered
52 .map(|resource| {
53 let source = EMBEDDED_HAL_RESOURCES
54 .iter()
55 .find_map(|(name, _, source)| (*name == resource).then_some(*source))
56 .unwrap_or_else(|| panic!("missing embedded HAL resource: {resource}"));
57 ModuleSource { resource, source }
58 })
59 .collect::<Vec<_>>();
60 order_module_sources(&sources)
61 .expect("embedded Foundation bootstrap dependencies must be acyclic")
62 .into_iter()
63 .map(|index| sources[index])
64 .collect()
65}
66
67pub fn embedded_cli_sources() -> Vec<ModuleSource<'static>> {
71 let sources = EMBEDDED_CLI_RESOURCES
72 .iter()
73 .map(|(resource, _, source)| ModuleSource { resource, source })
74 .collect::<Vec<_>>();
75 order_module_sources(&sources)
76 .expect("embedded CLI bootstrap dependencies must be acyclic")
77 .into_iter()
78 .map(|index| sources[index])
79 .collect()
80}
81
82pub fn compile_bytecode_bundle(sources: &[ModuleSource<'_>]) -> Result<Vec<u8>, String> {
83 let mut runtime = Runtime::core();
84 for &(name, _, source) in EMBEDDED_HAL_RESOURCES {
85 runtime.register_resource(name, source);
86 }
87 compile_bytecode_bundle_with_runtime(&mut runtime, sources, sources)
88}
89
90pub fn compile_package_bytecode_bundle(
96 context: &[ModuleSource<'_>],
97 sources: &[ModuleSource<'_>],
98) -> Result<Vec<u8>, String> {
99 let mut runtime = Runtime::new();
100 let ordered = foundation_root_first(sources);
101 for source in context {
102 runtime.register_resource(source.resource, source.source);
103 }
104 #[cfg(not(target_arch = "wasm32"))]
105 if package_needs_foundation_bootstrap(context, &ordered) {
106 runtime.bootstrap_source_foundation()?;
107 }
108 compile_bytecode_bundle_with_runtime(&mut runtime, context, &ordered)
109}
110
111#[cfg(not(target_arch = "wasm32"))]
112fn package_needs_foundation_bootstrap(
113 context: &[ModuleSource<'_>],
114 sources: &[ModuleSource<'_>],
115) -> bool {
116 let emits_foundation = sources
117 .iter()
118 .any(|source| source.resource == "std.foundation");
119 !emits_foundation
120 && context
121 .iter()
122 .any(|source| source.resource == "std.foundation")
123}
124
125fn foundation_root_first<'a>(sources: &[ModuleSource<'a>]) -> Vec<ModuleSource<'a>> {
126 if !sources
127 .iter()
128 .any(|source| source.resource == "std.foundation")
129 {
130 return sources.to_vec();
131 }
132 let mut ordered = Vec::with_capacity(sources.len());
133 for resource in std::iter::once("std.foundation").chain(EAGER_HAL_RESOURCES.iter().copied()) {
134 if let Some(source) = sources.iter().find(|source| source.resource == resource) {
135 ordered.push(*source);
136 }
137 }
138 let remaining = sources
139 .iter()
140 .filter(|source| {
141 !ordered
142 .iter()
143 .any(|ordered| ordered.resource == source.resource)
144 })
145 .copied()
146 .collect::<Vec<_>>();
147 ordered.extend(remaining);
148 ordered
149}
150
151fn compile_bytecode_bundle_with_runtime(
152 runtime: &mut Runtime,
153 context: &[ModuleSource<'_>],
154 sources: &[ModuleSource<'_>],
155) -> Result<Vec<u8>, String> {
156 for source in context {
157 runtime.register_resource(source.resource, source.source);
158 }
159 let mut encoded = Vec::new();
160 for index in order_module_sources(sources)? {
161 let source = &sources[index];
162 let (namespace_form, body) = split_namespace_form(source.source)?;
163 runtime
164 .eval_text(namespace_form)
165 .map_err(|error| format!("{}: namespace declaration: {error}", source.resource))?;
166 runtime.use_namespace(source.resource);
170 let artifact = core::with_definition_origin(kernel::VarOrigin::HalFallback, || {
171 runtime.compile_package_bytecode_artifact(body)
172 })
173 .map_err(|error| format!("{}: bytecode compilation: {error}", source.resource))?;
174 core::with_definition_origin(kernel::VarOrigin::HalFallback, || {
175 runtime.eval_bytecode_artifact(&artifact)
176 })
177 .map_err(|error| format!("{}: bytecode execution: {error}", source.resource))?;
178 let source_digest: [u8; 32] = Sha256::digest(source.source.as_bytes()).into();
179 let dependencies = namespace_dependencies(namespace_form)?;
180 let eager =
181 source.resource == "std.foundation" || EAGER_HAL_RESOURCES.contains(&source.resource);
182 encoded.push(BytecodeBundleModule {
183 resource: source.resource.to_owned(),
184 namespace_form: namespace_form.to_owned(),
185 source_digest,
186 dependencies,
187 eager,
188 artifact,
189 });
190 }
191 encode_bytecode_bundle(&encoded)
192}
193
194pub fn compile_embedded_foundation_bootstrap_bundle() -> Result<Vec<u8>, String> {
195 compile_bytecode_bundle(&embedded_foundation_bootstrap_sources())
196}
197
198pub fn compile_embedded_cli_bundle() -> Result<Vec<u8>, String> {
202 let foundation = embedded_foundation_bootstrap_sources();
203 let cli = embedded_cli_sources();
204 let mut context = foundation;
205 context.extend(cli.iter().copied());
206 compile_package_bytecode_bundle(&context, &cli)
207}
208
209pub fn compile_embedded_standard_library_bundle() -> Result<Vec<u8>, String> {
212 compile_embedded_foundation_bootstrap_bundle()
213}
214
215pub fn embedded_standard_library_sources() -> Vec<ModuleSource<'static>> {
218 embedded_foundation_bootstrap_sources()
219}
220
221pub fn eval_bytecode_bundle(runtime: &mut Runtime, bytes: &[u8]) -> Result<(), String> {
222 let modules = decode(bytes)?;
223 let mut names = std::collections::HashSet::with_capacity(modules.len());
224 for module in &modules {
225 if !names.insert(module.resource.clone()) {
226 return Err(format!(
227 "duplicate bytecode bundle module: {}",
228 module.resource
229 ));
230 }
231 }
232 let namespaces_before = runtime.namespace_registry.snapshot();
233 let environment_before = runtime.execution.snapshot();
234 let macros_before = runtime.macros.borrow().clone();
235 let protocols_before = runtime.protocols.snapshot();
236 let multimethods_before = core::snapshot_multimethods();
237 let resources_before = runtime.bytecode_resources.clone();
238 let loaded_before = runtime.loaded_resources.clone();
239 let loaded = (|| {
240 for module in &modules {
241 let source = if let Some(source) = runtime.resources.get(&module.resource) {
242 Some(source.clone())
243 } else {
244 #[cfg(not(target_arch = "wasm32"))]
245 {
246 runtime
247 .source_catalog
248 .as_ref()
249 .and_then(|catalog| catalog.path(&module.resource))
250 .as_ref()
251 .map(|path| {
252 std::fs::read_to_string(path).map_err(|error| {
253 format!("cannot read bundled source {}: {error}", path.display())
254 })
255 })
256 .transpose()?
257 }
258 #[cfg(target_arch = "wasm32")]
259 {
260 None
261 }
262 };
263 let source_is_current = source
264 .as_deref()
265 .map(|source| {
266 let digest: [u8; 32] = Sha256::digest(source.as_bytes()).into();
267 digest == module.source_digest
268 })
269 .unwrap_or(true);
270 if !source_is_current {
271 if module.eager {
272 return Err(format!(
273 "stale eager bytecode bundle module: {}",
274 module.resource
275 ));
276 }
277 continue;
278 }
279 runtime.register_bytecode_resource(
280 module.resource.clone(),
281 module.namespace_form.clone(),
282 module.artifact.clone(),
283 );
284 }
285 for module in modules.iter().filter(|module| module.eager) {
286 core::with_definition_origin(kernel::VarOrigin::HalFallback, || {
287 runtime.load_bytecode_resource(&module.resource).map(|_| ())
288 })
289 .map_err(|error| format!("{}: {error}", module.resource))?;
290 runtime.loaded_resources.insert(module.resource.clone());
291 }
292 runtime.use_namespace("user");
293 Ok(())
294 })();
295 if let Err(error) = loaded {
296 runtime.namespace_registry.restore(namespaces_before);
297 runtime.execution.restore(environment_before);
298 *runtime.macros.borrow_mut() = macros_before;
299 runtime.protocols.restore(protocols_before);
300 core::restore_multimethods(multimethods_before);
301 runtime.bytecode_resources = resources_before;
302 runtime.loaded_resources = loaded_before;
303 return Err(error);
304 }
305 Ok(())
306}
307
308pub fn eval_eager_bytecode_bundle_with_registries(
317 namespaces: &kernel::NamespaceRegistry<core::Value>,
318 protocols: &core::ProtocolRegistry,
319 bytes: &[u8],
320) -> Result<(), String> {
321 let modules = decode(bytes)?;
322 if let Some(module) = modules.iter().find(|module| !module.eager) {
323 return Err(format!(
324 "embedding bundle module must be eager: {}",
325 module.resource
326 ));
327 }
328 let mut positions = std::collections::HashMap::with_capacity(modules.len());
329 for (index, module) in modules.iter().enumerate() {
330 if positions.insert(module.resource.as_str(), index).is_some() {
331 return Err(format!(
332 "duplicate bytecode bundle module: {}",
333 module.resource
334 ));
335 }
336 }
337 for (index, module) in modules.iter().enumerate() {
338 for dependency in &module.dependencies {
339 if positions
340 .get(dependency.as_str())
341 .is_some_and(|dependency_index| *dependency_index >= index)
342 {
343 return Err(format!(
344 "{}: bundled dependency must appear first: {dependency}",
345 module.resource
346 ));
347 }
348 }
349 }
350 let programs = modules
351 .iter()
352 .map(|module| {
353 crate::vm::decode_program(&module.artifact)
354 .map(std::rc::Rc::new)
355 .map_err(|error| format!("{}: invalid bytecode artifact: {error}", module.resource))
356 })
357 .collect::<Result<Vec<_>, _>>()?;
358 let namespaces_before = namespaces.snapshot();
359 let protocols_before = protocols.snapshot();
360 let multimethods_before = core::snapshot_multimethods();
361 let loaded = (|| {
362 for (module, program) in modules.iter().zip(programs) {
363 let forms = kernel::parse_forms(&module.namespace_form)
364 .map_err(|error| format!("{}: namespace declaration: {error}", module.resource))?;
365 if forms.len() != 1 {
366 return Err(format!(
367 "{}: bundle namespace declaration must contain exactly one form",
368 module.resource
369 ));
370 }
371 let mut environment = std::collections::HashMap::new();
372 core::with_namespace_registry(namespaces, || {
373 core::with_protocols(protocols, || core::eval(&forms[0], &mut environment))
374 })
375 .map_err(|error| format!("{}: namespace declaration: {error}", module.resource))?;
376 core::with_namespace_registry(namespaces, || {
377 core::with_protocols(protocols, || {
378 crate::vm::execute_program_with_globals(program, namespaces)
379 .map_err(|error| error.to_string())
380 })
381 })
382 .map_err(|error| format!("{}: bytecode execution: {error}", module.resource))?;
383 }
384 Ok(())
385 })();
386 if let Err(error) = loaded {
387 namespaces.restore(namespaces_before);
388 protocols.restore(protocols_before);
389 core::restore_multimethods(multimethods_before);
390 return Err(error);
391 }
392 Ok(())
393}
394
395pub fn encode_bytecode_bundle(modules: &[BytecodeBundleModule]) -> Result<Vec<u8>, String> {
398 let modules = canonical_modules(modules)?;
399 let mut payload = Vec::new();
400 put_u32(&mut payload, modules.len())?;
401 for module in &modules {
402 put_bytes(&mut payload, module.resource.as_bytes())?;
403 put_bytes(&mut payload, module.namespace_form.as_bytes())?;
404 payload.extend_from_slice(&module.source_digest);
405 put_u32(&mut payload, module.dependencies.len())?;
406 for dependency in &module.dependencies {
407 put_bytes(&mut payload, dependency.as_bytes())?;
408 }
409 payload.push(u8::from(module.eager));
410 put_bytes(&mut payload, &module.artifact)?;
411 }
412 let checksum = Sha256::digest(&payload);
413 let mut output = Vec::with_capacity(4 + checksum.len() + payload.len());
414 output.extend_from_slice(MAGIC);
415 output.extend_from_slice(&checksum);
416 output.extend_from_slice(&payload);
417 Ok(output)
418}
419
420pub fn decode_bytecode_bundle(bytes: &[u8]) -> Result<Vec<BytecodeBundleModule>, String> {
421 if bytes.len() < 36 || &bytes[..4] != MAGIC {
422 return Err("invalid HBX0 bytecode bundle header".into());
423 }
424 let payload = &bytes[36..];
425 if Sha256::digest(payload)[..] != bytes[4..36] {
426 return Err("HBX0 bytecode bundle checksum mismatch".into());
427 }
428 let mut input = payload;
429 let count = take_u32(&mut input)? as usize;
430 let mut modules = Vec::with_capacity(count);
431 for _ in 0..count {
432 let resource = take_string(&mut input)?;
433 let namespace_form = take_string(&mut input)?;
434 let source_digest = take(&mut input, 32)?.try_into().unwrap();
435 let dependency_count = take_u32(&mut input)? as usize;
436 let dependencies = (0..dependency_count)
437 .map(|_| take_string(&mut input))
438 .collect::<Result<Vec<_>, _>>()?;
439 let eager = match take(&mut input, 1)?[0] {
440 0 => false,
441 1 => true,
442 _ => return Err("HBX0 bytecode bundle contains invalid eager flag".into()),
443 };
444 let artifact = take_bytes(&mut input)?.to_vec();
445 modules.push(BytecodeBundleModule {
446 resource,
447 namespace_form,
448 source_digest,
449 dependencies,
450 eager,
451 artifact,
452 });
453 }
454 if !input.is_empty() {
455 return Err("trailing bytes in HBX0 bytecode bundle".into());
456 }
457 validate_bundle_modules(&modules)?;
458 for module in &modules {
459 crate::vm::decode_program(&module.artifact)
460 .map_err(|error| format!("{}: invalid HBC0 artifact: {error}", module.resource))?;
461 }
462 Ok(modules)
463}
464
465fn decode(bytes: &[u8]) -> Result<Vec<BytecodeBundleModule>, String> {
466 decode_bytecode_bundle(bytes)
467}
468
469fn canonical_modules(
470 modules: &[BytecodeBundleModule],
471) -> Result<Vec<BytecodeBundleModule>, String> {
472 let mut by_resource = std::collections::BTreeMap::new();
473 for module in modules {
474 if by_resource
475 .insert(module.resource.clone(), module.clone())
476 .is_some()
477 {
478 return Err(format!("duplicate HBX0 module: {}", module.resource));
479 }
480 let mut dependencies = module.dependencies.clone();
481 dependencies.sort();
482 if dependencies.windows(2).any(|pair| pair[0] == pair[1]) {
483 return Err(format!("{}: duplicate HBX0 dependency", module.resource));
484 }
485 }
486 let mut ordered = Vec::with_capacity(modules.len());
487 while !by_resource.is_empty() {
488 let available = by_resource
489 .iter()
490 .find(|(_, module)| {
491 module
492 .dependencies
493 .iter()
494 .all(|dependency| !by_resource.contains_key(dependency))
495 })
496 .map(|(resource, _)| resource.clone())
497 .ok_or("HBX0 module dependencies contain a cycle")?;
498 let mut module = by_resource.remove(&available).unwrap();
499 module.dependencies.sort();
500 ordered.push(module);
501 }
502 validate_bundle_modules(&ordered)?;
503 Ok(ordered)
504}
505
506fn validate_bundle_modules(modules: &[BytecodeBundleModule]) -> Result<(), String> {
507 let mut positions = std::collections::HashMap::with_capacity(modules.len());
508 for (index, module) in modules.iter().enumerate() {
509 if module.resource.is_empty() {
510 return Err("HBX0 module resource must not be empty".into());
511 }
512 if module.namespace_form.is_empty() {
513 return Err(format!(
514 "{}: HBX0 namespace form must not be empty",
515 module.resource
516 ));
517 }
518 if positions.insert(module.resource.as_str(), index).is_some() {
519 return Err(format!("duplicate HBX0 module: {}", module.resource));
520 }
521 if module
522 .dependencies
523 .windows(2)
524 .any(|pair| pair[0] >= pair[1])
525 {
526 return Err(format!(
527 "{}: HBX0 dependencies must be unique and sorted",
528 module.resource
529 ));
530 }
531 }
532 for (index, module) in modules.iter().enumerate() {
533 for dependency in &module.dependencies {
534 if positions
535 .get(dependency.as_str())
536 .is_some_and(|position| *position >= index)
537 {
538 return Err(format!(
539 "{}: HBX0 dependency must appear first: {dependency}",
540 module.resource
541 ));
542 }
543 }
544 }
545 Ok(())
546}
547
548fn standard_library_namespace(namespace: &str) -> bool {
549 ["std.", "code.", "db.", "lang."]
550 .iter()
551 .any(|prefix| namespace.starts_with(prefix))
552}
553
554pub(super) fn namespace_dependencies(namespace_form: &str) -> Result<Vec<String>, String> {
555 let forms = kernel::parse_forms(namespace_form)?;
556 let Some(kernel::Form::List(items)) = forms.first() else {
557 return Err("standard-library module has invalid ns form".into());
558 };
559 let config = kernel::GeneratedNamespaceConfig::configure_with(&items[2..], |_| true)?;
560 let mut dependencies = config.required_namespaces().to_vec();
561 dependencies.extend(config.used_namespaces().iter().cloned());
562 dependencies.sort();
563 dependencies.dedup();
564 Ok(dependencies)
565}
566
567pub(super) fn split_namespace_form(source: &str) -> Result<(&str, &str), String> {
568 let start = source.find("(ns ").ok_or("HAL module is missing ns form")?;
569 let mut depth = 0usize;
570 let mut string = false;
571 let mut escape = false;
572 for (offset, ch) in source[start..].char_indices() {
573 if string {
574 if escape {
575 escape = false;
576 } else if ch == '\\' {
577 escape = true;
578 } else if ch == '"' {
579 string = false;
580 }
581 continue;
582 }
583 match ch {
584 '"' => string = true,
585 '(' => depth += 1,
586 ')' => {
587 depth = depth.checked_sub(1).ok_or("invalid ns form")?;
588 if depth == 0 {
589 let end = start + offset + ch.len_utf8();
590 return Ok((&source[start..end], &source[end..]));
591 }
592 }
593 _ => {}
594 }
595 }
596 Err("unterminated ns form".into())
597}
598
599fn put_u32(output: &mut Vec<u8>, value: usize) -> Result<(), String> {
600 let value = u32::try_from(value).map_err(|_| "foundation bundle exceeds u32 limits")?;
601 output.extend_from_slice(&value.to_le_bytes());
602 Ok(())
603}
604
605fn put_bytes(output: &mut Vec<u8>, value: &[u8]) -> Result<(), String> {
606 put_u32(output, value.len())?;
607 output.extend_from_slice(value);
608 Ok(())
609}
610
611fn take_u32(input: &mut &[u8]) -> Result<u32, String> {
612 let bytes = take(input, 4)?;
613 Ok(u32::from_le_bytes(bytes.try_into().unwrap()))
614}
615
616fn take_bytes<'a>(input: &mut &'a [u8]) -> Result<&'a [u8], String> {
617 let len = take_u32(input)? as usize;
618 take(input, len)
619}
620
621fn take_string(input: &mut &[u8]) -> Result<String, String> {
622 String::from_utf8(take_bytes(input)?.to_vec())
623 .map_err(|_| "foundation bundle contains invalid UTF-8".into())
624}
625
626fn take<'a>(input: &mut &'a [u8], len: usize) -> Result<&'a [u8], String> {
627 if input.len() < len {
628 return Err("truncated HBX0 bytecode bundle".into());
629 }
630 let (value, rest) = input.split_at(len);
631 *input = rest;
632 Ok(value)
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638
639 const COMPILER_GATE_STACK_SIZE: usize = 64 * 1024 * 1024;
640
641 fn on_compiler_gate_stack(test: impl FnOnce() + Send + 'static) {
642 std::thread::Builder::new()
643 .name("foundation-bytecode-compiler-gate".into())
644 .stack_size(COMPILER_GATE_STACK_SIZE)
649 .spawn(test)
650 .expect("spawn foundation compiler gate")
651 .join()
652 .expect("foundation compiler gate panicked");
653 }
654
655 #[test]
656 fn empty_host_bundle_round_trips_against_the_native_registry() {
657 on_compiler_gate_stack(|| {
658 let sources = embedded_standard_library_sources();
659 assert!(
660 sources.is_empty(),
661 "the standalone host embeds no HAL source"
662 );
663 let bytes = compile_bytecode_bundle(&sources).expect("compile empty host bundle");
664 let mut runtime = Runtime::core();
665 eval_bytecode_bundle(&mut runtime, &bytes).expect("load empty host bundle");
666 assert!(!runtime.bytecode_resources.contains_key("std.foundation"));
667 assert_eq!(
668 runtime.eval_native("(std.native.String/upper \"hara\")"),
669 Ok("\"HARA\"".into())
670 );
671 });
672 }
673
674 #[test]
675 fn bytecode_index_has_no_implicit_foundation_module() {
676 let modules = decode(&compile_bytecode_bundle(&[]).expect("compile empty host bundle"))
677 .expect("decode empty host bundle");
678 assert!(modules.is_empty());
679 }
680
681 #[test]
682 fn bundle_encoding_is_deterministic() {
683 let sources = [ModuleSource {
684 resource: "example.deterministic",
685 source: "(ns example.deterministic) (def answer 42)",
686 }];
687 let first = compile_bytecode_bundle(&sources).expect("first deterministic bundle");
688 let second = compile_bytecode_bundle(&sources).expect("second deterministic bundle");
689 assert_eq!(first, second);
690 }
691
692 #[test]
693 fn foundation_package_compiles_the_root_before_companions() {
694 let sources = [
695 ModuleSource {
696 resource: "std.foundation.bootstrap",
697 source: "(ns std.foundation.bootstrap) (def ready (str/starts-with? \"hara\" \"ha\"))",
698 },
699 ModuleSource {
700 resource: "std.foundation.string",
701 source: "(ns std.foundation.string (:config {:set-global-alias str})) (defn starts-with? [value prefix] true)",
702 },
703 ModuleSource {
704 resource: "std.foundation",
705 source: "(ns std.foundation) (def foundation-ready true)",
706 },
707 ];
708
709 let bytes = compile_package_bytecode_bundle(&sources, &sources)
710 .expect("compile source-owned Foundation package");
711 let modules = decode(&bytes).expect("decode Foundation package");
712 assert_eq!(modules[0].resource, "std.foundation");
713 assert!(modules
714 .iter()
715 .any(|module| module.resource == "std.foundation.string"));
716
717 let mut runtime = Runtime::core();
718 eval_bytecode_bundle(&mut runtime, &bytes).expect("load Foundation package");
719 runtime
720 .load_bytecode_resource("std.foundation.bootstrap")
721 .expect("load Foundation companion");
722 assert!(runtime.use_namespace("std.foundation.bootstrap"));
723 assert_eq!(runtime.eval_native("ready").unwrap(), "true");
724 }
725
726 #[test]
727 fn package_bundle_preserves_forward_globals_until_their_runtime_call_site() {
728 let sources = [ModuleSource {
729 resource: "demo.forward",
730 source: "(ns demo.forward) (defn answer [] (increment 41)) (defn increment [value] (+ value 1))",
731 }];
732 let bytes = compile_package_bytecode_bundle(&sources, &sources)
733 .expect("compile package with a forward global");
734 let mut runtime = Runtime::core();
735 eval_bytecode_bundle(&mut runtime, &bytes).expect("index forward package bundle");
736 runtime
737 .load_bytecode_resource("demo.forward")
738 .expect("load forward package module");
739 assert!(runtime.use_namespace("demo.forward"));
740 assert_eq!(runtime.eval_native("(answer)").unwrap(), "42");
741 }
742
743 #[test]
744 fn package_bundle_bootstraps_context_foundation_for_an_isolated_package() {
745 let context = [
746 ModuleSource {
747 resource: "std.foundation",
748 source: "(ns std.foundation) (defn atom [value] (Base/atom value)) (defmacro foundation-identity [value] value)",
749 },
750 ModuleSource {
751 resource: "demo.config",
752 source: "(ns demo.config) (def state (foundation-identity (atom {})))",
753 },
754 ];
755 let sources = [context[1]];
756 let bytes = compile_package_bytecode_bundle(&context, &sources)
757 .expect("compile package with intrinsic Foundation context");
758 let modules = decode(&bytes).expect("decode isolated package bundle");
759 assert_eq!(modules.len(), 1);
760 assert_eq!(modules[0].resource, "demo.config");
761 }
762
763 #[test]
764 fn stale_lazy_bytecode_yields_to_registered_source() {
765 let sources = [ModuleSource {
766 resource: "example.stale",
767 source: "(ns example.stale) (def answer 41)",
768 }];
769 let bytes = compile_bytecode_bundle(&sources).expect("compile stale fixture");
770 let mut runtime = Runtime::core();
771 runtime.register_resource("example.stale", "(ns example.stale) (def answer 42)");
772
773 eval_bytecode_bundle(&mut runtime, &bytes).expect("index bundle");
774
775 assert!(!runtime.bytecode_resources.contains_key("example.stale"));
776 assert_eq!(
777 runtime
778 .eval_native("(require [example.stale :as stale]) stale/answer")
779 .unwrap(),
780 "42"
781 );
782 }
783
784 #[test]
785 fn eager_failure_rolls_back_the_whole_bundle() {
786 let mut compiler = Runtime::core();
787 compiler.use_namespace("example.good");
788 let good_artifact = compiler
789 .compile_bytecode_artifact("(def marker 42)")
790 .expect("compile successful eager module");
791 compiler.use_namespace("example.bad");
792 let bad_artifact = compiler
793 .compile_bytecode_artifact("(throw \"boom\")")
794 .expect("compile failing eager module");
795 let good_digest = Sha256::digest(b"good").into();
796 let bad_digest = Sha256::digest(b"bad").into();
797 let modules = [
798 BytecodeBundleModule {
799 resource: "example.good".into(),
800 namespace_form: "(ns example.good)".into(),
801 source_digest: good_digest,
802 dependencies: vec![],
803 eager: true,
804 artifact: good_artifact,
805 },
806 BytecodeBundleModule {
807 resource: "example.bad".into(),
808 namespace_form: "(ns example.bad)".into(),
809 source_digest: bad_digest,
810 dependencies: vec![],
811 eager: true,
812 artifact: bad_artifact,
813 },
814 ];
815 let bytes = encode_bytecode_bundle(&modules).expect("encode transactional fixture");
816 let mut runtime = Runtime::core();
817 let namespaces_before = runtime
818 .namespace_registry
819 .all()
820 .into_iter()
821 .map(|namespace| namespace.name().as_str().to_owned())
822 .collect::<std::collections::HashSet<_>>();
823
824 let error = eval_bytecode_bundle(&mut runtime, &bytes).unwrap_err();
825
826 assert!(error.contains("example.bad"), "{error}");
827 assert!(!runtime.bytecode_resources.contains_key("example.good"));
828 assert!(!runtime.bytecode_resources.contains_key("example.bad"));
829 assert!(!runtime.loaded_resources.contains("example.good"));
830 assert!(!runtime.loaded_resources.contains("example.bad"));
831 assert_eq!(
832 runtime
833 .namespace_registry
834 .all()
835 .into_iter()
836 .map(|namespace| namespace.name().as_str().to_owned())
837 .collect::<std::collections::HashSet<_>>(),
838 namespaces_before
839 );
840 assert_eq!(runtime.namespace_registry.current().name().as_str(), "user");
841 }
842
843 #[test]
844 fn lazy_module_loads_dependency_before_consumer() {
845 let sources = [
846 ModuleSource {
847 resource: "example.protocol",
848 source: "(ns example.protocol) (defn emit-form [value] value)",
849 },
850 ModuleSource {
851 resource: "example.emit",
852 source: "(ns example.emit (:require [example.protocol :as compiler])) (defn emit [value] (compiler/emit-form value))",
853 },
854 ];
855 let bytes = compile_bytecode_bundle(&sources).expect("compile lazy protocol fixture");
856 let mut runtime = Runtime::core();
857 eval_bytecode_bundle(&mut runtime, &bytes).expect("index lazy protocol fixture");
858
859 runtime
860 .load_bytecode_resource("example.emit")
861 .expect("load protocol consumer and dependency");
862
863 assert!(runtime
864 .namespace_registry
865 .find("example.protocol")
866 .is_some());
867 assert!(runtime.namespace_registry.find("example.emit").is_some());
868 }
869
870 #[test]
871 fn lazy_alias_compiles_without_an_eager_edge_and_loads_on_first_call() {
872 let sources = [
873 ModuleSource {
874 resource: "example.lazy.target",
875 source: "(ns example.lazy.target) (defn answer [] 42)",
876 },
877 ModuleSource {
878 resource: "example.lazy.client",
879 source: "(ns example.lazy.client (:require [example.lazy.target :as target :lazy true])) (defn answer [] (target/answer))",
880 },
881 ];
882 let bytes = compile_bytecode_bundle(&sources).expect("compile lazy alias fixture");
883 let modules = decode(&bytes).expect("decode lazy alias fixture");
884 let client = modules
885 .iter()
886 .find(|module| module.resource == "example.lazy.client")
887 .expect("client module");
888 assert!(client.dependencies.is_empty());
889
890 let mut runtime = Runtime::core();
891 eval_bytecode_bundle(&mut runtime, &bytes).expect("index lazy alias fixture");
892 runtime
893 .load_bytecode_resource("example.lazy.client")
894 .expect("load lazy client");
895 assert!(runtime
896 .namespace_registry
897 .find("example.lazy.target")
898 .is_none());
899 assert!(runtime.use_namespace("example.lazy.client"));
900 assert_eq!(runtime.eval_native("(answer)").unwrap(), "42");
901 assert!(runtime
902 .namespace_registry
903 .find("example.lazy.target")
904 .is_some());
905 }
906
907 #[test]
908 fn bundle_compilation_orders_eager_dependencies_before_consumers() {
909 let sources = [
910 ModuleSource {
911 resource: "example.client",
912 source: "(ns example.client (:require [example.target :as target])) (def answer target/answer)",
913 },
914 ModuleSource {
915 resource: "example.target",
916 source: "(ns example.target) (def answer 42)",
917 },
918 ];
919 let bytes = compile_bytecode_bundle(&sources).expect("compile dependency fixture");
920 let modules = decode(&bytes).expect("decode dependency fixture");
921 assert_eq!(modules[0].resource, "example.target");
922 assert_eq!(modules[1].resource, "example.client");
923 }
924
925 #[test]
926 fn eager_host_module_inventory_is_empty() {
927 let sources = embedded_standard_library_sources()
928 .into_iter()
929 .filter(|source| {
930 source.resource == "std.foundation"
931 || EAGER_HAL_RESOURCES.contains(&source.resource)
932 })
933 .collect::<Vec<_>>();
934 assert!(
935 sources.is_empty(),
936 "the standalone host embeds no eager HAL modules"
937 );
938 let bytes = compile_bytecode_bundle(&sources).expect("compile empty eager inventory");
939 let mut runtime = Runtime::core();
940 eval_bytecode_bundle(&mut runtime, &bytes).expect("load empty eager inventory");
941 assert!(runtime
942 .namespace_registry
943 .find("std.foundation.string")
944 .is_none());
945 }
946
947 #[cfg(feature = "tracing-jit")]
948 #[test]
949 fn hbx_installed_functions_remain_eligible_for_jit_compilation() {
950 let mut compiler = Runtime::core();
951 compiler.use_namespace("example.jit");
952 let artifact = compiler
953 .compile_bytecode_artifact(
954 "(defn sum-to [n] (loop [i 0 total 0] (if (< i n) (recur (+ i 1) (+ total i)) total)))",
955 )
956 .expect("compile hot bundle function");
957 let bytes = encode_bytecode_bundle(&[BytecodeBundleModule {
958 resource: "example.jit".into(),
959 namespace_form: "(ns example.jit)".into(),
960 source_digest: Sha256::digest(b"example.jit hot function").into(),
961 dependencies: vec![],
962 eager: true,
963 artifact,
964 }])
965 .expect("encode eager JIT fixture");
966 let mut runtime = Runtime::core();
967
968 eval_bytecode_bundle(&mut runtime, &bytes).expect("load eager JIT fixture through HBX");
969 assert_eq!(
970 runtime.eval_native("(example.jit/sum-to 100)").unwrap(),
971 "4950"
972 );
973 let telemetry = crate::vm::machine::active_jit_telemetry();
974 assert!(
975 crate::vm::machine::active_compiled_trace_count() > 0,
976 "an HBC function installed through HBX must retain its program and JIT state: {telemetry:?}"
977 );
978 }
979
980 #[test]
981 fn embedded_bundle_has_no_foundation_bootstrap() {
982 on_compiler_gate_stack(|| {
983 let sources = embedded_standard_library_sources();
984 assert!(sources.is_empty());
985 let bytes = compile_bytecode_bundle(&sources).expect("compile empty host bootstrap");
986 let modules = decode(&bytes).expect("decode empty host bootstrap");
987 let actual = modules
988 .iter()
989 .map(|module| module.resource.as_str())
990 .collect::<Vec<_>>();
991 assert_eq!(
992 actual.len(),
993 sources.len(),
994 "bundle inventory must be exact"
995 );
996 let mut inventory = actual.clone();
997 inventory.sort_unstable();
998 assert!(inventory.is_empty());
999 assert!(crate::FOUNDATION_BOOTSTRAP_INVENTORY.is_empty());
1000 });
1001 }
1002
1003 #[test]
1004 fn empty_host_bundle_has_no_global_reads() {
1005 on_compiler_gate_stack(|| {
1006 let bytes = compile_bytecode_bundle(&[]).expect("compile empty host bundle");
1007 for module in decode(&bytes).expect("decode empty host bundle") {
1008 let program = crate::vm::decode_program(&module.artifact)
1009 .unwrap_or_else(|error| panic!("decode {}: {error}", module.resource));
1010 assert_eq!(program.namespace.as_deref(), Some(module.resource.as_str()));
1011 for function in &program.functions {
1012 for instruction in &function.code {
1013 if let crate::vm::Instruction::GetGlobal(index) = instruction {
1014 let name = program.constants[*index as usize].display();
1015 assert!(
1016 name.contains('/'),
1017 "{} contains caller-relative global read {name}",
1018 module.resource
1019 );
1020 }
1021 }
1022 }
1023 }
1024 });
1025 }
1026}