Skip to content

Commit bd84c73

Browse files
committed
Auto merge of #99123 - mystor:crossbeam_bridge, r=eddyb
proc_macro: use crossbeam channels for the proc_macro cross-thread bridge This is done by having the crossbeam dependency inserted into the `proc_macro` server code from the server side, to avoid adding a dependency to `proc_macro`. In addition, this introduces a -Z command-line option which will switch rustc to run proc-macros using this cross-thread executor. With the changes to the bridge in #98186, #98187, #98188 and #98189, the performance of the executor should be much closer to same-thread execution. In local testing, the crossbeam executor was substantially more performant than either of the two existing `CrossThread` strategies, so they have been removed to keep things simple. r? `@eddyb`
2 parents 8f68c43 + 6d1650f commit bd84c73

File tree

8 files changed

+133
-80
lines changed

8 files changed

+133
-80
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3885,6 +3885,7 @@ dependencies = [
38853885
name = "rustc_expand"
38863886
version = "0.0.0"
38873887
dependencies = [
3888+
"crossbeam-channel",
38883889
"rustc_ast",
38893890
"rustc_ast_passes",
38903891
"rustc_ast_pretty",

compiler/rustc_expand/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ rustc_parse = { path = "../rustc_parse" }
2424
rustc_session = { path = "../rustc_session" }
2525
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
2626
rustc_ast = { path = "../rustc_ast" }
27+
crossbeam-channel = "0.5.0"

compiler/rustc_expand/src/proc_macro.rs

+37-7
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,37 @@ use rustc_ast::tokenstream::{TokenStream, TokenTree};
88
use rustc_data_structures::sync::Lrc;
99
use rustc_errors::ErrorGuaranteed;
1010
use rustc_parse::parser::ForceCollect;
11+
use rustc_session::config::ProcMacroExecutionStrategy;
1112
use rustc_span::profiling::SpannedEventArgRecorder;
1213
use rustc_span::{Span, DUMMY_SP};
1314

14-
const EXEC_STRATEGY: pm::bridge::server::SameThread = pm::bridge::server::SameThread;
15+
struct CrossbeamMessagePipe<T> {
16+
tx: crossbeam_channel::Sender<T>,
17+
rx: crossbeam_channel::Receiver<T>,
18+
}
19+
20+
impl<T> pm::bridge::server::MessagePipe<T> for CrossbeamMessagePipe<T> {
21+
fn new() -> (Self, Self) {
22+
let (tx1, rx1) = crossbeam_channel::bounded(1);
23+
let (tx2, rx2) = crossbeam_channel::bounded(1);
24+
(CrossbeamMessagePipe { tx: tx1, rx: rx2 }, CrossbeamMessagePipe { tx: tx2, rx: rx1 })
25+
}
26+
27+
fn send(&mut self, value: T) {
28+
self.tx.send(value).unwrap();
29+
}
30+
31+
fn recv(&mut self) -> Option<T> {
32+
self.rx.recv().ok()
33+
}
34+
}
35+
36+
fn exec_strategy(ecx: &ExtCtxt<'_>) -> impl pm::bridge::server::ExecutionStrategy {
37+
pm::bridge::server::MaybeCrossThread::<CrossbeamMessagePipe<_>>::new(
38+
ecx.sess.opts.unstable_opts.proc_macro_execution_strategy
39+
== ProcMacroExecutionStrategy::CrossThread,
40+
)
41+
}
1542

1643
pub struct BangProcMacro {
1744
pub client: pm::bridge::client::Client<pm::TokenStream, pm::TokenStream>,
@@ -30,8 +57,9 @@ impl base::BangProcMacro for BangProcMacro {
3057
});
3158

3259
let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
60+
let strategy = exec_strategy(ecx);
3361
let server = proc_macro_server::Rustc::new(ecx);
34-
self.client.run(&EXEC_STRATEGY, server, input, proc_macro_backtrace).map_err(|e| {
62+
self.client.run(&strategy, server, input, proc_macro_backtrace).map_err(|e| {
3563
let mut err = ecx.struct_span_err(span, "proc macro panicked");
3664
if let Some(s) = e.as_str() {
3765
err.help(&format!("message: {}", s));
@@ -59,16 +87,17 @@ impl base::AttrProcMacro for AttrProcMacro {
5987
});
6088

6189
let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
90+
let strategy = exec_strategy(ecx);
6291
let server = proc_macro_server::Rustc::new(ecx);
63-
self.client
64-
.run(&EXEC_STRATEGY, server, annotation, annotated, proc_macro_backtrace)
65-
.map_err(|e| {
92+
self.client.run(&strategy, server, annotation, annotated, proc_macro_backtrace).map_err(
93+
|e| {
6694
let mut err = ecx.struct_span_err(span, "custom attribute panicked");
6795
if let Some(s) = e.as_str() {
6896
err.help(&format!("message: {}", s));
6997
}
7098
err.emit()
71-
})
99+
},
100+
)
72101
}
73102
}
74103

@@ -105,8 +134,9 @@ impl MultiItemModifier for DeriveProcMacro {
105134
recorder.record_arg_with_span(ecx.expansion_descr(), span);
106135
});
107136
let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
137+
let strategy = exec_strategy(ecx);
108138
let server = proc_macro_server::Rustc::new(ecx);
109-
match self.client.run(&EXEC_STRATEGY, server, input, proc_macro_backtrace) {
139+
match self.client.run(&strategy, server, input, proc_macro_backtrace) {
110140
Ok(stream) => stream,
111141
Err(e) => {
112142
let mut err = ecx.struct_span_err(span, "proc-macro derive panicked");

compiler/rustc_interface/src/tests.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_session::config::{
1111
};
1212
use rustc_session::config::{
1313
BranchProtection, Externs, OomStrategy, OutputType, OutputTypes, PAuthKey, PacRet,
14-
SymbolManglingVersion, WasiExecModel,
14+
ProcMacroExecutionStrategy, SymbolManglingVersion, WasiExecModel,
1515
};
1616
use rustc_session::config::{CFGuard, ExternEntry, LinkerPluginLto, LtoCli, SwitchWithOptPath};
1717
use rustc_session::lint::Level;
@@ -685,6 +685,7 @@ fn test_unstable_options_tracking_hash() {
685685
untracked!(print_mono_items, Some(String::from("abc")));
686686
untracked!(print_type_sizes, true);
687687
untracked!(proc_macro_backtrace, true);
688+
untracked!(proc_macro_execution_strategy, ProcMacroExecutionStrategy::CrossThread);
688689
untracked!(query_dep_graph, true);
689690
untracked!(save_analysis, true);
690691
untracked!(self_profile, SwitchWithOptPath::Enabled(None));

compiler/rustc_session/src/config.rs

+10
Original file line numberDiff line numberDiff line change
@@ -2972,3 +2972,13 @@ impl OomStrategy {
29722972
}
29732973
}
29742974
}
2975+
2976+
/// How to run proc-macro code when building this crate
2977+
#[derive(Clone, Copy, PartialEq, Hash, Debug)]
2978+
pub enum ProcMacroExecutionStrategy {
2979+
/// Run the proc-macro code on the same thread as the server.
2980+
SameThread,
2981+
2982+
/// Run the proc-macro code on a different thread.
2983+
CrossThread,
2984+
}

compiler/rustc_session/src/options.rs

+17
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@ mod desc {
415415
"one of (`none` (default), `basic`, `strong`, or `all`)";
416416
pub const parse_branch_protection: &str =
417417
"a `,` separated combination of `bti`, `b-key`, `pac-ret`, or `leaf`";
418+
pub const parse_proc_macro_execution_strategy: &str =
419+
"one of supported execution strategies (`same-thread`, or `cross-thread`)";
418420
}
419421

420422
mod parse {
@@ -1062,6 +1064,18 @@ mod parse {
10621064
}
10631065
true
10641066
}
1067+
1068+
pub(crate) fn parse_proc_macro_execution_strategy(
1069+
slot: &mut ProcMacroExecutionStrategy,
1070+
v: Option<&str>,
1071+
) -> bool {
1072+
*slot = match v {
1073+
Some("same-thread") => ProcMacroExecutionStrategy::SameThread,
1074+
Some("cross-thread") => ProcMacroExecutionStrategy::CrossThread,
1075+
_ => return false,
1076+
};
1077+
true
1078+
}
10651079
}
10661080

10671081
options! {
@@ -1457,6 +1471,9 @@ options! {
14571471
"print layout information for each type encountered (default: no)"),
14581472
proc_macro_backtrace: bool = (false, parse_bool, [UNTRACKED],
14591473
"show backtraces for panics during proc-macro execution (default: no)"),
1474+
proc_macro_execution_strategy: ProcMacroExecutionStrategy = (ProcMacroExecutionStrategy::SameThread,
1475+
parse_proc_macro_execution_strategy, [UNTRACKED],
1476+
"how to run proc-macro code (default: same-thread)"),
14601477
profile: bool = (false, parse_bool, [TRACKED],
14611478
"insert profiling code (default: no)"),
14621479
profile_closures: bool = (false, parse_no_flag, [UNTRACKED],

library/proc_macro/src/bridge/server.rs

+64-72
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
33
use super::*;
44

5+
use std::marker::PhantomData;
6+
57
// FIXME(eddyb) generate the definition of `HandleStore` in `server.rs`.
68
use super::client::HandleStore;
79

@@ -143,6 +145,41 @@ pub trait ExecutionStrategy {
143145
) -> Buffer;
144146
}
145147

148+
pub struct MaybeCrossThread<P> {
149+
cross_thread: bool,
150+
marker: PhantomData<P>,
151+
}
152+
153+
impl<P> MaybeCrossThread<P> {
154+
pub const fn new(cross_thread: bool) -> Self {
155+
MaybeCrossThread { cross_thread, marker: PhantomData }
156+
}
157+
}
158+
159+
impl<P> ExecutionStrategy for MaybeCrossThread<P>
160+
where
161+
P: MessagePipe<Buffer> + Send + 'static,
162+
{
163+
fn run_bridge_and_client(
164+
&self,
165+
dispatcher: &mut impl DispatcherTrait,
166+
input: Buffer,
167+
run_client: extern "C" fn(BridgeConfig<'_>) -> Buffer,
168+
force_show_panics: bool,
169+
) -> Buffer {
170+
if self.cross_thread {
171+
<CrossThread<P>>::new().run_bridge_and_client(
172+
dispatcher,
173+
input,
174+
run_client,
175+
force_show_panics,
176+
)
177+
} else {
178+
SameThread.run_bridge_and_client(dispatcher, input, run_client, force_show_panics)
179+
}
180+
}
181+
}
182+
146183
pub struct SameThread;
147184

148185
impl ExecutionStrategy for SameThread {
@@ -164,28 +201,31 @@ impl ExecutionStrategy for SameThread {
164201
}
165202
}
166203

167-
// NOTE(eddyb) Two implementations are provided, the second one is a bit
168-
// faster but neither is anywhere near as fast as same-thread execution.
204+
pub struct CrossThread<P>(PhantomData<P>);
169205

170-
pub struct CrossThread1;
206+
impl<P> CrossThread<P> {
207+
pub const fn new() -> Self {
208+
CrossThread(PhantomData)
209+
}
210+
}
171211

172-
impl ExecutionStrategy for CrossThread1 {
212+
impl<P> ExecutionStrategy for CrossThread<P>
213+
where
214+
P: MessagePipe<Buffer> + Send + 'static,
215+
{
173216
fn run_bridge_and_client(
174217
&self,
175218
dispatcher: &mut impl DispatcherTrait,
176219
input: Buffer,
177220
run_client: extern "C" fn(BridgeConfig<'_>) -> Buffer,
178221
force_show_panics: bool,
179222
) -> Buffer {
180-
use std::sync::mpsc::channel;
181-
182-
let (req_tx, req_rx) = channel();
183-
let (res_tx, res_rx) = channel();
223+
let (mut server, mut client) = P::new();
184224

185225
let join_handle = thread::spawn(move || {
186-
let mut dispatch = |buf| {
187-
req_tx.send(buf).unwrap();
188-
res_rx.recv().unwrap()
226+
let mut dispatch = |b: Buffer| -> Buffer {
227+
client.send(b);
228+
client.recv().expect("server died while client waiting for reply")
189229
};
190230

191231
run_client(BridgeConfig {
@@ -196,75 +236,27 @@ impl ExecutionStrategy for CrossThread1 {
196236
})
197237
});
198238

199-
for b in req_rx {
200-
res_tx.send(dispatcher.dispatch(b)).unwrap();
239+
while let Some(b) = server.recv() {
240+
server.send(dispatcher.dispatch(b));
201241
}
202242

203243
join_handle.join().unwrap()
204244
}
205245
}
206246

207-
pub struct CrossThread2;
247+
/// A message pipe used for communicating between server and client threads.
248+
pub trait MessagePipe<T>: Sized {
249+
/// Create a new pair of endpoints for the message pipe.
250+
fn new() -> (Self, Self);
208251

209-
impl ExecutionStrategy for CrossThread2 {
210-
fn run_bridge_and_client(
211-
&self,
212-
dispatcher: &mut impl DispatcherTrait,
213-
input: Buffer,
214-
run_client: extern "C" fn(BridgeConfig<'_>) -> Buffer,
215-
force_show_panics: bool,
216-
) -> Buffer {
217-
use std::sync::{Arc, Mutex};
218-
219-
enum State<T> {
220-
Req(T),
221-
Res(T),
222-
}
223-
224-
let mut state = Arc::new(Mutex::new(State::Res(Buffer::new())));
225-
226-
let server_thread = thread::current();
227-
let state2 = state.clone();
228-
let join_handle = thread::spawn(move || {
229-
let mut dispatch = |b| {
230-
*state2.lock().unwrap() = State::Req(b);
231-
server_thread.unpark();
232-
loop {
233-
thread::park();
234-
if let State::Res(b) = &mut *state2.lock().unwrap() {
235-
break b.take();
236-
}
237-
}
238-
};
239-
240-
let r = run_client(BridgeConfig {
241-
input,
242-
dispatch: (&mut dispatch).into(),
243-
force_show_panics,
244-
_marker: marker::PhantomData,
245-
});
246-
247-
// Wake up the server so it can exit the dispatch loop.
248-
drop(state2);
249-
server_thread.unpark();
250-
251-
r
252-
});
253-
254-
// Check whether `state2` was dropped, to know when to stop.
255-
while Arc::get_mut(&mut state).is_none() {
256-
thread::park();
257-
let mut b = match &mut *state.lock().unwrap() {
258-
State::Req(b) => b.take(),
259-
_ => continue,
260-
};
261-
b = dispatcher.dispatch(b.take());
262-
*state.lock().unwrap() = State::Res(b);
263-
join_handle.thread().unpark();
264-
}
252+
/// Send a message to the other endpoint of this pipe.
253+
fn send(&mut self, value: T);
265254

266-
join_handle.join().unwrap()
267-
}
255+
/// Receive a message from the other endpoint of this pipe.
256+
///
257+
/// Returns `None` if the other end of the pipe has been destroyed, and no
258+
/// message was received.
259+
fn recv(&mut self) -> Option<T>;
268260
}
269261

270262
fn run_server<

src/test/rustdoc-ui/z-help.stdout

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
-Z print-mono-items=val -- print the result of the monomorphization collection pass
115115
-Z print-type-sizes=val -- print layout information for each type encountered (default: no)
116116
-Z proc-macro-backtrace=val -- show backtraces for panics during proc-macro execution (default: no)
117+
-Z proc-macro-execution-strategy=val -- how to run proc-macro code (default: same-thread)
117118
-Z profile=val -- insert profiling code (default: no)
118119
-Z profile-closures=val -- profile size of closures
119120
-Z profile-emit=val -- file path to emit profiling data at runtime when using 'profile' (default based on relative source path)

0 commit comments

Comments
 (0)