1 //! Cranelift IR interpreter. 2 //! 3 //! This module partially contains the logic for interpreting Cranelift IR. 4 5 use crate::address::{Address, AddressFunctionEntry, AddressRegion, AddressSize}; 6 use crate::environment::{FuncIndex, FunctionStore}; 7 use crate::frame::Frame; 8 use crate::instruction::DfgInstructionContext; 9 use crate::state::{InterpreterFunctionRef, MemoryError, State}; 10 use crate::step::{ControlFlow, CraneliftTrap, StepError, step}; 11 use crate::value::{DataValueExt, ValueError}; 12 use cranelift_codegen::data_value::DataValue; 13 use cranelift_codegen::ir::{ 14 ArgumentPurpose, Block, Endianness, ExternalName, FuncRef, Function, GlobalValue, 15 GlobalValueData, LibCall, MemFlags, StackSlot, Type, 16 }; 17 use log::trace; 18 use smallvec::SmallVec; 19 use std::fmt::Debug; 20 use std::iter; 21 use thiserror::Error; 22 23 /// The Cranelift interpreter; this contains some high-level functions to control the interpreter's 24 /// flow. The interpreter state is defined separately (see [InterpreterState]) as the execution 25 /// semantics for each Cranelift instruction (see [step]). 26 pub struct Interpreter<'a> { 27 state: InterpreterState<'a>, 28 fuel: Option<u64>, 29 } 30 31 impl<'a> Interpreter<'a> { new(state: InterpreterState<'a>) -> Self32 pub fn new(state: InterpreterState<'a>) -> Self { 33 Self { state, fuel: None } 34 } 35 36 /// The `fuel` mechanism sets a number of instructions that 37 /// the interpreter can execute before stopping. If this 38 /// value is `None` (the default), no limit is imposed. with_fuel(self, fuel: Option<u64>) -> Self39 pub fn with_fuel(self, fuel: Option<u64>) -> Self { 40 Self { fuel, ..self } 41 } 42 43 /// Call a function by name; this is a helpful proxy for [Interpreter::call_by_index]. call_by_name( &mut self, func_name: &str, arguments: &[DataValue], ) -> Result<ControlFlow<'a>, InterpreterError>44 pub fn call_by_name( 45 &mut self, 46 func_name: &str, 47 arguments: &[DataValue], 48 ) -> Result<ControlFlow<'a>, InterpreterError> { 49 let index = self 50 .state 51 .functions 52 .index_of(func_name) 53 .ok_or_else(|| InterpreterError::UnknownFunctionName(func_name.to_string()))?; 54 self.call_by_index(index, arguments) 55 } 56 57 /// Call a function by its index in the [FunctionStore]; this is a proxy for 58 /// `Interpreter::call`. call_by_index( &mut self, index: FuncIndex, arguments: &[DataValue], ) -> Result<ControlFlow<'a>, InterpreterError>59 pub fn call_by_index( 60 &mut self, 61 index: FuncIndex, 62 arguments: &[DataValue], 63 ) -> Result<ControlFlow<'a>, InterpreterError> { 64 match self.state.functions.get_by_index(index) { 65 None => Err(InterpreterError::UnknownFunctionIndex(index)), 66 Some(func) => self.call(func, arguments), 67 } 68 } 69 70 /// Interpret a call to a [Function] given its [DataValue] arguments. call( &mut self, function: &'a Function, arguments: &[DataValue], ) -> Result<ControlFlow<'a>, InterpreterError>71 fn call( 72 &mut self, 73 function: &'a Function, 74 arguments: &[DataValue], 75 ) -> Result<ControlFlow<'a>, InterpreterError> { 76 trace!("Call: {}({:?})", function.name, arguments); 77 let first_block = function 78 .layout 79 .blocks() 80 .next() 81 .expect("to have a first block"); 82 let parameters = function.dfg.block_params(first_block); 83 self.state.push_frame(function); 84 self.state 85 .current_frame_mut() 86 .set_all(parameters, arguments.to_vec()); 87 88 self.block(first_block) 89 } 90 91 /// Interpret a [Block] in a [Function]. This drives the interpretation over sequences of 92 /// instructions, which may continue in other blocks, until the function returns. block(&mut self, block: Block) -> Result<ControlFlow<'a>, InterpreterError>93 fn block(&mut self, block: Block) -> Result<ControlFlow<'a>, InterpreterError> { 94 trace!("Block: {block}"); 95 let function = self.state.current_frame_mut().function(); 96 let layout = &function.layout; 97 let mut maybe_inst = layout.first_inst(block); 98 while let Some(inst) = maybe_inst { 99 if self.consume_fuel() == FuelResult::Stop { 100 return Err(InterpreterError::FuelExhausted); 101 } 102 103 let inst_context = DfgInstructionContext::new(inst, &function.dfg); 104 match step(&mut self.state, inst_context)? { 105 ControlFlow::Assign(values) => { 106 self.state 107 .current_frame_mut() 108 .set_all(function.dfg.inst_results(inst), values.to_vec()); 109 maybe_inst = layout.next_inst(inst) 110 } 111 ControlFlow::Continue => maybe_inst = layout.next_inst(inst), 112 ControlFlow::ContinueAt(block, block_arguments) => { 113 trace!("Block: {block}"); 114 self.state 115 .current_frame_mut() 116 .set_all(function.dfg.block_params(block), block_arguments.to_vec()); 117 maybe_inst = layout.first_inst(block) 118 } 119 ControlFlow::Call(called_function, arguments) => { 120 match self.call(called_function, &arguments)? { 121 ControlFlow::Return(rets) => { 122 self.state 123 .current_frame_mut() 124 .set_all(function.dfg.inst_results(inst), rets.to_vec()); 125 maybe_inst = layout.next_inst(inst) 126 } 127 ControlFlow::Trap(trap) => return Ok(ControlFlow::Trap(trap)), 128 cf => { 129 panic!("invalid control flow after call: {cf:?}") 130 } 131 } 132 } 133 ControlFlow::ReturnCall(callee, args) => { 134 self.state.pop_frame(); 135 136 return match self.call(callee, &args)? { 137 ControlFlow::Return(rets) => Ok(ControlFlow::Return(rets)), 138 ControlFlow::Trap(trap) => Ok(ControlFlow::Trap(trap)), 139 cf => { 140 panic!("invalid control flow after return_call: {cf:?}") 141 } 142 }; 143 } 144 ControlFlow::Return(returned_values) => { 145 self.state.pop_frame(); 146 return Ok(ControlFlow::Return(returned_values)); 147 } 148 ControlFlow::Trap(trap) => return Ok(ControlFlow::Trap(trap)), 149 } 150 } 151 Err(InterpreterError::Unreachable) 152 } 153 consume_fuel(&mut self) -> FuelResult154 fn consume_fuel(&mut self) -> FuelResult { 155 match self.fuel { 156 Some(0) => FuelResult::Stop, 157 Some(ref mut n) => { 158 *n -= 1; 159 FuelResult::Continue 160 } 161 162 // We do not have fuel enabled, so unconditionally continue 163 None => FuelResult::Continue, 164 } 165 } 166 } 167 168 #[derive(Debug, PartialEq, Clone)] 169 /// The result of consuming fuel. Signals if the caller should stop or continue. 170 pub enum FuelResult { 171 /// We still have `fuel` available and should continue execution. 172 Continue, 173 /// The available `fuel` has been exhausted, we should stop now. 174 Stop, 175 } 176 177 /// The ways interpretation can fail. 178 #[derive(Error, Debug)] 179 pub enum InterpreterError { 180 #[error("failed to interpret instruction")] 181 StepError(#[from] StepError), 182 #[error("reached an unreachable statement")] 183 Unreachable, 184 #[error("unknown function index (has it been added to the function store?): {0}")] 185 UnknownFunctionIndex(FuncIndex), 186 #[error("unknown function with name (has it been added to the function store?): {0}")] 187 UnknownFunctionName(String), 188 #[error("value error")] 189 ValueError(#[from] ValueError), 190 #[error("fuel exhausted")] 191 FuelExhausted, 192 } 193 194 pub type LibCallValues = SmallVec<[DataValue; 1]>; 195 pub type LibCallHandler = fn(LibCall, LibCallValues) -> Result<LibCallValues, CraneliftTrap>; 196 197 /// Maintains the [Interpreter]'s state, implementing the [State] trait. 198 pub struct InterpreterState<'a> { 199 pub functions: FunctionStore<'a>, 200 pub libcall_handler: LibCallHandler, 201 pub frame_stack: Vec<Frame<'a>>, 202 /// Number of bytes from the bottom of the stack where the current frame's stack space is 203 pub frame_offset: usize, 204 pub stack: Vec<u8>, 205 pub pinned_reg: DataValue, 206 pub native_endianness: Endianness, 207 } 208 209 impl Default for InterpreterState<'_> { default() -> Self210 fn default() -> Self { 211 let native_endianness = if cfg!(target_endian = "little") { 212 Endianness::Little 213 } else { 214 Endianness::Big 215 }; 216 Self { 217 functions: FunctionStore::default(), 218 libcall_handler: |_, _| Err(CraneliftTrap::UnreachableCodeReached), 219 frame_stack: vec![], 220 frame_offset: 0, 221 stack: Vec::with_capacity(1024), 222 pinned_reg: DataValue::I64(0), 223 native_endianness, 224 } 225 } 226 } 227 228 impl<'a> InterpreterState<'a> { with_function_store(self, functions: FunctionStore<'a>) -> Self229 pub fn with_function_store(self, functions: FunctionStore<'a>) -> Self { 230 Self { functions, ..self } 231 } 232 233 /// Registers a libcall handler with_libcall_handler(mut self, handler: LibCallHandler) -> Self234 pub fn with_libcall_handler(mut self, handler: LibCallHandler) -> Self { 235 self.libcall_handler = handler; 236 self 237 } 238 } 239 240 impl<'a> State<'a> for InterpreterState<'a> { get_function(&self, func_ref: FuncRef) -> Option<&'a Function>241 fn get_function(&self, func_ref: FuncRef) -> Option<&'a Function> { 242 self.functions 243 .get_from_func_ref(func_ref, self.frame_stack.last().unwrap().function()) 244 } get_current_function(&self) -> &'a Function245 fn get_current_function(&self) -> &'a Function { 246 self.current_frame().function() 247 } 248 get_libcall_handler(&self) -> LibCallHandler249 fn get_libcall_handler(&self) -> LibCallHandler { 250 self.libcall_handler 251 } 252 push_frame(&mut self, function: &'a Function)253 fn push_frame(&mut self, function: &'a Function) { 254 if let Some(frame) = self.frame_stack.iter().last() { 255 self.frame_offset += frame.function().fixed_stack_size() as usize; 256 } 257 258 // Grow the stack by the space necessary for this frame 259 self.stack 260 .extend(iter::repeat(0).take(function.fixed_stack_size() as usize)); 261 262 self.frame_stack.push(Frame::new(function)); 263 } pop_frame(&mut self)264 fn pop_frame(&mut self) { 265 if let Some(frame) = self.frame_stack.pop() { 266 // Shorten the stack after exiting the frame 267 self.stack 268 .truncate(self.stack.len() - frame.function().fixed_stack_size() as usize); 269 270 // Reset frame_offset to the start of this function 271 if let Some(frame) = self.frame_stack.iter().last() { 272 self.frame_offset -= frame.function().fixed_stack_size() as usize; 273 } 274 } 275 } 276 current_frame_mut(&mut self) -> &mut Frame<'a>277 fn current_frame_mut(&mut self) -> &mut Frame<'a> { 278 let num_frames = self.frame_stack.len(); 279 match num_frames { 280 0 => panic!("unable to retrieve the current frame because no frames were pushed"), 281 _ => &mut self.frame_stack[num_frames - 1], 282 } 283 } 284 current_frame(&self) -> &Frame<'a>285 fn current_frame(&self) -> &Frame<'a> { 286 let num_frames = self.frame_stack.len(); 287 match num_frames { 288 0 => panic!("unable to retrieve the current frame because no frames were pushed"), 289 _ => &self.frame_stack[num_frames - 1], 290 } 291 } 292 stack_address( &self, size: AddressSize, slot: StackSlot, offset: u64, ) -> Result<Address, MemoryError>293 fn stack_address( 294 &self, 295 size: AddressSize, 296 slot: StackSlot, 297 offset: u64, 298 ) -> Result<Address, MemoryError> { 299 let stack_slots = &self.get_current_function().sized_stack_slots; 300 let stack_slot = &stack_slots[slot]; 301 302 // offset must be `0 <= Offset < sizeof(SS)` 303 if offset >= stack_slot.size as u64 { 304 return Err(MemoryError::InvalidOffset { 305 offset, 306 max: stack_slot.size as u64, 307 }); 308 } 309 310 // Calculate the offset from the current frame to the requested stack slot 311 let slot_offset: u64 = stack_slots 312 .keys() 313 .filter(|k| k < &slot) 314 .map(|k| stack_slots[k].size as u64) 315 .sum(); 316 317 let final_offset = self.frame_offset as u64 + slot_offset + offset; 318 Address::from_parts(size, AddressRegion::Stack, 0, final_offset) 319 } 320 checked_load( &self, addr: Address, ty: Type, mem_flags: MemFlags, ) -> Result<DataValue, MemoryError>321 fn checked_load( 322 &self, 323 addr: Address, 324 ty: Type, 325 mem_flags: MemFlags, 326 ) -> Result<DataValue, MemoryError> { 327 let load_size = ty.bytes() as usize; 328 let addr_start = addr.offset as usize; 329 let addr_end = addr_start + load_size; 330 331 let src = match addr.region { 332 AddressRegion::Stack => { 333 if addr_end > self.stack.len() { 334 return Err(MemoryError::OutOfBoundsLoad { 335 addr, 336 load_size, 337 mem_flags, 338 }); 339 } 340 341 &self.stack[addr_start..addr_end] 342 } 343 _ => unimplemented!(), 344 }; 345 346 // Aligned flag is set and address is not aligned for the given type 347 if mem_flags.aligned() && addr_start % load_size != 0 { 348 return Err(MemoryError::MisalignedLoad { addr, load_size }); 349 } 350 351 Ok(match mem_flags.endianness(self.native_endianness) { 352 Endianness::Big => DataValue::read_from_slice_be(src, ty), 353 Endianness::Little => DataValue::read_from_slice_le(src, ty), 354 }) 355 } 356 checked_store( &mut self, addr: Address, v: DataValue, mem_flags: MemFlags, ) -> Result<(), MemoryError>357 fn checked_store( 358 &mut self, 359 addr: Address, 360 v: DataValue, 361 mem_flags: MemFlags, 362 ) -> Result<(), MemoryError> { 363 let store_size = v.ty().bytes() as usize; 364 let addr_start = addr.offset as usize; 365 let addr_end = addr_start + store_size; 366 367 let dst = match addr.region { 368 AddressRegion::Stack => { 369 if addr_end > self.stack.len() { 370 return Err(MemoryError::OutOfBoundsStore { 371 addr, 372 store_size, 373 mem_flags, 374 }); 375 } 376 377 &mut self.stack[addr_start..addr_end] 378 } 379 _ => unimplemented!(), 380 }; 381 382 // Aligned flag is set and address is not aligned for the given type 383 if mem_flags.aligned() && addr_start % store_size != 0 { 384 return Err(MemoryError::MisalignedStore { addr, store_size }); 385 } 386 387 Ok(match mem_flags.endianness(self.native_endianness) { 388 Endianness::Big => v.write_to_slice_be(dst), 389 Endianness::Little => v.write_to_slice_le(dst), 390 }) 391 } 392 function_address( &self, size: AddressSize, name: &ExternalName, ) -> Result<Address, MemoryError>393 fn function_address( 394 &self, 395 size: AddressSize, 396 name: &ExternalName, 397 ) -> Result<Address, MemoryError> { 398 let curr_func = self.get_current_function(); 399 let (entry, index) = match name { 400 ExternalName::User(username) => { 401 let ext_name = &curr_func.params.user_named_funcs()[*username]; 402 403 // TODO: This is not optimal since we are looking up by string name 404 let index = self.functions.index_of(&ext_name.to_string()).unwrap(); 405 406 (AddressFunctionEntry::UserFunction, index.as_u32()) 407 } 408 409 ExternalName::TestCase(testname) => { 410 // TODO: This is not optimal since we are looking up by string name 411 let index = self.functions.index_of(&testname.to_string()).unwrap(); 412 413 (AddressFunctionEntry::UserFunction, index.as_u32()) 414 } 415 ExternalName::LibCall(libcall) => { 416 // We don't properly have a "libcall" store, but we can use `LibCall::all()` 417 // and index into that. 418 let index = LibCall::all_libcalls() 419 .iter() 420 .position(|lc| lc == libcall) 421 .unwrap(); 422 423 (AddressFunctionEntry::LibCall, index as u32) 424 } 425 _ => unimplemented!("function_address: {:?}", name), 426 }; 427 428 Address::from_parts(size, AddressRegion::Function, entry as u64, index as u64) 429 } 430 get_function_from_address(&self, address: Address) -> Option<InterpreterFunctionRef<'a>>431 fn get_function_from_address(&self, address: Address) -> Option<InterpreterFunctionRef<'a>> { 432 let index = address.offset as u32; 433 if address.region != AddressRegion::Function { 434 return None; 435 } 436 437 match AddressFunctionEntry::from(address.entry) { 438 AddressFunctionEntry::UserFunction => self 439 .functions 440 .get_by_index(FuncIndex::from_u32(index)) 441 .map(InterpreterFunctionRef::from), 442 443 AddressFunctionEntry::LibCall => LibCall::all_libcalls() 444 .get(index as usize) 445 .copied() 446 .map(InterpreterFunctionRef::from), 447 } 448 } 449 450 /// Non-Recursively resolves a global value until its address is found resolve_global_value(&self, gv: GlobalValue) -> Result<DataValue, MemoryError>451 fn resolve_global_value(&self, gv: GlobalValue) -> Result<DataValue, MemoryError> { 452 // Resolving a Global Value is a "pointer" chasing operation that lends itself to 453 // using a recursive solution. However, resolving this in a recursive manner 454 // is a bad idea because its very easy to add a bunch of global values and 455 // blow up the call stack. 456 // 457 // Adding to the challenges of this, is that the operations possible with GlobalValues 458 // mean that we cannot use a simple loop to resolve each global value, we must keep 459 // a pending list of operations. 460 461 // These are the possible actions that we can perform 462 #[derive(Debug)] 463 enum ResolveAction { 464 Resolve(GlobalValue), 465 /// Perform an add on the current address 466 Add(DataValue), 467 /// Load From the current address and replace it with the loaded value 468 Load { 469 /// Offset added to the base pointer before doing the load. 470 offset: i32, 471 472 /// Type of the loaded value. 473 global_type: Type, 474 }, 475 } 476 477 let func = self.get_current_function(); 478 479 // We start with a sentinel value that will fail if we try to load / add to it 480 // without resolving the base GV First. 481 let mut current_val = DataValue::I8(0); 482 let mut action_stack = vec![ResolveAction::Resolve(gv)]; 483 484 loop { 485 match action_stack.pop() { 486 Some(ResolveAction::Resolve(gv)) => match func.global_values[gv] { 487 GlobalValueData::VMContext => { 488 // Fetch the VMContext value from the values of the first block in the function 489 let index = func 490 .signature 491 .params 492 .iter() 493 .enumerate() 494 .find(|(_, p)| p.purpose == ArgumentPurpose::VMContext) 495 .map(|(i, _)| i) 496 // This should be validated by the verifier 497 .expect("No VMCtx argument was found, but one is referenced"); 498 499 let first_block = 500 func.layout.blocks().next().expect("to have a first block"); 501 let vmctx_value = func.dfg.block_params(first_block)[index]; 502 current_val = self.current_frame().get(vmctx_value).clone(); 503 } 504 GlobalValueData::Load { 505 base, 506 offset, 507 global_type, 508 .. 509 } => { 510 action_stack.push(ResolveAction::Load { 511 offset: offset.into(), 512 global_type, 513 }); 514 action_stack.push(ResolveAction::Resolve(base)); 515 } 516 GlobalValueData::IAddImm { 517 base, 518 offset, 519 global_type, 520 } => { 521 let offset: i64 = offset.into(); 522 let dv = DataValue::int(offset as i128, global_type) 523 .map_err(|_| MemoryError::InvalidAddressType(global_type))?; 524 action_stack.push(ResolveAction::Add(dv)); 525 action_stack.push(ResolveAction::Resolve(base)); 526 } 527 GlobalValueData::Symbol { .. } => unimplemented!(), 528 GlobalValueData::DynScaleTargetConst { .. } => unimplemented!(), 529 }, 530 Some(ResolveAction::Add(dv)) => { 531 current_val = current_val 532 .add(dv.clone()) 533 .map_err(|_| MemoryError::InvalidAddress(dv))?; 534 } 535 Some(ResolveAction::Load { 536 offset, 537 global_type, 538 }) => { 539 let mut addr = Address::try_from(current_val)?; 540 let mem_flags = MemFlags::trusted(); 541 // We can forego bounds checking here since its performed in `checked_load` 542 addr.offset += offset as u64; 543 current_val = self.checked_load(addr, global_type, mem_flags)?; 544 } 545 546 // We are done resolving this, return the current value 547 None => return Ok(current_val), 548 } 549 } 550 } 551 get_pinned_reg(&self) -> DataValue552 fn get_pinned_reg(&self) -> DataValue { 553 self.pinned_reg.clone() 554 } 555 set_pinned_reg(&mut self, v: DataValue)556 fn set_pinned_reg(&mut self, v: DataValue) { 557 self.pinned_reg = v; 558 } 559 } 560 561 #[cfg(test)] 562 mod tests { 563 use super::*; 564 use crate::step::CraneliftTrap; 565 use cranelift_codegen::ir::TrapCode; 566 use cranelift_codegen::ir::immediates::Ieee32; 567 use cranelift_reader::parse_functions; 568 use smallvec::smallvec; 569 570 // Most interpreter tests should use the more ergonomic `test interpret` filetest but this 571 // unit test serves as a sanity check that the interpreter still works without all of the 572 // filetest infrastructure. 573 #[test] sanity()574 fn sanity() { 575 let code = "function %test() -> i8 { 576 block0: 577 v0 = iconst.i32 1 578 v1 = iadd_imm v0, 1 579 v2 = irsub_imm v1, 44 ; 44 - 2 == 42 (see irsub_imm's semantics) 580 v3 = icmp_imm eq v2, 42 581 return v3 582 }"; 583 584 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 585 let mut env = FunctionStore::default(); 586 env.add(func.name.to_string(), &func); 587 let state = InterpreterState::default().with_function_store(env); 588 let result = Interpreter::new(state).call_by_name("%test", &[]).unwrap(); 589 590 assert_eq!(result, ControlFlow::Return(smallvec![DataValue::I8(1)])); 591 } 592 593 // We don't have a way to check for traps with the current filetest infrastructure 594 #[test] udiv_by_zero_traps()595 fn udiv_by_zero_traps() { 596 let code = "function %test() -> i32 { 597 block0: 598 v0 = iconst.i32 1 599 v1 = udiv_imm.i32 v0, 0 600 return v1 601 }"; 602 603 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 604 let mut env = FunctionStore::default(); 605 env.add(func.name.to_string(), &func); 606 let state = InterpreterState::default().with_function_store(env); 607 let trap = Interpreter::new(state).call_by_name("%test", &[]).unwrap(); 608 609 assert_eq!( 610 trap, 611 ControlFlow::Trap(CraneliftTrap::User(TrapCode::INTEGER_DIVISION_BY_ZERO)) 612 ); 613 } 614 615 #[test] sdiv_min_by_neg_one_traps_with_overflow()616 fn sdiv_min_by_neg_one_traps_with_overflow() { 617 let code = "function %test() -> i8 { 618 block0: 619 v0 = iconst.i32 -2147483648 620 v1 = sdiv_imm.i32 v0, -1 621 return v1 622 }"; 623 624 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 625 let mut env = FunctionStore::default(); 626 env.add(func.name.to_string(), &func); 627 let state = InterpreterState::default().with_function_store(env); 628 let result = Interpreter::new(state).call_by_name("%test", &[]).unwrap(); 629 630 match result { 631 ControlFlow::Trap(CraneliftTrap::User(TrapCode::INTEGER_OVERFLOW)) => {} 632 _ => panic!("Unexpected ControlFlow: {result:?}"), 633 } 634 } 635 636 // This test verifies that functions can refer to each other using the function store. A double indirection is 637 // required, which is tricky to get right: a referenced function is a FuncRef when called but a FuncIndex inside the 638 // function store. This test would preferably be a CLIF filetest but the filetest infrastructure only looks at a 639 // single function at a time--we need more than one function in the store for this test. 640 #[test] function_references()641 fn function_references() { 642 let code = " 643 function %child(i32) -> i32 { 644 block0(v0: i32): 645 v1 = iadd_imm v0, -1 646 return v1 647 } 648 649 function %parent(i32) -> i32 { 650 fn42 = %child(i32) -> i32 651 block0(v0: i32): 652 v1 = iadd_imm v0, 1 653 v2 = call fn42(v1) 654 return v2 655 }"; 656 657 let mut env = FunctionStore::default(); 658 let funcs = parse_functions(code).unwrap().to_vec(); 659 funcs.iter().for_each(|f| env.add(f.name.to_string(), f)); 660 661 let state = InterpreterState::default().with_function_store(env); 662 let result = Interpreter::new(state) 663 .call_by_name("%parent", &[DataValue::I32(0)]) 664 .unwrap(); 665 666 assert_eq!(result, ControlFlow::Return(smallvec![DataValue::I32(0)])); 667 } 668 669 #[test] fuel()670 fn fuel() { 671 let code = "function %test() -> i8 { 672 block0: 673 v0 = iconst.i32 1 674 v1 = iadd_imm v0, 1 675 return v1 676 }"; 677 678 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 679 let mut env = FunctionStore::default(); 680 env.add(func.name.to_string(), &func); 681 682 // The default interpreter should not enable the fuel mechanism 683 let state = InterpreterState::default().with_function_store(env.clone()); 684 let result = Interpreter::new(state).call_by_name("%test", &[]).unwrap(); 685 686 assert_eq!(result, ControlFlow::Return(smallvec![DataValue::I32(2)])); 687 688 // With 2 fuel, we should execute the iconst and iadd, but not the return thus giving a 689 // fuel exhausted error 690 let state = InterpreterState::default().with_function_store(env.clone()); 691 let result = Interpreter::new(state) 692 .with_fuel(Some(2)) 693 .call_by_name("%test", &[]); 694 match result { 695 Err(InterpreterError::FuelExhausted) => {} 696 _ => panic!("Expected Err(FuelExhausted), but got {result:?}"), 697 } 698 699 // With 3 fuel, we should be able to execute the return instruction, and complete the test 700 let state = InterpreterState::default().with_function_store(env.clone()); 701 let result = Interpreter::new(state) 702 .with_fuel(Some(3)) 703 .call_by_name("%test", &[]) 704 .unwrap(); 705 706 assert_eq!(result, ControlFlow::Return(smallvec![DataValue::I32(2)])); 707 } 708 709 // Verifies that writing to the stack on a called function does not overwrite the parents 710 // stack slots. 711 #[test] stack_slots_multi_functions()712 fn stack_slots_multi_functions() { 713 let code = " 714 function %callee(i64, i64) -> i64 { 715 ss0 = explicit_slot 8 716 ss1 = explicit_slot 8 717 718 block0(v0: i64, v1: i64): 719 stack_store.i64 v0, ss0 720 stack_store.i64 v1, ss1 721 v2 = stack_load.i64 ss0 722 v3 = stack_load.i64 ss1 723 v4 = iadd.i64 v2, v3 724 return v4 725 } 726 727 function %caller(i64, i64, i64, i64) -> i64 { 728 fn0 = %callee(i64, i64) -> i64 729 ss0 = explicit_slot 8 730 ss1 = explicit_slot 8 731 732 block0(v0: i64, v1: i64, v2: i64, v3: i64): 733 stack_store.i64 v0, ss0 734 stack_store.i64 v1, ss1 735 736 v4 = call fn0(v2, v3) 737 738 v5 = stack_load.i64 ss0 739 v6 = stack_load.i64 ss1 740 741 v7 = iadd.i64 v4, v5 742 v8 = iadd.i64 v7, v6 743 744 return v8 745 }"; 746 747 let mut env = FunctionStore::default(); 748 let funcs = parse_functions(code).unwrap().to_vec(); 749 funcs.iter().for_each(|f| env.add(f.name.to_string(), f)); 750 751 let state = InterpreterState::default().with_function_store(env); 752 let result = Interpreter::new(state) 753 .call_by_name( 754 "%caller", 755 &[ 756 DataValue::I64(3), 757 DataValue::I64(5), 758 DataValue::I64(7), 759 DataValue::I64(11), 760 ], 761 ) 762 .unwrap(); 763 764 assert_eq!(result, ControlFlow::Return(smallvec![DataValue::I64(26)])) 765 } 766 767 #[test] out_of_slot_write_traps()768 fn out_of_slot_write_traps() { 769 let code = " 770 function %stack_write() { 771 ss0 = explicit_slot 8 772 773 block0: 774 v0 = iconst.i64 10 775 stack_store.i64 v0, ss0+8 776 return 777 }"; 778 779 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 780 let mut env = FunctionStore::default(); 781 env.add(func.name.to_string(), &func); 782 let state = InterpreterState::default().with_function_store(env); 783 let trap = Interpreter::new(state) 784 .call_by_name("%stack_write", &[]) 785 .unwrap(); 786 787 assert_eq!( 788 trap, 789 ControlFlow::Trap(CraneliftTrap::User(TrapCode::HEAP_OUT_OF_BOUNDS)) 790 ); 791 } 792 793 #[test] partial_out_of_slot_write_traps()794 fn partial_out_of_slot_write_traps() { 795 let code = " 796 function %stack_write() { 797 ss0 = explicit_slot 8 798 799 block0: 800 v0 = iconst.i64 10 801 stack_store.i64 v0, ss0+4 802 return 803 }"; 804 805 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 806 let mut env = FunctionStore::default(); 807 env.add(func.name.to_string(), &func); 808 let state = InterpreterState::default().with_function_store(env); 809 let trap = Interpreter::new(state) 810 .call_by_name("%stack_write", &[]) 811 .unwrap(); 812 813 assert_eq!( 814 trap, 815 ControlFlow::Trap(CraneliftTrap::User(TrapCode::HEAP_OUT_OF_BOUNDS)) 816 ); 817 } 818 819 #[test] out_of_slot_read_traps()820 fn out_of_slot_read_traps() { 821 let code = " 822 function %stack_load() { 823 ss0 = explicit_slot 8 824 825 block0: 826 v0 = stack_load.i64 ss0+8 827 return 828 }"; 829 830 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 831 let mut env = FunctionStore::default(); 832 env.add(func.name.to_string(), &func); 833 let state = InterpreterState::default().with_function_store(env); 834 let trap = Interpreter::new(state) 835 .call_by_name("%stack_load", &[]) 836 .unwrap(); 837 838 assert_eq!( 839 trap, 840 ControlFlow::Trap(CraneliftTrap::User(TrapCode::HEAP_OUT_OF_BOUNDS)) 841 ); 842 } 843 844 #[test] partial_out_of_slot_read_traps()845 fn partial_out_of_slot_read_traps() { 846 let code = " 847 function %stack_load() { 848 ss0 = explicit_slot 8 849 850 block0: 851 v0 = stack_load.i64 ss0+4 852 return 853 }"; 854 855 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 856 let mut env = FunctionStore::default(); 857 env.add(func.name.to_string(), &func); 858 let state = InterpreterState::default().with_function_store(env); 859 let trap = Interpreter::new(state) 860 .call_by_name("%stack_load", &[]) 861 .unwrap(); 862 863 assert_eq!( 864 trap, 865 ControlFlow::Trap(CraneliftTrap::User(TrapCode::HEAP_OUT_OF_BOUNDS)) 866 ); 867 } 868 869 #[test] partial_out_of_slot_read_by_addr_traps()870 fn partial_out_of_slot_read_by_addr_traps() { 871 let code = " 872 function %stack_load() { 873 ss0 = explicit_slot 8 874 875 block0: 876 v0 = stack_addr.i64 ss0 877 v1 = iconst.i64 4 878 v2 = iadd.i64 v0, v1 879 v3 = load.i64 v2 880 return 881 }"; 882 883 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 884 let mut env = FunctionStore::default(); 885 env.add(func.name.to_string(), &func); 886 let state = InterpreterState::default().with_function_store(env); 887 let trap = Interpreter::new(state) 888 .call_by_name("%stack_load", &[]) 889 .unwrap(); 890 891 assert_eq!( 892 trap, 893 ControlFlow::Trap(CraneliftTrap::User(TrapCode::HEAP_OUT_OF_BOUNDS)) 894 ); 895 } 896 897 #[test] partial_out_of_slot_write_by_addr_traps()898 fn partial_out_of_slot_write_by_addr_traps() { 899 let code = " 900 function %stack_store() { 901 ss0 = explicit_slot 8 902 903 block0: 904 v0 = stack_addr.i64 ss0 905 v1 = iconst.i64 4 906 v2 = iadd.i64 v0, v1 907 store.i64 v1, v2 908 return 909 }"; 910 911 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 912 let mut env = FunctionStore::default(); 913 env.add(func.name.to_string(), &func); 914 let state = InterpreterState::default().with_function_store(env); 915 let trap = Interpreter::new(state) 916 .call_by_name("%stack_store", &[]) 917 .unwrap(); 918 919 assert_eq!( 920 trap, 921 ControlFlow::Trap(CraneliftTrap::User(TrapCode::HEAP_OUT_OF_BOUNDS)) 922 ); 923 } 924 925 #[test] srem_trap()926 fn srem_trap() { 927 let code = "function %test() -> i64 { 928 block0: 929 v0 = iconst.i64 0x8000_0000_0000_0000 930 v1 = iconst.i64 -1 931 v2 = srem.i64 v0, v1 932 return v2 933 }"; 934 935 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 936 let mut env = FunctionStore::default(); 937 env.add(func.name.to_string(), &func); 938 let state = InterpreterState::default().with_function_store(env); 939 let trap = Interpreter::new(state).call_by_name("%test", &[]).unwrap(); 940 941 assert_eq!( 942 trap, 943 ControlFlow::Trap(CraneliftTrap::User(TrapCode::INTEGER_OVERFLOW)) 944 ); 945 } 946 947 #[test] libcall()948 fn libcall() { 949 let code = "function %test() -> i64 { 950 fn0 = colocated %CeilF32 (f32) -> f32 fast 951 block0: 952 v1 = f32const 0x0.5 953 v2 = call fn0(v1) 954 return v2 955 }"; 956 957 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 958 let mut env = FunctionStore::default(); 959 env.add(func.name.to_string(), &func); 960 let state = InterpreterState::default() 961 .with_function_store(env) 962 .with_libcall_handler(|libcall, args| { 963 Ok(smallvec![match (libcall, &args[..]) { 964 (LibCall::CeilF32, [DataValue::F32(a)]) => DataValue::F32(a.ceil()), 965 _ => panic!("Unexpected args"), 966 }]) 967 }); 968 969 let result = Interpreter::new(state).call_by_name("%test", &[]).unwrap(); 970 971 assert_eq!( 972 result, 973 ControlFlow::Return(smallvec![DataValue::F32(Ieee32::with_float(1.0))]) 974 ) 975 } 976 977 #[test] misaligned_store_traps()978 fn misaligned_store_traps() { 979 let code = " 980 function %test() { 981 ss0 = explicit_slot 16 982 983 block0: 984 v0 = stack_addr.i64 ss0 985 v1 = iconst.i64 1 986 store.i64 aligned v1, v0+2 987 return 988 }"; 989 990 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 991 let mut env = FunctionStore::default(); 992 env.add(func.name.to_string(), &func); 993 let state = InterpreterState::default().with_function_store(env); 994 let trap = Interpreter::new(state).call_by_name("%test", &[]).unwrap(); 995 996 assert_eq!(trap, ControlFlow::Trap(CraneliftTrap::HeapMisaligned)); 997 } 998 999 #[test] misaligned_load_traps()1000 fn misaligned_load_traps() { 1001 let code = " 1002 function %test() { 1003 ss0 = explicit_slot 16 1004 1005 block0: 1006 v0 = stack_addr.i64 ss0 1007 v1 = iconst.i64 1 1008 store.i64 aligned v1, v0 1009 v2 = load.i64 aligned v0+2 1010 return 1011 }"; 1012 1013 let func = parse_functions(code).unwrap().into_iter().next().unwrap(); 1014 let mut env = FunctionStore::default(); 1015 env.add(func.name.to_string(), &func); 1016 let state = InterpreterState::default().with_function_store(env); 1017 let trap = Interpreter::new(state).call_by_name("%test", &[]).unwrap(); 1018 1019 assert_eq!(trap, ControlFlow::Trap(CraneliftTrap::HeapMisaligned)); 1020 } 1021 1022 // When a trap occurs in a function called by another function, the trap was not being propagated 1023 // correctly. Instead the interpterer panicked with a invalid control flow state. 1024 // See this issue for more details: https://github.com/bytecodealliance/wasmtime/issues/6155 1025 #[test] trap_across_call_propagates_correctly()1026 fn trap_across_call_propagates_correctly() { 1027 let code = " 1028 function %u2() -> f32 system_v { 1029 ss0 = explicit_slot 69 1030 ss1 = explicit_slot 69 1031 ss2 = explicit_slot 69 1032 1033 block0: 1034 v0 = f32const -0x1.434342p-60 1035 v1 = stack_addr.i64 ss2+24 1036 store notrap aligned v0, v1 1037 return v0 1038 } 1039 1040 function %u1() -> f32 system_v { 1041 sig0 = () -> f32 system_v 1042 fn0 = colocated %u2 sig0 1043 1044 block0: 1045 v57 = call fn0() 1046 return v57 1047 }"; 1048 1049 let mut env = FunctionStore::default(); 1050 1051 let funcs = parse_functions(code).unwrap(); 1052 for func in &funcs { 1053 env.add(func.name.to_string(), func); 1054 } 1055 1056 let state = InterpreterState::default().with_function_store(env); 1057 let trap = Interpreter::new(state).call_by_name("%u1", &[]).unwrap(); 1058 1059 // Ensure that the correct trap was propagated. 1060 assert_eq!(trap, ControlFlow::Trap(CraneliftTrap::HeapMisaligned)); 1061 } 1062 } 1063