Skip to main content

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}