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