Skip to main content

nub_kernel/
lib.rs

1//! Nub: the JAR v3 microkernel.
2//!
3//! This crate defines the [`Arch`] substrate trait and the generic
4//! [`Kernel`] over it. The kernel is a *general VM* that invokes JAVM
5//! programs (see `~/docs/minimum-v3`). It is **not** block-apply
6//! specific — block-apply is layered on top in a separate crate.
7//!
8//! ## Layering
9//!
10//! ```text
11//!     callers (chain runtime / tests / RPC)
12//!                  │
13//!         jar-apply  (block-apply, gas, quota — separate crate, later)
14//!                  │
15//!              nub  (uniform Nub handle over backends)
16//!                  │
17//!     ┌────────────┼────────────────┐
18//!     │                             │
19//! nub-arch-local        nub-arch-x86
20//! (in-process,         (bare-metal guest,
21//!  std)                 no_std + no_main)
22//!     │                             │
23//!     └────────────┬────────────────┘
24//!                  │
25//!              nub-kernel  ← this crate
26//!              (Arch trait, Kernel<A: Arch>, types)
27//! ```
28//!
29//! ## State
30//!
31//! The kernel "owns the state": the invoking `Cap::Instance` and
32//! everything reachable from it. Concretely the [`Arch`] impl holds
33//! the storage (in-process structures for `nub-arch-local`,
34//! guest-resident structures for `nub-arch-x86`); the
35//! [`Kernel`] is a thin generic wrapper that delegates to the Arch.
36//!
37//! ## `no_std`
38//!
39//! This crate is `no_std` by default with an optional `std` feature
40//! (currently enabled by default for ergonomics on host targets). The
41//! Hyperlight Arch impl will pull the no_std build path; in-process
42//! consumers use the std build.
43
44#![cfg_attr(not(feature = "std"), no_std)]
45
46/// 32-byte content hash. Same shape as `javm_cap::CapHash`; defined
47/// here locally so this crate stays `no_std`. A future unification
48/// pass will share the type once `javm-cap` becomes `no_std`-clean.
49pub type CapHash = [u8; 32];
50
51/// Opaque, 32-byte handle to an Instance held by an `Arch`.
52///
53/// The Arch chooses how to interpret it. For the skeleton both
54/// backends use the cap content hash directly, so [`InstanceRef`] is
55/// effectively a [`CapHash`] wrapper. If a backend later wants a
56/// guest-internal handle (e.g. an index into a guest-side table for
57/// cheap lookup), the natural follow-up is to make this an associated
58/// type on [`Arch`].
59#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
60pub struct InstanceRef(pub [u8; 32]);
61
62impl InstanceRef {
63    pub const fn from_hash(hash: CapHash) -> Self {
64        Self(hash)
65    }
66
67    pub const fn as_bytes(&self) -> &[u8; 32] {
68        &self.0
69    }
70}
71
72/// Per-invocation knobs. Empty for the skeleton; fields will land as
73/// the kernel grows (gas budget overrides, quota budget, tracing,
74/// reentrancy depth limits, …).
75#[derive(Copy, Clone, Default, Debug)]
76pub struct InvokeOptions {}
77
78/// Result of a successful invocation. Extensible — fields land as
79/// needed (gas remaining, post-invocation cap hash, host-call trace,
80/// …). For the skeleton we expose only the JAVM HALT return value
81/// and gas used.
82#[derive(Copy, Clone, Debug)]
83pub struct InvokeOutcome {
84    pub return_value: u64,
85    pub gas_used: u64,
86}
87
88/// Low-level CPU/MMU substrate trait. An `Arch` impl runs in the same
89/// address space as the [`Kernel`] that calls it — it owns the
90/// kernel's state and provides the primitives (page mapping, ring
91/// transitions, exception handling, …) needed to execute JAVM
92/// programs. The skeleton trait only exposes [`invoke`](Arch::invoke)
93/// and [`state_root`](Arch::state_root); the substrate-specific
94/// primitives that the kernel will eventually drive (map_pages,
95/// install_handler, …) are intentionally not part of the public
96/// surface yet — they're encapsulated inside [`Arch::invoke`] for
97/// now.
98pub trait Arch {
99    type Error;
100
101    /// Invoke `endpoint` on the `Cap::Instance` identified by
102    /// `target`, passing `args` (SCALE-encoded, by convention). The
103    /// Arch impl is responsible for executing the underlying JAVM
104    /// program to termination (HALT / yield / fault / gas-exhausted)
105    /// and reporting the outcome.
106    fn invoke(
107        &mut self,
108        target: InstanceRef,
109        endpoint: u16,
110        args: &[u8],
111        opts: InvokeOptions,
112    ) -> Result<InvokeOutcome, Self::Error>;
113
114    /// Content-addressed root of the Arch's current state — the hash
115    /// of the invoking `Cap::Instance` after the most recent
116    /// invocation (or genesis if none).
117    fn state_root(&self) -> CapHash;
118}
119
120/// The kernel: a thin wrapper over an [`Arch`] impl that owns the
121/// state. `nub` is the microkernel that this represents; callers use
122/// it via the uniform `Nub` handle in the `nub` crate, which selects
123/// the backend (local interpreter vs hyperlight RPC) at construction
124/// time.
125pub struct Kernel<A: Arch> {
126    arch: A,
127}
128
129impl<A: Arch> Kernel<A> {
130    pub const fn new(arch: A) -> Self {
131        Self { arch }
132    }
133
134    pub fn invoke(
135        &mut self,
136        target: InstanceRef,
137        endpoint: u16,
138        args: &[u8],
139        opts: InvokeOptions,
140    ) -> Result<InvokeOutcome, A::Error> {
141        self.arch.invoke(target, endpoint, args, opts)
142    }
143
144    pub fn state_root(&self) -> CapHash {
145        self.arch.state_root()
146    }
147
148    pub fn arch(&self) -> &A {
149        &self.arch
150    }
151
152    pub fn arch_mut(&mut self) -> &mut A {
153        &mut self.arch
154    }
155}