Skip to main content

javm_transpiler/
linker.rs

1//! ELF → JAVM `Image`.
2//!
3//! The ELF parsing and the whole RV→PVM2 rewrite live in
4//! [`nub_linker`]: they are ISA work with no capability content. What
5//! remains here is the JAVM-specific half — wrapping the resulting
6//! [`ProgramBlob`] in the cap shape.
7
8use crate::TranspileError;
9use crate::layout::{PVM_PAGE_SIZE, cap_index};
10use javm_cap::Key;
11use javm_cap::abi::BARE_YIELD_RECEIVER_SLOT;
12use javm_cap::image::{EndpointDef, Image, ImageBuilder, MemoryMapping};
13use javm_cap::slot::SlotPath;
14use nub_program::{ProgramBlob, RegionKind};
15
16/// Link an RV ELF into a PVM2 [`Image`]. `Image::code` holds the raw
17/// RV+C+custom-0 bytes, mapped read-only at `CODE_BASE` by the runtime.
18pub fn link_elf(elf_data: &[u8]) -> Result<Image, TranspileError> {
19    Ok(image_from_blob(&nub_linker::link_elf(elf_data)?))
20}
21
22/// Wrap a personality-free [`ProgramBlob`] in the JAVM cap shape.
23///
24/// Each data region becomes one `Cap::Data` at its conventional cnode
25/// slot — `pinned_data` for the read-only region, `initial_data` for
26/// the rest — plus a declarative `MemoryMapping` pointing at that slot.
27/// Endpoints are re-keyed by [`Key`], and the bare-Frame yield-receiver
28/// slot is set.
29///
30/// This is a pure shape transform: page splitting, all-zero-page
31/// elision and content-dedup into the shared `arena` happen inside
32/// [`ImageBuilder::build`].
33///
34/// **Ordering is load-bearing.** `ImageBuilder` packs the arena in
35/// insertion order, so regions must be added in
36/// [`nub_program::Regions::iter`] order (stack, ro, rw, heap) and the
37/// code last, or the emitted bytes shift.
38pub fn image_from_blob(program: &ProgramBlob) -> Image {
39    let page_bytes = u64::from(PVM_PAGE_SIZE);
40    let mut builder = ImageBuilder::new();
41    let mut mappings: Vec<MemoryMapping> = Vec::new();
42
43    for region in program.regions.iter() {
44        let slot = Key::from(cap_index(region.kind));
45        let size = u64::from(region.page_count) * page_bytes;
46        mappings.push(MemoryMapping {
47            start: region.start(),
48            size,
49            source: SlotPath::root(slot.clone()),
50        });
51        // Stack and heap are zero-initialized, so they carry no bytes.
52        let bytes = program.region_data(region.kind).unwrap_or(&[]).to_vec();
53        builder = if region.kind == RegionKind::Ro {
54            builder.pinned_data(slot, bytes, size)
55        } else {
56            builder.initial_data(slot, bytes, size)
57        };
58    }
59
60    builder = builder.code(program.code.clone());
61    for (&index, endpoint) in &program.endpoints {
62        builder = builder.endpoint(
63            Key::from(index),
64            EndpointDef {
65                entry_pc: endpoint.entry_pc,
66                arg_registers: endpoint.arg_registers,
67                arg_cnode_size: endpoint.arg_meta,
68                initial_regs: endpoint.initial_regs.clone(),
69            },
70        );
71    }
72    for mapping in mappings {
73        builder = builder.mapping(mapping);
74    }
75
76    builder
77        .yield_receiver_slot(Some(Key::from(BARE_YIELD_RECEIVER_SLOT)))
78        .build()
79}