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