Skip to main content

nub_arch_local/
lib.rs

1//! In-process `Arch` impl: simulates the CPU + MMU substrate with
2//! Rust data structures. Runs directly in the host process; no
3//! sandbox, no cross-compilation.
4//!
5//! [`run_instance`] is the in-process counterpart to nub-arch-x86's
6//! JIT-driven `run_pvm_with_mem`: takes a published
7//! [`javm_cap::InstanceCap`] + its referenced
8//! [`javm_cap::image_cap::ImageCap`] (both `Global`-allocated
9//! locally), wires the bytecode + memory layout to
10//! [`javm_exec::Interpreter::run`], and produces an
11//! [`InvocationResult`].
12
13use javm_cap::image_cap::ImageCap;
14use javm_cap::instance::InstanceCap;
15use javm_exec::{
16    Access, CopyingMemory, EcallHandler, EcallKind, EcallResult, ExitReason, GasCounter,
17    Interpreter, PAGE_SIZE, PvmProgram, Regs, gas_cost::DEFAULT_MEM_CYCLES, unpack_bitmask,
18};
19use nub_arch_x86_abi::InvocationResult;
20use nub_kernel::{Arch, CapHash, InstanceRef, InvokeOptions, InvokeOutcome};
21
22/// In-process Arch backend.
23#[derive(Default)]
24pub struct LocalArch {
25    state_root: CapHash,
26}
27
28impl LocalArch {
29    pub fn new() -> Self {
30        Self::default()
31    }
32}
33
34/// Stub error type for the skeleton — the local backend cannot fail
35/// today. Replace with a real error enum when invocation lands.
36#[derive(Debug)]
37pub enum LocalArchError {}
38
39impl Arch for LocalArch {
40    type Error = LocalArchError;
41
42    fn invoke(
43        &mut self,
44        _target: InstanceRef,
45        _endpoint: u16,
46        _args: &[u8],
47        _opts: InvokeOptions,
48    ) -> Result<InvokeOutcome, Self::Error> {
49        Ok(InvokeOutcome {
50            return_value: 42,
51            gas_used: 0,
52        })
53    }
54
55    fn state_root(&self) -> CapHash {
56        self.state_root
57    }
58}
59
60/// Run an Instance through the byte-PVM interpreter, returning the
61/// same `InvocationResult` shape `nub-arch-x86`'s JIT path produces.
62/// The exit-reason mapping matches the JIT exit codes (HostCall=4,
63/// Trap=7, etc.) so the two backends agree on a well-formed program.
64///
65/// Endpoint dispatch: `endpoint_idx` selects
66/// `image.endpoints[endpoint_idx]`; the endpoint's `entry_pc` is used
67/// as the start PC. Caller-supplied `args` overlay φ[7..=10] on top
68/// of the endpoint's `initial_regs`. Memory is sized from
69/// `instance.mem_size` and seeded with each entry in
70/// `instance.rw_overlays` laid at its declared `start`.
71pub fn run_instance(
72    instance: &InstanceCap,
73    image: &ImageCap,
74    endpoint_idx: u8,
75    args: [u64; 4],
76    initial_gas: u64,
77) -> InvocationResult {
78    // ImageCap stores the packed bitmask (1 bit per code byte). The
79    // interpreter wants the unpacked form (1 byte per code byte).
80    let unpacked_bitmask = unpack_bitmask(image.bitmask.as_slice(), image.code.len());
81    let program = PvmProgram::new(
82        image.code.as_slice().to_vec(),
83        unpacked_bitmask,
84        image.jump_table.as_slice().to_vec(),
85        DEFAULT_MEM_CYCLES,
86    )
87    .expect("PvmProgram (bitmask len must match code len)");
88
89    let mut mem = CopyingMemory::new();
90    let mem_size_pages = page_round_up_u64(instance.mem_size as u64);
91    mem.map_region(0, mem_size_pages, Access::ReadWrite, None)
92        .expect("map base RW region");
93    for overlay_entry in instance.rw_overlays.iter() {
94        overlay(
95            &mut mem,
96            overlay_entry.start,
97            overlay_entry.bytes.as_slice(),
98            Access::ReadWrite,
99        );
100    }
101
102    let endpoint = image
103        .endpoints
104        .get(endpoint_idx as usize)
105        .expect("endpoint index out of range");
106
107    let mut regs = Regs::new();
108    regs.pc = endpoint.entry_pc;
109    // Endpoint baseline first, then layer the InstanceCap's persisted
110    // regs on top (publish_instance writes them; subsequent invokes
111    // observe them). Args overlay φ[7..=10] last.
112    regs.gpr = endpoint.initial_regs;
113    for (i, v) in instance.regs.iter().enumerate() {
114        if *v != 0 {
115            regs.gpr[i] = *v;
116        }
117    }
118    for (i, v) in args.iter().enumerate() {
119        regs.gpr[7 + i] = *v;
120    }
121
122    let mut gas = GasCounter::new(initial_gas);
123    let mut handler = LocalEcallHandler;
124    let exit = Interpreter::run(&program, &mut regs, &mut mem, &mut gas, &mut handler);
125
126    let (exit_reason, exit_arg) = match exit {
127        ExitReason::Halt => (0, 0),
128        ExitReason::Panic => (1, 0),
129        ExitReason::OutOfGas => (2, 0),
130        ExitReason::PageFault(addr) => (3, addr),
131        ExitReason::HostCall(imm) => (4, imm),
132        ExitReason::Ecall => (6, 0),
133        ExitReason::Trap => (7, 0),
134    };
135    InvocationResult {
136        exit_reason,
137        exit_arg,
138        return_value: regs.gpr[7],
139        gas_remaining: gas.remaining(),
140    }
141}
142
143fn page_round_up_u64(n: u64) -> u64 {
144    let p = PAGE_SIZE as u64;
145    n.div_ceil(p) * p
146}
147
148/// Overlay a sub-region of mem with a permission + initial bytes. No-op
149/// if `data` is empty.
150fn overlay(mem: &mut CopyingMemory, start: u32, data: &[u8], access: Access) {
151    if data.is_empty() {
152        return;
153    }
154    let size = page_round_up_u64(data.len() as u64);
155    mem.map_region(start as u64, size, access, Some(data))
156        .expect("map_region overlay");
157}
158
159/// Minimal `EcallHandler` for the local backend: every `ecall` /
160/// `ecalli` ends the run by surfacing the corresponding `ExitReason`,
161/// matching the JIT trampoline's exit shape.
162struct LocalEcallHandler;
163
164impl EcallHandler for LocalEcallHandler {
165    fn handle(
166        &mut self,
167        kind: EcallKind,
168        _regs: &mut Regs,
169        _mem: &mut dyn javm_exec::Memory,
170    ) -> EcallResult {
171        match kind {
172            EcallKind::Ecalli(imm) => EcallResult::Exit(ExitReason::HostCall(imm)),
173            EcallKind::Ecall => EcallResult::Exit(ExitReason::Ecall),
174        }
175    }
176}