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