1use crate::{
2 hash_encoded, AppendOutputError, Arg, InnerVm, InvokeArgs, MemoryManager, OuterVm,
3 PageFaultError, ProgramData, Reg,
4};
5use codec::{Compact, CompactLen};
6use corevm_host::{
7 CoreVmOutput, CoreVmPayload, Outcome, OutputStream, RangeSet, VideoMode, VmOutput, VmState,
8 PAGE_SIZE,
9};
10use jam_pvm_common::{ApiError, InvokeOutcome};
11use jam_types::{Hash, ServiceId};
12use log::{debug, trace};
13use polkavm::{MemoryMapBuilder, RETURN_TO_HOST};
14
15pub struct Engine<O: OuterVm> {
16 outer_vm: O,
17 args: InvokeArgs,
18 memory_man: MemoryManager<O::InnerVm>,
19 video: Option<VideoMode>,
20 frame_number: u64,
21 old_hash: Hash,
22 code_hash: Hash,
23 code_host: ServiceId,
24}
25
26impl<O: OuterVm> Engine<O> {
27 pub fn new(payload: CoreVmPayload, mut outer_vm: O) -> Result<Self, ApiError> {
28 let CoreVmPayload { gas, vm_state, program: guest_code_ref } = payload;
29 let max_exports = outer_vm.get_export_count();
30 let auth_output_len = outer_vm.get_auth_output_len();
31 let program_counter = vm_state.program_counter;
32 let program = {
33 let bytes = outer_vm.read_file(&guest_code_ref).expect("Failed to fetch program");
34 let corevm_blob = jam_program_blob::CoreVmProgramBlob::from_bytes(&bytes)
35 .expect("Failed to parse CoreVM blob");
36 polkavm::ProgramParts::from_bytes(corevm_blob.pvm_blob.into())
37 .expect("Failed to parse PolkaVM blob")
38 };
39 let memory_map = MemoryMapBuilder::new(PAGE_SIZE as u32)
40 .ro_data_size(program.ro_data_size)
41 .rw_data_size(program.rw_data_size)
42 .stack_size(program.stack_size)
43 .build()
44 .expect("Failed to build memory map");
45 let old_hash = compute_state_hash(&vm_state);
47 let mut args = InvokeArgs { regs: vm_state.regs, gas };
48 if program_counter == 0 {
49 args.set_reg(Reg::RA, RETURN_TO_HOST);
51 args.set_reg(Reg::SP, memory_map.stack_address_high() as u64);
52 }
53 let code = &program.code_and_jump_table[..];
54 let inner_vm = outer_vm.machine(code, program_counter).expect("Failed to load the code");
55 let program_data = ProgramData::new(&memory_map, program.ro_data, program.rw_data);
56 let memory_man = MemoryManager::new(
57 inner_vm,
58 &mut outer_vm,
59 program_data,
60 vm_state.mapped_heap_pages,
61 vm_state.resident_pages,
62 max_exports as usize,
63 auth_output_len as usize,
64 )
65 .expect("Failed to initialize memory pages");
66 Ok(Self {
67 outer_vm,
68 args,
69 memory_man,
70 video: None,
71 frame_number: vm_state.frame_number,
72 old_hash,
73 code_hash: guest_code_ref.hash,
74 code_host: guest_code_ref.service_id,
75 })
76 }
77
78 pub fn run(mut self) -> Result<(CoreVmOutput, O), ApiError> {
79 let (output, outer_vm) = loop {
80 let (outcome, gas, regs) =
81 self.memory_man.inner_vm.invoke(self.args.gas, self.args.regs)?;
82 trace!("Invoke outcome {:?}", DebugInvokeOutcome(outcome));
83 self.args.gas = gas;
84 self.args.regs = regs;
85 match outcome {
86 InvokeOutcome::Halt => {
87 break self.into_host_output(Outcome::Halt)?;
88 },
89 InvokeOutcome::PageFault(address) => {
90 match self.memory_man.touch(address) {
91 Ok(..) => {},
92 Err(PageFaultError::PageFault { page, num_pages }) => {
93 trace!("Hard page fault at address {:#x}", page * PAGE_SIZE);
94 break self.into_host_output(Outcome::PageFault { page, num_pages })?;
98 },
99 Err(PageFaultError::ApiError(e)) => return Err(e),
100 }
101 },
102 InvokeOutcome::HostCallFault(index) => match self.handle_host_call_fault(index) {
103 Ok(..) => {},
104 Err(AppendOutputError::OutputLimitReached) => {
105 trace!("Output limit reached");
106 break self.into_host_output(Outcome::OutputLimitReached)?;
107 },
108 Err(AppendOutputError::PageFault { page, num_pages }) => {
109 trace!(
110 "Hard page fault at address {:#x}-{:#x}",
111 page * PAGE_SIZE,
112 (page + num_pages) * PAGE_SIZE
113 );
114 break self.into_host_output(Outcome::PageFault { page, num_pages })?;
115 },
116 Err(AppendOutputError::ApiError(e)) => return Err(e),
117 },
118 InvokeOutcome::Panic => {
119 break self.into_host_output(Outcome::Panic)?;
120 },
121 InvokeOutcome::OutOfGas => {
122 break self.into_host_output(Outcome::OutOfGas)?;
123 },
124 }
125 };
126 debug!("Finished with outcome {:?}", output.vm_output.outcome);
127 Ok((output, outer_vm))
128 }
129
130 fn handle_host_call_fault(&mut self, index: u64) -> Result<(), AppendOutputError> {
131 let args = &mut self.args;
132 match index {
133 index_for_corevm_call!(gas) => {
134 args.set_return_value(args.gas);
135 },
136 index_for_corevm_call!(yield_console_data) => {
137 let mut i = 0;
138 let stream: u64 = Arg::get(args, &mut i);
139 let inner_src: u64 = Arg::get(args, &mut i);
140 let length: u64 = Arg::get(args, &mut i);
141 let stream = match stream {
142 1 => OutputStream::Stdout,
143 2 => OutputStream::Stderr,
144 other => panic!("Invalid output stream specified: {other}"),
145 };
146 trace!("Call yield_console_data({stream:?}, {inner_src:#x}, {length})");
147 args.set_return_value(1_u64);
148 self.memory_man.append_output(stream, inner_src, length)?;
149 args.set_return_value(0_u64);
150 },
151 index_for_corevm_call!(yield_video_frame) => {
152 let mut i = 0;
153 let inner_src: u64 = Arg::get(args, &mut i);
154 let length: u64 = Arg::get(args, &mut i);
155 trace!("Call yield_video_frame({inner_src:#x}, {length})");
156 args.set_return_value(1_u64);
157 self.memory_man.append_output(OutputStream::Video, inner_src, length)?;
158 args.set_return_value(0_u64);
159 self.frame_number += 1;
160 },
161 index_for_corevm_call!(alloc) => {
162 let mut i = 0;
163 let size: u64 = Arg::get(args, &mut i);
164 let (address, _size) = match self.memory_man.alloc(size) {
165 Some((address, size)) => (address, size),
166 None => {
167 debug!("Failed to map guest memory block of size {}", size);
168 (0, 0)
169 },
170 };
171 trace!("Call alloc({size}) = {address:#x}");
172 args.set_return_value(address);
173 },
174 index_for_corevm_call!(free) => {
175 let mut i = 0;
176 let address: u64 = Arg::get(args, &mut i);
177 let size: u64 = Arg::get(args, &mut i);
178 self.memory_man.dealloc(address, size)?;
179 trace!("Call free({address:#x}, {size})");
180 args.set_return_value(0_u64);
181 },
182 index_for_corevm_call!(video_mode) => {
183 let mut i = 0;
184 let width: u64 = Arg::get(args, &mut i);
185 let height: u64 = Arg::get(args, &mut i);
186 let refresh_rate: u64 = Arg::get(args, &mut i);
187 let format: u64 = Arg::get(args, &mut i);
188 trace!("Call video_mode({width}, {height}, {refresh_rate}, {format})");
189 self.video = Some(VideoMode {
190 width: width as u32,
191 height: height as u32,
192 mode: format as u32,
193 refresh_rate: refresh_rate as u16,
194 });
195 args.set_return_value(0_u64);
196 },
197 index => {
198 panic!("Unknown host call index: {}", index);
199 },
200 }
201 Ok(())
202 }
203
204 fn into_host_output(mut self, outcome: Outcome) -> Result<(CoreVmOutput, O), ApiError> {
205 let (
206 mapped_heap_pages,
207 resident_pages,
208 touched_imported_pages,
209 updated_pages,
210 num_memory_pages,
211 stream_len,
212 ) = self.memory_man.export(&mut self.outer_vm)?;
213 let program_counter =
214 self.memory_man.inner_vm.expunge().expect("Failed to close VM handle");
215 let vm_state = VmState {
216 mapped_heap_pages,
217 resident_pages,
218 regs: self.args.regs,
219 program_counter,
220 frame_number: self.frame_number,
221 };
222 let new_hash = compute_state_hash(&vm_state);
223 let work_output = CoreVmOutput {
224 vm_output: VmOutput {
225 remaining_gas: self.args.gas,
226 outcome,
227 num_memory_pages,
228 stream_len,
229 },
230 vm_state,
231 old_hash: self.old_hash,
232 new_hash,
233 touched_imported_pages,
234 updated_pages,
235 video: self.video,
236 guest_code_hash: self.code_hash,
237 guest_code_host: self.code_host,
238 };
239 Ok((work_output, self.outer_vm))
240 }
241}
242
243pub fn compute_state_hash(vm: &VmState) -> Hash {
245 hash_encoded((
246 &vm.regs,
247 vm.program_counter,
248 vm.frame_number,
249 &vm.mapped_heap_pages,
250 &vm.resident_pages,
251 ))
252}
253
254pub fn initial_state_hash() -> Hash {
256 let initial_vm_state = VmState::initial();
257 compute_state_hash(&initial_vm_state)
258}
259
260pub(crate) struct DebugInvokeOutcome(pub InvokeOutcome);
261
262impl core::fmt::Debug for DebugInvokeOutcome {
263 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
264 match self.0 {
265 InvokeOutcome::Halt => f.write_str("Halt"),
266 InvokeOutcome::PageFault(address) =>
267 f.debug_tuple("PageFault").field(&format_args!("{address:#x}")).finish(),
268 InvokeOutcome::HostCallFault(i) => f.debug_tuple("HostCallFault").field(&i).finish(),
269 InvokeOutcome::Panic => f.write_str("Panic"),
270 InvokeOutcome::OutOfGas => f.write_str("OutOfGas"),
271 }
272 }
273}
274
275#[rustfmt::skip]
277macro_rules! index_for_corevm_call {
278 (gas) => {0};
279 (alloc) => {1};
280 (free) => {2};
281 (yield_console_data) => {3};
282 (yield_video_frame) => {4};
283 (video_mode) => {5};
284}
285
286use index_for_corevm_call;
287
288pub fn get_work_output_len(
289 mapped_heap_pages: &RangeSet,
290 resident_pages: &RangeSet,
291 num_imported_pages: usize,
292 num_updated_pages: usize,
293) -> usize {
294 let imported_pages_encoded_len = core::mem::size_of::<(u64, Hash)>() * num_imported_pages +
295 Compact::<u64>::compact_len(&(num_imported_pages as u64));
296 let updated_pages_encoded_len = core::mem::size_of::<(u64, Hash)>() * num_updated_pages +
297 Compact::<u64>::compact_len(&(num_updated_pages as u64));
298 MAX_STATIC_WORK_OUTPUT_LEN +
299 mapped_heap_pages.encoded_len() +
300 resident_pages.encoded_len() +
301 imported_pages_encoded_len +
302 updated_pages_encoded_len
303}
304
305const MAX_STATIC_WORK_OUTPUT_LEN: usize = 264;
308
309pub const MAX_TOTAL_OUTPUT_BLOB_SIZE: usize = 48 * 1024;
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use jam_types::{max_exports, Encode, SignedGas};
316
317 #[test]
318 fn max_static_work_output_len_is_correct() {
319 assert_eq!(MAX_STATIC_WORK_OUTPUT_LEN, encoded_work_output_len(max_exports()));
320 }
321
322 fn encoded_work_output_len(num_exports: u32) -> usize {
323 let output = CoreVmOutput {
324 vm_output: VmOutput {
325 remaining_gas: SignedGas::MAX,
326 outcome: Outcome::Halt,
327 num_memory_pages: num_exports,
328 stream_len: [u32::MAX; OutputStream::COUNT],
329 },
330 vm_state: VmState {
331 frame_number: Default::default(),
332 regs: Default::default(),
333 program_counter: Default::default(),
334 mapped_heap_pages: Default::default(),
335 resident_pages: Default::default(),
336 },
337 old_hash: Default::default(),
338 new_hash: Default::default(),
339 updated_pages: Default::default(),
340 touched_imported_pages: Default::default(),
341 video: Some(VideoMode {
342 mode: Default::default(),
343 width: Default::default(),
344 height: Default::default(),
345 refresh_rate: Default::default(),
346 }),
347 guest_code_hash: Default::default(),
348 guest_code_host: Default::default(),
349 };
350 output.encode().len() -
351 output.updated_pages.encode().len() -
352 output.touched_imported_pages.encode().len() -
353 output.vm_state.mapped_heap_pages.encode().len() -
354 output.vm_state.resident_pages.encode().len()
355 }
356}