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}