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