1#![cfg_attr(not(all(target_os = "linux", target_arch = "x86_64")), allow(unused))]
25#![cfg(all(target_os = "linux", target_arch = "x86_64"))]
26
27use javm_cap::NUM_REGS;
28use javm_cap::image::{Image, PinnedCap};
29use javm_cap::slot::SlotIdx;
30use javm_cap::{Cap, CapHash};
31use nub::{InvocationResult, Nub};
32use std::sync::{Mutex, OnceLock};
33
34const EXIT_HOSTCALL: u32 = 4;
38
39const INITIAL_GAS: u64 = 100_000_000_000;
41
42pub struct BuiltCaps {
49 pub data_caps: Vec<(CapHash, Cap)>,
52 pub image_cap: Cap,
54 pub image_hash: CapHash,
55 pub cnode_cap: Cap,
57 pub cnode_hash: CapHash,
58 pub instance_cap: Cap,
60 pub instance_hash: CapHash,
61 pub endpoint_idx: u8,
62}
63
64impl BuiltCaps {
65 pub fn for_image(image: &Image, endpoint_idx: u8) -> Self {
68 let endpoint = image
69 .endpoints
70 .get(&endpoint_idx)
71 .unwrap_or_else(|| panic!("endpoint {endpoint_idx} not declared"));
72
73 let mut data_caps: Vec<(CapHash, Cap)> = Vec::new();
76 let mut pinned_hashes: Vec<(SlotIdx, CapHash)> = Vec::new();
77 let mut initial_hashes: Vec<(SlotIdx, CapHash)> = Vec::new();
78
79 for (slot, pinned) in &image.pinned_slots {
80 let (h, cap) = match pinned {
81 PinnedCap::Data { content, size } => {
82 let cap = Cap::data_inline_with_size(content, *size);
83 let h = ssz::hash_tree_root(&cap);
84 (h, Some(cap))
85 }
86 PinnedCap::Image { content_hash } => {
87 (*content_hash, None)
90 }
91 };
92 pinned_hashes.push((*slot, h));
93 if let Some(c) = cap {
94 data_caps.push((h, c));
95 }
96 }
97 for (slot, init) in &image.initial_slots {
98 let cap = Cap::data_inline_with_size(&init.content, init.size);
99 let h = ssz::hash_tree_root(&cap);
100 initial_hashes.push((*slot, h));
101 data_caps.push((h, cap));
102 }
103
104 let image_cap = Cap::image_with_slots(image, &pinned_hashes, &initial_hashes)
106 .expect("image_with_slots");
107 let image_hash = ssz::hash_tree_root(&image_cap);
108
109 let cnode_cap = Cap::empty_cnode(0).expect("empty_cnode");
111 let cnode_hash = ssz::hash_tree_root(&cnode_cap);
112
113 let (mem_size, overlays) = build_overlays(image);
115 let overlay_slices: Vec<(u32, &[u8])> = overlays
116 .iter()
117 .map(|(start, bytes)| (*start, bytes.as_slice()))
118 .collect();
119
120 let mut regs = [0u64; NUM_REGS];
121 for (&i, &v) in &endpoint.initial_regs {
122 if let Some(slot) = regs.get_mut(i as usize) {
123 *slot = v;
124 }
125 }
126
127 let instance_cap = Cap::instance_with_overlays(
128 [0u8; 32],
129 image_hash,
130 cnode_hash,
131 &overlay_slices,
132 mem_size,
133 regs,
134 0,
135 0,
136 );
137 let instance_hash = ssz::hash_tree_root(&instance_cap);
138
139 BuiltCaps {
140 data_caps,
141 image_cap,
142 image_hash,
143 cnode_cap,
144 cnode_hash,
145 instance_cap,
146 instance_hash,
147 endpoint_idx,
148 }
149 }
150
151 fn put_into(&self, nub: &mut Nub) {
154 for (h, cap) in &self.data_caps {
155 nub.put_cap_with_hash(*h, cap)
156 .unwrap_or_else(|e| panic!("put_cap_with_hash data: {e}"));
157 }
158 nub.put_cap_with_hash(self.image_hash, &self.image_cap)
159 .unwrap_or_else(|e| panic!("put_cap_with_hash image: {e}"));
160 nub.put_cap_with_hash(self.cnode_hash, &self.cnode_cap)
161 .unwrap_or_else(|e| panic!("put_cap_with_hash cnode: {e}"));
162 nub.put_cap_with_hash(self.instance_hash, &self.instance_cap)
163 .unwrap_or_else(|e| panic!("put_cap_with_hash instance: {e}"));
164 }
165}
166
167pub fn run_interpreter(built: &BuiltCaps) -> (u64, u64) {
172 let mut nub = Nub::new_local();
173 built.put_into(&mut nub);
174 let result = nub
175 .invoke_cached(built.instance_hash, built.endpoint_idx, [0; 4], INITIAL_GAS)
176 .unwrap_or_else(|e| panic!("interpreter invoke_cached: {e}"));
177 finish(&result)
178}
179
180pub fn run_recompiler(built: &BuiltCaps) -> (u64, u64) {
183 let mut nub = nub_hyperlight().lock().expect("nub mutex");
184 built.put_into(&mut nub);
185 let result = nub
186 .invoke_cached(built.instance_hash, built.endpoint_idx, [0; 4], INITIAL_GAS)
187 .unwrap_or_else(|e| panic!("recompiler invoke_cached: {e}"));
188 finish(&result)
189}
190
191fn finish(result: &InvocationResult) -> (u64, u64) {
192 assert_eq!(
193 result.exit_reason, EXIT_HOSTCALL,
194 "unexpected exit_reason {} (exit_arg={})",
195 result.exit_reason, result.exit_arg,
196 );
197 assert_eq!(
198 result.exit_arg, 0,
199 "expected HostCall(0) trampoline halt, got HostCall({})",
200 result.exit_arg,
201 );
202 let gas_used = INITIAL_GAS.saturating_sub(result.gas_remaining);
203 (result.return_value, gas_used)
204}
205
206fn nub_hyperlight() -> &'static Mutex<Nub> {
208 static NUB: OnceLock<Mutex<Nub>> = OnceLock::new();
209 NUB.get_or_init(|| Mutex::new(Nub::new_hyperlight().expect("Hyperlight sandbox")))
210}
211
212fn build_overlays(image: &Image) -> (u32, Vec<(u32, Vec<u8>)>) {
217 let mut mem_size: u32 = 0;
218 let mut overlays: Vec<(u32, Vec<u8>)> = Vec::new();
219
220 for mapping in &image.memory_mappings {
221 let end = (mapping.start + mapping.size) as u32;
222 if end > mem_size {
223 mem_size = end;
224 }
225
226 let target = mapping.source.target();
227 if let Some(PinnedCap::Data { content, .. }) = image.pinned_slots.get(&target) {
228 if !content.is_empty() {
229 overlays.push((mapping.start as u32, content.clone()));
230 }
231 } else if let Some(init) = image.initial_slots.get(&target)
232 && !init.content.is_empty()
233 {
234 overlays.push((mapping.start as u32, init.content.clone()));
235 }
236 }
237
238 (mem_size, overlays)
239}