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`, `page_count`,
4//! and `access` to each DATA cap appearing in a transpiler-emitted
5//! Image. Today the only consumer is [`crate::linker::link_elf`],
6//! which uses [`ProgramLayout::stack_top`] to compute the initial
7//! SP value baked into every endpoint's
8//! [`javm_cap::image::EndpointDef::initial_regs`]. The page-count
9//! and base-page metadata will also feed declarative
10//! `Image.memory_mappings` once the kernel honors them at instance
11//! init (future work).
12//!
13//! Cap-index convention: 64 = CODE, 65 = stack, 66 = ro, 67 = rw,
14//! 68 = heap. Address layout starts at page 0 and stacks linearly:
15//! stack lives at `[0, stack_pages)`, ro at `[stack_pages,
16//! stack_pages + ro_pages)`, etc.
17
18use crate::program::Access;
19
20/// Cap index of the CODE cap in transpiler-emitted blobs. Matches the
21/// JAR `init_cap` field.
22pub const CODE_CAP_INDEX: u8 = 64;
23/// Cap index of the stack DATA cap.
24pub const STACK_CAP_INDEX: u8 = 65;
25/// Cap index of the read-only DATA cap (`.rodata`).
26pub const RO_CAP_INDEX: u8 = 66;
27/// Cap index of the read-write DATA cap (`.data` + `.bss`).
28pub const RW_CAP_INDEX: u8 = 67;
29/// Cap index of the heap DATA cap.
30pub const HEAP_CAP_INDEX: u8 = 68;
31/// PVM page size in bytes.
32pub const PVM_PAGE_SIZE: u32 = 4096;
33
34/// One DATA cap's layout: where it lives in the manifest, where it maps
35/// in guest memory, and at what access mode.
36#[derive(Debug, Clone, Copy)]
37pub struct DataCapEntry {
38    pub cap_index: u8,
39    pub base_page: u32,
40    pub page_count: u32,
41    pub access: Access,
42}
43
44/// Full DATA-cap layout of a transpiler-emitted blob. `stack` is
45/// always present; `ro`, `rw`, `heap` are present only when their
46/// page count is non-zero. Args bytes are delivered separately
47/// (kernel-allocated cap at bare-Frame slot 4), so they are not part
48/// of the layout.
49#[derive(Debug, Clone)]
50pub struct ProgramLayout {
51    pub stack: DataCapEntry,
52    pub ro: Option<DataCapEntry>,
53    pub rw: Option<DataCapEntry>,
54    pub heap: Option<DataCapEntry>,
55}
56
57impl ProgramLayout {
58    /// Compute the layout from per-region page counts. `stack_pages`
59    /// must be ≥ 1 in any sane build, but the function does not enforce
60    /// that. `ro_pages`, `rw_pages`, `heap_pages` of zero omit those
61    /// caps entirely.
62    pub fn compute(stack_pages: u32, ro_pages: u32, rw_pages: u32, heap_pages: u32) -> Self {
63        let mut next_page = 0u32;
64
65        let stack = DataCapEntry {
66            cap_index: STACK_CAP_INDEX,
67            base_page: next_page,
68            page_count: stack_pages,
69            access: Access::RW,
70        };
71        next_page += stack_pages;
72
73        let ro = if ro_pages > 0 {
74            let e = DataCapEntry {
75                cap_index: RO_CAP_INDEX,
76                base_page: next_page,
77                page_count: ro_pages,
78                access: Access::RO,
79            };
80            next_page += ro_pages;
81            Some(e)
82        } else {
83            None
84        };
85
86        let rw = if rw_pages > 0 {
87            let e = DataCapEntry {
88                cap_index: RW_CAP_INDEX,
89                base_page: next_page,
90                page_count: rw_pages,
91                access: Access::RW,
92            };
93            next_page += rw_pages;
94            Some(e)
95        } else {
96            None
97        };
98
99        let heap = if heap_pages > 0 {
100            let e = DataCapEntry {
101                cap_index: HEAP_CAP_INDEX,
102                base_page: next_page,
103                page_count: heap_pages,
104                access: Access::RW,
105            };
106            Some(e)
107        } else {
108            None
109        };
110
111        Self {
112            stack,
113            ro,
114            rw,
115            heap,
116        }
117    }
118
119    /// Iterate every DATA cap entry in cap-index (and base-page) order:
120    /// stack, ro?, rw?, heap?.
121    pub fn data_caps(&self) -> impl Iterator<Item = &DataCapEntry> + '_ {
122        std::iter::once(&self.stack)
123            .chain(self.ro.iter())
124            .chain(self.rw.iter())
125            .chain(self.heap.iter())
126    }
127
128    /// Top-of-stack address (initial SP). RISC-V SP grows downward, so
129    /// the first push lands at `stack_top - 8`.
130    pub fn stack_top(&self) -> u64 {
131        (self.stack.base_page + self.stack.page_count) as u64 * PVM_PAGE_SIZE as u64
132    }
133
134    /// Total pages across all DATA caps in this layout.
135    pub fn total_data_pages(&self) -> u32 {
136        self.data_caps().map(|d| d.page_count).sum()
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn layout_minimal_stack_only() {
146        let l = ProgramLayout::compute(1, 0, 0, 0);
147        assert_eq!(l.stack.cap_index, STACK_CAP_INDEX);
148        assert_eq!(l.stack.base_page, 0);
149        assert_eq!(l.stack.page_count, 1);
150        assert!(l.ro.is_none());
151        assert!(l.rw.is_none());
152        assert!(l.heap.is_none());
153        assert_eq!(l.stack_top(), 4096);
154        assert_eq!(l.total_data_pages(), 1);
155    }
156
157    #[test]
158    fn layout_full_stack_ro_rw_heap() {
159        let l = ProgramLayout::compute(2, 1, 1, 4);
160        assert_eq!(l.stack.base_page, 0);
161        assert_eq!(l.ro.as_ref().unwrap().base_page, 2);
162        assert_eq!(l.rw.as_ref().unwrap().base_page, 3);
163        assert_eq!(l.heap.as_ref().unwrap().base_page, 4);
164        assert_eq!(l.stack_top(), 2 * 4096);
165        assert_eq!(l.total_data_pages(), 2 + 1 + 1 + 4);
166    }
167}