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 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
100 {
101 return core::without_direct_native_execution(|| {
102 compile_package_bytecode_bundle_inner(context, sources)
103 });
104 }
105 #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
106 compile_package_bytecode_bundle_inner(context, sources)
107}
108
109fn compile_package_bytecode_bundle_inner(
110 context: &[ModuleSource<'_>],
111 sources: &[ModuleSource<'_>],
112) -> Result<Vec<u8>, String> {
113 let mut runtime = Runtime::new();
114 let ordered = foundation_root_first(sources);
115 for source in context {
116 runtime.register_resource(source.resource, source.source);
117 }
118 #[cfg(not(target_arch = "wasm32"))]
119 if package_needs_foundation_bootstrap(context, &ordered)
120 || ordered.iter().any(|source| source.resource == "std.foundation")
121 {
122 runtime.bootstrap_source_foundation()?;
123 }
124 runtime.configure_execution_backend("interpreter")?;
128 #[cfg(not(target_arch = "wasm32"))]
135 for source in context_modules_to_load(context, &ordered)? {
136 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
137 let result = core::without_direct_native_execution(|| {
138 runtime.eval_native(&format!("(require (quote {}))", source.resource))
139 });
140 #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
141 let result = runtime.eval_native(&format!("(require (quote {}))", source.resource));
142 result.map_err(|error| format!("{}: context loading: {error}", source.resource))?;
143 }
144 compile_bytecode_bundle_with_runtime(&mut runtime, &[], &ordered)
148}
149
150fn context_modules_to_load<'a>(
151 context: &[ModuleSource<'a>],
152 selected: &[ModuleSource<'a>],
153) -> Result<Vec<ModuleSource<'a>>, String> {
154 let selected_names = selected
155 .iter()
156 .map(|source| source.resource)
157 .collect::<std::collections::HashSet<_>>();
158 let positions = context
159 .iter()
160 .enumerate()
161 .map(|(index, source)| (source.resource, index))
162 .collect::<std::collections::HashMap<_, _>>();
163 let mut pending = Vec::new();
164 let mut preload = std::collections::HashSet::new();
165 for source in selected {
166 let (dependencies, script_dependencies) = module_dependencies(source.source)?;
167 pending.extend(
168 dependencies
169 .into_iter()
170 .filter(|name| {
171 !name.starts_with("std.foundation") && !selected_names.contains(name.as_str())
172 }),
173 );
174 preload.extend(script_dependencies.iter().cloned());
175 pending.extend(script_dependencies);
176 }
177 let needs_postgres_spec = selected_names
182 .iter()
183 .any(|name| name.starts_with("postgres."));
184 let needs_xtalk_spec = selected_names
185 .iter()
186 .any(|name| name.starts_with("xt."));
187 pending.extend(
188 context
189 .iter()
190 .filter(|source| {
191 ((needs_postgres_spec && source.resource == "lang.model.v1.spec-postgres")
192 || (needs_xtalk_spec && source.resource == "lang.model.v1.spec-xtalk"))
193 && !selected_names.contains(source.resource)
194 })
195 .map(|source| source.resource.to_owned()),
196 );
197 let mut required = std::collections::HashSet::new();
198 while let Some(name) = pending.pop() {
199 if (!preload.contains(&name) && selected_names.contains(name.as_str()))
200 || !required.insert(name.clone())
201 {
202 continue;
203 }
204 let Some(&index) = positions.get(name.as_str()) else {
205 continue;
206 };
207 let (dependencies, script_dependencies) = module_dependencies(context[index].source)?;
208 pending.extend(
209 dependencies
210 .into_iter()
211 .filter(|dependency| {
212 !dependency.starts_with("std.foundation")
213 && !selected_names.contains(dependency.as_str())
214 }),
215 );
216 preload.extend(script_dependencies.iter().cloned());
217 pending.extend(script_dependencies);
218 }
219 let mut ordered = order_module_sources(context)?
220 .into_iter()
221 .map(|index| context[index])
222 .filter(|source| required.contains(source.resource))
223 .collect::<Vec<_>>();
224 ordered.sort_by_key(|source| {
228 let priority = (needs_postgres_spec && source.resource == "lang.model.v1.spec-postgres")
229 || (needs_xtalk_spec && source.resource == "lang.model.v1.spec-xtalk");
230 (!priority, !preload.contains(source.resource), source.resource)
231 });
232 Ok(ordered)
233}
234
235pub(super) fn module_dependencies(source: &str) -> Result<(Vec<String>, Vec<String>), String> {
236 let (namespace_form, body) = split_namespace_form(source)?;
237 let dependencies = namespace_dependencies(namespace_form)?;
238 let mut script_dependencies = Vec::new();
239 for form in kernel::parse_forms(body)? {
240 collect_script_dependencies(&form, &mut script_dependencies);
241 }
242 script_dependencies.sort();
243 script_dependencies.dedup();
244 Ok((dependencies, script_dependencies))
245}
246
247fn collect_script_dependencies(form: &kernel::Form, output: &mut Vec<String>) {
248 let form = match form {
249 kernel::Form::Metadata(_, value) => value.as_ref(),
250 value => value,
251 };
252 let kernel::Form::List(items) = form else {
253 return;
254 };
255 let is_script = matches!(
256 items.first(),
257 Some(kernel::Form::Symbol(name)) if name == "l/script" || name == "script"
258 );
259 if !is_script {
260 return;
261 }
262 for option in items.iter().skip(2) {
263 let kernel::Form::Map(entries) = option else {
264 continue;
265 };
266 for (key, value) in entries {
267 if !matches!(key, kernel::Form::Keyword(name) if name == "require") {
268 continue;
269 }
270 let kernel::Form::Vector(requirements) = value else {
271 continue;
272 };
273 for requirement in requirements {
274 let kernel::Form::Vector(spec) = requirement else {
275 continue;
276 };
277 if let Some(kernel::Form::Symbol(name)) = spec.first() {
278 output.push(name.clone());
279 }
280 }
281 }
282 }
283}
284
285#[cfg(not(target_arch = "wasm32"))]
286fn package_needs_foundation_bootstrap(
287 context: &[ModuleSource<'_>],
288 sources: &[ModuleSource<'_>],
289) -> bool {
290 let emits_foundation = sources
291 .iter()
292 .any(|source| source.resource == "std.foundation");
293 !emits_foundation
294 && context
295 .iter()
296 .any(|source| source.resource == "std.foundation")
297}
298
299fn foundation_root_first<'a>(sources: &[ModuleSource<'a>]) -> Vec<ModuleSource<'a>> {
300 if !sources
301 .iter()
302 .any(|source| source.resource == "std.foundation")
303 {
304 return sources.to_vec();
305 }
306 let mut ordered = Vec::with_capacity(sources.len());
307 for resource in std::iter::once("std.foundation").chain(EAGER_HAL_RESOURCES.iter().copied()) {
308 if let Some(source) = sources.iter().find(|source| source.resource == resource) {
309 ordered.push(*source);
310 }
311 }
312 let remaining = sources
313 .iter()
314 .filter(|source| {
315 !ordered
316 .iter()
317 .any(|ordered| ordered.resource == source.resource)
318 })
319 .copied()
320 .collect::<Vec<_>>();
321 ordered.extend(remaining);
322 ordered
323}
324
325fn compile_bytecode_bundle_with_runtime(
326 runtime: &mut Runtime,
327 context: &[ModuleSource<'_>],
328 sources: &[ModuleSource<'_>],
329) -> Result<Vec<u8>, String> {
330 for source in context {
331 runtime.register_resource(source.resource, source.source);
332 }
333 let mut encoded = Vec::new();
334 for index in order_module_sources(sources)? {
335 let source = &sources[index];
336 let (namespace_form, body) = split_namespace_form(source.source)?;
337 runtime
338 .eval_text(namespace_form)
339 .map_err(|error| format!("{}: namespace declaration: {error}", source.resource))?;
340 runtime.use_namespace(source.resource);
344 let artifact = core::with_definition_origin(kernel::VarOrigin::HalFallback, || {
345 runtime.compile_package_bytecode_artifact(body)
346 })
347 .map_err(|error| format!("{}: bytecode compilation: {error}", source.resource))?;
348 core::with_definition_origin(kernel::VarOrigin::HalFallback, || {
349 runtime.eval_bytecode_artifact(&artifact)
350 })
351 .map_err(|error| format!("{}: bytecode execution: {error}", source.resource))?;
352 let source_digest: [u8; 32] = Sha256::digest(source.source.as_bytes()).into();
353 let dependencies = namespace_dependencies(namespace_form)?;
354 let eager =
355 source.resource == "std.foundation" || EAGER_HAL_RESOURCES.contains(&source.resource);
356 encoded.push(BytecodeBundleModule {
357 resource: source.resource.to_owned(),
358 namespace_form: namespace_form.to_owned(),
359 source_digest,
360 dependencies,
361 eager,
362 artifact,
363 });
364 }
365 encode_bytecode_bundle(&encoded)
366}
367
368pub fn compile_embedded_foundation_bootstrap_bundle() -> Result<Vec<u8>, String> {
369 compile_bytecode_bundle(&embedded_foundation_bootstrap_sources())
370}
371
372pub fn compile_embedded_cli_bundle() -> Result<Vec<u8>, String> {
376 let foundation = embedded_foundation_bootstrap_sources();
377 let cli = embedded_cli_sources();
378 let mut context = foundation;
379 context.extend(cli.iter().copied());
380 compile_package_bytecode_bundle(&context, &cli)
381}
382
383pub fn compile_embedded_standard_library_bundle() -> Result<Vec<u8>, String> {
386 compile_embedded_foundation_bootstrap_bundle()
387}
388
389pub fn embedded_standard_library_sources() -> Vec<ModuleSource<'static>> {
392 embedded_foundation_bootstrap_sources()
393}
394
395pub fn eval_bytecode_bundle(runtime: &mut Runtime, bytes: &[u8]) -> Result<(), String> {
396 let modules = decode(bytes)?;
397 let mut names = std::collections::HashSet::with_capacity(modules.len());
398 for module in &modules {
399 if !names.insert(module.resource.clone()) {
400 return Err(format!(
401 "duplicate bytecode bundle module: {}",
402 module.resource
403 ));
404 }
405 }
406 let namespaces_before = runtime.namespace_registry.snapshot();
407 let environment_before = runtime.execution.snapshot();
408 let macros_before = runtime.macros.borrow().clone();
409 let protocols_before = runtime.protocols.snapshot();
410 let multimethods_before = core::snapshot_multimethods();
411 let resources_before = runtime.bytecode_resources.clone();
412 let loaded_before = runtime.loaded_resources.clone();
413 let loaded = (|| {
414 for module in &modules {
415 let source = if let Some(source) = runtime.resources.get(&module.resource) {
416 Some(source.clone())
417 } else {
418 #[cfg(not(target_arch = "wasm32"))]
419 {
420 runtime
421 .source_catalog
422 .as_ref()
423 .and_then(|catalog| catalog.path(&module.resource))
424 .as_ref()
425 .map(|path| {
426 std::fs::read_to_string(path).map_err(|error| {
427 format!("cannot read bundled source {}: {error}", path.display())
428 })
429 })
430 .transpose()?
431 }
432 #[cfg(target_arch = "wasm32")]
433 {
434 None
435 }
436 };
437 let source_is_current = source
438 .as_deref()
439 .map(|source| {
440 let digest: [u8; 32] = Sha256::digest(source.as_bytes()).into();
441 digest == module.source_digest
442 })
443 .unwrap_or(true);
444 if !source_is_current {
445 if module.eager {
446 return Err(format!(
447 "stale eager bytecode bundle module: {}",
448 module.resource
449 ));
450 }
451 continue;
452 }
453 runtime.register_bytecode_resource(
454 module.resource.clone(),
455 module.namespace_form.clone(),
456 module.artifact.clone(),
457 );
458 }
459 for module in modules.iter().filter(|module| module.eager) {
460 core::with_definition_origin(kernel::VarOrigin::HalFallback, || {
461 runtime.load_bytecode_resource(&module.resource).map(|_| ())
462 })
463 .map_err(|error| format!("{}: {error}", module.resource))?;
464 runtime.loaded_resources.insert(module.resource.clone());
465 }
466 runtime.use_namespace("user");
467 Ok(())
468 })();
469 if let Err(error) = loaded {
470 runtime.namespace_registry.restore(namespaces_before);
471 runtime.execution.restore(environment_before);
472 *runtime.macros.borrow_mut() = macros_before;
473 runtime.protocols.restore(protocols_before);
474 core::restore_multimethods(multimethods_before);
475 runtime.bytecode_resources = resources_before;
476 runtime.loaded_resources = loaded_before;
477 return Err(error);
478 }
479 Ok(())
480}
481
482pub fn eval_eager_bytecode_bundle_with_registries(
491 namespaces: &kernel::NamespaceRegistry<core::Value>,
492 protocols: &core::ProtocolRegistry,
493 bytes: &[u8],
494) -> Result<(), String> {
495 let modules = decode(bytes)?;
496 if let Some(module) = modules.iter().find(|module| !module.eager) {
497 return Err(format!(
498 "embedding bundle module must be eager: {}",
499 module.resource
500 ));
501 }
502 let mut positions = std::collections::HashMap::with_capacity(modules.len());
503 for (index, module) in modules.iter().enumerate() {
504 if positions.insert(module.resource.as_str(), index).is_some() {
505 return Err(format!(
506 "duplicate bytecode bundle module: {}",
507 module.resource
508 ));
509 }
510 }
511 for (index, module) in modules.iter().enumerate() {
512 for dependency in &module.dependencies {
513 if positions
514 .get(dependency.as_str())
515 .is_some_and(|dependency_index| *dependency_index >= index)
516 {
517 return Err(format!(
518 "{}: bundled dependency must appear first: {dependency}",
519 module.resource
520 ));
521 }
522 }
523 }
524 let programs = modules
525 .iter()
526 .map(|module| {
527 crate::vm::decode_program(&module.artifact)
528 .map(std::rc::Rc::new)
529 .map_err(|error| format!("{}: invalid bytecode artifact: {error}", module.resource))
530 })
531 .collect::<Result<Vec<_>, _>>()?;
532 let namespaces_before = namespaces.snapshot();
533 let protocols_before = protocols.snapshot();
534 let multimethods_before = core::snapshot_multimethods();
535 let loaded = (|| {
536 for (module, program) in modules.iter().zip(programs) {
537 let forms = kernel::parse_forms(&module.namespace_form)
538 .map_err(|error| format!("{}: namespace declaration: {error}", module.resource))?;
539 if forms.len() != 1 {
540 return Err(format!(
541 "{}: bundle namespace declaration must contain exactly one form",
542 module.resource
543 ));
544 }
545 let mut environment = std::collections::HashMap::new();
546 core::with_namespace_registry(namespaces, || {
547 core::with_protocols(protocols, || core::eval(&forms[0], &mut environment))
548 })
549 .map_err(|error| format!("{}: namespace declaration: {error}", module.resource))?;
550 core::with_namespace_registry(namespaces, || {
551 core::with_protocols(protocols, || {
552 crate::vm::execute_program_with_globals(program, namespaces)
553 .map_err(|error| error.to_string())
554 })
555 })
556 .map_err(|error| format!("{}: bytecode execution: {error}", module.resource))?;
557 }
558 Ok(())
559 })();
560 if let Err(error) = loaded {
561 namespaces.restore(namespaces_before);
562 protocols.restore(protocols_before);
563 core::restore_multimethods(multimethods_before);
564 return Err(error);
565 }
566 Ok(())
567}
568
569pub fn encode_bytecode_bundle(modules: &[BytecodeBundleModule]) -> Result<Vec<u8>, String> {
572 let modules = canonical_modules(modules)?;
573 let mut payload = Vec::new();
574 put_u32(&mut payload, modules.len())?;
575 for module in &modules {
576 put_bytes(&mut payload, module.resource.as_bytes())?;
577 put_bytes(&mut payload, module.namespace_form.as_bytes())?;
578 payload.extend_from_slice(&module.source_digest);
579 put_u32(&mut payload, module.dependencies.len())?;
580 for dependency in &module.dependencies {
581 put_bytes(&mut payload, dependency.as_bytes())?;
582 }
583 payload.push(u8::from(module.eager));
584 put_bytes(&mut payload, &module.artifact)?;
585 }
586 let checksum = Sha256::digest(&payload);
587 let mut output = Vec::with_capacity(4 + checksum.len() + payload.len());
588 output.extend_from_slice(MAGIC);
589 output.extend_from_slice(&checksum);
590 output.extend_from_slice(&payload);
591 Ok(output)
592}
593
594pub fn decode_bytecode_bundle(bytes: &[u8]) -> Result<Vec<BytecodeBundleModule>, String> {
595 if bytes.len() < 36 || &bytes[..4] != MAGIC {
596 return Err("invalid HBX0 bytecode bundle header".into());
597 }
598 let payload = &bytes[36..];
599 if Sha256::digest(payload)[..] != bytes[4..36] {
600 return Err("HBX0 bytecode bundle checksum mismatch".into());
601 }
602 let mut input = payload;
603 let count = take_u32(&mut input)? as usize;
604 let mut modules = Vec::with_capacity(count);
605 for _ in 0..count {
606 let resource = take_string(&mut input)?;
607 let namespace_form = take_string(&mut input)?;
608 let source_digest = take(&mut input, 32)?.try_into().unwrap();
609 let dependency_count = take_u32(&mut input)? as usize;
610 let dependencies = (0..dependency_count)
611 .map(|_| take_string(&mut input))
612 .collect::<Result<Vec<_>, _>>()?;
613 let eager = match take(&mut input, 1)?[0] {
614 0 => false,
615 1 => true,
616 _ => return Err("HBX0 bytecode bundle contains invalid eager flag".into()),
617 };
618 let artifact = take_bytes(&mut input)?.to_vec();
619 modules.push(BytecodeBundleModule {
620 resource,
621 namespace_form,
622 source_digest,
623 dependencies,
624 eager,
625 artifact,
626 });
627 }
628 if !input.is_empty() {
629 return Err("trailing bytes in HBX0 bytecode bundle".into());
630 }
631 validate_bundle_modules(&modules)?;
632 for module in &modules {
633 crate::vm::decode_program(&module.artifact)
634 .map_err(|error| format!("{}: invalid HBC0 artifact: {error}", module.resource))?;
635 }
636 Ok(modules)
637}
638
639fn decode(bytes: &[u8]) -> Result<Vec<BytecodeBundleModule>, String> {
640 decode_bytecode_bundle(bytes)
641}
642
643fn canonical_modules(
644 modules: &[BytecodeBundleModule],
645) -> Result<Vec<BytecodeBundleModule>, String> {
646 let mut by_resource = std::collections::BTreeMap::new();
647 for module in modules {
648 if by_resource
649 .insert(module.resource.clone(), module.clone())
650 .is_some()
651 {
652 return Err(format!("duplicate HBX0 module: {}", module.resource));
653 }
654 let mut dependencies = module.dependencies.clone();
655 dependencies.sort();
656 if dependencies.windows(2).any(|pair| pair[0] == pair[1]) {
657 return Err(format!("{}: duplicate HBX0 dependency", module.resource));
658 }
659 }
660 let mut ordered = Vec::with_capacity(modules.len());
661 while !by_resource.is_empty() {
662 let available = by_resource
663 .iter()
664 .find(|(_, module)| {
665 module
666 .dependencies
667 .iter()
668 .all(|dependency| !by_resource.contains_key(dependency))
669 })
670 .map(|(resource, _)| resource.clone())
671 .ok_or("HBX0 module dependencies contain a cycle")?;
672 let mut module = by_resource.remove(&available).unwrap();
673 module.dependencies.sort();
674 ordered.push(module);
675 }
676 validate_bundle_modules(&ordered)?;
677 Ok(ordered)
678}
679
680fn validate_bundle_modules(modules: &[BytecodeBundleModule]) -> Result<(), String> {
681 let mut positions = std::collections::HashMap::with_capacity(modules.len());
682 for (index, module) in modules.iter().enumerate() {
683 if module.resource.is_empty() {
684 return Err("HBX0 module resource must not be empty".into());
685 }
686 if module.namespace_form.is_empty() {
687 return Err(format!(
688 "{}: HBX0 namespace form must not be empty",
689 module.resource
690 ));
691 }
692 if positions.insert(module.resource.as_str(), index).is_some() {
693 return Err(format!("duplicate HBX0 module: {}", module.resource));
694 }
695 if module
696 .dependencies
697 .windows(2)
698 .any(|pair| pair[0] >= pair[1])
699 {
700 return Err(format!(
701 "{}: HBX0 dependencies must be unique and sorted",
702 module.resource
703 ));
704 }
705 }
706 for (index, module) in modules.iter().enumerate() {
707 for dependency in &module.dependencies {
708 if positions
709 .get(dependency.as_str())
710 .is_some_and(|position| *position >= index)
711 {
712 return Err(format!(
713 "{}: HBX0 dependency must appear first: {dependency}",
714 module.resource
715 ));
716 }
717 }
718 }
719 Ok(())
720}
721
722fn standard_library_namespace(namespace: &str) -> bool {
723 ["std.", "code.", "db.", "lang."]
724 .iter()
725 .any(|prefix| namespace.starts_with(prefix))
726}
727
728pub(super) fn namespace_dependencies(namespace_form: &str) -> Result<Vec<String>, String> {
729 let forms = kernel::parse_forms(namespace_form)?;
730 let Some(kernel::Form::List(items)) = forms.first() else {
731 return Err("standard-library module has invalid ns form".into());
732 };
733 let config = kernel::GeneratedNamespaceConfig::configure_with(&items[2..], |_| true)?;
734 let mut dependencies = config.required_namespaces().to_vec();
735 dependencies.extend(config.used_namespaces().iter().cloned());
736 dependencies.sort();
737 dependencies.dedup();
738 Ok(dependencies)
739}
740
741pub(super) fn split_namespace_form(source: &str) -> Result<(&str, &str), String> {
742 let start = source.find("(ns ").ok_or("HAL module is missing ns form")?;
743 let mut depth = 0usize;
744 let mut string = false;
745 let mut escape = false;
746 for (offset, ch) in source[start..].char_indices() {
747 if string {
748 if escape {
749 escape = false;
750 } else if ch == '\\' {
751 escape = true;
752 } else if ch == '"' {
753 string = false;
754 }
755 continue;
756 }
757 match ch {
758 '"' => string = true,
759 '(' => depth += 1,
760 ')' => {
761 depth = depth.checked_sub(1).ok_or("invalid ns form")?;
762 if depth == 0 {
763 let end = start + offset + ch.len_utf8();
764 return Ok((&source[start..end], &source[end..]));
765 }
766 }
767 _ => {}
768 }
769 }
770 Err("unterminated ns form".into())
771}
772
773fn put_u32(output: &mut Vec<u8>, value: usize) -> Result<(), String> {
774 let value = u32::try_from(value).map_err(|_| "foundation bundle exceeds u32 limits")?;
775 output.extend_from_slice(&value.to_le_bytes());
776 Ok(())
777}
778
779fn put_bytes(output: &mut Vec<u8>, value: &[u8]) -> Result<(), String> {
780 put_u32(output, value.len())?;
781 output.extend_from_slice(value);
782 Ok(())
783}
784
785fn take_u32(input: &mut &[u8]) -> Result<u32, String> {
786 let bytes = take(input, 4)?;
787 Ok(u32::from_le_bytes(bytes.try_into().unwrap()))
788}
789
790fn take_bytes<'a>(input: &mut &'a [u8]) -> Result<&'a [u8], String> {
791 let len = take_u32(input)? as usize;
792 take(input, len)
793}
794
795fn take_string(input: &mut &[u8]) -> Result<String, String> {
796 String::from_utf8(take_bytes(input)?.to_vec())
797 .map_err(|_| "foundation bundle contains invalid UTF-8".into())
798}
799
800fn take<'a>(input: &mut &'a [u8], len: usize) -> Result<&'a [u8], String> {
801 if input.len() < len {
802 return Err("truncated HBX0 bytecode bundle".into());
803 }
804 let (value, rest) = input.split_at(len);
805 *input = rest;
806 Ok(value)
807}
808
809#[cfg(test)]
810mod tests {
811 use super::*;
812
813 const COMPILER_GATE_STACK_SIZE: usize = 64 * 1024 * 1024;
814
815 fn on_compiler_gate_stack(test: impl FnOnce() + Send + 'static) {
816 std::thread::Builder::new()
817 .name("foundation-bytecode-compiler-gate".into())
818 .stack_size(COMPILER_GATE_STACK_SIZE)
823 .spawn(test)
824 .expect("spawn foundation compiler gate")
825 .join()
826 .expect("foundation compiler gate panicked");
827 }
828
829 #[test]
830 fn empty_host_bundle_round_trips_against_the_native_registry() {
831 on_compiler_gate_stack(|| {
832 let sources = embedded_standard_library_sources();
833 assert!(
834 sources.is_empty(),
835 "the standalone host embeds no HAL source"
836 );
837 let bytes = compile_bytecode_bundle(&sources).expect("compile empty host bundle");
838 let mut runtime = Runtime::core();
839 eval_bytecode_bundle(&mut runtime, &bytes).expect("load empty host bundle");
840 assert!(!runtime.bytecode_resources.contains_key("std.foundation"));
841 assert_eq!(
842 runtime.eval_native("(std.native.String/upper \"hara\")"),
843 Ok("\"HARA\"".into())
844 );
845 });
846 }
847
848 #[test]
849 fn bytecode_index_has_no_implicit_foundation_module() {
850 let modules = decode(&compile_bytecode_bundle(&[]).expect("compile empty host bundle"))
851 .expect("decode empty host bundle");
852 assert!(modules.is_empty());
853 }
854
855 #[test]
856 fn bundle_encoding_is_deterministic() {
857 let sources = [ModuleSource {
858 resource: "example.deterministic",
859 source: "(ns example.deterministic) (def answer 42)",
860 }];
861 let first = compile_bytecode_bundle(&sources).expect("first deterministic bundle");
862 let second = compile_bytecode_bundle(&sources).expect("second deterministic bundle");
863 assert_eq!(first, second);
864 }
865
866 #[test]
867 fn foundation_package_compiles_the_root_before_companions() {
868 let sources = [
869 ModuleSource {
870 resource: "std.foundation.bootstrap",
871 source: "(ns std.foundation.bootstrap) (def ready (str/starts-with? \"hara\" \"ha\"))",
872 },
873 ModuleSource {
874 resource: "std.foundation.string",
875 source: "(ns std.foundation.string (:config {:set-global-alias str})) (defn starts-with? [value prefix] true)",
876 },
877 ModuleSource {
878 resource: "std.foundation",
879 source: "(ns std.foundation) (def foundation-ready true)",
880 },
881 ];
882
883 let bytes = compile_package_bytecode_bundle(&sources, &sources)
884 .expect("compile source-owned Foundation package");
885 let modules = decode(&bytes).expect("decode Foundation package");
886 assert_eq!(modules[0].resource, "std.foundation");
887 assert!(modules
888 .iter()
889 .any(|module| module.resource == "std.foundation.string"));
890
891 let mut runtime = Runtime::core();
892 eval_bytecode_bundle(&mut runtime, &bytes).expect("load Foundation package");
893 runtime
894 .load_bytecode_resource("std.foundation.bootstrap")
895 .expect("load Foundation companion");
896 assert!(runtime.use_namespace("std.foundation.bootstrap"));
897 assert_eq!(runtime.eval_native("ready").unwrap(), "true");
898 }
899
900 #[test]
901 fn package_bundle_preserves_forward_globals_until_their_runtime_call_site() {
902 let sources = [ModuleSource {
903 resource: "demo.forward",
904 source: "(ns demo.forward) (defn answer [] (increment 41)) (defn increment [value] (+ value 1))",
905 }];
906 let bytes = compile_package_bytecode_bundle(&sources, &sources)
907 .expect("compile package with a forward global");
908 let mut runtime = Runtime::core();
909 eval_bytecode_bundle(&mut runtime, &bytes).expect("index forward package bundle");
910 runtime
911 .load_bytecode_resource("demo.forward")
912 .expect("load forward package module");
913 assert!(runtime.use_namespace("demo.forward"));
914 assert_eq!(runtime.eval_native("(answer)").unwrap(), "42");
915 }
916
917 #[test]
918 fn package_bundle_bootstraps_context_foundation_for_an_isolated_package() {
919 let context = [
920 ModuleSource {
921 resource: "std.foundation",
922 source: "(ns std.foundation) (defn atom [value] (Base/atom value)) (defmacro foundation-identity [value] value)",
923 },
924 ModuleSource {
925 resource: "demo.config",
926 source: "(ns demo.config) (def state (foundation-identity (atom {})))",
927 },
928 ];
929 let sources = [context[1]];
930 let bytes = compile_package_bytecode_bundle(&context, &sources)
931 .expect("compile package with intrinsic Foundation context");
932 let modules = decode(&bytes).expect("decode isolated package bundle");
933 assert_eq!(modules.len(), 1);
934 assert_eq!(modules[0].resource, "demo.config");
935 }
936
937 #[test]
938 fn package_bundle_loads_context_macros_before_selected_modules() {
939 let context = [
940 ModuleSource {
941 resource: "demo.context",
942 source: "(ns demo.context) (defmacro context-identity [value] value)",
943 },
944 ModuleSource {
945 resource: "demo.client",
946 source: "(ns demo.client (:require [demo.context :refer-macros [context-identity]])) (def answer (context-identity 42))",
947 },
948 ];
949 let bytes = compile_package_bytecode_bundle(&context, &context[1..])
950 .expect("compile selected module against context macro");
951 let modules = decode(&bytes).expect("decode context macro package");
952 assert_eq!(modules.len(), 1);
953 assert_eq!(modules[0].resource, "demo.client");
954 }
955
956 #[test]
957 fn stale_lazy_bytecode_yields_to_registered_source() {
958 let sources = [ModuleSource {
959 resource: "example.stale",
960 source: "(ns example.stale) (def answer 41)",
961 }];
962 let bytes = compile_bytecode_bundle(&sources).expect("compile stale fixture");
963 let mut runtime = Runtime::core();
964 runtime.register_resource("example.stale", "(ns example.stale) (def answer 42)");
965
966 eval_bytecode_bundle(&mut runtime, &bytes).expect("index bundle");
967
968 assert!(!runtime.bytecode_resources.contains_key("example.stale"));
969 assert_eq!(
970 runtime
971 .eval_native("(require [example.stale :as stale]) stale/answer")
972 .unwrap(),
973 "42"
974 );
975 }
976
977 #[test]
978 fn eager_failure_rolls_back_the_whole_bundle() {
979 let mut compiler = Runtime::core();
980 compiler.use_namespace("example.good");
981 let good_artifact = compiler
982 .compile_bytecode_artifact("(def marker 42)")
983 .expect("compile successful eager module");
984 compiler.use_namespace("example.bad");
985 let bad_artifact = compiler
986 .compile_bytecode_artifact("(throw \"boom\")")
987 .expect("compile failing eager module");
988 let good_digest = Sha256::digest(b"good").into();
989 let bad_digest = Sha256::digest(b"bad").into();
990 let modules = [
991 BytecodeBundleModule {
992 resource: "example.good".into(),
993 namespace_form: "(ns example.good)".into(),
994 source_digest: good_digest,
995 dependencies: vec![],
996 eager: true,
997 artifact: good_artifact,
998 },
999 BytecodeBundleModule {
1000 resource: "example.bad".into(),
1001 namespace_form: "(ns example.bad)".into(),
1002 source_digest: bad_digest,
1003 dependencies: vec![],
1004 eager: true,
1005 artifact: bad_artifact,
1006 },
1007 ];
1008 let bytes = encode_bytecode_bundle(&modules).expect("encode transactional fixture");
1009 let mut runtime = Runtime::core();
1010 let namespaces_before = runtime
1011 .namespace_registry
1012 .all()
1013 .into_iter()
1014 .map(|namespace| namespace.name().as_str().to_owned())
1015 .collect::<std::collections::HashSet<_>>();
1016
1017 let error = eval_bytecode_bundle(&mut runtime, &bytes).unwrap_err();
1018
1019 assert!(error.contains("example.bad"), "{error}");
1020 assert!(!runtime.bytecode_resources.contains_key("example.good"));
1021 assert!(!runtime.bytecode_resources.contains_key("example.bad"));
1022 assert!(!runtime.loaded_resources.contains("example.good"));
1023 assert!(!runtime.loaded_resources.contains("example.bad"));
1024 assert_eq!(
1025 runtime
1026 .namespace_registry
1027 .all()
1028 .into_iter()
1029 .map(|namespace| namespace.name().as_str().to_owned())
1030 .collect::<std::collections::HashSet<_>>(),
1031 namespaces_before
1032 );
1033 assert_eq!(runtime.namespace_registry.current().name().as_str(), "user");
1034 }
1035
1036 #[test]
1037 fn lazy_module_loads_dependency_before_consumer() {
1038 let sources = [
1039 ModuleSource {
1040 resource: "example.protocol",
1041 source: "(ns example.protocol) (defn emit-form [value] value)",
1042 },
1043 ModuleSource {
1044 resource: "example.emit",
1045 source: "(ns example.emit (:require [example.protocol :as compiler])) (defn emit [value] (compiler/emit-form value))",
1046 },
1047 ];
1048 let bytes = compile_bytecode_bundle(&sources).expect("compile lazy protocol fixture");
1049 let mut runtime = Runtime::core();
1050 eval_bytecode_bundle(&mut runtime, &bytes).expect("index lazy protocol fixture");
1051
1052 runtime
1053 .load_bytecode_resource("example.emit")
1054 .expect("load protocol consumer and dependency");
1055
1056 assert!(runtime
1057 .namespace_registry
1058 .find("example.protocol")
1059 .is_some());
1060 assert!(runtime.namespace_registry.find("example.emit").is_some());
1061 }
1062
1063 #[test]
1064 fn lazy_alias_compiles_without_an_eager_edge_and_loads_on_first_call() {
1065 let sources = [
1066 ModuleSource {
1067 resource: "example.lazy.target",
1068 source: "(ns example.lazy.target) (defn answer [] 42)",
1069 },
1070 ModuleSource {
1071 resource: "example.lazy.client",
1072 source: "(ns example.lazy.client (:require [example.lazy.target :as target :lazy true])) (defn answer [] (target/answer))",
1073 },
1074 ];
1075 let bytes = compile_bytecode_bundle(&sources).expect("compile lazy alias fixture");
1076 let modules = decode(&bytes).expect("decode lazy alias fixture");
1077 let client = modules
1078 .iter()
1079 .find(|module| module.resource == "example.lazy.client")
1080 .expect("client module");
1081 assert!(client.dependencies.is_empty());
1082
1083 let mut runtime = Runtime::core();
1084 eval_bytecode_bundle(&mut runtime, &bytes).expect("index lazy alias fixture");
1085 runtime
1086 .load_bytecode_resource("example.lazy.client")
1087 .expect("load lazy client");
1088 assert!(runtime
1089 .namespace_registry
1090 .find("example.lazy.target")
1091 .is_none());
1092 assert!(runtime.use_namespace("example.lazy.client"));
1093 assert_eq!(runtime.eval_native("(answer)").unwrap(), "42");
1094 assert!(runtime
1095 .namespace_registry
1096 .find("example.lazy.target")
1097 .is_some());
1098 }
1099
1100 #[test]
1101 fn bundle_compilation_orders_eager_dependencies_before_consumers() {
1102 let sources = [
1103 ModuleSource {
1104 resource: "example.client",
1105 source: "(ns example.client (:require [example.target :as target])) (def answer target/answer)",
1106 },
1107 ModuleSource {
1108 resource: "example.target",
1109 source: "(ns example.target) (def answer 42)",
1110 },
1111 ];
1112 let bytes = compile_bytecode_bundle(&sources).expect("compile dependency fixture");
1113 let modules = decode(&bytes).expect("decode dependency fixture");
1114 assert_eq!(modules[0].resource, "example.target");
1115 assert_eq!(modules[1].resource, "example.client");
1116 }
1117
1118 #[test]
1119 fn eager_host_module_inventory_is_empty() {
1120 let sources = embedded_standard_library_sources()
1121 .into_iter()
1122 .filter(|source| {
1123 source.resource == "std.foundation"
1124 || EAGER_HAL_RESOURCES.contains(&source.resource)
1125 })
1126 .collect::<Vec<_>>();
1127 assert!(
1128 sources.is_empty(),
1129 "the standalone host embeds no eager HAL modules"
1130 );
1131 let bytes = compile_bytecode_bundle(&sources).expect("compile empty eager inventory");
1132 let mut runtime = Runtime::core();
1133 eval_bytecode_bundle(&mut runtime, &bytes).expect("load empty eager inventory");
1134 assert!(runtime
1135 .namespace_registry
1136 .find("std.foundation.string")
1137 .is_none());
1138 }
1139
1140 #[cfg(feature = "tracing-jit")]
1141 #[test]
1142 fn hbx_installed_functions_remain_eligible_for_jit_compilation() {
1143 let mut compiler = Runtime::core();
1144 compiler.use_namespace("example.jit");
1145 let artifact = compiler
1146 .compile_bytecode_artifact(
1147 "(defn sum-to [n] (loop [i 0 total 0] (if (< i n) (recur (+ i 1) (+ total i)) total)))",
1148 )
1149 .expect("compile hot bundle function");
1150 let bytes = encode_bytecode_bundle(&[BytecodeBundleModule {
1151 resource: "example.jit".into(),
1152 namespace_form: "(ns example.jit)".into(),
1153 source_digest: Sha256::digest(b"example.jit hot function").into(),
1154 dependencies: vec![],
1155 eager: true,
1156 artifact,
1157 }])
1158 .expect("encode eager JIT fixture");
1159 let mut runtime = Runtime::core();
1160
1161 eval_bytecode_bundle(&mut runtime, &bytes).expect("load eager JIT fixture through HBX");
1162 assert_eq!(
1163 runtime.eval_native("(example.jit/sum-to 100)").unwrap(),
1164 "4950"
1165 );
1166 let telemetry = crate::vm::machine::active_jit_telemetry();
1167 assert!(
1168 crate::vm::machine::active_compiled_trace_count() > 0,
1169 "an HBC function installed through HBX must retain its program and JIT state: {telemetry:?}"
1170 );
1171 }
1172
1173 #[test]
1174 fn embedded_bundle_has_no_foundation_bootstrap() {
1175 on_compiler_gate_stack(|| {
1176 let sources = embedded_standard_library_sources();
1177 assert!(sources.is_empty());
1178 let bytes = compile_bytecode_bundle(&sources).expect("compile empty host bootstrap");
1179 let modules = decode(&bytes).expect("decode empty host bootstrap");
1180 let actual = modules
1181 .iter()
1182 .map(|module| module.resource.as_str())
1183 .collect::<Vec<_>>();
1184 assert_eq!(
1185 actual.len(),
1186 sources.len(),
1187 "bundle inventory must be exact"
1188 );
1189 let mut inventory = actual.clone();
1190 inventory.sort_unstable();
1191 assert!(inventory.is_empty());
1192 assert!(crate::FOUNDATION_BOOTSTRAP_INVENTORY.is_empty());
1193 });
1194 }
1195
1196 #[test]
1197 fn empty_host_bundle_has_no_global_reads() {
1198 on_compiler_gate_stack(|| {
1199 let bytes = compile_bytecode_bundle(&[]).expect("compile empty host bundle");
1200 for module in decode(&bytes).expect("decode empty host bundle") {
1201 let program = crate::vm::decode_program(&module.artifact)
1202 .unwrap_or_else(|error| panic!("decode {}: {error}", module.resource));
1203 assert_eq!(program.namespace.as_deref(), Some(module.resource.as_str()));
1204 for function in &program.functions {
1205 for instruction in &function.code {
1206 if let crate::vm::Instruction::GetGlobal(index) = instruction {
1207 let name = program.constants[*index as usize].display();
1208 assert!(
1209 name.contains('/'),
1210 "{} contains caller-relative global read {name}",
1211 module.resource
1212 );
1213 }
1214 }
1215 }
1216 }
1217 });
1218 }
1219}