Skip to content

Commit 0d1cede

Browse files
authored
Merge pull request #1166 from eggyal/lazy-jit-multithreaded
Multithreading support for lazy-jit
2 parents 089d986 + 57c6eb7 commit 0d1cede

File tree

5 files changed

+101
-16
lines changed

5 files changed

+101
-16
lines changed

docs/usage.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ $ $cg_clif_dir/build/bin/cg_clif -Cllvm-args=mode=jit -Cprefer-dynamic my_crate.
4040
```
4141

4242
There is also an experimental lazy jit mode. In this mode functions are only compiled once they are
43-
first called. It currently does not work with multi-threaded programs. When a not yet compiled
44-
function is called from another thread than the main thread, you will get an ICE.
43+
first called.
4544

4645
```bash
4746
$ $cg_clif_dir/build/cargo lazy-jit

example/std_example.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ fn main() {
1515
let stderr = ::std::io::stderr();
1616
let mut stderr = stderr.lock();
1717

18-
// FIXME support lazy jit when multi threading
19-
#[cfg(not(lazy_jit))]
2018
std::thread::spawn(move || {
2119
println!("Hello from another thread!");
2220
});

scripts/tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function base_sysroot_tests() {
4747
$MY_RUSTC -Cllvm-args=mode=jit -Cprefer-dynamic example/std_example.rs --target "$HOST_TRIPLE"
4848

4949
echo "[JIT-lazy] std_example"
50-
$MY_RUSTC -Cllvm-args=mode=jit-lazy -Cprefer-dynamic example/std_example.rs --cfg lazy_jit --target "$HOST_TRIPLE"
50+
$MY_RUSTC -Cllvm-args=mode=jit-lazy -Cprefer-dynamic example/std_example.rs --target "$HOST_TRIPLE"
5151
else
5252
echo "[JIT] std_example (skipped)"
5353
fi

src/driver/jit.rs

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
44
use std::cell::RefCell;
55
use std::ffi::CString;
6+
use std::lazy::{Lazy, SyncOnceCell};
67
use std::os::raw::{c_char, c_int};
8+
use std::sync::{mpsc, Mutex};
79

810
use cranelift_codegen::binemit::{NullStackMapSink, NullTrapSink};
911
use rustc_codegen_ssa::CrateInfo;
@@ -23,6 +25,40 @@ thread_local! {
2325
static LAZY_JIT_STATE: RefCell<Option<JitState>> = RefCell::new(None);
2426
}
2527

28+
/// The Sender owned by the rustc thread
29+
static GLOBAL_MESSAGE_SENDER: SyncOnceCell<Mutex<mpsc::Sender<UnsafeMessage>>> = SyncOnceCell::new();
30+
31+
/// A message that is sent from the jitted runtime to the rustc thread.
32+
/// Senders are responsible for upholding `Send` semantics.
33+
enum UnsafeMessage {
34+
/// Request that the specified `Instance` be lazily jitted.
35+
///
36+
/// Nothing accessible through `instance_ptr` may be moved or mutated by the sender after
37+
/// this message is sent.
38+
JitFn {
39+
instance_ptr: *const Instance<'static>,
40+
trampoline_ptr: *const u8,
41+
tx: mpsc::Sender<*const u8>,
42+
},
43+
}
44+
unsafe impl Send for UnsafeMessage {}
45+
46+
impl UnsafeMessage {
47+
/// Send the message.
48+
fn send(self) -> Result<(), mpsc::SendError<UnsafeMessage>> {
49+
thread_local! {
50+
/// The Sender owned by the local thread
51+
static LOCAL_MESSAGE_SENDER: Lazy<mpsc::Sender<UnsafeMessage>> = Lazy::new(||
52+
GLOBAL_MESSAGE_SENDER
53+
.get().unwrap()
54+
.lock().unwrap()
55+
.clone()
56+
);
57+
}
58+
LOCAL_MESSAGE_SENDER.with(|sender| sender.send(self))
59+
}
60+
}
61+
2662
fn create_jit_module<'tcx>(
2763
tcx: TyCtxt<'tcx>,
2864
backend_config: &BackendConfig,
@@ -116,11 +152,6 @@ pub(crate) fn run_jit(tcx: TyCtxt<'_>, backend_config: BackendConfig) -> ! {
116152
.chain(backend_config.jit_args.iter().map(|arg| &**arg))
117153
.map(|arg| CString::new(arg).unwrap())
118154
.collect::<Vec<_>>();
119-
let mut argv = args.iter().map(|arg| arg.as_ptr()).collect::<Vec<_>>();
120-
121-
// Push a null pointer as a terminating argument. This is required by POSIX and
122-
// useful as some dynamic linkers use it as a marker to jump over.
123-
argv.push(std::ptr::null());
124155

125156
let start_sig = Signature {
126157
params: vec![
@@ -141,12 +172,49 @@ pub(crate) fn run_jit(tcx: TyCtxt<'_>, backend_config: BackendConfig) -> ! {
141172

142173
let f: extern "C" fn(c_int, *const *const c_char) -> c_int =
143174
unsafe { ::std::mem::transmute(finalized_start) };
144-
let ret = f(args.len() as c_int, argv.as_ptr());
145-
std::process::exit(ret);
175+
176+
let (tx, rx) = mpsc::channel();
177+
GLOBAL_MESSAGE_SENDER.set(Mutex::new(tx)).unwrap();
178+
179+
// Spawn the jitted runtime in a new thread so that this rustc thread can handle messages
180+
// (eg to lazily JIT further functions as required)
181+
std::thread::spawn(move || {
182+
let mut argv = args.iter().map(|arg| arg.as_ptr()).collect::<Vec<_>>();
183+
184+
// Push a null pointer as a terminating argument. This is required by POSIX and
185+
// useful as some dynamic linkers use it as a marker to jump over.
186+
argv.push(std::ptr::null());
187+
188+
let ret = f(args.len() as c_int, argv.as_ptr());
189+
std::process::exit(ret);
190+
});
191+
192+
// Handle messages
193+
loop {
194+
match rx.recv().unwrap() {
195+
// lazy JIT compilation request - compile requested instance and return pointer to result
196+
UnsafeMessage::JitFn { instance_ptr, trampoline_ptr, tx } => {
197+
tx.send(jit_fn(instance_ptr, trampoline_ptr))
198+
.expect("jitted runtime hung up before response to lazy JIT request was sent");
199+
}
200+
}
201+
}
146202
}
147203

148204
#[no_mangle]
149-
extern "C" fn __clif_jit_fn(instance_ptr: *const Instance<'static>) -> *const u8 {
205+
extern "C" fn __clif_jit_fn(instance_ptr: *const Instance<'static>, trampoline_ptr: *const u8) -> *const u8 {
206+
// send the JIT request to the rustc thread, with a channel for the response
207+
let (tx, rx) = mpsc::channel();
208+
UnsafeMessage::JitFn { instance_ptr, trampoline_ptr, tx }
209+
.send()
210+
.expect("rustc thread hung up before lazy JIT request was sent");
211+
212+
// block on JIT compilation result
213+
rx.recv()
214+
.expect("rustc thread hung up before responding to sent lazy JIT request")
215+
}
216+
217+
fn jit_fn(instance_ptr: *const Instance<'static>, trampoline_ptr: *const u8) -> *const u8 {
150218
rustc_middle::ty::tls::with(|tcx| {
151219
// lift is used to ensure the correct lifetime for instance.
152220
let instance = tcx.lift(unsafe { *instance_ptr }).unwrap();
@@ -160,6 +228,17 @@ extern "C" fn __clif_jit_fn(instance_ptr: *const Instance<'static>) -> *const u8
160228
let name = tcx.symbol_name(instance).name;
161229
let sig = crate::abi::get_function_sig(tcx, jit_module.isa().triple(), instance);
162230
let func_id = jit_module.declare_function(name, Linkage::Export, &sig).unwrap();
231+
232+
let current_ptr = jit_module.read_got_entry(func_id);
233+
234+
// If the function's GOT entry has already been updated to point at something other
235+
// than the shim trampoline, don't re-jit but just return the new pointer instead.
236+
// This does not need synchronization as this code is executed only by a sole rustc
237+
// thread.
238+
if current_ptr != trampoline_ptr {
239+
return current_ptr;
240+
}
241+
163242
jit_module.prepare_for_function_redefine(func_id).unwrap();
164243

165244
let mut cx = crate::CodegenCx::new(tcx, backend_config, jit_module.isa(), false);
@@ -254,7 +333,7 @@ fn codegen_shim<'tcx>(cx: &mut CodegenCx<'tcx>, module: &mut JITModule, inst: In
254333
Linkage::Import,
255334
&Signature {
256335
call_conv: module.target_config().default_call_conv,
257-
params: vec![AbiParam::new(pointer_type)],
336+
params: vec![AbiParam::new(pointer_type), AbiParam::new(pointer_type)],
258337
returns: vec![AbiParam::new(pointer_type)],
259338
},
260339
)
@@ -267,6 +346,7 @@ fn codegen_shim<'tcx>(cx: &mut CodegenCx<'tcx>, module: &mut JITModule, inst: In
267346
let mut builder_ctx = FunctionBuilderContext::new();
268347
let mut trampoline_builder = FunctionBuilder::new(trampoline, &mut builder_ctx);
269348

349+
let trampoline_fn = module.declare_func_in_func(func_id, trampoline_builder.func);
270350
let jit_fn = module.declare_func_in_func(jit_fn, trampoline_builder.func);
271351
let sig_ref = trampoline_builder.func.import_signature(sig);
272352

@@ -276,7 +356,8 @@ fn codegen_shim<'tcx>(cx: &mut CodegenCx<'tcx>, module: &mut JITModule, inst: In
276356

277357
trampoline_builder.switch_to_block(entry_block);
278358
let instance_ptr = trampoline_builder.ins().iconst(pointer_type, instance_ptr as u64 as i64);
279-
let jitted_fn = trampoline_builder.ins().call(jit_fn, &[instance_ptr]);
359+
let trampoline_ptr = trampoline_builder.ins().func_addr(pointer_type, trampoline_fn);
360+
let jitted_fn = trampoline_builder.ins().call(jit_fn, &[instance_ptr, trampoline_ptr]);
280361
let jitted_fn = trampoline_builder.func.dfg.inst_results(jitted_fn)[0];
281362
let call_inst = trampoline_builder.ins().call_indirect(sig_ref, jitted_fn, &fn_args);
282363
let ret_vals = trampoline_builder.func.dfg.inst_results(call_inst).to_vec();

src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
#![feature(rustc_private, decl_macro, never_type, hash_drain_filter, vec_into_raw_parts)]
1+
#![feature(
2+
rustc_private,
3+
decl_macro,
4+
never_type,
5+
hash_drain_filter,
6+
vec_into_raw_parts,
7+
once_cell,
8+
)]
29
#![warn(rust_2018_idioms)]
310
#![warn(unused_lifetimes)]
411
#![warn(unreachable_pub)]

0 commit comments

Comments
 (0)