Skip to content

Commit 536606b

Browse files
committed
Auto merge of #122939 - joboet:proc_macro_bridge_state, r=petrochenkov
Simplify proc macro bridge state Currently, `proc_macro` uses a `ScopedCell` to store the client-side proc-macro bridge. Unfortunately, this requires the `Bridge`, which has non-negligible size, to be copied out and back again on every access. In some cases, the optimizer might be able to elide these copies, but in general, this is suboptimal. This PR removes `ScopedCell` and employs a similar trick as in [`scoped_tls`](https://crates.io/crates/scoped-tls), meaning that the only thing stored in TLS is a pointer to the state, which now is a `RefCell`. Access to the pointer is then scoped so that it is always within the lifetime of the reference to the state. Unfortunately, `scoped_tls` requires the referenced type to be `'static`, which `Bridge` is not, therefore we cannot simply copy that macro but have to reimplement its TLS trick. This removes the `#[forbid(unsafe_code)]` on the `client` module so that we do not have to export `Bridge`, which currently is private, to the whole crate. I can change that, if necessary.
2 parents 519d892 + ac770f7 commit 536606b

File tree

3 files changed

+61
-135
lines changed

3 files changed

+61
-135
lines changed

library/proc_macro/src/bridge/client.rs

+60-68
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use super::*;
44

5+
use std::cell::RefCell;
56
use std::marker::PhantomData;
67
use std::sync::atomic::AtomicU32;
78

@@ -189,61 +190,61 @@ struct Bridge<'a> {
189190
impl<'a> !Send for Bridge<'a> {}
190191
impl<'a> !Sync for Bridge<'a> {}
191192

192-
enum BridgeState<'a> {
193-
/// No server is currently connected to this client.
194-
NotConnected,
193+
#[allow(unsafe_code)]
194+
mod state {
195+
use super::Bridge;
196+
use std::cell::{Cell, RefCell};
197+
use std::ptr;
195198

196-
/// A server is connected and available for requests.
197-
Connected(Bridge<'a>),
198-
199-
/// Access to the bridge is being exclusively acquired
200-
/// (e.g., during `BridgeState::with`).
201-
InUse,
202-
}
199+
thread_local! {
200+
static BRIDGE_STATE: Cell<*const ()> = const { Cell::new(ptr::null()) };
201+
}
203202

204-
enum BridgeStateL {}
203+
pub(super) fn set<'bridge, R>(state: &RefCell<Bridge<'bridge>>, f: impl FnOnce() -> R) -> R {
204+
struct RestoreOnDrop(*const ());
205+
impl Drop for RestoreOnDrop {
206+
fn drop(&mut self) {
207+
BRIDGE_STATE.set(self.0);
208+
}
209+
}
205210

206-
impl<'a> scoped_cell::ApplyL<'a> for BridgeStateL {
207-
type Out = BridgeState<'a>;
208-
}
211+
let inner = ptr::from_ref(state).cast();
212+
let outer = BRIDGE_STATE.replace(inner);
213+
let _restore = RestoreOnDrop(outer);
209214

210-
thread_local! {
211-
static BRIDGE_STATE: scoped_cell::ScopedCell<BridgeStateL> =
212-
const { scoped_cell::ScopedCell::new(BridgeState::NotConnected) };
213-
}
215+
f()
216+
}
214217

215-
impl BridgeState<'_> {
216-
/// Take exclusive control of the thread-local
217-
/// `BridgeState`, and pass it to `f`, mutably.
218-
/// The state will be restored after `f` exits, even
219-
/// by panic, including modifications made to it by `f`.
220-
///
221-
/// N.B., while `f` is running, the thread-local state
222-
/// is `BridgeState::InUse`.
223-
fn with<R>(f: impl FnOnce(&mut BridgeState<'_>) -> R) -> R {
224-
BRIDGE_STATE.with(|state| state.replace(BridgeState::InUse, f))
218+
pub(super) fn with<R>(
219+
f: impl for<'bridge> FnOnce(Option<&RefCell<Bridge<'bridge>>>) -> R,
220+
) -> R {
221+
let state = BRIDGE_STATE.get();
222+
// SAFETY: the only place where the pointer is set is in `set`. It puts
223+
// back the previous value after the inner call has returned, so we know
224+
// that as long as the pointer is not null, it came from a reference to
225+
// a `RefCell<Bridge>` that outlasts the call to this function. Since `f`
226+
// works the same for any lifetime of the bridge, including the actual
227+
// one, we can lie here and say that the lifetime is `'static` without
228+
// anyone noticing.
229+
let bridge = unsafe { state.cast::<RefCell<Bridge<'static>>>().as_ref() };
230+
f(bridge)
225231
}
226232
}
227233

228234
impl Bridge<'_> {
229235
fn with<R>(f: impl FnOnce(&mut Bridge<'_>) -> R) -> R {
230-
BridgeState::with(|state| match state {
231-
BridgeState::NotConnected => {
232-
panic!("procedural macro API is used outside of a procedural macro");
233-
}
234-
BridgeState::InUse => {
235-
panic!("procedural macro API is used while it's already in use");
236-
}
237-
BridgeState::Connected(bridge) => f(bridge),
236+
state::with(|state| {
237+
let bridge = state.expect("procedural macro API is used outside of a procedural macro");
238+
let mut bridge = bridge
239+
.try_borrow_mut()
240+
.expect("procedural macro API is used while it's already in use");
241+
f(&mut bridge)
238242
})
239243
}
240244
}
241245

242246
pub(crate) fn is_available() -> bool {
243-
BridgeState::with(|state| match state {
244-
BridgeState::Connected(_) | BridgeState::InUse => true,
245-
BridgeState::NotConnected => false,
246-
})
247+
state::with(|s| s.is_some())
247248
}
248249

249250
/// A client-side RPC entry-point, which may be using a different `proc_macro`
@@ -282,11 +283,7 @@ fn maybe_install_panic_hook(force_show_panics: bool) {
282283
HIDE_PANICS_DURING_EXPANSION.call_once(|| {
283284
let prev = panic::take_hook();
284285
panic::set_hook(Box::new(move |info| {
285-
let show = BridgeState::with(|state| match state {
286-
BridgeState::NotConnected => true,
287-
BridgeState::Connected(_) | BridgeState::InUse => force_show_panics,
288-
});
289-
if show {
286+
if force_show_panics || !is_available() {
290287
prev(info)
291288
}
292289
}));
@@ -312,29 +309,24 @@ fn run_client<A: for<'a, 's> DecodeMut<'a, 's, ()>, R: Encode<()>>(
312309
let (globals, input) = <(ExpnGlobals<Span>, A)>::decode(reader, &mut ());
313310

314311
// Put the buffer we used for input back in the `Bridge` for requests.
315-
let new_state =
316-
BridgeState::Connected(Bridge { cached_buffer: buf.take(), dispatch, globals });
317-
318-
BRIDGE_STATE.with(|state| {
319-
state.set(new_state, || {
320-
let output = f(input);
321-
322-
// Take the `cached_buffer` back out, for the output value.
323-
buf = Bridge::with(|bridge| bridge.cached_buffer.take());
324-
325-
// HACK(eddyb) Separate encoding a success value (`Ok(output)`)
326-
// from encoding a panic (`Err(e: PanicMessage)`) to avoid
327-
// having handles outside the `bridge.enter(|| ...)` scope, and
328-
// to catch panics that could happen while encoding the success.
329-
//
330-
// Note that panics should be impossible beyond this point, but
331-
// this is defensively trying to avoid any accidental panicking
332-
// reaching the `extern "C"` (which should `abort` but might not
333-
// at the moment, so this is also potentially preventing UB).
334-
buf.clear();
335-
Ok::<_, ()>(output).encode(&mut buf, &mut ());
336-
})
337-
})
312+
let state = RefCell::new(Bridge { cached_buffer: buf.take(), dispatch, globals });
313+
314+
let output = state::set(&state, || f(input));
315+
316+
// Take the `cached_buffer` back out, for the output value.
317+
buf = RefCell::into_inner(state).cached_buffer;
318+
319+
// HACK(eddyb) Separate encoding a success value (`Ok(output)`)
320+
// from encoding a panic (`Err(e: PanicMessage)`) to avoid
321+
// having handles outside the `bridge.enter(|| ...)` scope, and
322+
// to catch panics that could happen while encoding the success.
323+
//
324+
// Note that panics should be impossible beyond this point, but
325+
// this is defensively trying to avoid any accidental panicking
326+
// reaching the `extern "C"` (which should `abort` but might not
327+
// at the moment, so this is also potentially preventing UB).
328+
buf.clear();
329+
Ok::<_, ()>(output).encode(&mut buf, &mut ());
338330
}))
339331
.map_err(PanicMessage::from)
340332
.unwrap_or_else(|e| {

library/proc_macro/src/bridge/mod.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ macro_rules! reverse_decode {
154154
mod arena;
155155
#[allow(unsafe_code)]
156156
mod buffer;
157-
#[forbid(unsafe_code)]
157+
#[deny(unsafe_code)]
158158
pub mod client;
159159
#[allow(unsafe_code)]
160160
mod closure;
@@ -166,8 +166,6 @@ mod handle;
166166
#[forbid(unsafe_code)]
167167
mod rpc;
168168
#[allow(unsafe_code)]
169-
mod scoped_cell;
170-
#[allow(unsafe_code)]
171169
mod selfless_reify;
172170
#[forbid(unsafe_code)]
173171
pub mod server;

library/proc_macro/src/bridge/scoped_cell.rs

-64
This file was deleted.

0 commit comments

Comments
 (0)