Skip to main content

javm/
local.rs

1//! [`JavmLocal`] — the JAVM in-process kernel: the [`nub::LocalKernel`]
2//! impl driven by the [`Nub`](crate::Nub) Local backend.
3//!
4//! Holds the host-side [`CacheDirectory`] (source of truth for caps
5//! published via [`Nub::put_cap`](crate::Nub::put_cap)) and lowers
6//! invocations onto the PVM2 (RISC-V) interpreter via
7//! `nub_arch_local`.
8
9use anyhow::Result;
10use javm_cap::cap::Cap;
11use javm_cap::cap::image::ImageCap;
12use javm_cap::cap::instance::InstanceCap;
13use javm_cap::{CacheDirectory, CapHashOrRef};
14use nub::{CapHash, InvocationResult, LocalKernel, ObjHash};
15use nub_arch_local::{ExitingEcallHandler, ProgramSpec, RoOverlay, run_program};
16use nub_exec::Regs;
17
18/// The JAVM Local-backend kernel: cap directory + interpreter wiring.
19pub struct JavmLocal {
20    cache: CacheDirectory,
21    /// Stub parity with the historical `Kernel<LocalArch>` state root
22    /// (all zeroes until block-apply lands).
23    state_root: CapHash,
24}
25
26impl Default for JavmLocal {
27    fn default() -> Self {
28        Self {
29            cache: CacheDirectory::new(),
30            state_root: [0; 32],
31        }
32    }
33}
34
35impl JavmLocal {
36    /// Typed, encode-free publish — the fast path behind
37    /// [`Nub::put_cap`](crate::Nub::put_cap) via `nub::Nub::with_local`.
38    pub fn put_cap(&mut self, cap: &Cap) -> Result<CapHash> {
39        self.cache
40            .put_cap(cap)
41            .map_err(|e| anyhow::anyhow!("put_cap (local): {e}"))
42    }
43
44    /// Typed pre-hashed publish. See
45    /// [`Nub::put_cap_with_hash`](crate::Nub::put_cap_with_hash).
46    pub fn put_cap_with_hash(&mut self, hash: CapHash, cap: &Cap) -> Result<()> {
47        self.cache
48            .put_cap_with_hash(hash, cap)
49            .map_err(|e| anyhow::anyhow!("put_cap_with_hash (local): {e}"))
50    }
51
52    /// Decode a personality-encoded (rkyv-archived) `Cap` payload —
53    /// the host-side mirror of the guest's `put_object` decode
54    /// (`javm-guest-x86/src/state_cache.rs`).
55    fn decode(bytes: &[u8]) -> Result<Cap> {
56        let mut aligned = rkyv::util::AlignedVec::<16>::with_capacity(bytes.len());
57        aligned.extend_from_slice(bytes);
58        let archived = rkyv::access::<rkyv::Archived<Cap>, rkyv::rancor::Error>(aligned.as_slice())
59            .map_err(|e| anyhow::anyhow!("rkyv access: {e}"))?;
60        rkyv::deserialize::<Cap, rkyv::rancor::Error>(archived)
61            .map_err(|e| anyhow::anyhow!("rkyv deserialize: {e}"))
62    }
63}
64
65impl LocalKernel for JavmLocal {
66    fn put_object(&mut self, bytes: &[u8]) -> Result<ObjHash> {
67        let cap = Self::decode(bytes).map_err(|e| anyhow::anyhow!("put_object: {e}"))?;
68        self.cache
69            .put_cap(&cap)
70            .map_err(|e| anyhow::anyhow!("put_object: {e}"))
71    }
72
73    fn put_object_with_hash(&mut self, hash: ObjHash, bytes: &[u8]) -> Result<()> {
74        let cap = Self::decode(bytes).map_err(|e| anyhow::anyhow!("put_object_with_hash: {e}"))?;
75        self.cache
76            .put_cap_with_hash(hash, &cap)
77            .map_err(|e| anyhow::anyhow!("put_object_with_hash: {e}"))
78    }
79
80    fn invoke(
81        &mut self,
82        root: ObjHash,
83        endpoint: u32,
84        args: [u64; 4],
85        initial_gas: u64,
86    ) -> Result<InvocationResult> {
87        // Resolve the instance + image from the in-process cache and
88        // drive the PVM2 (RISC-V) interpreter.
89        let instance_cap = self
90            .cache
91            .get(CapHashOrRef::Hash(root))
92            .ok_or_else(|| anyhow::anyhow!("invoke_cached: instance not published"))?;
93        let inst = match &*instance_cap {
94            Cap::Instance(i) => i.clone(),
95            _ => {
96                return Err(anyhow::anyhow!(
97                    "invoke_cached: cap at hash is not an Instance"
98                ));
99            }
100        };
101        let image_cap = self
102            .cache
103            .get(CapHashOrRef::Hash(inst.image_hash))
104            .ok_or_else(|| anyhow::anyhow!("invoke_cached: image not in cache"))?;
105        let img = match &*image_cap {
106            Cap::Image(i) => i.clone(),
107            _ => {
108                return Err(anyhow::anyhow!(
109                    "invoke_cached: cap at image_hash is not an Image"
110                ));
111            }
112        };
113
114        // V1 single-byte ABI: the endpoint selector is a single-byte
115        // Key into the sparse endpoint list (matching the guest's
116        // `build_frame_inner`).
117        Ok(run_instance(
118            &inst,
119            &img,
120            (endpoint & 0xFF) as u8,
121            args,
122            initial_gas,
123        ))
124    }
125
126    fn state_root(&self) -> ObjHash {
127        self.state_root
128    }
129}
130
131/// Run an Instance through the PVM2 (RISC-V) interpreter by lowering
132/// the JAVM cap layout into a [`ProgramSpec`].
133///
134/// Endpoint dispatch: `endpoint_idx` selects
135/// `image.endpoints[endpoint_idx]`; the endpoint's `entry_pc` is used
136/// as the start PC. Caller-supplied `args` overlay φ[7..=10] on top
137/// of the endpoint's `initial_regs`. Memory is seeded from the
138/// Instance's `mem` DataCap (the whole RW extent), with pinned mappings
139/// re-laid read-only.
140fn run_instance(
141    instance: &InstanceCap,
142    image: &ImageCap,
143    endpoint_idx: u8,
144    args: [u64; 4],
145    initial_gas: u64,
146) -> InvocationResult {
147    let data_base = javm_cap::layout::DATA_BASE;
148    let data_extent = instance.mem.content_len();
149    let mut mem_image = vec![0u8; data_extent as usize];
150    if data_extent > 0 {
151        // Seed the whole extent from the Instance's memory image (the immutable
152        // backing — both initial and pinned content). No cache lookup needed.
153        instance.mem.copy_into(0, &mut mem_image);
154    }
155    // Pinned mappings become read-only re-lays (same bytes, from the
156    // seeded image) so a guest store faults, matching the recompiler's
157    // PinnedCapRo direct map.
158    let mut ro_overlays = Vec::new();
159    for m in image.mappings.iter() {
160        if m.path().is_empty() || !image.mapping_is_pinned(m.start as u32) {
161            continue;
162        }
163        let off = (m.start.saturating_sub(data_base as u64)) as usize;
164        let len = (m.size as usize).min(mem_image.len().saturating_sub(off));
165        if len > 0 {
166            ro_overlays.push(RoOverlay {
167                start: m.start as u32,
168                image_off: off,
169                len,
170            });
171        }
172    }
173
174    // V1 single-byte ABI: the endpoint selector is a single-byte Key into the
175    // sparse endpoint list.
176    let target = javm_cap::Key::from(endpoint_idx);
177    let (_, endpoint) = image
178        .endpoints
179        .iter()
180        .find(|(k, _)| *k == target)
181        .expect("endpoint key not defined");
182
183    let mut regs = Regs::new();
184    regs.pc = endpoint.entry_pc;
185    // Endpoint baseline first, then layer the InstanceCap's persisted
186    // regs on top (publish_instance writes them; subsequent invokes
187    // observe them). Args overlay φ[7..=10] last.
188    // Persisted file is the 13 host-mapped slots; x3/x4 (slots 13/14) start
189    // at 0 (Regs::new zeros them), matching the recompiler.
190    regs.gpr[..javm_cap::NUM_REGS].copy_from_slice(&endpoint.initial_regs);
191    for (i, v) in instance.regs.iter().enumerate() {
192        if *v != 0 {
193            regs.gpr[i] = *v;
194        }
195    }
196    for (i, v) in args.iter().enumerate() {
197        regs.gpr[7 + i] = *v;
198    }
199
200    // The executable code region, mapped RO at the fixed CODE_BASE
201    // (PC = CODE_BASE + byte_offset).
202    let (code_base, code_bytes) = image
203        .code_mapping()
204        .expect("image has no executable code mapping");
205
206    let spec = ProgramSpec {
207        code_base,
208        code: code_bytes,
209        data_base,
210        mem_image: &mem_image,
211        ro_overlays: &ro_overlays,
212        declared_mem_size: instance.mem_size(),
213        regs,
214    };
215    let mut handler = ExitingEcallHandler;
216    run_program(&spec, &mut handler, initial_gas)
217}