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 /// Cranelift codegen error. 330 #[error("Cranelift codegen error")] 331 CodegenError(#[from] CodegenError), 332 /// Module Error 333 #[error("Module error")] 334 ModuleError(#[from] ModuleError), 335 /// Memory mapping error. 336 #[error("Memory mapping error")] 337 IoError(#[from] std::io::Error), 338 } 339 340 /// A container for laying out the [ValueData]s in memory in a way that the [Trampoline] can 341 /// understand. 342 struct UnboxedValues(Vec<u128>); 343 344 impl UnboxedValues { 345 /// The size in bytes of each slot location in the allocated [DataValue]s. Though [DataValue]s 346 /// could be smaller than 16 bytes (e.g. `I16`), this simplifies the creation of the [DataValue] 347 /// array and could be used to align the slots to the largest used [DataValue] (i.e. 128-bit 348 /// vectors). 349 const SLOT_SIZE: usize = 16; 350 351 /// Build the arguments vector for passing the [DataValue]s into the [Trampoline]. The size of 352 /// `u128` used here must match [Trampoline::SLOT_SIZE]. 353 pub fn make_arguments(arguments: &[DataValue], signature: &ir::Signature) -> Self { 354 assert_eq!(arguments.len(), signature.params.len()); 355 let mut values_vec = vec![0; max(signature.params.len(), signature.returns.len())]; 356 357 // Store the argument values into `values_vec`. 358 for ((arg, slot), param) in arguments.iter().zip(&mut values_vec).zip(&signature.params) { 359 assert!( 360 arg.ty() == param.value_type || arg.is_vector() || arg.is_bool(), 361 "argument type mismatch: {} != {}", 362 arg.ty(), 363 param.value_type 364 ); 365 unsafe { 366 arg.write_value_to(slot); 367 } 368 } 369 370 Self(values_vec) 371 } 372 373 /// Return a pointer to the underlying memory for passing to the trampoline. 374 pub fn as_mut_ptr(&mut self) -> *mut u128 { 375 self.0.as_mut_ptr() 376 } 377 378 /// Collect the returned [DataValue]s into a [Vec]. The size of `u128` used here must match 379 /// [Trampoline::SLOT_SIZE]. 380 pub fn collect_returns(&self, signature: &ir::Signature) -> Vec<DataValue> { 381 assert!(self.0.len() >= signature.returns.len()); 382 let mut returns = Vec::with_capacity(signature.returns.len()); 383 384 // Extract the returned values from this vector. 385 for (slot, param) in self.0.iter().zip(&signature.returns) { 386 let value = unsafe { DataValue::read_value_from(slot, param.value_type) }; 387 returns.push(value); 388 } 389 390 returns 391 } 392 } 393 394 /// Build the Cranelift IR for moving the memory-allocated [DataValue]s to their correct location 395 /// (e.g. register, stack) prior to calling a [CompiledFunction]. The [Function] returned by 396 /// [make_trampoline] is compiled to a [Trampoline]. Note that this uses the [TargetIsa]'s default 397 /// calling convention so we must also check that the [CompiledFunction] has the same calling 398 /// convention (see [TestFileCompiler::compile]). 399 fn make_trampoline(name: UserFuncName, signature: &ir::Signature, isa: &dyn TargetIsa) -> Function { 400 // Create the trampoline signature: (callee_address: pointer, values_vec: pointer) -> () 401 let pointer_type = isa.pointer_type(); 402 let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv); 403 wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `callee_address` parameter. 404 wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `values_vec` parameter. 405 406 let mut func = ir::Function::with_name_signature(name, wrapper_sig); 407 408 // The trampoline has a single block filled with loads, one call to callee_address, and some loads. 409 let mut builder_context = FunctionBuilderContext::new(); 410 let mut builder = FunctionBuilder::new(&mut func, &mut builder_context); 411 let block0 = builder.create_block(); 412 builder.append_block_params_for_function_params(block0); 413 builder.switch_to_block(block0); 414 builder.seal_block(block0); 415 416 // Extract the incoming SSA values. 417 let (callee_value, values_vec_ptr_val) = { 418 let params = builder.func.dfg.block_params(block0); 419 (params[0], params[1]) 420 }; 421 422 // Load the argument values out of `values_vec`. 423 let callee_args = signature 424 .params 425 .iter() 426 .enumerate() 427 .map(|(i, param)| { 428 // Calculate the type to load from memory, using integers for booleans (no encodings). 429 let ty = param.value_type.coerce_bools_to_ints(); 430 431 // We always store vector types in little-endian byte order as DataValue. 432 let mut flags = ir::MemFlags::trusted(); 433 if param.value_type.is_vector() { 434 flags.set_endianness(ir::Endianness::Little); 435 } 436 437 // Load the value. 438 let loaded = builder.ins().load( 439 ty, 440 flags, 441 values_vec_ptr_val, 442 (i * UnboxedValues::SLOT_SIZE) as i32, 443 ); 444 445 // For booleans, we want to type-convert the loaded integer into a boolean and ensure 446 // that we are using the architecture's canonical boolean representation (presumably 447 // comparison will emit this). 448 if param.value_type.is_bool() { 449 let b = builder.ins().icmp_imm(IntCC::NotEqual, loaded, 0); 450 451 // icmp_imm always produces a `b1`, `bextend` it if we need a larger bool 452 if param.value_type.bits() > 1 { 453 builder.ins().bextend(param.value_type, b) 454 } else { 455 b 456 } 457 } else if param.value_type.is_bool_vector() { 458 let zero_constant = builder.func.dfg.constants.insert(vec![0; 16].into()); 459 let zero_vec = builder.ins().vconst(ty, zero_constant); 460 builder.ins().icmp(IntCC::NotEqual, loaded, zero_vec) 461 } else { 462 loaded 463 } 464 }) 465 .collect::<Vec<_>>(); 466 467 // Call the passed function. 468 let new_sig = builder.import_signature(signature.clone()); 469 let call = builder 470 .ins() 471 .call_indirect(new_sig, callee_value, &callee_args); 472 473 // Store the return values into `values_vec`. 474 let results = builder.func.dfg.inst_results(call).to_vec(); 475 for ((i, value), param) in results.iter().enumerate().zip(&signature.returns) { 476 // Before storing return values, we convert booleans to their integer representation. 477 let value = if param.value_type.lane_type().is_bool() { 478 let ty = param.value_type.lane_type().as_int(); 479 builder.ins().bint(ty, *value) 480 } else { 481 *value 482 }; 483 // We always store vector types in little-endian byte order as DataValue. 484 let mut flags = ir::MemFlags::trusted(); 485 if param.value_type.is_vector() { 486 flags.set_endianness(ir::Endianness::Little); 487 } 488 // Store the value. 489 builder.ins().store( 490 flags, 491 value, 492 values_vec_ptr_val, 493 (i * UnboxedValues::SLOT_SIZE) as i32, 494 ); 495 } 496 497 builder.ins().return_(&[]); 498 builder.finalize(); 499 500 func 501 } 502 503 #[cfg(test)] 504 mod test { 505 use super::*; 506 use cranelift_reader::{parse_functions, parse_test, ParseOptions}; 507 508 fn parse(code: &str) -> Function { 509 parse_functions(code).unwrap().into_iter().nth(0).unwrap() 510 } 511 512 #[test] 513 fn nop() { 514 let code = String::from( 515 " 516 test run 517 function %test() -> b8 { 518 block0: 519 nop 520 v1 = bconst.b8 true 521 return v1 522 }", 523 ); 524 525 // extract function 526 let test_file = parse_test(code.as_str(), ParseOptions::default()).unwrap(); 527 assert_eq!(1, test_file.functions.len()); 528 let function = test_file.functions[0].0.clone(); 529 530 // execute function 531 let mut compiler = TestFileCompiler::with_default_host_isa().unwrap(); 532 compiler.declare_function(&function).unwrap(); 533 compiler.define_function(function.clone()).unwrap(); 534 compiler.create_trampoline_for_function(&function).unwrap(); 535 let compiled = compiler.compile().unwrap(); 536 let trampoline = compiled.get_trampoline(&function).unwrap(); 537 let returned = trampoline.call(&[]); 538 assert_eq!(returned, vec![DataValue::B(true)]) 539 } 540 541 #[test] 542 fn trampolines() { 543 let function = parse( 544 " 545 function %test(f32, i8, i64x2, b1) -> f32x4, b64 { 546 block0(v0: f32, v1: i8, v2: i64x2, v3: b1): 547 v4 = vconst.f32x4 [0x0.1 0x0.2 0x0.3 0x0.4] 548 v5 = bconst.b64 true 549 return v4, v5 550 }", 551 ); 552 553 let compiler = TestFileCompiler::with_default_host_isa().unwrap(); 554 let trampoline = make_trampoline( 555 UserFuncName::user(0, 0), 556 &function.signature, 557 compiler.module.isa(), 558 ); 559 assert!(format!("{}", trampoline).ends_with( 560 "sig0 = (f32, i8, i64x2, b1) -> f32x4, b64 fast 561 562 block0(v0: i64, v1: i64): 563 v2 = load.f32 notrap aligned v1 564 v3 = load.i8 notrap aligned v1+16 565 v4 = load.i64x2 notrap aligned little v1+32 566 v5 = load.i8 notrap aligned v1+48 567 v6 = icmp_imm ne v5, 0 568 v7, v8 = call_indirect sig0, v0(v2, v3, v4, v6) 569 store notrap aligned little v7, v1 570 v9 = bint.i64 v8 571 store notrap aligned v9, v1+16 572 return 573 } 574 " 575 )); 576 } 577 } 578