Skip to content

Commit 4563a7e

Browse files
committed
Auto merge of rust-lang#122939 - joboet:proc_macro_bridge_state, r=<try>
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 d6eb0f5 + bca4a3b commit 4563a7e

File tree

3 files changed

+70
-127
lines changed

3 files changed

+70
-127
lines changed

library/proc_macro/src/bridge/client.rs

Lines changed: 69 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,13 @@ macro_rules! define_client_side {
174174
}
175175
with_api!(self, self, define_client_side);
176176

177+
enum BridgeState<'a, 'bridge> {
178+
NotConnected,
179+
Connected(&'a mut Bridge<'bridge>),
180+
InUse,
181+
}
182+
183+
#[repr(align(2))]
177184
struct Bridge<'a> {
178185
/// Reusable buffer (only `clear`-ed, never shrunk), primarily
179186
/// used for making requests.
@@ -189,45 +196,59 @@ struct Bridge<'a> {
189196
impl<'a> !Send for Bridge<'a> {}
190197
impl<'a> !Sync for Bridge<'a> {}
191198

192-
enum BridgeState<'a> {
193-
/// No server is currently connected to this client.
194-
NotConnected,
199+
#[allow(unsafe_code)]
200+
mod state {
201+
use super::{Bridge, BridgeState};
202+
use std::cell::Cell;
203+
use std::ptr;
195204

196-
/// A server is connected and available for requests.
197-
Connected(Bridge<'a>),
205+
struct RestoreOnDrop(*mut ());
198206

199-
/// Access to the bridge is being exclusively acquired
200-
/// (e.g., during `BridgeState::with`).
201-
InUse,
202-
}
207+
impl Drop for RestoreOnDrop {
208+
fn drop(&mut self) {
209+
BRIDGE_STATE.set(self.0);
210+
}
211+
}
203212

204-
enum BridgeStateL {}
213+
thread_local! {
214+
static BRIDGE_STATE: Cell<*mut ()> = const { Cell::new(ptr::null_mut()) };
215+
}
205216

206-
impl<'a> scoped_cell::ApplyL<'a> for BridgeStateL {
207-
type Out = BridgeState<'a>;
208-
}
217+
pub(super) fn connect<'bridge, R>(bridge: &mut Bridge<'bridge>, f: impl FnOnce() -> R) -> R {
218+
let inner = ptr::from_mut(bridge).cast();
219+
let outer = BRIDGE_STATE.replace(inner);
220+
let _restore = RestoreOnDrop(outer);
209221

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

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))
225+
pub(super) fn with<R>(f: impl for<'bridge> FnOnce(BridgeState<'_, 'bridge>) -> R) -> R {
226+
// As `Bridge` has an alignment of 2, this cannot be a valid pointer.
227+
// Use it to indicate that the bridge is in use.
228+
let state = BRIDGE_STATE.replace(ptr::without_provenance_mut(1));
229+
let _restore = RestoreOnDrop(state);
230+
let bridge = if state.is_null() {
231+
BridgeState::NotConnected
232+
} else if state.addr() == 1 {
233+
BridgeState::InUse
234+
} else {
235+
// SAFETY: the only place where the pointer is set is in `connect`.
236+
// It puts back the previous value after the inner call has returned,
237+
// so we know that as long as the pointer is valid, it came from a
238+
// mutable reference to a `Bridge` that outlasts the call to this
239+
// function. Since `f` must work for any lifetime of the bridge,
240+
// including the actual one, we can lie here and say that the
241+
// lifetime is `'static` without anyone noticing.
242+
BridgeState::Connected(unsafe { &mut *(state as *mut Bridge<'static>) })
243+
};
244+
245+
f(bridge)
225246
}
226247
}
227248

228249
impl Bridge<'_> {
229250
fn with<R>(f: impl FnOnce(&mut Bridge<'_>) -> R) -> R {
230-
BridgeState::with(|state| match state {
251+
state::with(|state| match state {
231252
BridgeState::NotConnected => {
232253
panic!("procedural macro API is used outside of a procedural macro");
233254
}
@@ -240,10 +261,7 @@ impl Bridge<'_> {
240261
}
241262

242263
pub(crate) fn is_available() -> bool {
243-
BridgeState::with(|state| match state {
244-
BridgeState::Connected(_) | BridgeState::InUse => true,
245-
BridgeState::NotConnected => false,
246-
})
264+
state::with(|s| matches!(s, BridgeState::Connected(_) | BridgeState::InUse))
247265
}
248266

249267
/// A client-side RPC entry-point, which may be using a different `proc_macro`
@@ -282,11 +300,7 @@ fn maybe_install_panic_hook(force_show_panics: bool) {
282300
HIDE_PANICS_DURING_EXPANSION.call_once(|| {
283301
let prev = panic::take_hook();
284302
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 {
303+
if force_show_panics || !is_available() {
290304
prev(info)
291305
}
292306
}));
@@ -312,29 +326,24 @@ fn run_client<A: for<'a, 's> DecodeMut<'a, 's, ()>, R: Encode<()>>(
312326
let (globals, input) = <(ExpnGlobals<Span>, A)>::decode(reader, &mut ());
313327

314328
// 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-
})
329+
let mut bridge = Bridge { cached_buffer: buf.take(), dispatch, globals };
330+
331+
let output = state::connect(&mut bridge, || f(input));
332+
333+
// Take the `cached_buffer` back out, for the output value.
334+
buf = bridge.cached_buffer;
335+
336+
// HACK(eddyb) Separate encoding a success value (`Ok(output)`)
337+
// from encoding a panic (`Err(e: PanicMessage)`) to avoid
338+
// having handles outside the `bridge.enter(|| ...)` scope, and
339+
// to catch panics that could happen while encoding the success.
340+
//
341+
// Note that panics should be impossible beyond this point, but
342+
// this is defensively trying to avoid any accidental panicking
343+
// reaching the `extern "C"` (which should `abort` but might not
344+
// at the moment, so this is also potentially preventing UB).
345+
buf.clear();
346+
Ok::<_, ()>(output).encode(&mut buf, &mut ());
338347
}))
339348
.map_err(PanicMessage::from)
340349
.unwrap_or_else(|e| {

library/proc_macro/src/bridge/mod.rs

Lines changed: 1 addition & 3 deletions
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

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)