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