1 //! Provides functionality for compiling and running CLIF IR for `run` tests. 2 use anyhow::{anyhow, Result}; 3 use core::mem; 4 use cranelift_codegen::data_value::DataValue; 5 use cranelift_codegen::ir::{ 6 condcodes::IntCC, ExternalName, Function, InstBuilder, Signature, UserExternalName, 7 UserFuncName, 8 }; 9 use cranelift_codegen::isa::TargetIsa; 10 use cranelift_codegen::{ir, settings, CodegenError, Context}; 11 use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; 12 use cranelift_jit::{JITBuilder, JITModule}; 13 use cranelift_module::{FuncId, Linkage, Module, ModuleError}; 14 use cranelift_native::builder_with_options; 15 use cranelift_reader::TestFile; 16 use std::cmp::max; 17 use std::collections::hash_map::Entry; 18 use std::collections::HashMap; 19 use thiserror::Error; 20 21 const TESTFILE_NAMESPACE: u32 = 0; 22 23 /// Holds information about a previously defined function. 24 #[derive(Debug)] 25 struct DefinedFunction { 26 /// This is the name that the function is internally known as. 27 /// 28 /// The JIT module does not support linking / calling [TestcaseName]'s, so 29 /// we rename every function into a [UserExternalName]. 30 /// 31 /// By doing this we also have to rename functions that previously were using a 32 /// [UserFuncName], since they may now be in conflict after the renaming that 33 /// occurred. 34 new_name: UserExternalName, 35 36 /// The function signature 37 signature: ir::Signature, 38 39 /// JIT [FuncId] 40 func_id: FuncId, 41 } 42 43 /// Compile a test case. 44 /// 45 /// Several Cranelift functions need the ability to run Cranelift IR (e.g. `test_run`); this 46 /// [TestFileCompiler] provides a way for compiling Cranelift [Function]s to 47 /// `CompiledFunction`s and subsequently calling them through the use of a `Trampoline`. As its 48 /// name indicates, this compiler is limited: any functionality that requires knowledge of things 49 /// outside the [Function] will likely not work (e.g. global values, calls). For an example of this 50 /// "outside-of-function" functionality, see `cranelift_jit::backend::JITBackend`. 51 /// 52 /// ``` 53 /// use cranelift_filetests::TestFileCompiler; 54 /// use cranelift_reader::parse_functions; 55 /// use cranelift_codegen::data_value::DataValue; 56 /// 57 /// let code = "test run \n function %add(i32, i32) -> i32 { block0(v0:i32, v1:i32): v2 = iadd v0, v1 return v2 }".into(); 58 /// let func = parse_functions(code).unwrap().into_iter().nth(0).unwrap(); 59 /// let mut compiler = TestFileCompiler::with_default_host_isa().unwrap(); 60 /// compiler.declare_function(&func).unwrap(); 61 /// compiler.define_function(func.clone()).unwrap(); 62 /// compiler.create_trampoline_for_function(&func).unwrap(); 63 /// let compiled = compiler.compile().unwrap(); 64 /// let trampoline = compiled.get_trampoline(&func).unwrap(); 65 /// 66 /// let returned = trampoline.call(&vec![DataValue::I32(2), DataValue::I32(40)]); 67 /// assert_eq!(vec![DataValue::I32(42)], returned); 68 /// ``` 69 pub struct TestFileCompiler { 70 module: JITModule, 71 ctx: Context, 72 73 /// Holds info about the functions that have already been defined. 74 /// Use look them up by their original [UserFuncName] since that's how the caller 75 /// passes them to us. 76 defined_functions: HashMap<UserFuncName, DefinedFunction>, 77 78 /// We deduplicate trampolines by the signature of the function that they target. 79 /// This map holds as a key the [Signature] of the target function, and as a value 80 /// the [UserFuncName] of the trampoline for that [Signature]. 81 /// 82 /// The trampoline is defined in `defined_functions` as any other regular function. 83 trampolines: HashMap<Signature, UserFuncName>, 84 } 85 86 impl TestFileCompiler { 87 /// Build a [TestFileCompiler] from a [TargetIsa]. For functions to be runnable on the 88 /// host machine, this [TargetIsa] must match the host machine's ISA (see 89 /// [TestFileCompiler::with_host_isa]). 90 pub fn new(isa: Box<dyn TargetIsa>) -> Self { 91 let builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names()); 92 let module = JITModule::new(builder); 93 let ctx = module.make_context(); 94 95 Self { 96 module, 97 ctx, 98 defined_functions: HashMap::new(), 99 trampolines: HashMap::new(), 100 } 101 } 102 103 /// Build a [TestFileCompiler] using the host machine's ISA and the passed flags. 104 pub fn with_host_isa(flags: settings::Flags) -> Result<Self> { 105 let builder = 106 builder_with_options(true).expect("Unable to build a TargetIsa for the current host"); 107 let isa = builder.finish(flags)?; 108 Ok(Self::new(isa)) 109 } 110 111 /// Build a [TestFileCompiler] using the host machine's ISA and the default flags for this 112 /// ISA. 113 pub fn with_default_host_isa() -> Result<Self> { 114 let flags = settings::Flags::new(settings::builder()); 115 Self::with_host_isa(flags) 116 } 117 118 /// Registers all functions in a [TestFile]. Additionally creates a trampoline for each one 119 /// of them. 120 pub fn add_testfile(&mut self, testfile: &TestFile) -> Result<()> { 121 // Declare all functions in the file, so that they may refer to each other. 122 for (func, _) in &testfile.functions { 123 self.declare_function(func)?; 124 } 125 126 // Define all functions and trampolines 127 for (func, _) in &testfile.functions { 128 self.define_function(func.clone())?; 129 self.create_trampoline_for_function(func)?; 130 } 131 132 Ok(()) 133 } 134 135 /// Declares a function an registers it as a linkable and callable target internally 136 pub fn declare_function(&mut self, func: &Function) -> Result<()> { 137 let next_id = self.defined_functions.len() as u32; 138 match self.defined_functions.entry(func.name.clone()) { 139 Entry::Occupied(_) => { 140 anyhow::bail!("Duplicate function with name {} found!", &func.name) 141 } 142 Entry::Vacant(v) => { 143 let name = func.name.to_string(); 144 let func_id = 145 self.module 146 .declare_function(&name, Linkage::Local, &func.signature)?; 147 148 v.insert(DefinedFunction { 149 new_name: UserExternalName::new(TESTFILE_NAMESPACE, next_id), 150 signature: func.signature.clone(), 151 func_id, 152 }); 153 } 154 }; 155 156 Ok(()) 157 } 158 159 /// Renames the function to its new [UserExternalName], as well as any other function that 160 /// it may reference. 161 /// 162 /// We have to do this since the JIT cannot link Testcase functions. 163 fn apply_func_rename( 164 &self, 165 mut func: Function, 166 defined_func: &DefinedFunction, 167 ) -> Result<Function> { 168 // First, rename the function 169 let func_original_name = func.name; 170 func.name = UserFuncName::User(defined_func.new_name.clone()); 171 172 // Rename any functions that it references 173 // Do this in stages to appease the borrow checker 174 let mut redefines = Vec::with_capacity(func.dfg.ext_funcs.len()); 175 for (ext_ref, ext_func) in &func.dfg.ext_funcs { 176 let old_name = match &ext_func.name { 177 ExternalName::TestCase(tc) => UserFuncName::Testcase(tc.clone()), 178 ExternalName::User(username) => { 179 UserFuncName::User(func.params.user_named_funcs()[*username].clone()) 180 } 181 // The other cases don't need renaming, so lets just continue... 182 _ => continue, 183 }; 184 185 let target_df = self.defined_functions.get(&old_name).ok_or(anyhow!( 186 "Undeclared function {} is referenced by {}!", 187 &old_name, 188 &func_original_name 189 ))?; 190 191 redefines.push((ext_ref, target_df.new_name.clone())); 192 } 193 194 // Now register the redefines 195 for (ext_ref, new_name) in redefines.into_iter() { 196 // Register the new name in the func, so that we can get a reference to it. 197 let new_name_ref = func.params.ensure_user_func_name(new_name); 198 199 // Finally rename the ExtFunc 200 func.dfg.ext_funcs[ext_ref].name = ExternalName::User(new_name_ref); 201 } 202 203 Ok(func) 204 } 205 206 /// Defines the body of a function 207 pub fn define_function(&mut self, func: Function) -> Result<()> { 208 let defined_func = self 209 .defined_functions 210 .get(&func.name) 211 .ok_or(anyhow!("Undeclared function {} found!", &func.name))?; 212 213 self.ctx.func = self.apply_func_rename(func, defined_func)?; 214 self.module 215 .define_function(defined_func.func_id, &mut self.ctx)?; 216 self.module.clear_context(&mut self.ctx); 217 Ok(()) 218 } 219 220 /// Creates and registers a trampoline for a function if none exists. 221 pub fn create_trampoline_for_function(&mut self, func: &Function) -> Result<()> { 222 if !self.defined_functions.contains_key(&func.name) { 223 anyhow::bail!("Undeclared function {} found!", &func.name); 224 } 225 226 // Check if a trampoline for this function signature already exists 227 if self.trampolines.contains_key(&func.signature) { 228 return Ok(()); 229 } 230 231 // Create a trampoline and register it 232 let name = UserFuncName::user(TESTFILE_NAMESPACE, self.defined_functions.len() as u32); 233 let trampoline = make_trampoline(name.clone(), &func.signature, self.module.isa()); 234 235 self.declare_function(&trampoline)?; 236 self.define_function(trampoline)?; 237 238 self.trampolines.insert(func.signature.clone(), name); 239 240 Ok(()) 241 } 242 243 /// Finalize this TestFile and link all functions. 244 pub fn compile(mut self) -> Result<CompiledTestFile, CompilationError> { 245 // Finalize the functions which we just defined, which resolves any 246 // outstanding relocations (patching in addresses, now that they're 247 // available). 248 self.module.finalize_definitions(); 249 250 Ok(CompiledTestFile { 251 module: Some(self.module), 252 defined_functions: self.defined_functions, 253 trampolines: self.trampolines, 254 }) 255 } 256 } 257 258 /// A finalized Test File 259 pub struct CompiledTestFile { 260 /// We need to store [JITModule] since it contains the underlying memory for the functions. 261 /// Store it in an [Option] so that we can later drop it. 262 module: Option<JITModule>, 263 264 /// Holds info about the functions that have been registered in `module`. 265 /// See [TestFileCompiler] for more info. 266 defined_functions: HashMap<UserFuncName, DefinedFunction>, 267 268 /// Trampolines available in this [JITModule]. 269 /// See [TestFileCompiler] for more info. 270 trampolines: HashMap<Signature, UserFuncName>, 271 } 272 273 impl CompiledTestFile { 274 /// Return a trampoline for calling. 275 /// 276 /// Returns None if [TestFileCompiler::create_trampoline_for_function] wasn't called for this function. 277 pub fn get_trampoline(&self, func: &Function) -> Option<Trampoline> { 278 let defined_func = self.defined_functions.get(&func.name)?; 279 let trampoline_id = self 280 .trampolines 281 .get(&func.signature) 282 .and_then(|name| self.defined_functions.get(name)) 283 .map(|df| df.func_id)?; 284 Some(Trampoline { 285 module: self.module.as_ref()?, 286 func_id: defined_func.func_id, 287 func_signature: &defined_func.signature, 288 trampoline_id, 289 }) 290 } 291 } 292 293 impl Drop for CompiledTestFile { 294 fn drop(&mut self) { 295 // Freeing the module's memory erases the compiled functions. 296 // This should be safe since their pointers never leave this struct. 297 unsafe { self.module.take().unwrap().free_memory() } 298 } 299 } 300 301 /// A callable trampoline 302 pub struct Trampoline<'a> { 303 module: &'a JITModule, 304 func_id: FuncId, 305 func_signature: &'a Signature, 306 trampoline_id: FuncId, 307 } 308 309 impl<'a> Trampoline<'a> { 310 /// Call the target function of this trampoline, passing in [DataValue]s using a compiled trampoline. 311 pub fn call(&self, arguments: &[DataValue]) -> Vec<DataValue> { 312 let mut values = UnboxedValues::make_arguments(arguments, &self.func_signature); 313 let arguments_address = values.as_mut_ptr(); 314 315 let function_ptr = self.module.get_finalized_function(self.func_id); 316 let trampoline_ptr = self.module.get_finalized_function(self.trampoline_id); 317 318 let callable_trampoline: fn(*const u8, *mut u128) -> () = 319 unsafe { mem::transmute(trampoline_ptr) }; 320 callable_trampoline(function_ptr, arguments_address); 321 322 values.collect_returns(&self.func_signature) 323 } 324 } 325 326 /// Compilation Error when compiling a function. 327 #[derive(Error, Debug)] 328 pub enum CompilationError { 329 /// This Target ISA is invalid for the current host. 330 #[error("Cross-compilation not currently supported; use the host's default calling convention \ 331 or remove the specified calling convention in the function signature to use the host's default.")] 332 InvalidTargetIsa, 333 /// Cranelift codegen error. 334 #[error("Cranelift codegen error")] 335 CodegenError(#[from] CodegenError), 336 /// Module Error 337 #[error("Module error")] 338 ModuleError(#[from] ModuleError), 339 /// Memory mapping error. 340 #[error("Memory mapping error")] 341 IoError(#[from] std::io::Error), 342 } 343 344 /// A container for laying out the [ValueData]s in memory in a way that the [Trampoline] can 345 /// understand. 346 struct UnboxedValues(Vec<u128>); 347 348 impl UnboxedValues { 349 /// The size in bytes of each slot location in the allocated [DataValue]s. Though [DataValue]s 350 /// could be smaller than 16 bytes (e.g. `I16`), this simplifies the creation of the [DataValue] 351 /// array and could be used to align the slots to the largest used [DataValue] (i.e. 128-bit 352 /// vectors). 353 const SLOT_SIZE: usize = 16; 354 355 /// Build the arguments vector for passing the [DataValue]s into the [Trampoline]. The size of 356 /// `u128` used here must match [Trampoline::SLOT_SIZE]. 357 pub fn make_arguments(arguments: &[DataValue], signature: &ir::Signature) -> Self { 358 assert_eq!(arguments.len(), signature.params.len()); 359 let mut values_vec = vec![0; max(signature.params.len(), signature.returns.len())]; 360 361 // Store the argument values into `values_vec`. 362 for ((arg, slot), param) in arguments.iter().zip(&mut values_vec).zip(&signature.params) { 363 assert!( 364 arg.ty() == param.value_type || arg.is_vector() || arg.is_bool(), 365 "argument type mismatch: {} != {}", 366 arg.ty(), 367 param.value_type 368 ); 369 unsafe { 370 arg.write_value_to(slot); 371 } 372 } 373 374 Self(values_vec) 375 } 376 377 /// Return a pointer to the underlying memory for passing to the trampoline. 378 pub fn as_mut_ptr(&mut self) -> *mut u128 { 379 self.0.as_mut_ptr() 380 } 381 382 /// Collect the returned [DataValue]s into a [Vec]. The size of `u128` used here must match 383 /// [Trampoline::SLOT_SIZE]. 384 pub fn collect_returns(&self, signature: &ir::Signature) -> Vec<DataValue> { 385 assert!(self.0.len() >= signature.returns.len()); 386 let mut returns = Vec::with_capacity(signature.returns.len()); 387 388 // Extract the returned values from this vector. 389 for (slot, param) in self.0.iter().zip(&signature.returns) { 390 let value = unsafe { DataValue::read_value_from(slot, param.value_type) }; 391 returns.push(value); 392 } 393 394 returns 395 } 396 } 397 398 /// Build the Cranelift IR for moving the memory-allocated [DataValue]s to their correct location 399 /// (e.g. register, stack) prior to calling a [CompiledFunction]. The [Function] returned by 400 /// [make_trampoline] is compiled to a [Trampoline]. Note that this uses the [TargetIsa]'s default 401 /// calling convention so we must also check that the [CompiledFunction] has the same calling 402 /// convention (see [TestFileCompiler::compile]). 403 fn make_trampoline(name: UserFuncName, signature: &ir::Signature, isa: &dyn TargetIsa) -> Function { 404 // Create the trampoline signature: (callee_address: pointer, values_vec: pointer) -> () 405 let pointer_type = isa.pointer_type(); 406 let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv); 407 wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `callee_address` parameter. 408 wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `values_vec` parameter. 409 410 let mut func = ir::Function::with_name_signature(name, wrapper_sig); 411 412 // The trampoline has a single block filled with loads, one call to callee_address, and some loads. 413 let mut builder_context = FunctionBuilderContext::new(); 414 let mut builder = FunctionBuilder::new(&mut func, &mut builder_context); 415 let block0 = builder.create_block(); 416 builder.append_block_params_for_function_params(block0); 417 builder.switch_to_block(block0); 418 builder.seal_block(block0); 419 420 // Extract the incoming SSA values. 421 let (callee_value, values_vec_ptr_val) = { 422 let params = builder.func.dfg.block_params(block0); 423 (params[0], params[1]) 424 }; 425 426 // Load the argument values out of `values_vec`. 427 let callee_args = signature 428 .params 429 .iter() 430 .enumerate() 431 .map(|(i, param)| { 432 // Calculate the type to load from memory, using integers for booleans (no encodings). 433 let ty = param.value_type.coerce_bools_to_ints(); 434 435 // We always store vector types in little-endian byte order as DataValue. 436 let mut flags = ir::MemFlags::trusted(); 437 if param.value_type.is_vector() { 438 flags.set_endianness(ir::Endianness::Little); 439 } 440 441 // Load the value. 442 let loaded = builder.ins().load( 443 ty, 444 flags, 445 values_vec_ptr_val, 446 (i * UnboxedValues::SLOT_SIZE) as i32, 447 ); 448 449 // For booleans, we want to type-convert the loaded integer into a boolean and ensure 450 // that we are using the architecture's canonical boolean representation (presumably 451 // comparison will emit this). 452 if param.value_type.is_bool() { 453 let b = builder.ins().icmp_imm(IntCC::NotEqual, loaded, 0); 454 455 // icmp_imm always produces a `b1`, `bextend` it if we need a larger bool 456 if param.value_type.bits() > 1 { 457 builder.ins().bextend(param.value_type, b) 458 } else { 459 b 460 } 461 } else if param.value_type.is_bool_vector() { 462 let zero_constant = builder.func.dfg.constants.insert(vec![0; 16].into()); 463 let zero_vec = builder.ins().vconst(ty, zero_constant); 464 builder.ins().icmp(IntCC::NotEqual, loaded, zero_vec) 465 } else { 466 loaded 467 } 468 }) 469 .collect::<Vec<_>>(); 470 471 // Call the passed function. 472 let new_sig = builder.import_signature(signature.clone()); 473 let call = builder 474 .ins() 475 .call_indirect(new_sig, callee_value, &callee_args); 476 477 // Store the return values into `values_vec`. 478 let results = builder.func.dfg.inst_results(call).to_vec(); 479 for ((i, value), param) in results.iter().enumerate().zip(&signature.returns) { 480 // Before storing return values, we convert booleans to their integer representation. 481 let value = if param.value_type.lane_type().is_bool() { 482 let ty = param.value_type.lane_type().as_int(); 483 builder.ins().bint(ty, *value) 484 } else { 485 *value 486 }; 487 // We always store vector types in little-endian byte order as DataValue. 488 let mut flags = ir::MemFlags::trusted(); 489 if param.value_type.is_vector() { 490 flags.set_endianness(ir::Endianness::Little); 491 } 492 // Store the value. 493 builder.ins().store( 494 flags, 495 value, 496 values_vec_ptr_val, 497 (i * UnboxedValues::SLOT_SIZE) as i32, 498 ); 499 } 500 501 builder.ins().return_(&[]); 502 builder.finalize(); 503 504 func 505 } 506 507 #[cfg(test)] 508 mod test { 509 use super::*; 510 use cranelift_reader::{parse_functions, parse_test, ParseOptions}; 511 512 fn parse(code: &str) -> Function { 513 parse_functions(code).unwrap().into_iter().nth(0).unwrap() 514 } 515 516 #[test] 517 fn nop() { 518 let code = String::from( 519 " 520 test run 521 function %test() -> b8 { 522 block0: 523 nop 524 v1 = bconst.b8 true 525 return v1 526 }", 527 ); 528 529 // extract function 530 let test_file = parse_test(code.as_str(), ParseOptions::default()).unwrap(); 531 assert_eq!(1, test_file.functions.len()); 532 let function = test_file.functions[0].0.clone(); 533 534 // execute function 535 let mut compiler = TestFileCompiler::with_default_host_isa().unwrap(); 536 compiler.declare_function(&function).unwrap(); 537 compiler.define_function(function.clone()).unwrap(); 538 compiler.create_trampoline_for_function(&function).unwrap(); 539 let compiled = compiler.compile().unwrap(); 540 let trampoline = compiled.get_trampoline(&function).unwrap(); 541 let returned = trampoline.call(&[]); 542 assert_eq!(returned, vec![DataValue::B(true)]) 543 } 544 545 #[test] 546 fn trampolines() { 547 let function = parse( 548 " 549 function %test(f32, i8, i64x2, b1) -> f32x4, b64 { 550 block0(v0: f32, v1: i8, v2: i64x2, v3: b1): 551 v4 = vconst.f32x4 [0x0.1 0x0.2 0x0.3 0x0.4] 552 v5 = bconst.b64 true 553 return v4, v5 554 }", 555 ); 556 557 let compiler = TestFileCompiler::with_default_host_isa().unwrap(); 558 let trampoline = make_trampoline( 559 UserFuncName::user(0, 0), 560 &function.signature, 561 compiler.module.isa(), 562 ); 563 assert!(format!("{}", trampoline).ends_with( 564 "sig0 = (f32, i8, i64x2, b1) -> f32x4, b64 fast 565 566 block0(v0: i64, v1: i64): 567 v2 = load.f32 notrap aligned v1 568 v3 = load.i8 notrap aligned v1+16 569 v4 = load.i64x2 notrap aligned little v1+32 570 v5 = load.i8 notrap aligned v1+48 571 v6 = icmp_imm ne v5, 0 572 v7, v8 = call_indirect sig0, v0(v2, v3, v4, v6) 573 store notrap aligned little v7, v1 574 v9 = bint.i64 v8 575 store notrap aligned v9, v1+16 576 return 577 } 578 " 579 )); 580 } 581 } 582