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