nub_arch_local/program.rs
1//! Lower a [`ProgramBlob`] into a runnable [`ProgramSpec`].
2//!
3//! This is the "no personality" personality: the shortest path from a
4//! linked program to a running one, with no capability graph, no
5//! content addressing and no store. A personality that has those does
6//! this lowering itself from its own object types (JAVM:
7//! `javm::JavmLocal::run_instance`) — and must arrive at exactly the
8//! same `ProgramSpec`, since gas is a function of it.
9
10use nub_exec::Regs;
11use nub_program::ProgramBlob;
12use nub_program::abi::{CODE_BASE, DATA_BASE};
13
14use crate::{ProgramSpec, RoOverlay};
15
16/// Why a [`ProgramBlob`] could not be prepared for execution.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum PrepareError {
19 /// The blob declares no endpoint with this index.
20 NoSuchEndpoint(u8),
21}
22
23impl core::fmt::Display for PrepareError {
24 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
25 match self {
26 PrepareError::NoSuchEndpoint(i) => write!(f, "program declares no endpoint {i}"),
27 }
28 }
29}
30
31impl core::error::Error for PrepareError {}
32
33/// A [`ProgramBlob`] lowered to the buffers a [`ProgramSpec`] borrows.
34///
35/// `ProgramSpec` borrows its memory image and overlay list, so
36/// something has to own them; that is this type. Build it once, then
37/// hand out `spec()` as often as you like.
38pub struct PreparedProgram {
39 code: Vec<u8>,
40 mem_image: Vec<u8>,
41 ro_overlays: Vec<RoOverlay>,
42 declared_mem_size: u32,
43 regs: Regs,
44}
45
46impl PreparedProgram {
47 /// Prepare `endpoint` of `blob` for entry with `args` in φ[7..=10].
48 ///
49 /// Mirrors the cap-path lowering exactly: the whole data extent is
50 /// materialized flat from `DATA_BASE`, the read-only region is
51 /// re-laid as an [`RoOverlay`] so guest stores fault, the register
52 /// file starts from the endpoint's `initial_regs` (which the linker
53 /// seeded with the stack top), and `declared_mem_size` is the data
54 /// high-water mark that selects the load/store gas tier.
55 pub fn new(blob: &ProgramBlob, endpoint: u8, args: [u64; 4]) -> Result<Self, PrepareError> {
56 let ep = blob
57 .endpoints
58 .get(&endpoint)
59 .ok_or(PrepareError::NoSuchEndpoint(endpoint))?;
60
61 let mem_image = blob.memory_image();
62
63 // The read-only region is re-laid over the seeded image, same
64 // bytes, so a guest store faults — matching the recompiler's
65 // pinned direct map.
66 let ro_overlays = blob
67 .regions
68 .iter()
69 .filter(|r| r.kind.is_read_only())
70 .map(|r| RoOverlay {
71 start: r.start() as u32,
72 image_off: (r.start() - u64::from(DATA_BASE)) as usize,
73 len: r.size() as usize,
74 })
75 .collect();
76
77 let mut regs = Regs::new();
78 regs.pc = ep.entry_pc;
79 for (&idx, &value) in &ep.initial_regs {
80 if let Some(slot) = regs.gpr.get_mut(idx as usize) {
81 *slot = value;
82 }
83 }
84 for (i, v) in args.iter().enumerate() {
85 regs.gpr[7 + i] = *v;
86 }
87
88 Ok(PreparedProgram {
89 code: blob.code.clone(),
90 declared_mem_size: DATA_BASE + blob.regions.data_extent() as u32,
91 mem_image,
92 ro_overlays,
93 regs,
94 })
95 }
96
97 /// Borrow the prepared spec, ready for
98 /// [`run_program`](crate::run_program).
99 pub fn spec(&self) -> ProgramSpec<'_> {
100 ProgramSpec {
101 code_base: CODE_BASE,
102 code: &self.code,
103 data_base: DATA_BASE,
104 mem_image: &self.mem_image,
105 ro_overlays: &self.ro_overlays,
106 declared_mem_size: self.declared_mem_size,
107 regs: self.regs.clone(),
108 }
109 }
110
111 /// Entry PC of the endpoint this was prepared for.
112 pub fn entry_pc(&self) -> u64 {
113 self.regs.pc
114 }
115}
116
117/// Convenience: prepare and run `endpoint` of `blob` once.
118///
119/// Each call builds a fresh address space, so guest statics do not
120/// persist between calls.
121pub fn run_blob(
122 blob: &ProgramBlob,
123 endpoint: u8,
124 args: [u64; 4],
125 initial_gas: u64,
126) -> Result<nub_arch_x86_abi::InvocationResult, PrepareError> {
127 let prepared = PreparedProgram::new(blob, endpoint, args)?;
128 let mut handler = crate::ExitingEcallHandler;
129 Ok(crate::run_program(
130 &prepared.spec(),
131 &mut handler,
132 initial_gas,
133 ))
134}