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 acccess. 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 }; 477 s.set_jit_trap(regs, None, trap); 478 } 479 None => { 480 match s.test_if_trap(regs, None, |_| false) { 481 // This shouldn't be possible, so this is a fatal error 482 // if it happens. 483 TrapTest::NotWasm => { 484 panic!("pulley trap at {pc:?} without trap code registered") 485 } 486 487 // Not possible with our closure above returning `false`. 488 #[cfg(has_host_compiler_backend)] 489 TrapTest::HandledByEmbedder => unreachable!(), 490 491 // Trap was handled, yay! We don't use `jmp_buf`. 492 TrapTest::Trap { .. } => {} 493 } 494 } 495 } 496 }); 497 498 longjmp(vm, setjmp); 499 } 500 501 fn setjmp(vm: &Vm) -> Setjmp { 502 let mut xregs = [0; 16]; 503 let mut fregs = [0.0; 16]; 504 for (i, reg) in callee_save_xregs() { 505 xregs[i] = vm[reg].get_u64(); 506 } 507 for (i, reg) in callee_save_fregs() { 508 fregs[i] = vm[reg].get_f64(); 509 } 510 Setjmp { 511 xregs, 512 fregs, 513 fp: vm.fp(), 514 lr: vm.lr(), 515 } 516 } 517 518 /// Perform a "longjmp" by restoring the "setjmp" context saved when this 519 /// started. 520 fn longjmp(vm: &mut Vm, setjmp: Setjmp) { 521 let Setjmp { 522 xregs, 523 fregs, 524 fp, 525 lr, 526 } = setjmp; 527 unsafe { 528 for (i, reg) in callee_save_xregs() { 529 vm[reg].set_u64(xregs[i]); 530 } 531 for (i, reg) in callee_save_fregs() { 532 vm[reg].set_f64(fregs[i]); 533 } 534 vm.set_fp(fp); 535 vm.set_lr(lr); 536 } 537 } 538 539 fn callee_save_xregs() -> impl Iterator<Item = (usize, XReg)> { 540 (0..16).map(|i| (i.into(), XReg::new(i + 16).unwrap())) 541 } 542 543 fn callee_save_fregs() -> impl Iterator<Item = (usize, FReg)> { 544 (0..16).map(|i| (i.into(), FReg::new(i + 16).unwrap())) 545 } 546