diff options
| author | Nika Layzell <nika@thelayzells.com> | 2022-09-04 12:53:29 -0400 |
|---|---|---|
| committer | Nika Layzell <nika@thelayzells.com> | 2022-09-04 14:06:26 -0400 |
| commit | efda49712b35009c0096217ce2aa262fc5ab8174 (patch) | |
| tree | d883f1393ce90018bd05cae7ad3a943211f7659b /library/proc_macro/src | |
| parent | b11bf65e4aaa125952b6479a63f36e9e83efc32c (diff) | |
| download | rust-efda49712b35009c0096217ce2aa262fc5ab8174.tar.gz rust-efda49712b35009c0096217ce2aa262fc5ab8174.zip | |
proc_macro/bridge: use the cross-thread executor for nested proc-macros
While working on some other changes in the bridge, I noticed that when running a nested proc-macro (which is currently only possible using the unstable `TokenStream::expand_expr`), any symbols held by the proc-macro client would be invalidated, as the same thread would be used for the nested macro by default, and the interner doesn't handle nested use. After discussing with @eddyb, we decided the best approach might be to force the use of the cross-thread executor for nested invocations, as it will never re-use thread-local storage, avoiding the issue. This shouldn't impact performance, as expand_expr is still unstable, and infrequently used. This was chosen rather than making the client symbol interner handle nested invocations, as that would require replacing the internal interner `Vec` with a `BTreeMap` (as valid symbol id ranges could now be disjoint), and the symbol interner is known to be fairly perf-sensitive. This patch adds checks to the execution strategy to use the cross-thread executor when doing nested invocations. An alternative implementation strategy could be to track this information in the `ExtCtxt`, however a thread-local in the `proc_macro` crate was chosen to add an assertion so that `rust-analyzer` is aware of the issue if it implements `expand_expr` in the future. r? @eddyb
Diffstat (limited to 'library/proc_macro/src')
| -rw-r--r-- | library/proc_macro/src/bridge/server.rs | 37 |
1 files changed, 36 insertions, 1 deletions
diff --git a/library/proc_macro/src/bridge/server.rs b/library/proc_macro/src/bridge/server.rs index e47a77f6c13..8202c40d631 100644 --- a/library/proc_macro/src/bridge/server.rs +++ b/library/proc_macro/src/bridge/server.rs @@ -2,6 +2,7 @@ use super::*; +use std::cell::Cell; use std::marker::PhantomData; // FIXME(eddyb) generate the definition of `HandleStore` in `server.rs`. @@ -143,6 +144,38 @@ pub trait ExecutionStrategy { ) -> Buffer; } +thread_local! { + /// While running a proc-macro with the same-thread executor, this flag will + /// be set, forcing nested proc-macro invocations (e.g. due to + /// `TokenStream::expand_expr`) to be run using a cross-thread executor. + /// + /// This is required as the thread-local state in the proc_macro client does + /// not handle being re-entered, and will invalidate all `Symbol`s when + /// entering a nested macro. + static ALREADY_RUNNING_SAME_THREAD: Cell<bool> = Cell::new(false); +} + +/// Keep `ALREADY_RUNNING_SAME_THREAD` (see also its documentation) +/// set to `true`, preventing same-thread reentrance. +struct RunningSameThreadGuard(()); + +impl RunningSameThreadGuard { + fn new() -> Self { + let already_running = ALREADY_RUNNING_SAME_THREAD.replace(true); + assert!( + !already_running, + "same-thread nesting (\"reentrance\") of proc macro executions is not supported" + ); + RunningSameThreadGuard(()) + } +} + +impl Drop for RunningSameThreadGuard { + fn drop(&mut self) { + ALREADY_RUNNING_SAME_THREAD.set(false); + } +} + pub struct MaybeCrossThread<P> { cross_thread: bool, marker: PhantomData<P>, @@ -165,7 +198,7 @@ where run_client: extern "C" fn(BridgeConfig<'_>) -> Buffer, force_show_panics: bool, ) -> Buffer { - if self.cross_thread { + if self.cross_thread || ALREADY_RUNNING_SAME_THREAD.get() { <CrossThread<P>>::new().run_bridge_and_client( dispatcher, input, @@ -188,6 +221,8 @@ impl ExecutionStrategy for SameThread { run_client: extern "C" fn(BridgeConfig<'_>) -> Buffer, force_show_panics: bool, ) -> Buffer { + let _guard = RunningSameThreadGuard::new(); + let mut dispatch = |buf| dispatcher.dispatch(buf); run_client(BridgeConfig { |
