Skip to main content

javm_transpiler/
layout.rs

1//! Shared program data-region layout for transpiler-emitted Images.
2//!
3//! [`ProgramLayout`] assigns `cap_index`, `base_page`, and `page_count`
4//! to each DATA cap appearing in a transpiler-emitted Image. The
5//! consumer is [`crate::linker::link_elf`], which uses
6//! [`ProgramLayout::stack_top`] to compute the initial SP value baked
7//! into every endpoint's
8//! [`javm_cap::image::EndpointDef::initial_regs`]. The page-count and
9//! base-page metadata also feed declarative `Image.memory_mappings`.
10//!
11//! Cap-index convention: 65 = stack, 66 = ro, 67 = rw, 68 = heap.
12//! Code is not a cap — it is the Image's dedicated `code` field mapped
13//! read-only at [`CODE_BASE`]. Data is laid out from
14//! [`javm_cap::layout::DATA_BASE`] (256 MiB) upward and stacks linearly:
15//! stack lives at `[DATA_BASE, DATA_BASE + stack_pages)`, ro at
16//! `[DATA_BASE + stack_pages, …)`, etc.
17
18/// Cap index of the stack DATA cap.
19pub const STACK_CAP_INDEX: u8 = 65;
20/// Cap index of the read-only DATA cap (`.rodata`).
21pub const RO_CAP_INDEX: u8 = 66;
22/// Cap index of the read-write DATA cap (`.data` + `.bss`).
23pub const RW_CAP_INDEX: u8 = 67;
24/// Cap index of the heap DATA cap.
25pub const HEAP_CAP_INDEX: u8 = 68;
26/// PVM page size in bytes.
27pub const PVM_PAGE_SIZE: u32 = 4096;
28
29/// Guest virtual address where the code region is mapped read-only.
30///
31/// The canonical definition is the PVM2 ABI constant
32/// [`javm_cap::layout::CODE_BASE`] (re-exported here for transpiler
33/// call sites). Code occupies `[CODE_BASE, DATA_BASE)`; data regions
34/// (stack/ro/rw/heap) occupy `[DATA_BASE, 4 GiB)` (see [`ProgramLayout`]).
35/// The linker asserts code stays below [`DATA_BASE`] and the data
36/// layout stays within the 4 GiB guest range.
37pub use javm_cap::layout::CODE_BASE;
38/// Re-exported PVM2 ABI layout constants (see [`javm_cap::layout`]).
39pub use javm_cap::layout::{DATA_BASE, MAX_CODE_SIZE};
40
41/// One DATA cap's layout: where it lives in the manifest and where it
42/// maps in guest memory.
43#[derive(Debug, Clone, Copy)]
44pub struct DataCapEntry {
45    pub cap_index: u8,
46    pub base_page: u32,
47    pub page_count: u32,
48}
49
50/// Full DATA-cap layout of a transpiler-emitted blob. `stack` is
51/// always present; `ro`, `rw`, `heap` are present only when their
52/// page count is non-zero. Args bytes are delivered separately
53/// (kernel-allocated cap at bare-Frame slot 4), so they are not part
54/// of the layout.
55#[derive(Debug, Clone)]
56pub struct ProgramLayout {
57    pub stack: DataCapEntry,
58    pub ro: Option<DataCapEntry>,
59    pub rw: Option<DataCapEntry>,
60    pub heap: Option<DataCapEntry>,
61}
62
63impl ProgramLayout {
64    /// Compute the layout from per-region page counts. `stack_pages`
65    /// must be ≥ 1 in any sane build, but the function does not enforce
66    /// that. `ro_pages`, `rw_pages`, `heap_pages` of zero omit those
67    /// caps entirely.
68    pub fn compute(stack_pages: u32, ro_pages: u32, rw_pages: u32, heap_pages: u32) -> Self {
69        // Data starts at DATA_BASE (256 MiB), above the code region.
70        let mut next_page = javm_cap::layout::DATA_BASE / PVM_PAGE_SIZE;
71
72        let stack = DataCapEntry {
73            cap_index: STACK_CAP_INDEX,
74            base_page: next_page,
75            page_count: stack_pages,
76        };
77        next_page += stack_pages;
78
79        let ro = if ro_pages > 0 {
80            let e = DataCapEntry {
81                cap_index: RO_CAP_INDEX,
82                base_page: next_page,
83                page_count: ro_pages,
84            };
85            next_page += ro_pages;
86            Some(e)
87        } else {
88            None
89        };
90
91        let rw = if rw_pages > 0 {
92            let e = DataCapEntry {
93                cap_index: RW_CAP_INDEX,
94                base_page: next_page,
95                page_count: rw_pages,
96            };
97            next_page += rw_pages;
98            Some(e)
99        } else {
100            None
101        };
102
103        let heap = if heap_pages > 0 {
104            let e = DataCapEntry {
105                cap_index: HEAP_CAP_INDEX,
106                base_page: next_page,
107                page_count: heap_pages,
108            };
109            Some(e)
110        } else {
111            None
112        };
113
114        Self {
115            stack,
116            ro,
117            rw,
118            heap,
119        }
120    }
121
122    /// Iterate every DATA cap entry in cap-index (and base-page) order:
123    /// stack, ro?, rw?, heap?.
124    pub fn data_caps(&self) -> impl Iterator<Item = &DataCapEntry> + '_ {
125        std::iter::once(&self.stack)
126            .chain(self.ro.iter())
127            .chain(self.rw.iter())
128            .chain(self.heap.iter())
129    }
130
131    /// Top-of-stack address (initial SP). RISC-V SP grows downward, so
132    /// the first push lands at `stack_top - 8`.
133    pub fn stack_top(&self) -> u64 {
134        (self.stack.base_page + self.stack.page_count) as u64 * PVM_PAGE_SIZE as u64
135    }
136
137    /// Total pages across all DATA caps in this layout.
138    pub fn total_data_pages(&self) -> u32 {
139        self.data_caps().map(|d| d.page_count).sum()
140    }
141}