1 use core::mem;
2 use cranelift_codegen::binemit::{NullRelocSink, NullStackmapSink, NullTrapSink};
3 use cranelift_codegen::ir::Function;
4 use cranelift_codegen::isa::TargetIsa;
5 use cranelift_codegen::{settings, Context};
6 use cranelift_native::builder as host_isa_builder;
7 use memmap::MmapMut;
8 
9 /// Run a function on a host
10 pub struct FunctionRunner {
11     function: Function,
12     isa: Box<dyn TargetIsa>,
13 }
14 
15 impl FunctionRunner {
16     /// Build a function runner from a function and the ISA to run on (must be the host machine's ISA)
17     pub fn new(function: Function, isa: Box<dyn TargetIsa>) -> Self {
18         Self { function, isa }
19     }
20 
21     /// Build a function runner using the host machine's ISA and the passed flags
22     pub fn with_host_isa(function: Function, flags: settings::Flags) -> Self {
23         let builder = host_isa_builder().expect("Unable to build a TargetIsa for the current host");
24         let isa = builder.finish(flags);
25         Self::new(function, isa)
26     }
27 
28     /// Build a function runner using the host machine's ISA and the default flags for this ISA
29     pub fn with_default_host_isa(function: Function) -> Self {
30         let flags = settings::Flags::new(settings::builder());
31         Self::with_host_isa(function, flags)
32     }
33 
34     /// Compile and execute a single function, expecting a boolean to be returned; a 'true' value is
35     /// interpreted as a successful test execution and mapped to Ok whereas a 'false' value is
36     /// interpreted as a failed test and mapped to Err.
37     pub fn run(&self) -> Result<(), String> {
38         let func = self.function.clone();
39         if !(func.signature.params.is_empty()
40             && func.signature.returns.len() == 1
41             && func.signature.returns.first().unwrap().value_type.is_bool())
42         {
43             return Err(String::from(
44                 "Functions must have a signature like: () -> boolean",
45             ));
46         }
47 
48         if func.signature.call_conv != self.isa.default_call_conv() {
49             return Err(String::from(
50                 "Functions only run on the host's default calling convention; remove the specified calling convention in the function signature to use the host's default.",
51             ));
52         }
53 
54         // set up the context
55         let mut context = Context::new();
56         context.func = func;
57 
58         // compile and encode the result to machine code
59         let relocs = &mut NullRelocSink {};
60         let traps = &mut NullTrapSink {};
61         let stackmaps = &mut NullStackmapSink {};
62         let code_info = context
63             .compile(self.isa.as_ref())
64             .map_err(|e| e.to_string())?;
65         let mut code_page =
66             MmapMut::map_anon(code_info.total_size as usize).map_err(|e| e.to_string())?;
67 
68         unsafe {
69             context.emit_to_memory(
70                 self.isa.as_ref(),
71                 code_page.as_mut_ptr(),
72                 relocs,
73                 traps,
74                 stackmaps,
75             );
76         };
77 
78         let code_page = code_page.make_exec().map_err(|e| e.to_string())?;
79         let callable_fn: fn() -> bool = unsafe { mem::transmute(code_page.as_ptr()) };
80 
81         // execute
82         if callable_fn() {
83             Ok(())
84         } else {
85             Err(format!("Failed: {}", context.func.name.to_string()))
86         }
87     }
88 }
89 
90 #[cfg(test)]
91 mod test {
92     use super::*;
93     use cranelift_reader::{parse_test, ParseOptions};
94 
95     #[test]
96     fn nop() {
97         let code = String::from(
98             "
99             test run
100             function %test() -> b8 {
101             block0:
102                 nop
103                 v1 = bconst.b8 true
104                 return v1
105             }",
106         );
107 
108         // extract function
109         let test_file = parse_test(code.as_str(), ParseOptions::default()).unwrap();
110         assert_eq!(1, test_file.functions.len());
111         let function = test_file.functions[0].0.clone();
112 
113         // execute function
114         let runner = FunctionRunner::with_default_host_isa(function);
115         runner.run().unwrap() // will panic if execution fails
116     }
117 }
118