1use anyhow::Result;
21use javm_cap::{CacheDirectory, CapHashOrRef, cap::Cap};
22use nub_arch_local::LocalArch;
23use nub_host_kvm::sandbox::{
24 GuestBinary, MultiUseSandbox, SandboxConfiguration, UninitializedSandbox,
25};
26use nub_kernel::Kernel;
27
28#[cfg(feature = "heap-diag")]
29use nub_arch_x86_abi::FN_ID_NUB_HEAP_STATS;
30use nub_arch_x86_abi::{
31 ArchivedInvocationResult, FN_ID_NUB_INVOKE_CACHED, FN_ID_NUB_SMOKE, InvokePacket,
32};
33pub use nub_arch_x86_abi::{CapHash as AbiCapHash, InvocationResult};
34pub use nub_kernel::{CapHash, InstanceRef, InvokeOptions, InvokeOutcome};
35
36use rkyv::primitive::ArchivedU64;
37use rkyv::util::AlignedVec;
38
39#[cfg(feature = "heap-diag")]
42#[derive(Debug, Clone, Copy)]
43pub struct HeapStats {
44 pub allocated_bytes: u64,
45 pub allocation_count: u64,
46 pub fragment_count: u64,
47 pub available_bytes: u64,
48}
49
50const NUB_ARCH_X86_BLOB_PATH: &str = env!("NUB_ARCH_X86_BLOB");
53
54pub struct Nub {
56 backend: Backend,
57 local_cache: CacheDirectory,
64}
65
66enum Backend {
67 Local(Kernel<LocalArch>),
68 Hyperlight(Box<HyperlightDriver>),
69}
70
71struct HyperlightDriver {
75 sandbox: MultiUseSandbox,
76 state_root_cache: CapHash,
77}
78
79impl Nub {
80 pub fn new_local() -> Self {
82 Self {
83 backend: Backend::Local(Kernel::new(LocalArch::new())),
84 local_cache: CacheDirectory::new(),
85 }
86 }
87
88 pub fn new_hyperlight() -> Result<Self> {
91 let mut cfg = SandboxConfiguration::default();
92 cfg.set_scratch_size(512 * 1024 * 1024);
93 cfg.set_input_data_size(16 * 1024 * 1024);
94 cfg.set_output_data_size(16 * 1024 * 1024);
95 cfg.set_heap_size(256 * 1024 * 1024);
96 let uninit = UninitializedSandbox::new(
97 GuestBinary::FilePath(NUB_ARCH_X86_BLOB_PATH.to_string()),
98 Some(cfg),
99 )?;
100 let sandbox = uninit.evolve()?;
101 Ok(Self {
102 backend: Backend::Hyperlight(Box::new(HyperlightDriver {
103 sandbox,
104 state_root_cache: [0; 32],
105 })),
106 local_cache: CacheDirectory::new(),
107 })
108 }
109
110 pub fn invoke(
113 &mut self,
114 target: InstanceRef,
115 endpoint: u16,
116 args: &[u8],
117 opts: InvokeOptions,
118 ) -> Result<InvokeOutcome> {
119 match &mut self.backend {
120 Backend::Local(k) => Ok(k
121 .invoke(target, endpoint, args, opts)
122 .expect("LocalArch::Error is uninhabited")),
123 Backend::Hyperlight(h) => h.invoke(target, endpoint, args, opts),
124 }
125 }
126
127 pub fn state_root(&self) -> CapHash {
129 match &self.backend {
130 Backend::Local(k) => k.state_root(),
131 Backend::Hyperlight(h) => h.state_root_cache,
132 }
133 }
134
135 #[cfg(feature = "heap-diag")]
138 pub fn heap_stats(&mut self) -> Result<HeapStats> {
139 match &mut self.backend {
140 Backend::Local(_) => Err(anyhow::anyhow!(
141 "heap_stats: Local backend has no guest heap"
142 )),
143 Backend::Hyperlight(h) => {
144 let raw: Vec<u8> = h.sandbox.call_raw(FN_ID_NUB_HEAP_STATS, &[])?;
145 if raw.len() != 32 {
146 return Err(anyhow::anyhow!(
147 "heap_stats: expected 32 bytes, got {}",
148 raw.len()
149 ));
150 }
151 let parse = |off: usize| u64::from_le_bytes(raw[off..off + 8].try_into().unwrap());
152 Ok(HeapStats {
153 allocated_bytes: parse(0),
154 allocation_count: parse(8),
155 fragment_count: parse(16),
156 available_bytes: parse(24),
157 })
158 }
159 }
160 }
161
162 pub fn put_cap(&mut self, cap: &javm_cap::Cap) -> Result<AbiCapHash> {
168 match &mut self.backend {
169 Backend::Local(_) => self
170 .local_cache
171 .put_cap(cap)
172 .map_err(|e| anyhow::anyhow!("put_cap (local): {e}")),
173 Backend::Hyperlight(h) => h
174 .sandbox
175 .put_cap(cap)
176 .map_err(|e| anyhow::anyhow!("put_cap: {e}")),
177 }
178 }
179
180 pub fn put_cap_with_hash(&mut self, hash: AbiCapHash, cap: &javm_cap::Cap) -> Result<()> {
194 match &mut self.backend {
195 Backend::Local(_) => self
196 .local_cache
197 .put_cap_with_hash(hash, cap)
198 .map_err(|e| anyhow::anyhow!("put_cap_with_hash (local): {e}")),
199 Backend::Hyperlight(h) => h
200 .sandbox
201 .put_cap_with_hash(hash, cap)
202 .map_err(|e| anyhow::anyhow!("put_cap_with_hash: {e}")),
203 }
204 }
205
206 pub fn invoke_cached(
210 &mut self,
211 instance_hash: AbiCapHash,
212 endpoint_idx: u8,
213 args: [u64; 4],
214 initial_gas: u64,
215 ) -> Result<InvocationResult> {
216 match &mut self.backend {
217 Backend::Local(_) => {
218 let instance_cap = self
221 .local_cache
222 .get(CapHashOrRef::Hash(instance_hash))
223 .ok_or_else(|| anyhow::anyhow!("invoke_cached: instance not published"))?;
224 let inst = match &*instance_cap {
225 Cap::Instance(i) => i.clone(),
226 _ => {
227 return Err(anyhow::anyhow!(
228 "invoke_cached: cap at hash is not an Instance"
229 ));
230 }
231 };
232 let image_cap = self
233 .local_cache
234 .get(CapHashOrRef::Hash(inst.image_hash))
235 .ok_or_else(|| anyhow::anyhow!("invoke_cached: image not in cache"))?;
236 let img = match &*image_cap {
237 Cap::Image(i) => i.clone(),
238 _ => {
239 return Err(anyhow::anyhow!(
240 "invoke_cached: cap at image_hash is not an Image"
241 ));
242 }
243 };
244 Ok(nub_arch_local::run_instance(
245 &inst,
246 &img,
247 endpoint_idx,
248 args,
249 initial_gas,
250 ))
251 }
252 Backend::Hyperlight(h) => {
253 let packet = InvokePacket {
257 instance_hash,
258 endpoint_idx: endpoint_idx as u32,
259 _pad: 0,
260 args,
261 initial_gas,
262 };
263 let result_bytes = h
264 .sandbox
265 .call_raw(FN_ID_NUB_INVOKE_CACHED, packet.as_bytes())?;
266
267 let mut aligned = AlignedVec::<16>::with_capacity(result_bytes.len());
268 aligned.extend_from_slice(&result_bytes);
269 let archived = rkyv::access::<ArchivedInvocationResult, rkyv::rancor::Error>(
270 aligned.as_slice(),
271 )
272 .map_err(|e| anyhow::anyhow!("rkyv-access InvocationResult: {e}"))?;
273 Ok(InvocationResult {
274 exit_reason: archived.exit_reason.to_native(),
275 exit_arg: archived.exit_arg.to_native(),
276 return_value: archived.return_value.to_native(),
277 gas_remaining: archived.gas_remaining.to_native(),
278 })
279 }
280 }
281 }
282}
283
284impl HyperlightDriver {
285 fn invoke(
286 &mut self,
287 _target: InstanceRef,
288 _endpoint: u16,
289 _args: &[u8],
290 _opts: InvokeOptions,
291 ) -> Result<InvokeOutcome> {
292 let result_bytes = self.sandbox.call_raw(FN_ID_NUB_SMOKE, &[])?;
295 let mut aligned = AlignedVec::<16>::with_capacity(result_bytes.len());
296 aligned.extend_from_slice(&result_bytes);
297 let archived = rkyv::access::<ArchivedU64, rkyv::rancor::Error>(aligned.as_slice())
298 .map_err(|e| anyhow::anyhow!("rkyv-access u64 from nub_smoke: {e}"))?;
299 Ok(InvokeOutcome {
300 return_value: archived.to_native(),
301 gas_used: 0,
302 })
303 }
304}