Skip to main content

hara_native/vm/
bundle.rs

1//! Deterministic indexed container for the embedded Foundation bootstrap.
2
3use 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/// One validated module in the shared HBX0 container format.
22///
23/// Products such as Hoplite use this descriptor to package application HBC0
24/// artifacts without maintaining a second, subtly different bundle codec.
25#[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
64/// Returns the embedded CLI/test-support namespace closure in deterministic
65/// dependency order. Foundation namespaces are deliberately excluded because
66/// they are already supplied by the runtime's Foundation artifact.
67pub 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
87/// Compiles a package against the portable core runtime. `context` is
88/// registered for resolving imports and macros, while only `sources` are
89/// emitted into the resulting HBX0 bundle. A source-owned Foundation package
90/// evaluates `std.foundation` first so its companion namespaces can resolve
91/// the root surface during their own compilation.
92pub 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        // Required modules and macro expansion are allowed to select their
144        // own namespaces. Pin compilation to the module being emitted so
145        // aliases become canonical globals owned by its declaration.
146        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
175/// Compiles the immutable CLI and `code.test` closure against the already
176/// bootstrapped Foundation context. The resulting bundle is installed lazily,
177/// so a test or CLI process pays only for the namespaces it actually requires.
178pub 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
186/// Compatibility name retained for embedding hosts built against the original
187/// standard-library bundle API. The embedded artifact is now Foundation-only.
188pub fn compile_embedded_standard_library_bundle() -> Result<Vec<u8>, String> {
189    compile_embedded_foundation_bootstrap_bundle()
190}
191
192/// Compatibility name retained for callers that previously inspected the
193/// embedded standard-library sources.
194pub 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
283/// Transactionally load a fully eager HBX0 application bundle into an
284/// embedding host's existing namespace and protocol registries.
285///
286/// The ordinary [`eval_bytecode_bundle`] API additionally indexes lazy
287/// standard-library resources on a [`Runtime`]. Worker hosts such as Hoplite
288/// already own their registries and package every application module eagerly,
289/// so this narrower entry point preserves that ownership without falling back
290/// to source compilation.
291pub 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
370/// Encode modules into the deterministic, checksummed HBX0 container shared by
371/// the Rust, Truffle/native-image, and embedding runtimes.
372pub 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            // Compiling the complete portable library exercises the recursive
620            // debug evaluator used to establish macro and declaration state.
621            // Keep that test-only headroom local instead of requiring callers
622            // to raise RUST_MIN_STACK for the entire test process.
623            .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}