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