@@ -4,13 +4,12 @@ use core::mem;
44use cranelift_codegen:: data_value:: DataValue ;
55use cranelift_codegen:: ir:: { condcodes:: IntCC , Function , InstBuilder , Signature } ;
66use cranelift_codegen:: isa:: TargetIsa ;
7- use cranelift_codegen:: { ir, settings, CodegenError , Context } ;
7+ use cranelift_codegen:: { ir, settings, CodegenError } ;
88use cranelift_frontend:: { FunctionBuilder , FunctionBuilderContext } ;
9+ use cranelift_jit:: { JITBuilder , JITModule } ;
10+ use cranelift_module:: { FuncId , Linkage , Module , ModuleError } ;
911use cranelift_native:: builder_with_options;
10- use log:: trace;
11- use memmap2:: { Mmap , MmapMut } ;
1212use std:: cmp:: max;
13- use std:: collections:: HashMap ;
1413use thiserror:: Error ;
1514
1615/// Compile a single function.
@@ -25,25 +24,26 @@ use thiserror::Error;
2524/// ```
2625/// use cranelift_filetests::SingleFunctionCompiler;
2726/// use cranelift_reader::parse_functions;
27+ /// use cranelift_codegen::data_value::DataValue;
2828///
2929/// let code = "test run \n function %add(i32, i32) -> i32 { block0(v0:i32, v1:i32): v2 = iadd v0, v1 return v2 }".into();
3030/// let func = parse_functions(code).unwrap().into_iter().nth(0).unwrap();
31- /// let mut compiler = SingleFunctionCompiler::with_default_host_isa().unwrap();
31+ /// let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap();
3232/// let compiled_func = compiler.compile(func).unwrap();
33- /// println!("Address of compiled function: {:p}", compiled_func.as_ptr());
33+ ///
34+ /// let returned = compiled_func.call(&vec![DataValue::I32(2), DataValue::I32(40)]);
35+ /// assert_eq!(vec![DataValue::I32(42)], returned);
3436/// ```
3537pub struct SingleFunctionCompiler {
3638 isa : Box < dyn TargetIsa > ,
37- trampolines : HashMap < Signature , Trampoline > ,
3839}
3940
4041impl SingleFunctionCompiler {
4142 /// Build a [SingleFunctionCompiler] from a [TargetIsa]. For functions to be runnable on the
4243 /// host machine, this [TargetIsa] must match the host machine's ISA (see
4344 /// [SingleFunctionCompiler::with_host_isa]).
4445 pub fn new ( isa : Box < dyn TargetIsa > ) -> Self {
45- let trampolines = HashMap :: new ( ) ;
46- Self { isa, trampolines }
46+ Self { isa }
4747 }
4848
4949 /// Build a [SingleFunctionCompiler] using the host machine's ISA and the passed flags.
@@ -66,27 +66,47 @@ impl SingleFunctionCompiler {
6666 /// - compile the [Function]
6767 /// - compile a `Trampoline` for the [Function]'s signature (or used a cached `Trampoline`;
6868 /// this makes it possible to call functions when the signature is not known until runtime.
69- pub fn compile ( & mut self , function : Function ) -> Result < CompiledFunction , CompilationError > {
69+ pub fn compile ( self , function : Function ) -> Result < CompiledFunction , CompilationError > {
7070 let signature = function. signature . clone ( ) ;
7171 if signature. call_conv != self . isa . default_call_conv ( ) {
7272 return Err ( CompilationError :: InvalidTargetIsa ) ;
7373 }
7474
75- // Compile the function itself.
76- let code_page = compile ( function, self . isa . as_ref ( ) ) ?;
77-
78- // Compile the trampoline to call it, if necessary (it may be cached).
79- let isa = self . isa . as_ref ( ) ;
80- let trampoline = self
81- . trampolines
82- . entry ( signature. clone ( ) )
83- . or_insert_with ( || {
84- let ir = make_trampoline ( & signature, isa) ;
85- let code = compile ( ir, isa) . expect ( "failed to compile trampoline" ) ;
86- Trampoline :: new ( code)
87- } ) ;
88-
89- Ok ( CompiledFunction :: new ( code_page, signature, trampoline) )
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+ ) )
90110 }
91111}
92112
@@ -100,34 +120,16 @@ pub enum CompilationError {
100120 /// Cranelift codegen error.
101121 #[ error( "Cranelift codegen error" ) ]
102122 CodegenError ( #[ from] CodegenError ) ,
123+ /// Module Error
124+ #[ error( "Module error" ) ]
125+ ModuleError ( #[ from] ModuleError ) ,
103126 /// Memory mapping error.
104127 #[ error( "Memory mapping error" ) ]
105128 IoError ( #[ from] std:: io:: Error ) ,
106129}
107130
108- /// Contains the compiled code to move memory-allocated [DataValue]s to the correct location (e.g.
109- /// register, stack) dictated by the calling convention before calling a [CompiledFunction]. Without
110- /// this, it would be quite difficult to correctly place [DataValue]s since both the calling
111- /// convention and function signature are not known until runtime. See [make_trampoline] for the
112- /// Cranelift IR used to build this.
113- pub struct Trampoline {
114- page : Mmap ,
115- }
116-
117- impl Trampoline {
118- /// Build a new [Trampoline].
119- pub fn new ( page : Mmap ) -> Self {
120- Self { page }
121- }
122-
123- /// Return a pointer to the compiled code.
124- fn as_ptr ( & self ) -> * const u8 {
125- self . page . as_ptr ( )
126- }
127- }
128-
129131/// Container for the compiled code of a [Function]. This wrapper allows users to call the compiled
130- /// function through the use of a [Trampoline] .
132+ /// function through the use of a trampoline .
131133///
132134/// ```
133135/// use cranelift_filetests::SingleFunctionCompiler;
@@ -136,47 +138,62 @@ impl Trampoline {
136138///
137139/// let code = "test run \n function %add(i32, i32) -> i32 { block0(v0:i32, v1:i32): v2 = iadd v0, v1 return v2 }".into();
138140/// let func = parse_functions(code).unwrap().into_iter().nth(0).unwrap();
139- /// let mut compiler = SingleFunctionCompiler::with_default_host_isa().unwrap();
141+ /// let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap();
140142/// let compiled_func = compiler.compile(func).unwrap();
141143///
142144/// let returned = compiled_func.call(&vec![DataValue::I32(2), DataValue::I32(40)]);
143145/// assert_eq!(vec![DataValue::I32(42)], returned);
144146/// ```
145- pub struct CompiledFunction < ' a > {
146- page : Mmap ,
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 > ,
147151 signature : Signature ,
148- trampoline : & ' a Trampoline ,
152+ func_id : FuncId ,
153+ trampoline_id : FuncId ,
149154}
150155
151- impl < ' a > CompiledFunction < ' a > {
156+ impl CompiledFunction {
152157 /// Build a new [CompiledFunction].
153- pub fn new ( page : Mmap , signature : Signature , trampoline : & ' a Trampoline ) -> Self {
158+ pub fn new (
159+ module : JITModule ,
160+ signature : Signature ,
161+ func_id : FuncId ,
162+ trampoline_id : FuncId ,
163+ ) -> Self {
154164 Self {
155- page ,
165+ module : Some ( module ) ,
156166 signature,
157- trampoline,
167+ func_id,
168+ trampoline_id,
158169 }
159170 }
160171
161- /// Return a pointer to the compiled code.
162- pub fn as_ptr ( & self ) -> * const u8 {
163- self . page . as_ptr ( )
164- }
165-
166- /// Call the [CompiledFunction], passing in [DataValue]s using a compiled [Trampoline].
172+ /// Call the [CompiledFunction], passing in [DataValue]s using a compiled trampoline.
167173 pub fn call ( & self , arguments : & [ DataValue ] ) -> Vec < DataValue > {
168174 let mut values = UnboxedValues :: make_arguments ( arguments, & self . signature ) ;
169175 let arguments_address = values. as_mut_ptr ( ) ;
170- let function_address = self . as_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 ) ;
171180
172181 let callable_trampoline: fn ( * const u8 , * mut u128 ) -> ( ) =
173- unsafe { mem:: transmute ( self . trampoline . as_ptr ( ) ) } ;
174- callable_trampoline ( function_address , arguments_address) ;
182+ unsafe { mem:: transmute ( trampoline_ptr ) } ;
183+ callable_trampoline ( function_ptr , arguments_address) ;
175184
176185 values. collect_returns ( & self . signature )
177186 }
178187}
179188
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+
180197/// A container for laying out the [ValueData]s in memory in a way that the [Trampoline] can
181198/// understand.
182199struct UnboxedValues ( Vec < u128 > ) ;
@@ -231,32 +248,6 @@ impl UnboxedValues {
231248 }
232249}
233250
234- /// Compile a [Function] to its executable bytes in memory.
235- ///
236- /// This currently returns a [Mmap], a type from an external crate, so we wrap this up before
237- /// exposing it in public APIs.
238- fn compile ( function : Function , isa : & dyn TargetIsa ) -> Result < Mmap , CompilationError > {
239- // Set up the context.
240- let mut context = Context :: new ( ) ;
241- context. func = function;
242-
243- // Compile and encode the result to machine code.
244- let compiled_code = context. compile ( isa) . map_err ( |err| err. inner ) ?;
245- let mut code_page = MmapMut :: map_anon ( compiled_code. code_info ( ) . total_size as usize ) ?;
246-
247- code_page. copy_from_slice ( compiled_code. code_buffer ( ) ) ;
248-
249- let code_page = code_page. make_exec ( ) ?;
250- trace ! (
251- "Compiled function {} with signature {} at: {:p}" ,
252- context. func. name,
253- context. func. signature,
254- code_page. as_ptr( )
255- ) ;
256-
257- Ok ( code_page)
258- }
259-
260251/// Build the Cranelift IR for moving the memory-allocated [DataValue]s to their correct location
261252/// (e.g. register, stack) prior to calling a [CompiledFunction]. The [Function] returned by
262253/// [make_trampoline] is compiled to a [Trampoline]. Note that this uses the [TargetIsa]'s default
@@ -383,7 +374,7 @@ mod test {
383374 let function = test_file. functions [ 0 ] . 0 . clone ( ) ;
384375
385376 // execute function
386- let mut compiler = SingleFunctionCompiler :: with_default_host_isa ( ) . unwrap ( ) ;
377+ let compiler = SingleFunctionCompiler :: with_default_host_isa ( ) . unwrap ( ) ;
387378 let compiled_function = compiler. compile ( function) . unwrap ( ) ;
388379 let returned = compiled_function. call ( & [ ] ) ;
389380 assert_eq ! ( returned, vec![ DataValue :: B ( true ) ] )
0 commit comments