1 use crate::error::OutOfMemory;
2 use crate::runtime::vm::vmcontext::VMArrayCallNative;
3 use crate::runtime::vm::{
4     StoreBox, TrapRegisters, TrapTest, VMContext, VMOpaqueContext, f32x4, f64x2, i8x16, tls,
5 };
6 use crate::{Engine, ValRaw};
7 use core::marker;
8 use core::ptr::NonNull;
9 use pulley_interpreter::interp::{DoneReason, RegType, TrapKind, Val, Vm, XRegVal};
10 use pulley_interpreter::{Reg, XReg};
11 use wasmtime_environ::{BuiltinFunctionIndex, HostCall, Trap};
12 use wasmtime_unwinder::Handler;
13 use wasmtime_unwinder::Unwind;
14 
15 /// Interpreter state stored within a `Store<T>`.
16 #[repr(transparent)]
17 pub struct Interpreter {
18     /// Pulley VM state, stored in a `StoreBox<T>`.
19     ///
20     /// This representation has a dual purpose of (a) having a low overhead if
21     /// pulley is disabled (just a null pointer) and (b) enabling safe access to
22     /// the `Vm` in the face of recursive calls.
23     ///
24     /// For (b) that's the most tricky part of this, but the basic problem looks
25     /// like:
26     ///
27     /// * The host initially executes some WebAssembly.
28     /// * This acquires a `&mut Vm` and does some execution.
29     /// * The WebAssembly then invokes the host.
30     /// * This bottoms out in `CallIndirectHost` which means that we'll do a
31     ///   dynamic dispatch to a function pointer in pulley registers.
32     /// * The function we call gets unfettered access to `StoreContextMut<T>`
33     /// * When the function returns our original `&mut Vm` pointer is
34     ///   invalidated, so it has to be re-acquired.
35     ///
36     /// The usage of `StoreBox` here solves this conundrum by storing the
37     /// `InterpreterRef` at-rest as a `NonNull<Vm>` as opposed to a `&mut Vm`.
38     /// This is required to model how after a host call the VM state must be
39     /// re-acquire from store state to re-assert that it has an exclusive
40     /// borrow.
41     ///
42     /// This in turn models how VM state could be modified as part of the
43     /// recursive function call, for example with another VM execution itself.
44     ///
45     /// Note that the safety of this all relies not only on correctly managing
46     /// this pointer but it also requires that this pointer is never
47     /// deallocated while an `InterpreterRef` is live. The `InterpreterRef` type
48     /// carries a borrow of this type to ensure this isn't dropped
49     /// independently, and then this file never overwrites this private field to
50     /// otherwise guarantee this.
51     pulley: StoreBox<VmState>,
52 }
53 
54 struct VmState {
55     vm: Vm,
56     resume_at_pc: Option<usize>,
57 }
58 
59 impl Interpreter {
60     /// Creates a new interpreter ready to interpret code.
61     pub fn new(engine: &Engine) -> Result<Interpreter, OutOfMemory> {
62         let ret = Interpreter {
63             pulley: StoreBox::new(VmState {
64                 vm: Vm::with_stack(engine.config().max_wasm_stack),
65                 resume_at_pc: None,
66             })?,
67         };
68         engine.profiler().register_interpreter(&ret);
69         Ok(ret)
70     }
71 
72     /// Returns the `InterpreterRef` structure which can be used to actually
73     /// execute interpreted code.
74     pub fn as_interpreter_ref(&mut self) -> InterpreterRef<'_> {
75         InterpreterRef {
76             vm: self.pulley.get(),
77             _phantom: marker::PhantomData,
78         }
79     }
80 
81     pub fn pulley(&self) -> &Vm {
82         let state = unsafe { self.pulley.get().as_ref() };
83         &state.vm
84     }
85 
86     /// Get an implementation of `Unwind` used to walk the Pulley stack.
87     pub fn unwinder(&self) -> &'static dyn Unwind {
88         &UnwindPulley
89     }
90 }
91 
92 /// Wrapper around `&mut pulley_interpreter::Vm` to enable compiling this to a
93 /// zero-sized structure when pulley is disabled at compile time.
94 #[repr(transparent)]
95 pub struct InterpreterRef<'a> {
96     vm: NonNull<VmState>,
97     _phantom: marker::PhantomData<&'a mut VmState>,
98 }
99 
100 /// An implementation of stack-walking details specifically designed
101 /// for unwinding Pulley's runtime stack.
102 pub struct UnwindPulley;
103 
104 unsafe impl Unwind for UnwindPulley {
105     fn next_older_fp_from_fp_offset(&self) -> usize {
106         0
107     }
108     fn next_older_sp_from_fp_offset(&self) -> usize {
109         if cfg!(target_pointer_width = "32") {
110             8
111         } else {
112             16
113         }
114     }
115     unsafe fn get_next_older_pc_from_fp(&self, fp: usize) -> usize {
116         // The calling convention always pushes the return pointer (aka the PC
117         // of the next older frame) just before this frame.
118         unsafe { *(fp as *mut usize).offset(1) }
119     }
120     fn assert_fp_is_aligned(&self, fp: usize) {
121         let expected = if cfg!(target_pointer_width = "32") {
122             8
123         } else {
124             16
125         };
126         assert_eq!(fp % expected, 0, "stack should always be aligned");
127     }
128 }
129 
130 impl InterpreterRef<'_> {
131     fn vm_state(&mut self) -> &mut VmState {
132         // SAFETY: This is a bit of a tricky code. The safety here is isolated
133         // to this file, but not isolated to just this function call.
134         //
135         // An `InterpreterRef` guarantees that we have a pointer to a `Vm`, and
136         // that pointer originates from a `StoreBox<VM>` in the store itself.
137         // One level of safety here relies on that never being deallocated or
138         // overwritten, which this file upholds as it's a private field only
139         // this module can access.
140         //
141         // Another aspect upheld by `InterpreterRef` is that it transfers, to
142         // the compiler, a mutable borrow of the store (e.g `struct Interpreter`
143         // above) to this reference. While this doesn't actually hold such a
144         // lifetime-bound pointer it guarantees that only one of these can be
145         // active at a time per interpreter.
146         //
147         // Finally the lifetime of the returned `Vm` is bound to `self` which
148         // ensures that there is at most one per `InterpreterRef`.
149         //
150         // All put together this should allow at most one `&mut Vm` per-store,
151         // which is one guarantee we need for this to be safe.
152         //
153         // Otherwise this is then done to represent how across host function
154         // calls the interpreter needs to be re-borrowed as the state may have
155         // changed as part of the dynamic host call.
156         unsafe { self.vm.as_mut() }
157     }
158 
159     fn vm(&mut self) -> &mut Vm {
160         &mut self.vm_state().vm
161     }
162 
163     /// Invokes interpreted code.
164     ///
165     /// The `bytecode` pointer should previously have been produced by Cranelift
166     /// and `callee` / `caller` / `args_and_results` are normal array-call
167     /// arguments being passed around.
168     pub unsafe fn call(
169         mut self,
170         mut bytecode: NonNull<u8>,
171         callee: NonNull<VMOpaqueContext>,
172         caller: NonNull<VMContext>,
173         args_and_results: NonNull<[ValRaw]>,
174     ) -> bool {
175         // Initialize argument registers with the ABI arguments.
176         let args = [
177             XRegVal::new_ptr(callee.as_ptr()).into(),
178             XRegVal::new_ptr(caller.as_ptr()).into(),
179             XRegVal::new_ptr(args_and_results.cast::<u8>().as_ptr()).into(),
180             XRegVal::new_u64(args_and_results.len() as u64).into(),
181         ];
182 
183         let mut vm = self.vm();
184 
185         let old_lr = unsafe { vm.call_start(&args) };
186 
187         // Run the interpreter as much as possible until it finishes, and then
188         // handle each finish condition differently.
189         let ret = loop {
190             match unsafe { vm.call_run(bytecode) } {
191                 // If the VM returned entirely then read the return value and
192                 // return that (it indicates whether a trap happened or not.
193                 DoneReason::ReturnToHost(()) => {
194                     match unsafe { vm.call_end(old_lr, [RegType::XReg]).next().unwrap() } {
195                         #[allow(
196                             clippy::cast_possible_truncation,
197                             reason = "intentionally reading the lower bits only"
198                         )]
199                         Val::XReg(xreg) => break (xreg.get_u32() as u8) != 0,
200                         _ => unreachable!(),
201                     }
202                 }
203                 // If the VM wants to call out to the host then dispatch that
204                 // here based on `id`. Once that returns we typically resume
205                 // execution at `resume`.
206                 DoneReason::CallIndirectHost { id, resume } => {
207                     unsafe {
208                         self.call_indirect_host(id);
209                     }
210 
211                     // After the host has finished take a look at what hostcall
212                     // was just made. The `raise` hostcall gets special handling
213                     // for its non-local transfer of control flow.
214                     //
215                     // Also note that for non-`raise` hostcalls the
216                     // `state.resume_at_pc` value should always be `None`.
217                     if u32::from(id) == HostCall::Builtin(BuiltinFunctionIndex::raise()).index() {
218                         bytecode = self.take_resume_at_pc();
219                     } else {
220                         debug_assert!(self.vm_state().resume_at_pc.is_none());
221                         bytecode = resume;
222                     }
223                     vm = self.vm();
224                 }
225                 // If the VM trapped then process that here and return `false`.
226                 DoneReason::Trap { pc, kind } => {
227                     bytecode = self.trap(pc, kind);
228                     vm = self.vm();
229                 }
230             }
231         };
232 
233         ret
234     }
235 
236     /// Handles the `call_indirect_host` instruction, dispatching the `sig`
237     /// number here which corresponds to `wasmtime_environ::HostCall`.
238     #[allow(
239         clippy::cast_possible_truncation,
240         clippy::cast_sign_loss,
241         unused,
242         reason = "macro-generated code"
243     )]
244     #[cfg_attr(
245         not(feature = "component-model"),
246         expect(unused_macro_rules, reason = "macro-code")
247     )]
248     unsafe fn call_indirect_host(&mut self, id: u8) {
249         let id = u32::from(id);
250         let fnptr = self.vm()[XReg::x0].get_ptr();
251         let mut arg_reg = 1;
252 
253         /// Helper macro to invoke a builtin.
254         ///
255         /// Used as:
256         ///
257         /// `call(@builtin(ty1, ty2, ...) -> retty)` - invoke a core or
258         /// component builtin with the macro-defined signature.
259         ///
260         /// `call(@host Ty(ty1, ty2, ...) -> retty)` - invoke a host function
261         /// with the type `Ty`. The other types in the macro are checked by
262         /// rustc to match the actual `Ty` definition in Rust.
263         macro_rules! call {
264             (@builtin($($param:ident),*) $(-> $result:ident)?) => {{
265                 #[allow(improper_ctypes_definitions, reason = "__m128i known not FFI-safe")]
266                 type T = unsafe extern "C" fn($(call!(@ty $param)),*) $(-> call!(@ty $result))?;
267                 call!(@host T($($param),*) $(-> $result)?);
268             }};
269             (@host $ty:ident($($param:ident),*) $(-> $result:ident)?) => {{
270 
271                 // Decode each argument according to this macro, pulling
272                 // arguments from successive registers.
273                 let ret = unsafe {
274                     let mut vm = self.vm();
275                     // Convert the pointer from pulley to a native function pointer.
276                     union GetNative {
277                         fnptr: *mut u8,
278                         host: $ty,
279                     }
280                     let host = GetNative { fnptr }.host;
281                     host($({
282                         let reg = XReg::new(arg_reg).unwrap();
283                         arg_reg += 1;
284                         call!(@get $param vm[reg])
285                     }),*)
286                 };
287                 let _ = arg_reg; // silence last dead arg_reg increment warning
288 
289                 let state = self.vm_state();
290                 let _vm = &mut state.vm;
291 
292                 // Store the return value, if one is here, in x0.
293                 $(
294                     call!(@set $result ret => _vm[XReg::x0]);
295                 )?
296                 let _ = ret; // silence warning if no return value
297 
298                 // Return from the outer `call_indirect_host` host function as
299                 // it's been processed.
300                 return;
301             }};
302 
303             // Conversion from macro-defined types to Rust host types.
304             (@ty bool) => (bool);
305             (@ty u8) => (u8);
306             (@ty u32) => (u32);
307             (@ty i32) => (i32);
308             (@ty u64) => (u64);
309             (@ty i64) => (i64);
310             (@ty f32) => (f32);
311             (@ty f64) => (f64);
312             (@ty i8x16) => (i8x16);
313             (@ty f32x4) => (f32x4);
314             (@ty f64x2) => (f64x2);
315             (@ty vmctx) => (*mut VMContext);
316             (@ty pointer) => (*mut u8);
317             (@ty ptr_u8) => (*mut u8);
318             (@ty ptr_u16) => (*mut u16);
319             (@ty ptr_size) => (*mut usize);
320             (@ty size) => (usize);
321 
322             // Conversion from a pulley register value to the macro-defined
323             // type.
324             (@get u8 $reg:expr) => ($reg.get_i32() as u8);
325             (@get u32 $reg:expr) => ($reg.get_u32());
326             (@get u64 $reg:expr) => ($reg.get_u64());
327             (@get f32 $reg:expr) => (unreachable::<f32, _>($reg));
328             (@get f64 $reg:expr) => (unreachable::<f64, _>($reg));
329             (@get i8x16 $reg:expr) => (unreachable::<i8x16, _>($reg));
330             (@get f32x4 $reg:expr) => (unreachable::<f32x4, _>($reg));
331             (@get f64x2 $reg:expr) => (unreachable::<f64x2, _>($reg));
332             (@get vmctx $reg:expr) => ($reg.get_ptr());
333             (@get pointer $reg:expr) => ($reg.get_ptr());
334             (@get ptr $reg:expr) => ($reg.get_ptr());
335             (@get nonnull $reg:expr) => (NonNull::new($reg.get_ptr()).unwrap());
336             (@get ptr_u8 $reg:expr) => ($reg.get_ptr());
337             (@get ptr_u16 $reg:expr) => ($reg.get_ptr());
338             (@get ptr_size $reg:expr) => ($reg.get_ptr());
339             (@get size $reg:expr) => ($reg.get_ptr::<u8>() as usize);
340 
341             // Conversion from a Rust value back into a macro-defined type,
342             // stored in a pulley register.
343             (@set bool $src:expr => $dst:expr) => ($dst.set_i32(i32::from($src)));
344             (@set u32 $src:expr => $dst:expr) => ($dst.set_u32($src));
345             (@set u64 $src:expr => $dst:expr) => ($dst.set_u64($src));
346             (@set f32 $src:expr => $dst:expr) => (unreachable::<f32, _>(($dst, $src)));
347             (@set f64 $src:expr => $dst:expr) => (unreachable::<f64, _>(($dst, $src)));
348             (@set i8x16 $src:expr => $dst:expr) => (unreachable::<i8x16, _>(($dst, $src)));
349             (@set f32x4 $src:expr => $dst:expr) => (unreachable::<f32x4, _>(($dst, $src)));
350             (@set f64x2 $src:expr => $dst:expr) => (unreachable::<f64x2, _>(($dst, $src)));
351             (@set pointer $src:expr => $dst:expr) => ($dst.set_ptr($src));
352             (@set size $src:expr => $dst:expr) => ($dst.set_ptr($src as *mut u8));
353         }
354 
355         // With the helper macro above structure this into:
356         //
357         // foreach [core, component]
358         //   * dispatch the call-the-host function pointer type
359         //   * dispatch all builtins by their index.
360         //
361         // The hope is that this is relatively easy for LLVM to optimize since
362         // it's a bunch of:
363         //
364         //  if id == 0 { ...;  return; }
365         //  if id == 1 { ...;  return; }
366         //  if id == 2 { ...;  return; }
367         //  ...
368         //
369 
370         if id == const { HostCall::ArrayCall.index() } {
371             call!(@host VMArrayCallNative(nonnull, nonnull, nonnull, size) -> bool);
372         }
373 
374         macro_rules! core {
375             (
376                 $(
377                     $( #[cfg($attr:meta)] )?
378                     $name:ident($($pname:ident: $param:ident ),* ) $(-> $result:ident)?;
379                 )*
380             ) => {
381                 $(
382                     $( #[cfg($attr)] )?
383                     if id == const { HostCall::Builtin(BuiltinFunctionIndex::$name()).index() } {
384                         call!(@builtin($($param),*) $(-> $result)?);
385                     }
386                 )*
387             }
388         }
389         wasmtime_environ::foreach_builtin_function!(core);
390 
391         #[cfg(feature = "component-model")]
392         {
393             use crate::runtime::vm::component::VMLoweringCallee;
394             use wasmtime_environ::component::ComponentBuiltinFunctionIndex;
395 
396             if id == const { HostCall::ComponentLowerImport.index() } {
397                 call!(@host VMLoweringCallee(nonnull, nonnull, u32, u32, nonnull, size) -> bool);
398             }
399 
400             macro_rules! component {
401                 (
402                     $(
403                         $( #[cfg($attr:meta)] )?
404                         $name:ident($($pname:ident: $param:ident ),* ) $(-> $result:ident)?;
405                     )*
406                 ) => {
407                     $(
408                         $( #[cfg($attr)] )?
409                         if id == const { HostCall::ComponentBuiltin(ComponentBuiltinFunctionIndex::$name()).index() } {
410                             call!(@builtin($($param),*) $(-> $result)?);
411                         }
412                     )*
413                 }
414             }
415             wasmtime_environ::foreach_builtin_component_function!(component);
416         }
417 
418         // if we got this far then something has gone seriously wrong.
419         return unreachable(());
420 
421         fn unreachable<T, U>(_: U) -> T {
422             unreachable!()
423         }
424     }
425 
426     /// Configures Pulley to be able to resume to the specified exception
427     /// handler.
428     ///
429     /// This is executed from a `raise` hostcall when an exception is being
430     /// raised.
431     ///
432     /// # Safety
433     ///
434     /// Requires that all the parameters here are valid and will leave Pulley
435     /// in a valid state for executing.
436     pub(crate) unsafe fn resume_to_exception_handler(
437         &mut self,
438         handler: &Handler,
439         payload1: usize,
440         payload2: usize,
441     ) {
442         unsafe {
443             let vm = self.vm();
444             vm[XReg::x0].set_u64(payload1 as u64);
445             vm[XReg::x1].set_u64(payload2 as u64);
446             vm[XReg::sp].set_ptr(core::ptr::with_exposed_provenance_mut::<u8>(handler.sp));
447             vm.set_fp(core::ptr::with_exposed_provenance_mut(handler.fp));
448         }
449         let state = self.vm_state();
450         debug_assert!(state.resume_at_pc.is_none());
451         self.vm_state().resume_at_pc = Some(handler.pc);
452     }
453 
454     /// Handles an interpreter trap. This will initialize the trap state stored
455     /// in TLS via the `test_if_trap` helper below by reading the pc/fp of the
456     /// interpreter and seeing if that's a valid opcode to trap at.
457     fn trap(&mut self, pc: NonNull<u8>, kind: Option<TrapKind>) -> NonNull<u8> {
458         let regs = TrapRegisters {
459             pc: pc.as_ptr() as usize,
460             fp: self.vm().fp() as usize,
461         };
462         let handler = tls::with(|s| {
463             let s = s.unwrap();
464             match kind {
465                 Some(kind) => {
466                     let trap = match kind {
467                         TrapKind::IntegerOverflow => Trap::IntegerOverflow,
468                         TrapKind::DivideByZero => Trap::IntegerDivisionByZero,
469                         TrapKind::BadConversionToInteger => Trap::BadConversionToInteger,
470                         TrapKind::MemoryOutOfBounds => Trap::MemoryOutOfBounds,
471                         TrapKind::DisabledOpcode => Trap::DisabledOpcode,
472                         TrapKind::StackOverflow => Trap::StackOverflow,
473                     };
474                     s.set_jit_trap(regs, None, trap);
475                     s.entry_trap_handler()
476                 }
477                 None => {
478                     match s.test_if_trap(regs, None, |_| false) {
479                         // This shouldn't be possible, so this is a fatal error
480                         // if it happens.
481                         TrapTest::NotWasm => {
482                             panic!("pulley trap at {pc:?} without trap code registered")
483                         }
484 
485                         // Not possible with our closure above returning `false`.
486                         #[cfg(has_host_compiler_backend)]
487                         TrapTest::HandledByEmbedder => unreachable!(),
488 
489                         // Trap was handled, yay! Configure interpreter state
490                         // to resume at the exception handler.
491                         TrapTest::Trap(handler) => handler,
492                     }
493                 }
494             }
495         });
496         unsafe {
497             self.resume_to_exception_handler(&handler, 0, 0);
498         }
499         self.take_resume_at_pc()
500     }
501 
502     fn take_resume_at_pc(&mut self) -> NonNull<u8> {
503         let pc = self.vm_state().resume_at_pc.take().unwrap();
504         let pc = core::ptr::with_exposed_provenance_mut(pc);
505         NonNull::new(pc).unwrap()
506     }
507 }
508