Skip to content

Commit 4d2a2cf

Browse files
authored
cranelift: Use cranelift-jit in runtests (#4453)
* cranelift: Use JIT in runtests Using `cranelift-jit` in run tests allows us to preform relocations and libcalls. This is important since some instruction lowerings fallback to libcall's when an extension is missing, or when it's too complicated to implement manually. This is also a first step to being able to test `call`'s between functions in the runtest suite. It should also make it easier to eventually test TLS relocations, symbol resolution and ABI's. Another benefit of this is that we also get to test the JIT more, since it now runs the runtests, and gets some fuzzing via `fuzzgen` (which uses the `SingleFunctionCompiler`). This change causes regressions in terms of runtime for the filetests. I haven't done any serious benchmarking but what I've been seeing is that it now takes about ~3 seconds to run the testsuite while it previously took around 2 seconds. * Add FMA tests for X86
1 parent 97b2680 commit 4d2a2cf

13 files changed

Lines changed: 121 additions & 109 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cranelift/filetests/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ cranelift-interpreter = { path = "../interpreter", version = "0.88.0" }
1616
cranelift-native = { path = "../native", version = "0.88.0" }
1717
cranelift-reader = { path = "../reader", version = "0.88.0" }
1818
cranelift-preopt = { path = "../preopt", version = "0.88.0" }
19+
cranelift-jit = { path = "../jit", version = "0.88.0" }
20+
cranelift-module = { path = "../module", version = "0.88.0" }
1921
file-per-thread-logger = "0.1.2"
2022
filecheck = "0.5.0"
2123
gimli = { version = "0.26.0", default-features = false, features = ["read"] }
2224
log = "0.4.6"
23-
memmap2 = "0.2.1"
2425
num_cpus = "1.8.0"
2526
target-lexicon = "0.12"
2627
thiserror = "1.0.15"

cranelift/filetests/filetests/runtests/ceil.clif

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
test interpret
22
test run
33
target x86_64
4+
target x86_64 has_sse41=false
45
target aarch64
56
target s390x
67

cranelift/filetests/filetests/runtests/floor.clif

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
test interpret
22
test run
33
target x86_64
4+
target x86_64 has_sse41=false
45
target aarch64
56
target s390x
67

cranelift/filetests/filetests/runtests/fma-interpreter.clif

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
test interpret
2+
target x86_64 has_avx=false has_fma=false
23

34
; The interpreter can run `fma.clif` on most platforms, however on `x86_64-pc-windows-gnu` we
45
; use libm which has issues with some inputs. We should delete this file and enable the interpreter
5-
; on the main `fma.clif` file once those are fixed.
6+
; on the main `fma.clif` file once those are fixed. The same issue applies to x86 with fma disabled
7+
; since it will call the native runtime's fma function.
68

79
; See: https://github.com/bytecodealliance/wasmtime/pull/4517
810
; See: https://github.com/rust-lang/libm/issues/263

cranelift/filetests/filetests/runtests/nearest.clif

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
test interpret
22
test run
33
target x86_64
4+
target x86_64 has_sse41=false
45
target aarch64
56
target s390x
67

cranelift/filetests/filetests/runtests/trunc.clif

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
test interpret
22
test run
33
target x86_64
4+
target x86_64 has_sse41=false
45
target aarch64
56
target s390x
67

cranelift/filetests/src/function_runner.rs

Lines changed: 82 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ use core::mem;
44
use cranelift_codegen::data_value::DataValue;
55
use cranelift_codegen::ir::{condcodes::IntCC, Function, InstBuilder, Signature};
66
use cranelift_codegen::isa::TargetIsa;
7-
use cranelift_codegen::{ir, settings, CodegenError, Context};
7+
use cranelift_codegen::{ir, settings, CodegenError};
88
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
9+
use cranelift_jit::{JITBuilder, JITModule};
10+
use cranelift_module::{FuncId, Linkage, Module, ModuleError};
911
use cranelift_native::builder_with_options;
10-
use log::trace;
11-
use memmap2::{Mmap, MmapMut};
1212
use std::cmp::max;
13-
use std::collections::HashMap;
1413
use 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
/// ```
3537
pub struct SingleFunctionCompiler {
3638
isa: Box<dyn TargetIsa>,
37-
trampolines: HashMap<Signature, Trampoline>,
3839
}
3940

4041
impl 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.
182199
struct 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

Comments
 (0)