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}