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