1 //! Provides functionality for compiling and running CLIF IR for `run` tests. 2 use anyhow::{Result, anyhow}; 3 use core::mem; 4 use cranelift::prelude::Imm64; 5 use cranelift_codegen::cursor::{Cursor, FuncCursor}; 6 use cranelift_codegen::data_value::DataValue; 7 use cranelift_codegen::ir::{ 8 ExternalName, Function, InstBuilder, InstructionData, LibCall, Opcode, Signature, 9 UserExternalName, UserFuncName, 10 }; 11 use cranelift_codegen::isa::{OwnedTargetIsa, TargetIsa}; 12 use cranelift_codegen::{CodegenError, Context, ir, settings}; 13 use cranelift_control::ControlPlane; 14 use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; 15 use cranelift_jit::{JITBuilder, JITModule}; 16 use cranelift_module::{FuncId, Linkage, Module, ModuleError}; 17 use cranelift_native::builder_with_options; 18 use cranelift_reader::TestFile; 19 use pulley_interpreter::interp as pulley; 20 use std::cell::Cell; 21 use std::cmp::max; 22 use std::collections::hash_map::Entry; 23 use std::collections::{HashMap, HashSet}; 24 use std::ptr::NonNull; 25 use target_lexicon::Architecture; 26 use thiserror::Error; 27 28 const TESTFILE_NAMESPACE: u32 = 0; 29 30 /// Holds information about a previously defined function. 31 #[derive(Debug)] 32 struct DefinedFunction { 33 /// This is the name that the function is internally known as. 34 /// 35 /// The JIT module does not support linking / calling [TestcaseName]'s, so 36 /// we rename every function into a [UserExternalName]. 37 /// 38 /// By doing this we also have to rename functions that previously were using a 39 /// [UserFuncName], since they may now be in conflict after the renaming that 40 /// occurred. 41 new_name: UserExternalName, 42 43 /// The function signature 44 signature: ir::Signature, 45 46 /// JIT [FuncId] 47 func_id: FuncId, 48 } 49 50 /// Compile a test case. 51 /// 52 /// Several Cranelift functions need the ability to run Cranelift IR (e.g. `test_run`); this 53 /// [TestFileCompiler] provides a way for compiling Cranelift [Function]s to 54 /// `CompiledFunction`s and subsequently calling them through the use of a `Trampoline`. As its 55 /// name indicates, this compiler is limited: any functionality that requires knowledge of things 56 /// outside the [Function] will likely not work (e.g. global values, calls). For an example of this 57 /// "outside-of-function" functionality, see `cranelift_jit::backend::JITBackend`. 58 /// 59 /// ``` 60 /// # let ctrl_plane = &mut Default::default(); 61 /// use cranelift_filetests::TestFileCompiler; 62 /// use cranelift_reader::parse_functions; 63 /// use cranelift_codegen::data_value::DataValue; 64 /// 65 /// let code = "test run \n function %add(i32, i32) -> i32 { block0(v0:i32, v1:i32): v2 = iadd v0, v1 return v2 }".into(); 66 /// let func = parse_functions(code).unwrap().into_iter().nth(0).unwrap(); 67 /// let mut compiler = TestFileCompiler::with_default_host_isa().unwrap(); 68 /// compiler.declare_function(&func).unwrap(); 69 /// compiler.define_function(func.clone(), ctrl_plane).unwrap(); 70 /// compiler.create_trampoline_for_function(&func, ctrl_plane).unwrap(); 71 /// let compiled = compiler.compile().unwrap(); 72 /// let trampoline = compiled.get_trampoline(&func).unwrap(); 73 /// 74 /// let returned = trampoline.call(&compiled, &vec![DataValue::I32(2), DataValue::I32(40)]); 75 /// assert_eq!(vec![DataValue::I32(42)], returned); 76 /// ``` 77 pub struct TestFileCompiler { 78 module: JITModule, 79 ctx: Context, 80 81 /// Holds info about the functions that have already been defined. 82 /// Use look them up by their original [UserFuncName] since that's how the caller 83 /// passes them to us. 84 defined_functions: HashMap<UserFuncName, DefinedFunction>, 85 86 /// We deduplicate trampolines by the signature of the function that they target. 87 /// This map holds as a key the [Signature] of the target function, and as a value 88 /// the [UserFuncName] of the trampoline for that [Signature]. 89 /// 90 /// The trampoline is defined in `defined_functions` as any other regular function. 91 trampolines: HashMap<Signature, UserFuncName>, 92 } 93 94 impl TestFileCompiler { 95 /// Build a [TestFileCompiler] from a [TargetIsa]. For functions to be runnable on the 96 /// host machine, this [TargetIsa] must match the host machine's ISA (see 97 /// [TestFileCompiler::with_host_isa]). 98 pub fn new(isa: OwnedTargetIsa) -> Self { 99 let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names()); 100 let _ = &mut builder; // require mutability on all architectures 101 #[cfg(target_arch = "x86_64")] 102 { 103 builder.symbol_lookup_fn(Box::new(|name| { 104 if name == "__cranelift_x86_pshufb" { 105 Some(__cranelift_x86_pshufb as *const u8) 106 } else { 107 None 108 } 109 })); 110 } 111 112 // On Unix platforms force `libm` to get linked into this executable 113 // because tests that use libcalls rely on this library being present. 114 // Without this it's been seen that when cross-compiled to riscv64 the 115 // final binary doesn't link in `libm`. 116 #[cfg(unix)] 117 { 118 unsafe extern "C" { 119 safe fn cosf(f: f32) -> f32; 120 } 121 let f = std::hint::black_box(1.2_f32); 122 assert_eq!(f.cos(), cosf(f)); 123 } 124 125 let module = JITModule::new(builder); 126 let ctx = module.make_context(); 127 128 Self { 129 module, 130 ctx, 131 defined_functions: HashMap::new(), 132 trampolines: HashMap::new(), 133 } 134 } 135 136 /// Build a [TestFileCompiler] using the host machine's ISA and the passed flags. 137 pub fn with_host_isa(flags: settings::Flags) -> Result<Self> { 138 let builder = 139 builder_with_options(true).expect("Unable to build a TargetIsa for the current host"); 140 let isa = builder.finish(flags)?; 141 Ok(Self::new(isa)) 142 } 143 144 /// Build a [TestFileCompiler] using the host machine's ISA and the default flags for this 145 /// ISA. 146 pub fn with_default_host_isa() -> Result<Self> { 147 let flags = settings::Flags::new(settings::builder()); 148 Self::with_host_isa(flags) 149 } 150 151 /// Declares and compiles all functions in `functions`. Additionally creates a trampoline for 152 /// each one of them. 153 pub fn add_functions( 154 &mut self, 155 functions: &[Function], 156 ctrl_planes: Vec<ControlPlane>, 157 ) -> Result<()> { 158 // Declare all functions in the file, so that they may refer to each other. 159 for func in functions { 160 self.declare_function(func)?; 161 } 162 163 let ctrl_planes = ctrl_planes 164 .into_iter() 165 .chain(std::iter::repeat(ControlPlane::default())); 166 167 // Define all functions and trampolines 168 for (func, ref mut ctrl_plane) in functions.iter().zip(ctrl_planes) { 169 self.define_function(func.clone(), ctrl_plane)?; 170 self.create_trampoline_for_function(func, ctrl_plane)?; 171 } 172 173 Ok(()) 174 } 175 176 /// Registers all functions in a [TestFile]. Additionally creates a trampoline for each one 177 /// of them. 178 pub fn add_testfile(&mut self, testfile: &TestFile) -> Result<()> { 179 let functions = testfile 180 .functions 181 .iter() 182 .map(|(f, _)| f) 183 .cloned() 184 .collect::<Vec<_>>(); 185 186 self.add_functions(&functions[..], Vec::new())?; 187 Ok(()) 188 } 189 190 /// Declares a function an registers it as a linkable and callable target internally 191 pub fn declare_function(&mut self, func: &Function) -> Result<()> { 192 let next_id = self.defined_functions.len() as u32; 193 match self.defined_functions.entry(func.name.clone()) { 194 Entry::Occupied(_) => { 195 anyhow::bail!("Duplicate function with name {} found!", &func.name) 196 } 197 Entry::Vacant(v) => { 198 let name = func.name.to_string(); 199 let func_id = 200 self.module 201 .declare_function(&name, Linkage::Local, &func.signature)?; 202 203 v.insert(DefinedFunction { 204 new_name: UserExternalName::new(TESTFILE_NAMESPACE, next_id), 205 signature: func.signature.clone(), 206 func_id, 207 }); 208 } 209 }; 210 211 Ok(()) 212 } 213 214 /// Renames the function to its new [UserExternalName], as well as any other function that 215 /// it may reference. 216 /// 217 /// We have to do this since the JIT cannot link Testcase functions. 218 fn apply_func_rename( 219 &self, 220 mut func: Function, 221 defined_func: &DefinedFunction, 222 ) -> Result<Function> { 223 // First, rename the function 224 let func_original_name = func.name; 225 func.name = UserFuncName::User(defined_func.new_name.clone()); 226 227 // Rename any functions that it references 228 // Do this in stages to appease the borrow checker 229 let mut redefines = Vec::with_capacity(func.dfg.ext_funcs.len()); 230 for (ext_ref, ext_func) in &func.dfg.ext_funcs { 231 let old_name = match &ext_func.name { 232 ExternalName::TestCase(tc) => UserFuncName::Testcase(tc.clone()), 233 ExternalName::User(username) => { 234 UserFuncName::User(func.params.user_named_funcs()[*username].clone()) 235 } 236 // The other cases don't need renaming, so lets just continue... 237 _ => continue, 238 }; 239 240 let target_df = self.defined_functions.get(&old_name).ok_or(anyhow!( 241 "Undeclared function {} is referenced by {}!", 242 &old_name, 243 &func_original_name 244 ))?; 245 246 redefines.push((ext_ref, target_df.new_name.clone())); 247 } 248 249 // Now register the redefines 250 for (ext_ref, new_name) in redefines.into_iter() { 251 // Register the new name in the func, so that we can get a reference to it. 252 let new_name_ref = func.params.ensure_user_func_name(new_name); 253 254 // Finally rename the ExtFunc 255 func.dfg.ext_funcs[ext_ref].name = ExternalName::User(new_name_ref); 256 } 257 258 Ok(func) 259 } 260 261 /// Defines the body of a function 262 pub fn define_function( 263 &mut self, 264 mut func: Function, 265 ctrl_plane: &mut ControlPlane, 266 ) -> Result<()> { 267 Self::replace_hostcall_references(&mut func); 268 269 let defined_func = self 270 .defined_functions 271 .get(&func.name) 272 .ok_or(anyhow!("Undeclared function {} found!", &func.name))?; 273 274 self.ctx.func = self.apply_func_rename(func, defined_func)?; 275 self.module.define_function_with_control_plane( 276 defined_func.func_id, 277 &mut self.ctx, 278 ctrl_plane, 279 )?; 280 self.module.clear_context(&mut self.ctx); 281 Ok(()) 282 } 283 284 fn replace_hostcall_references(func: &mut Function) { 285 // For every `func_addr` referring to a hostcall that we 286 // define, replace with an `iconst` with the actual 287 // address. Then modify the external func references to 288 // harmless libcall references (that will be unused so 289 // ignored). 290 let mut funcrefs_to_remove = HashSet::new(); 291 let mut cursor = FuncCursor::new(func); 292 while let Some(_block) = cursor.next_block() { 293 while let Some(inst) = cursor.next_inst() { 294 match &cursor.func.dfg.insts[inst] { 295 InstructionData::FuncAddr { 296 opcode: Opcode::FuncAddr, 297 func_ref, 298 } => { 299 let ext_func = &cursor.func.dfg.ext_funcs[*func_ref]; 300 let hostcall_addr = match &ext_func.name { 301 ExternalName::TestCase(tc) if tc.raw() == b"__cranelift_throw" => { 302 Some(__cranelift_throw as usize) 303 } 304 _ => None, 305 }; 306 307 if let Some(addr) = hostcall_addr { 308 funcrefs_to_remove.insert(*func_ref); 309 cursor.func.dfg.insts[inst] = InstructionData::UnaryImm { 310 opcode: Opcode::Iconst, 311 imm: Imm64::new(addr as i64), 312 }; 313 } 314 } 315 _ => {} 316 } 317 } 318 } 319 320 for to_remove in funcrefs_to_remove { 321 func.dfg.ext_funcs[to_remove].name = ExternalName::LibCall(LibCall::Probestack); 322 } 323 } 324 325 /// Creates and registers a trampoline for a function if none exists. 326 pub fn create_trampoline_for_function( 327 &mut self, 328 func: &Function, 329 ctrl_plane: &mut ControlPlane, 330 ) -> Result<()> { 331 if !self.defined_functions.contains_key(&func.name) { 332 anyhow::bail!("Undeclared function {} found!", &func.name); 333 } 334 335 // Check if a trampoline for this function signature already exists 336 if self.trampolines.contains_key(&func.signature) { 337 return Ok(()); 338 } 339 340 // Create a trampoline and register it 341 let name = UserFuncName::user(TESTFILE_NAMESPACE, self.defined_functions.len() as u32); 342 let trampoline = make_trampoline(name.clone(), &func.signature, self.module.isa()); 343 344 self.declare_function(&trampoline)?; 345 self.define_function(trampoline, ctrl_plane)?; 346 347 self.trampolines.insert(func.signature.clone(), name); 348 349 Ok(()) 350 } 351 352 /// Finalize this TestFile and link all functions. 353 pub fn compile(mut self) -> Result<CompiledTestFile, CompilationError> { 354 // Finalize the functions which we just defined, which resolves any 355 // outstanding relocations (patching in addresses, now that they're 356 // available). 357 self.module.finalize_definitions()?; 358 359 Ok(CompiledTestFile { 360 module: Some(self.module), 361 defined_functions: self.defined_functions, 362 trampolines: self.trampolines, 363 }) 364 } 365 } 366 367 /// A finalized Test File 368 pub struct CompiledTestFile { 369 /// We need to store [JITModule] since it contains the underlying memory for the functions. 370 /// Store it in an [Option] so that we can later drop it. 371 module: Option<JITModule>, 372 373 /// Holds info about the functions that have been registered in `module`. 374 /// See [TestFileCompiler] for more info. 375 defined_functions: HashMap<UserFuncName, DefinedFunction>, 376 377 /// Trampolines available in this [JITModule]. 378 /// See [TestFileCompiler] for more info. 379 trampolines: HashMap<Signature, UserFuncName>, 380 } 381 382 impl CompiledTestFile { 383 /// Return a trampoline for calling. 384 /// 385 /// Returns None if [TestFileCompiler::create_trampoline_for_function] wasn't called for this function. 386 pub fn get_trampoline(&self, func: &Function) -> Option<Trampoline<'_>> { 387 let defined_func = self.defined_functions.get(&func.name)?; 388 let trampoline_id = self 389 .trampolines 390 .get(&func.signature) 391 .and_then(|name| self.defined_functions.get(name)) 392 .map(|df| df.func_id)?; 393 Some(Trampoline { 394 module: self.module.as_ref()?, 395 func_id: defined_func.func_id, 396 func_signature: &defined_func.signature, 397 trampoline_id, 398 }) 399 } 400 } 401 402 impl Drop for CompiledTestFile { 403 fn drop(&mut self) { 404 // Freeing the module's memory erases the compiled functions. 405 // This should be safe since their pointers never leave this struct. 406 unsafe { self.module.take().unwrap().free_memory() } 407 } 408 } 409 410 std::thread_local! { 411 /// TLS slot used to store a CompiledTestFile reference so that it 412 /// can be recovered when a hostcall (such as the exception-throw 413 /// handler) is invoked. 414 pub static COMPILED_TEST_FILE: Cell<*const CompiledTestFile> = Cell::new(std::ptr::null()); 415 } 416 417 /// A callable trampoline 418 pub struct Trampoline<'a> { 419 module: &'a JITModule, 420 func_id: FuncId, 421 func_signature: &'a Signature, 422 trampoline_id: FuncId, 423 } 424 425 impl<'a> Trampoline<'a> { 426 /// Call the target function of this trampoline, passing in [DataValue]s using a compiled trampoline. 427 pub fn call(&self, compiled: &CompiledTestFile, arguments: &[DataValue]) -> Vec<DataValue> { 428 let mut values = UnboxedValues::make_arguments(arguments, &self.func_signature); 429 let arguments_address = values.as_mut_ptr(); 430 431 let function_ptr = self.module.get_finalized_function(self.func_id); 432 let trampoline_ptr = self.module.get_finalized_function(self.trampoline_id); 433 434 COMPILED_TEST_FILE.set(compiled as *const _); 435 unsafe { 436 self.call_raw(trampoline_ptr, function_ptr, arguments_address); 437 } 438 COMPILED_TEST_FILE.set(std::ptr::null()); 439 440 values.collect_returns(&self.func_signature) 441 } 442 443 unsafe fn call_raw( 444 &self, 445 trampoline_ptr: *const u8, 446 function_ptr: *const u8, 447 arguments_address: *mut u128, 448 ) { 449 match self.module.isa().triple().architecture { 450 // For the pulley target this is pulley bytecode, not machine code, 451 // so run the interpreter. 452 Architecture::Pulley32 453 | Architecture::Pulley64 454 | Architecture::Pulley32be 455 | Architecture::Pulley64be => { 456 let mut state = pulley::Vm::new(); 457 unsafe { 458 state.call( 459 NonNull::new(trampoline_ptr.cast_mut()).unwrap(), 460 &[ 461 pulley::XRegVal::new_ptr(function_ptr.cast_mut()).into(), 462 pulley::XRegVal::new_ptr(arguments_address).into(), 463 ], 464 [], 465 ); 466 } 467 } 468 469 // Other targets natively execute this machine code. 470 _ => { 471 let callable_trampoline: fn(*const u8, *mut u128) -> () = 472 unsafe { mem::transmute(trampoline_ptr) }; 473 callable_trampoline(function_ptr, arguments_address); 474 } 475 } 476 } 477 } 478 479 /// Compilation Error when compiling a function. 480 #[derive(Error, Debug)] 481 pub enum CompilationError { 482 /// Cranelift codegen error. 483 #[error("Cranelift codegen error")] 484 CodegenError(#[from] CodegenError), 485 /// Module Error 486 #[error("Module error")] 487 ModuleError(#[from] ModuleError), 488 /// Memory mapping error. 489 #[error("Memory mapping error")] 490 IoError(#[from] std::io::Error), 491 } 492 493 /// A container for laying out the [ValueData]s in memory in a way that the [Trampoline] can 494 /// understand. 495 struct UnboxedValues(Vec<u128>); 496 497 impl UnboxedValues { 498 /// The size in bytes of each slot location in the allocated [DataValue]s. Though [DataValue]s 499 /// could be smaller than 16 bytes (e.g. `I16`), this simplifies the creation of the [DataValue] 500 /// array and could be used to align the slots to the largest used [DataValue] (i.e. 128-bit 501 /// vectors). 502 const SLOT_SIZE: usize = 16; 503 504 /// Build the arguments vector for passing the [DataValue]s into the [Trampoline]. The size of 505 /// `u128` used here must match [Trampoline::SLOT_SIZE]. 506 pub fn make_arguments(arguments: &[DataValue], signature: &ir::Signature) -> Self { 507 assert_eq!(arguments.len(), signature.params.len()); 508 let mut values_vec = vec![0; max(signature.params.len(), signature.returns.len())]; 509 510 // Store the argument values into `values_vec`. 511 for ((arg, slot), param) in arguments.iter().zip(&mut values_vec).zip(&signature.params) { 512 assert!( 513 arg.ty() == param.value_type || arg.is_vector(), 514 "argument type mismatch: {} != {}", 515 arg.ty(), 516 param.value_type 517 ); 518 unsafe { 519 arg.write_value_to(slot); 520 } 521 } 522 523 Self(values_vec) 524 } 525 526 /// Return a pointer to the underlying memory for passing to the trampoline. 527 pub fn as_mut_ptr(&mut self) -> *mut u128 { 528 self.0.as_mut_ptr() 529 } 530 531 /// Collect the returned [DataValue]s into a [Vec]. The size of `u128` used here must match 532 /// [Trampoline::SLOT_SIZE]. 533 pub fn collect_returns(&self, signature: &ir::Signature) -> Vec<DataValue> { 534 assert!(self.0.len() >= signature.returns.len()); 535 let mut returns = Vec::with_capacity(signature.returns.len()); 536 537 // Extract the returned values from this vector. 538 for (slot, param) in self.0.iter().zip(&signature.returns) { 539 let value = unsafe { DataValue::read_value_from(slot, param.value_type) }; 540 returns.push(value); 541 } 542 543 returns 544 } 545 } 546 547 /// Build the Cranelift IR for moving the memory-allocated [DataValue]s to their correct location 548 /// (e.g. register, stack) prior to calling a [CompiledFunction]. The [Function] returned by 549 /// [make_trampoline] is compiled to a [Trampoline]. Note that this uses the [TargetIsa]'s default 550 /// calling convention so we must also check that the [CompiledFunction] has the same calling 551 /// convention (see [TestFileCompiler::compile]). 552 fn make_trampoline(name: UserFuncName, signature: &ir::Signature, isa: &dyn TargetIsa) -> Function { 553 // Create the trampoline signature: (callee_address: pointer, values_vec: pointer) -> () 554 let pointer_type = isa.pointer_type(); 555 let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv); 556 wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `callee_address` parameter. 557 wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `values_vec` parameter. 558 559 let mut func = ir::Function::with_name_signature(name, wrapper_sig); 560 561 // The trampoline has a single block filled with loads, one call to callee_address, and some loads. 562 let mut builder_context = FunctionBuilderContext::new(); 563 let mut builder = FunctionBuilder::new(&mut func, &mut builder_context); 564 let block0 = builder.create_block(); 565 builder.append_block_params_for_function_params(block0); 566 builder.switch_to_block(block0); 567 builder.seal_block(block0); 568 569 // Extract the incoming SSA values. 570 let (callee_value, values_vec_ptr_val) = { 571 let params = builder.func.dfg.block_params(block0); 572 (params[0], params[1]) 573 }; 574 575 // Load the argument values out of `values_vec`. 576 let callee_args = signature 577 .params 578 .iter() 579 .enumerate() 580 .map(|(i, param)| { 581 // We always store vector types in little-endian byte order as DataValue. 582 let mut flags = ir::MemFlags::trusted(); 583 if param.value_type.is_vector() { 584 flags.set_endianness(ir::Endianness::Little); 585 } 586 587 // Load the value. 588 builder.ins().load( 589 param.value_type, 590 flags, 591 values_vec_ptr_val, 592 (i * UnboxedValues::SLOT_SIZE) as i32, 593 ) 594 }) 595 .collect::<Vec<_>>(); 596 597 // Call the passed function. 598 let new_sig = builder.import_signature(signature.clone()); 599 let call = builder 600 .ins() 601 .call_indirect(new_sig, callee_value, &callee_args); 602 603 // Store the return values into `values_vec`. 604 let results = builder.func.dfg.inst_results(call).to_vec(); 605 for ((i, value), param) in results.iter().enumerate().zip(&signature.returns) { 606 // We always store vector types in little-endian byte order as DataValue. 607 let mut flags = ir::MemFlags::trusted(); 608 if param.value_type.is_vector() { 609 flags.set_endianness(ir::Endianness::Little); 610 } 611 // Store the value. 612 builder.ins().store( 613 flags, 614 *value, 615 values_vec_ptr_val, 616 (i * UnboxedValues::SLOT_SIZE) as i32, 617 ); 618 } 619 620 builder.ins().return_(&[]); 621 builder.finalize(); 622 623 func 624 } 625 626 /// Hostcall invoked directly from a compiled function body to test 627 /// exception throws. 628 /// 629 /// This function does not return normally: it either uses the 630 /// unwinder to jump directly to a Cranelift frame further up the 631 /// stack, if a handler is found; or it panics, if not. 632 #[cfg(any( 633 target_arch = "x86_64", 634 target_arch = "aarch64", 635 target_arch = "s390x", 636 target_arch = "riscv64" 637 ))] 638 extern "C-unwind" fn __cranelift_throw( 639 entry_fp: usize, 640 exit_fp: usize, 641 exit_pc: usize, 642 tag: u32, 643 payload1: usize, 644 payload2: usize, 645 ) -> ! { 646 let compiled_test_file = unsafe { &*COMPILED_TEST_FILE.get() }; 647 let unwind_host = wasmtime_unwinder::UnwindHost; 648 let module_lookup = |pc| { 649 compiled_test_file 650 .module 651 .as_ref() 652 .unwrap() 653 .lookup_wasmtime_exception_data(pc) 654 }; 655 unsafe { 656 match wasmtime_unwinder::compute_throw_action( 657 &unwind_host, 658 module_lookup, 659 exit_pc, 660 exit_fp, 661 entry_fp, 662 tag, 663 ) { 664 wasmtime_unwinder::ThrowAction::Handler { pc, sp, fp } => { 665 wasmtime_unwinder::resume_to_exception_handler(pc, sp, fp, payload1, payload2); 666 } 667 wasmtime_unwinder::ThrowAction::None => { 668 panic!("Expected a handler to exit for throw of tag {tag} at pc {exit_pc:x}"); 669 } 670 } 671 } 672 } 673 674 #[cfg(not(any( 675 target_arch = "x86_64", 676 target_arch = "aarch64", 677 target_arch = "s390x", 678 target_arch = "riscv64" 679 )))] 680 extern "C-unwind" fn __cranelift_throw( 681 _entry_fp: usize, 682 _exit_fp: usize, 683 _exit_pc: usize, 684 _tag: u32, 685 _payload1: usize, 686 _payload2: usize, 687 ) -> ! { 688 panic!("Throw not implemented on platforms without native backends."); 689 } 690 691 #[cfg(target_arch = "x86_64")] 692 use std::arch::x86_64::__m128i; 693 #[cfg(target_arch = "x86_64")] 694 #[expect( 695 improper_ctypes_definitions, 696 reason = "manually verified to work for now" 697 )] 698 extern "C" fn __cranelift_x86_pshufb(a: __m128i, b: __m128i) -> __m128i { 699 union U { 700 reg: __m128i, 701 mem: [u8; 16], 702 } 703 704 unsafe { 705 let a = U { reg: a }.mem; 706 let b = U { reg: b }.mem; 707 708 let select = |arr: &[u8; 16], byte: u8| { 709 if byte & 0x80 != 0 { 710 0x00 711 } else { 712 arr[(byte & 0xf) as usize] 713 } 714 }; 715 716 U { 717 mem: [ 718 select(&a, b[0]), 719 select(&a, b[1]), 720 select(&a, b[2]), 721 select(&a, b[3]), 722 select(&a, b[4]), 723 select(&a, b[5]), 724 select(&a, b[6]), 725 select(&a, b[7]), 726 select(&a, b[8]), 727 select(&a, b[9]), 728 select(&a, b[10]), 729 select(&a, b[11]), 730 select(&a, b[12]), 731 select(&a, b[13]), 732 select(&a, b[14]), 733 select(&a, b[15]), 734 ], 735 } 736 .reg 737 } 738 } 739 740 #[cfg(test)] 741 mod test { 742 use super::*; 743 use cranelift_reader::{ParseOptions, parse_functions, parse_test}; 744 745 fn parse(code: &str) -> Function { 746 parse_functions(code).unwrap().into_iter().nth(0).unwrap() 747 } 748 749 #[test] 750 fn nop() { 751 // Skip this test when cranelift doesn't support the native platform. 752 if cranelift_native::builder().is_err() { 753 return; 754 } 755 let code = String::from( 756 " 757 test run 758 function %test() -> i8 { 759 block0: 760 nop 761 v1 = iconst.i8 -1 762 return v1 763 }", 764 ); 765 let ctrl_plane = &mut ControlPlane::default(); 766 767 // extract function 768 let test_file = parse_test(code.as_str(), ParseOptions::default()).unwrap(); 769 assert_eq!(1, test_file.functions.len()); 770 let function = test_file.functions[0].0.clone(); 771 772 // execute function 773 let mut compiler = TestFileCompiler::with_default_host_isa().unwrap(); 774 compiler.declare_function(&function).unwrap(); 775 compiler 776 .define_function(function.clone(), ctrl_plane) 777 .unwrap(); 778 compiler 779 .create_trampoline_for_function(&function, ctrl_plane) 780 .unwrap(); 781 let compiled = compiler.compile().unwrap(); 782 let trampoline = compiled.get_trampoline(&function).unwrap(); 783 let returned = trampoline.call(&compiled, &[]); 784 assert_eq!(returned, vec![DataValue::I8(-1)]) 785 } 786 787 #[test] 788 fn trampolines() { 789 // Skip this test when cranelift doesn't support the native platform. 790 if cranelift_native::builder().is_err() { 791 return; 792 } 793 let function = parse( 794 " 795 function %test(f32, i8, i64x2, i8) -> f32x4, i64 { 796 block0(v0: f32, v1: i8, v2: i64x2, v3: i8): 797 v4 = vconst.f32x4 [0x0.1 0x0.2 0x0.3 0x0.4] 798 v5 = iconst.i64 -1 799 return v4, v5 800 }", 801 ); 802 803 let compiler = TestFileCompiler::with_default_host_isa().unwrap(); 804 let trampoline = make_trampoline( 805 UserFuncName::user(0, 0), 806 &function.signature, 807 compiler.module.isa(), 808 ); 809 println!("{trampoline}"); 810 assert!(format!("{trampoline}").ends_with( 811 "sig0 = (f32, i8, i64x2, i8) -> f32x4, i64 fast 812 813 block0(v0: i64, v1: i64): 814 v2 = load.f32 notrap aligned v1 815 v3 = load.i8 notrap aligned v1+16 816 v4 = load.i64x2 notrap aligned little v1+32 817 v5 = load.i8 notrap aligned v1+48 818 v6, v7 = call_indirect sig0, v0(v2, v3, v4, v5) 819 store notrap aligned little v6, v1 820 store notrap aligned v7, v1+16 821 return 822 } 823 " 824 )); 825 } 826 } 827