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