1use crate::{
2 hash_encoded, BoundedInput, BoundedWorkOutput, FatalError, FsNode, HostCallError,
3 HostCallHandler, InitError, InnerVm, InvokeArgs, KernelContext, Lookup, MemoryMap, OpenedFile,
4 OuterVm, OutputBuffers, Reg, RunError, TouchError,
5};
6use alloc::vec::Vec;
7use codec::DecodeAll;
8use corevm_codec::video;
9use corevm_host::{
10 fs, CoreVmExtrinsics, CoreVmOutput, CoreVmPayload, ExecEnv, Outcome, OutputStream, PageNum,
11 Range, VmOutput, PAGE_SIZE,
12};
13use jam_pvm_common::InvokeOutcome;
14use jam_types::{max_exports, VecSet};
15use log::{debug, trace};
16use polkakernel::{KernelState, Machine};
17use polkavm::{ArcBytes, RETURN_TO_HOST};
18
19pub struct WorkPackageParams {
21 pub encoded_size: u32,
23 pub auth_output_size: u32,
25 pub export_count: u16,
27}
28
29#[derive(Debug)]
31pub struct InputStats {
32 pub accessed_imported_pages: VecSet<PageNum>,
34 pub num_service_messages_read: u32,
36 pub num_input_chunks_read: u32,
38}
39
40pub struct Engine<O: OuterVm> {
42 pub(crate) args: InvokeArgs,
43 pub(crate) outer_vm: O,
44 pub(crate) inner_vm: O::InnerVm,
45 pub(crate) exec: ExecEnv,
46 pub(crate) num_video_frames: u64,
48 pub(crate) num_audio_bytes: u64,
51 pub(crate) timestamp: u64,
57 pub(crate) memory_map: MemoryMap,
58 pub(crate) export_count: u16,
60 pub(crate) output: OutputBuffers,
62 pub(crate) ro_data: ArcBytes,
63 pub(crate) rw_data: ArcBytes,
64 pub(crate) work_output: BoundedWorkOutput,
65 pub(crate) host_call_handlers: Vec<HostCallHandler<O>>,
66 pub(crate) host_call_names: Vec<&'static str>,
68 pub(crate) input: BoundedInput,
69 pub(crate) video_encoder: Option<video::Encoder>,
71}
72
73impl<O: OuterVm> Engine<O> {
74 pub fn new(
75 payload: CoreVmPayload,
76 extrinsics: CoreVmExtrinsics,
77 work_package_params: WorkPackageParams,
78 mut outer_vm: O,
79 ) -> Result<Self, InitError> {
80 let CoreVmPayload { gas, vm_state, exec_ref } = payload;
81 let exec = outer_vm.read_file(&exec_ref)?;
82 let exec = ExecEnv::decode_all(&mut &exec[..]).map_err(|_| InitError::ExecBlob)?;
83 let export_count = work_package_params.export_count;
84 let auth_output_len = work_package_params.auth_output_size;
85 let mut program_counter = vm_state.program_counter;
86 let program = {
87 let bytes = outer_vm.read_file(&exec.program)?;
88 let corevm_blob = jam_program_blob_common::CoreVmProgramBlob::from_bytes(&bytes)
89 .ok_or(InitError::ProgramBlob)?;
90 polkavm::ProgramParts::from_bytes(corevm_blob.pvm_blob.into())?
91 };
92 let polkavm_memory_map = polkavm::MemoryMapBuilder::new(PAGE_SIZE)
93 .ro_data_size(program.ro_data_size)
94 .rw_data_size(program.rw_data_size)
95 .stack_size(program.stack_size)
96 .build()
97 .map_err(|_| InitError::ProgramBlob)?;
98 let code_and_jump_table = program.code_and_jump_table.clone();
99 let ro_data = program.ro_data.clone();
100 let rw_data = program.rw_data.clone();
101 let program_blob =
102 polkavm::ProgramBlob::from_parts(program).map_err(|_| InitError::ProgramBlob)?;
103 let (host_call_handlers, host_call_names) = Self::create_host_call_handlers(&program_blob);
104 let old_hash = hash_encoded(&vm_state);
106 let args = InvokeArgs { regs: vm_state.regs, gas };
107 let initial_program_run = program_counter == 0;
108 let mut libc = false;
109 if initial_program_run {
110 debug!("This is initial program run");
111 program_counter = program_blob
112 .exports()
113 .find(|export| {
114 let name = export.symbol().as_bytes();
115 if name == b"main" {
116 debug!("Found `main` (no libc) entry point");
117 return true;
118 }
119 if name == b"_pvm_start" {
120 debug!("Found `_pvm_start` (libc) entry point");
121 libc = true;
122 return true;
123 }
124 false
125 })
126 .expect("Neither `main` nor `_pvm_start` entry point found")
127 .program_counter()
128 .0
129 .into();
130 }
131 let input = BoundedInput::new(work_package_params.encoded_size, extrinsics);
132 let inner_vm = outer_vm
133 .machine(&code_and_jump_table[..], program_counter)
134 .expect("Failed to load the code");
135 let memory_map = MemoryMap::new(&polkavm_memory_map);
136 let kernel_state = KernelState {
137 fds: vm_state
138 .kernel
139 .fds
140 .iter()
141 .map(|(fd, file)| {
142 let (block_ref, position) = (file.block_ref, file.position);
144 let mut lookup = Lookup { outer_vm: &mut outer_vm };
145 let node = fs::Node::open(&block_ref, &mut lookup)?;
146 let node = match node {
147 fs::Node::File(mut file) => {
148 file.seek(position)?;
149 FsNode::File(file)
150 },
151 fs::Node::Dir(dir) => FsNode::Dir { dir, position },
152 };
153 let opened_file = OpenedFile { block_ref, node };
154 Ok((*fd, opened_file))
155 })
156 .collect::<Result<_, fs::Error>>()?,
157 };
158 let heap_address_range = memory_map.heap_range();
159 let heap_page_range =
160 Range::new(heap_address_range.start / PAGE_SIZE, heap_address_range.end / PAGE_SIZE);
161 assert!(u32::from(export_count) <= max_exports());
163 assert!(
164 ro_data.len() as u32 <=
165 memory_map.ro_data_range().end - memory_map.ro_data_range().start
166 );
167 assert!(
168 rw_data.len() as u32 <=
169 memory_map.rw_data_range().end - memory_map.rw_data_range().start
170 );
171 let mut engine = Self {
172 work_output: BoundedWorkOutput::try_from(
174 CoreVmOutput {
175 vm_output: VmOutput {
176 remaining_gas: 0,
177 outcome: Outcome::Halt,
178 num_memory_pages: u32::from(export_count),
179 stream_len: [u32::MAX; OutputStream::COUNT],
180 },
181 vm_state,
182 old_hash,
183 new_hash: Default::default(),
184 touched_imported_pages: Default::default(),
185 updated_pages: Default::default(),
186 exec_ref,
187 outgoing_messages: Default::default(),
188 processed_service_messages: Default::default(),
189 },
190 kernel_state,
191 auth_output_len,
192 heap_page_range,
193 )
194 .expect("Empty work output shouldn't exceed max_report_elective_data"),
195 args,
196 host_call_handlers,
197 host_call_names,
198 num_video_frames: 0,
199 num_audio_bytes: 0,
200 timestamp: 0,
201 outer_vm,
202 inner_vm,
203 memory_map,
204 ro_data,
205 rw_data,
206 export_count,
207 output: Default::default(),
208 exec,
209 input,
210 video_encoder: None,
211 };
212 if initial_program_run {
213 let default_sp = polkavm_memory_map.stack_address_high() as u64;
215 if libc {
216 let args = core::mem::take(&mut engine.exec.args);
217 let env = core::mem::take(&mut engine.exec.env);
218 let mut context = KernelContext { engine: &mut engine, error: None };
219 context
220 .init(
221 default_sp,
222 RETURN_TO_HOST,
223 args.iter().map(|a| a.as_ref()),
224 env.iter().map(|a| a.as_ref()),
225 )
226 .expect("Failed to init arguments");
227 engine.exec.args = args;
228 engine.exec.env = env;
229 } else {
230 engine.args.set_reg(Reg::RA, RETURN_TO_HOST);
231 engine.args.set_reg(Reg::SP, default_sp);
232 }
233 }
234 Ok(engine)
235 }
236
237 pub fn run(mut self) -> Result<(CoreVmOutput, O, InputStats), RunError> {
238 let mut restart_host_call = self.work_output.vm_state.restart_host_call.is_some();
241 let (output, outer_vm, input_stats) = loop {
242 if let Some(index) = self.work_output.take_restart_host_call() {
243 use Outcome::*;
244 if restart_host_call {
245 debug!("Restarting host-call {:?}", self.pretty_host_call(index));
246 }
247 let outcome = match self.handle_host_call_fault(index) {
248 Ok(()) => {
249 None
251 },
252 Err(HostCallError::Outcome(
253 outcome @ Panic | outcome @ Halt | outcome @ OutOfGas,
254 )) => Some(outcome),
255 Err(HostCallError::Outcome(outcome @ TimeLimitReached)) => {
256 debug!(
258 "Time limit reached in host-call {:?}",
259 self.pretty_host_call(index)
260 );
261 Some(outcome)
262 },
263 Err(HostCallError::Outcome(outcome @ OutputLimitReached)) => {
264 if restart_host_call {
265 return Err(FatalError::NotEnoughOutputSpace.into());
266 }
267 debug!(
268 "Output limit reached in host-call {:?}",
269 self.pretty_host_call(index)
270 );
271 self.work_output.set_restart_host_call(index);
272 Some(outcome)
273 },
274 Err(HostCallError::Outcome(outcome @ InputLimitReached)) => {
275 if restart_host_call {
276 return Err(FatalError::NotEnoughInputSpace.into());
277 }
278 self.work_output.set_restart_host_call(index);
279 Some(outcome)
280 },
281 Err(HostCallError::Outcome(outcome @ WaitingForInput(..))) => {
282 self.work_output.set_restart_host_call(index);
283 Some(outcome)
284 },
285 Err(HostCallError::Outcome(outcome @ PageFault { page, num_pages })) => {
286 if restart_host_call {
287 debug!(
288 "Hard page fault at {:?} while restarting host-call {:?}",
289 self.memory_map.pretty_page_range(page.0..page.0 + num_pages),
290 self.pretty_host_call(index)
291 );
292 return Err(FatalError::NotEnoughInputSpace.into());
295 }
296 debug!(
297 "Hard page fault at {:#x?} in host-call {:?}",
298 self.memory_map.pretty_page_range(page.0..page.0 + num_pages),
299 self.pretty_host_call(index)
300 );
301 self.work_output.set_restart_host_call(index);
302 Some(outcome)
303 },
304 Err(HostCallError::Jam(e)) => return Err(e.into()),
305 Err(HostCallError::Fs(e)) => return Err(e.into()),
306 Err(HostCallError::Fatal(e)) => return Err(e.into()),
307 };
308 if let Some(outcome) = outcome {
309 break self.suspend(outcome)?;
310 }
311 restart_host_call = false;
312 }
313 let (outcome, gas, regs) = self.inner_vm.invoke(self.args.gas, self.args.regs)?;
314 trace!("Invoke outcome {:?}", self.pretty_invoke_outcome(outcome));
315 self.args.gas = gas;
316 self.args.regs = regs;
317 match outcome {
318 InvokeOutcome::Halt => {
319 break self.suspend(Outcome::Halt)?;
320 },
321 InvokeOutcome::PageFault(address) => {
322 match self.touch(PageNum::from_address(address as u32)) {
323 Ok(..) => {},
324 Err(TouchError::PageFault { page, num_pages }) => {
325 trace!("Hard page fault at {:?}", self.memory_map.pretty_page(page.0));
326 break self.suspend(Outcome::PageFault { page, num_pages })?;
330 },
331 Err(TouchError::Jam(e)) => return Err(e.into()),
332 Err(TouchError::InvalidMemoryAccess) =>
333 return Err(RunError::Fatal(FatalError::InvalidMemoryAccess)),
334 Err(TouchError::InputLimitReached) =>
335 break self.suspend(Outcome::InputLimitReached)?,
336 }
337 },
338 InvokeOutcome::HostCallFault(index) =>
339 self.work_output.set_restart_host_call(index),
340 InvokeOutcome::Panic => {
341 break self.suspend(Outcome::Panic)?;
342 },
343 InvokeOutcome::OutOfGas => {
344 break self.suspend(Outcome::OutOfGas)?;
345 },
346 }
347 };
348 debug!("Finished with outcome {:?}", output.vm_output.outcome);
349 Ok((output, outer_vm, input_stats))
350 }
351
352 fn handle_host_call_fault(&mut self, index: u64) -> Result<(), HostCallError> {
353 let handler =
354 self.host_call_handlers.get(index as usize).ok_or(FatalError::UnknownHostCall)?;
355 handler(self)
356 }
357}