1 //! Provides functionality for compiling and running CLIF IR for `run` tests. 2 use anyhow::Result; 3 use core::mem; 4 use cranelift_codegen::data_value::DataValue; 5 use cranelift_codegen::ir::{condcodes::IntCC, Function, InstBuilder, Signature}; 6 use cranelift_codegen::isa::TargetIsa; 7 use cranelift_codegen::{ir, settings, CodegenError}; 8 use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; 9 use cranelift_jit::{JITBuilder, JITModule}; 10 use cranelift_module::{FuncId, Linkage, Module, ModuleError}; 11 use cranelift_native::builder_with_options; 12 use std::cmp::max; 13 use thiserror::Error; 14 15 /// Compile a single function. 16 /// 17 /// Several Cranelift functions need the ability to run Cranelift IR (e.g. `test_run`); this 18 /// [SingleFunctionCompiler] provides a way for compiling Cranelift [Function]s to 19 /// `CompiledFunction`s and subsequently calling them through the use of a `Trampoline`. As its 20 /// name indicates, this compiler is limited: any functionality that requires knowledge of things 21 /// outside the [Function] will likely not work (e.g. global values, calls). For an example of this 22 /// "outside-of-function" functionality, see `cranelift_jit::backend::JITBackend`. 23 /// 24 /// ``` 25 /// use cranelift_filetests::SingleFunctionCompiler; 26 /// use cranelift_reader::parse_functions; 27 /// use cranelift_codegen::data_value::DataValue; 28 /// 29 /// let code = "test run \n function %add(i32, i32) -> i32 { block0(v0:i32, v1:i32): v2 = iadd v0, v1 return v2 }".into(); 30 /// let func = parse_functions(code).unwrap().into_iter().nth(0).unwrap(); 31 /// let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap(); 32 /// let compiled_func = compiler.compile(func).unwrap(); 33 /// 34 /// let returned = compiled_func.call(&vec![DataValue::I32(2), DataValue::I32(40)]); 35 /// assert_eq!(vec![DataValue::I32(42)], returned); 36 /// ``` 37 pub struct SingleFunctionCompiler { 38 isa: Box<dyn TargetIsa>, 39 } 40 41 impl SingleFunctionCompiler { 42 /// Build a [SingleFunctionCompiler] from a [TargetIsa]. For functions to be runnable on the 43 /// host machine, this [TargetIsa] must match the host machine's ISA (see 44 /// [SingleFunctionCompiler::with_host_isa]). 45 pub fn new(isa: Box<dyn TargetIsa>) -> Self { 46 Self { isa } 47 } 48 49 /// Build a [SingleFunctionCompiler] using the host machine's ISA and the passed flags. 50 pub fn with_host_isa(flags: settings::Flags) -> Result<Self> { 51 let builder = 52 builder_with_options(true).expect("Unable to build a TargetIsa for the current host"); 53 let isa = builder.finish(flags)?; 54 Ok(Self::new(isa)) 55 } 56 57 /// Build a [SingleFunctionCompiler] using the host machine's ISA and the default flags for this 58 /// ISA. 59 pub fn with_default_host_isa() -> Result<Self> { 60 let flags = settings::Flags::new(settings::builder()); 61 Self::with_host_isa(flags) 62 } 63 64 /// Compile the passed [Function] to a `CompiledFunction`. This function will: 65 /// - check that the default ISA calling convention is used (to ensure it can be called) 66 /// - compile the [Function] 67 /// - compile a `Trampoline` for the [Function]'s signature (or used a cached `Trampoline`; 68 /// this makes it possible to call functions when the signature is not known until runtime. 69 pub fn compile(self, function: Function) -> Result<CompiledFunction, CompilationError> { 70 let signature = function.signature.clone(); 71 if signature.call_conv != self.isa.default_call_conv() { 72 return Err(CompilationError::InvalidTargetIsa); 73 } 74 75 let trampoline = make_trampoline(&signature, self.isa.as_ref()); 76 77 let builder = JITBuilder::with_isa(self.isa, cranelift_module::default_libcall_names()); 78 let mut module = JITModule::new(builder); 79 let mut ctx = module.make_context(); 80 81 let name = format!("{}", function.name); 82 let func_id = module.declare_function(&name, Linkage::Local, &function.signature)?; 83 84 // Build and declare the trampoline in the module 85 let trampoline_name = format!("{}", trampoline.name); 86 let trampoline_id = 87 module.declare_function(&trampoline_name, Linkage::Local, &trampoline.signature)?; 88 89 // Define both functions 90 let func_signature = function.signature.clone(); 91 ctx.func = function; 92 module.define_function(func_id, &mut ctx)?; 93 module.clear_context(&mut ctx); 94 95 ctx.func = trampoline; 96 module.define_function(trampoline_id, &mut ctx)?; 97 module.clear_context(&mut ctx); 98 99 // Finalize the functions which we just defined, which resolves any 100 // outstanding relocations (patching in addresses, now that they're 101 // available). 102 module.finalize_definitions(); 103 104 Ok(CompiledFunction::new( 105 module, 106 func_signature, 107 func_id, 108 trampoline_id, 109 )) 110 } 111 } 112 113 /// Compilation Error when compiling a function. 114 #[derive(Error, Debug)] 115 pub enum CompilationError { 116 /// This Target ISA is invalid for the current host. 117 #[error("Cross-compilation not currently supported; use the host's default calling convention \ 118 or remove the specified calling convention in the function signature to use the host's default.")] 119 InvalidTargetIsa, 120 /// Cranelift codegen error. 121 #[error("Cranelift codegen error")] 122 CodegenError(#[from] CodegenError), 123 /// Module Error 124 #[error("Module error")] 125 ModuleError(#[from] ModuleError), 126 /// Memory mapping error. 127 #[error("Memory mapping error")] 128 IoError(#[from] std::io::Error), 129 } 130 131 /// Container for the compiled code of a [Function]. This wrapper allows users to call the compiled 132 /// function through the use of a trampoline. 133 /// 134 /// ``` 135 /// use cranelift_filetests::SingleFunctionCompiler; 136 /// use cranelift_reader::parse_functions; 137 /// use cranelift_codegen::data_value::DataValue; 138 /// 139 /// let code = "test run \n function %add(i32, i32) -> i32 { block0(v0:i32, v1:i32): v2 = iadd v0, v1 return v2 }".into(); 140 /// let func = parse_functions(code).unwrap().into_iter().nth(0).unwrap(); 141 /// let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap(); 142 /// let compiled_func = compiler.compile(func).unwrap(); 143 /// 144 /// let returned = compiled_func.call(&vec![DataValue::I32(2), DataValue::I32(40)]); 145 /// assert_eq!(vec![DataValue::I32(42)], returned); 146 /// ``` 147 pub struct CompiledFunction { 148 /// We need to store this since it contains the underlying memory for the functions 149 /// Store it in an [Option] so that we can later drop it. 150 module: Option<JITModule>, 151 signature: Signature, 152 func_id: FuncId, 153 trampoline_id: FuncId, 154 } 155 156 impl CompiledFunction { 157 /// Build a new [CompiledFunction]. 158 pub fn new( 159 module: JITModule, 160 signature: Signature, 161 func_id: FuncId, 162 trampoline_id: FuncId, 163 ) -> Self { 164 Self { 165 module: Some(module), 166 signature, 167 func_id, 168 trampoline_id, 169 } 170 } 171 172 /// Call the [CompiledFunction], passing in [DataValue]s using a compiled trampoline. 173 pub fn call(&self, arguments: &[DataValue]) -> Vec<DataValue> { 174 let mut values = UnboxedValues::make_arguments(arguments, &self.signature); 175 let arguments_address = values.as_mut_ptr(); 176 177 let module = self.module.as_ref().unwrap(); 178 let function_ptr = module.get_finalized_function(self.func_id); 179 let trampoline_ptr = module.get_finalized_function(self.trampoline_id); 180 181 let callable_trampoline: fn(*const u8, *mut u128) -> () = 182 unsafe { mem::transmute(trampoline_ptr) }; 183 callable_trampoline(function_ptr, arguments_address); 184 185 values.collect_returns(&self.signature) 186 } 187 } 188 189 impl Drop for CompiledFunction { 190 fn drop(&mut self) { 191 // Freeing the module's memory erases the compiled functions. 192 // This should be safe since their pointers never leave this struct. 193 unsafe { self.module.take().unwrap().free_memory() } 194 } 195 } 196 197 /// A container for laying out the [ValueData]s in memory in a way that the [Trampoline] can 198 /// understand. 199 struct UnboxedValues(Vec<u128>); 200 201 impl UnboxedValues { 202 /// The size in bytes of each slot location in the allocated [DataValue]s. Though [DataValue]s 203 /// could be smaller than 16 bytes (e.g. `I16`), this simplifies the creation of the [DataValue] 204 /// array and could be used to align the slots to the largest used [DataValue] (i.e. 128-bit 205 /// vectors). 206 const SLOT_SIZE: usize = 16; 207 208 /// Build the arguments vector for passing the [DataValue]s into the [Trampoline]. The size of 209 /// `u128` used here must match [Trampoline::SLOT_SIZE]. 210 pub fn make_arguments(arguments: &[DataValue], signature: &ir::Signature) -> Self { 211 assert_eq!(arguments.len(), signature.params.len()); 212 let mut values_vec = vec![0; max(signature.params.len(), signature.returns.len())]; 213 214 // Store the argument values into `values_vec`. 215 for ((arg, slot), param) in arguments.iter().zip(&mut values_vec).zip(&signature.params) { 216 assert!( 217 arg.ty() == param.value_type || arg.is_vector() || arg.is_bool(), 218 "argument type mismatch: {} != {}", 219 arg.ty(), 220 param.value_type 221 ); 222 unsafe { 223 arg.write_value_to(slot); 224 } 225 } 226 227 Self(values_vec) 228 } 229 230 /// Return a pointer to the underlying memory for passing to the trampoline. 231 pub fn as_mut_ptr(&mut self) -> *mut u128 { 232 self.0.as_mut_ptr() 233 } 234 235 /// Collect the returned [DataValue]s into a [Vec]. The size of `u128` used here must match 236 /// [Trampoline::SLOT_SIZE]. 237 pub fn collect_returns(&self, signature: &ir::Signature) -> Vec<DataValue> { 238 assert!(self.0.len() >= signature.returns.len()); 239 let mut returns = Vec::with_capacity(signature.returns.len()); 240 241 // Extract the returned values from this vector. 242 for (slot, param) in self.0.iter().zip(&signature.returns) { 243 let value = unsafe { DataValue::read_value_from(slot, param.value_type) }; 244 returns.push(value); 245 } 246 247 returns 248 } 249 } 250 251 /// Build the Cranelift IR for moving the memory-allocated [DataValue]s to their correct location 252 /// (e.g. register, stack) prior to calling a [CompiledFunction]. The [Function] returned by 253 /// [make_trampoline] is compiled to a [Trampoline]. Note that this uses the [TargetIsa]'s default 254 /// calling convention so we must also check that the [CompiledFunction] has the same calling 255 /// convention (see [SingleFunctionCompiler::compile]). 256 fn make_trampoline(signature: &ir::Signature, isa: &dyn TargetIsa) -> Function { 257 // Create the trampoline signature: (callee_address: pointer, values_vec: pointer) -> () 258 let pointer_type = isa.pointer_type(); 259 let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv); 260 wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `callee_address` parameter. 261 wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `values_vec` parameter. 262 263 let mut func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig); 264 265 // The trampoline has a single block filled with loads, one call to callee_address, and some loads. 266 let mut builder_context = FunctionBuilderContext::new(); 267 let mut builder = FunctionBuilder::new(&mut func, &mut builder_context); 268 let block0 = builder.create_block(); 269 builder.append_block_params_for_function_params(block0); 270 builder.switch_to_block(block0); 271 builder.seal_block(block0); 272 273 // Extract the incoming SSA values. 274 let (callee_value, values_vec_ptr_val) = { 275 let params = builder.func.dfg.block_params(block0); 276 (params[0], params[1]) 277 }; 278 279 // Load the argument values out of `values_vec`. 280 let callee_args = signature 281 .params 282 .iter() 283 .enumerate() 284 .map(|(i, param)| { 285 // Calculate the type to load from memory, using integers for booleans (no encodings). 286 let ty = param.value_type.coerce_bools_to_ints(); 287 288 // Load the value. 289 let loaded = builder.ins().load( 290 ty, 291 ir::MemFlags::trusted(), 292 values_vec_ptr_val, 293 (i * UnboxedValues::SLOT_SIZE) as i32, 294 ); 295 296 // For booleans, we want to type-convert the loaded integer into a boolean and ensure 297 // that we are using the architecture's canonical boolean representation (presumably 298 // comparison will emit this). 299 if param.value_type.is_bool() { 300 let b = builder.ins().icmp_imm(IntCC::NotEqual, loaded, 0); 301 302 // icmp_imm always produces a `b1`, `bextend` it if we need a larger bool 303 if param.value_type.bits() > 1 { 304 builder.ins().bextend(param.value_type, b) 305 } else { 306 b 307 } 308 } else if param.value_type.is_bool_vector() { 309 let zero_constant = builder.func.dfg.constants.insert(vec![0; 16].into()); 310 let zero_vec = builder.ins().vconst(ty, zero_constant); 311 builder.ins().icmp(IntCC::NotEqual, loaded, zero_vec) 312 } else { 313 loaded 314 } 315 }) 316 .collect::<Vec<_>>(); 317 318 // Call the passed function. 319 let new_sig = builder.import_signature(signature.clone()); 320 let call = builder 321 .ins() 322 .call_indirect(new_sig, callee_value, &callee_args); 323 324 // Store the return values into `values_vec`. 325 let results = builder.func.dfg.inst_results(call).to_vec(); 326 for ((i, value), param) in results.iter().enumerate().zip(&signature.returns) { 327 // Before storing return values, we convert booleans to their integer representation. 328 let value = if param.value_type.lane_type().is_bool() { 329 let ty = param.value_type.lane_type().as_int(); 330 builder.ins().bint(ty, *value) 331 } else { 332 *value 333 }; 334 // Store the value. 335 builder.ins().store( 336 ir::MemFlags::trusted(), 337 value, 338 values_vec_ptr_val, 339 (i * UnboxedValues::SLOT_SIZE) as i32, 340 ); 341 } 342 343 builder.ins().return_(&[]); 344 builder.finalize(); 345 346 func 347 } 348 349 #[cfg(test)] 350 mod test { 351 use super::*; 352 use cranelift_reader::{parse_functions, parse_test, ParseOptions}; 353 354 fn parse(code: &str) -> Function { 355 parse_functions(code).unwrap().into_iter().nth(0).unwrap() 356 } 357 358 #[test] 359 fn nop() { 360 let code = String::from( 361 " 362 test run 363 function %test() -> b8 { 364 block0: 365 nop 366 v1 = bconst.b8 true 367 return v1 368 }", 369 ); 370 371 // extract function 372 let test_file = parse_test(code.as_str(), ParseOptions::default()).unwrap(); 373 assert_eq!(1, test_file.functions.len()); 374 let function = test_file.functions[0].0.clone(); 375 376 // execute function 377 let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap(); 378 let compiled_function = compiler.compile(function).unwrap(); 379 let returned = compiled_function.call(&[]); 380 assert_eq!(returned, vec![DataValue::B(true)]) 381 } 382 383 #[test] 384 fn trampolines() { 385 let function = parse( 386 " 387 function %test(f32, i8, i64x2, b1) -> f32x4, b64 { 388 block0(v0: f32, v1: i8, v2: i64x2, v3: b1): 389 v4 = vconst.f32x4 [0x0.1 0x0.2 0x0.3 0x0.4] 390 v5 = bconst.b64 true 391 return v4, v5 392 }", 393 ); 394 395 let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap(); 396 let trampoline = make_trampoline(&function.signature, compiler.isa.as_ref()); 397 assert!(format!("{}", trampoline).ends_with( 398 "sig0 = (f32, i8, i64x2, b1) -> f32x4, b64 fast 399 400 block0(v0: i64, v1: i64): 401 v2 = load.f32 notrap aligned v1 402 v3 = load.i8 notrap aligned v1+16 403 v4 = load.i64x2 notrap aligned v1+32 404 v5 = load.i8 notrap aligned v1+48 405 v6 = icmp_imm ne v5, 0 406 v7, v8 = call_indirect sig0, v0(v2, v3, v4, v6) 407 store notrap aligned v7, v1 408 v9 = bint.i64 v8 409 store notrap aligned v9, v1+16 410 return 411 } 412 " 413 )); 414 } 415 } 416