Skip to content

proc_macro/bridge: use the cross-thread executor for nested proc-macros #101414

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion library/proc_macro/src/bridge/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use super::*;

use std::cell::Cell;
use std::marker::PhantomData;

// FIXME(eddyb) generate the definition of `HandleStore` in `server.rs`.
Expand Down Expand Up @@ -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);
}
}
Comment on lines +162 to +177
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, these two functions may need #[inline]? I should've really ran perf, my bad :(


pub struct MaybeCrossThread<P> {
cross_thread: bool,
marker: PhantomData<P>,
Expand All @@ -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,
Expand All @@ -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 {
Expand Down
23 changes: 20 additions & 3 deletions src/test/ui/proc-macro/auxiliary/expand-expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,21 @@ fn assert_ts_eq(lhs: &TokenStream, rhs: &TokenStream) {
pub fn expand_expr_is(input: TokenStream) -> TokenStream {
let mut iter = input.into_iter();
let mut expected_tts = Vec::new();
loop {
let comma = loop {
match iter.next() {
Some(TokenTree::Punct(ref p)) if p.as_char() == ',' => break,
Some(TokenTree::Punct(p)) if p.as_char() == ',' => break p,
Some(tt) => expected_tts.push(tt),
None => panic!("expected comma"),
}
}
};

// Make sure that `Ident` and `Literal` objects from this proc-macro's
// environment are not invalidated when `expand_expr` recursively invokes
// another macro by taking a local copy, and checking it after the fact.
let pre_expand_span = comma.span();
let pre_expand_ident = Ident::new("ident", comma.span());
let pre_expand_literal = Literal::string("literal");
let pre_expand_call_site = Span::call_site();

let expected = expected_tts.into_iter().collect::<TokenStream>();
let expanded = iter.collect::<TokenStream>().expand_expr().expect("expand_expr failed");
Expand All @@ -100,6 +108,15 @@ pub fn expand_expr_is(input: TokenStream) -> TokenStream {
// Also compare the raw tts to make sure they line up.
assert_ts_eq(&expected, &expanded);

assert!(comma.span().eq(&pre_expand_span), "pre-expansion span is still equal");
assert_eq!(pre_expand_ident.to_string(), "ident", "pre-expansion identifier is still valid");
assert_eq!(
pre_expand_literal.to_string(),
"\"literal\"",
"pre-expansion literal is still valid"
);
assert!(Span::call_site().eq(&pre_expand_call_site), "pre-expansion call-site is still equal");

TokenStream::new()
}

Expand Down