Skip to content
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

proc_macro/bridge: cache static spans in proc_macro's client thread-local state #98187

Merged
merged 3 commits into from
Jun 27, 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
30 changes: 14 additions & 16 deletions compiler/rustc_expand/src/proc_macro_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use rustc_span::def_id::CrateNum;
use rustc_span::symbol::{self, kw, sym, Symbol};
use rustc_span::{BytePos, FileName, Pos, SourceFile, Span};

use pm::bridge::{server, TokenTree};
use pm::bridge::{server, ExpnGlobals, TokenTree};
use pm::{Delimiter, Level, LineColumn, Spacing};
use std::ops::Bound;
use std::{ascii, panic};
Expand Down Expand Up @@ -370,7 +370,7 @@ impl<'a, 'b> Rustc<'a, 'b> {
}

fn lit(&mut self, kind: token::LitKind, symbol: Symbol, suffix: Option<Symbol>) -> Literal {
Literal { lit: token::Lit::new(kind, symbol, suffix), span: server::Span::call_site(self) }
Literal { lit: token::Lit::new(kind, symbol, suffix), span: self.call_site }
}
}

Expand Down Expand Up @@ -547,7 +547,7 @@ impl server::Group for Rustc<'_, '_> {
Group {
delimiter,
stream: stream.unwrap_or_default(),
span: DelimSpan::from_single(server::Span::call_site(self)),
span: DelimSpan::from_single(self.call_site),
flatten: false,
}
}
Expand Down Expand Up @@ -579,7 +579,7 @@ impl server::Group for Rustc<'_, '_> {

impl server::Punct for Rustc<'_, '_> {
fn new(&mut self, ch: char, spacing: Spacing) -> Self::Punct {
Punct::new(ch, spacing == Spacing::Joint, server::Span::call_site(self))
Punct::new(ch, spacing == Spacing::Joint, self.call_site)
}

fn as_char(&mut self, punct: Self::Punct) -> char {
Expand Down Expand Up @@ -829,18 +829,6 @@ impl server::Span for Rustc<'_, '_> {
}
}

fn def_site(&mut self) -> Self::Span {
self.def_site
}

fn call_site(&mut self) -> Self::Span {
self.call_site
}

fn mixed_site(&mut self) -> Self::Span {
self.mixed_site
}

fn source_file(&mut self, span: Self::Span) -> Self::SourceFile {
self.sess().source_map().lookup_char_pos(span.lo()).file
}
Expand Down Expand Up @@ -926,3 +914,13 @@ impl server::Span for Rustc<'_, '_> {
})
}
}

impl server::Server for Rustc<'_, '_> {
fn globals(&mut self) -> ExpnGlobals<Self::Span> {
ExpnGlobals {
def_site: self.def_site,
call_site: self.call_site,
mixed_site: self.mixed_site,
}
}
}
140 changes: 85 additions & 55 deletions library/proc_macro/src/bridge/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,20 @@ impl Clone for SourceFile {
}
}

impl Span {
pub(crate) fn def_site() -> Span {
Bridge::with(|bridge| bridge.globals.def_site)
}

pub(crate) fn call_site() -> Span {
Bridge::with(|bridge| bridge.globals.call_site)
}

pub(crate) fn mixed_site() -> Span {
Bridge::with(|bridge| bridge.globals.mixed_site)
}
}

impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.debug())
Expand Down Expand Up @@ -263,6 +277,21 @@ macro_rules! define_client_side {
}
with_api!(self, self, define_client_side);

struct Bridge<'a> {
/// Reusable buffer (only `clear`-ed, never shrunk), primarily
/// used for making requests.
cached_buffer: Buffer,

/// Server-side function that the client uses to make requests.
dispatch: closure::Closure<'a, Buffer, Buffer>,

/// Provided globals for this macro expansion.
globals: ExpnGlobals<Span>,
}

impl<'a> !Send for Bridge<'a> {}
impl<'a> !Sync for Bridge<'a> {}

enum BridgeState<'a> {
/// No server is currently connected to this client.
NotConnected,
Expand Down Expand Up @@ -305,34 +334,6 @@ impl BridgeState<'_> {
}

impl Bridge<'_> {
pub(crate) fn is_available() -> bool {
BridgeState::with(|state| match state {
BridgeState::Connected(_) | BridgeState::InUse => true,
BridgeState::NotConnected => false,
})
}

fn enter<R>(self, f: impl FnOnce() -> R) -> R {
let force_show_panics = self.force_show_panics;
// Hide the default panic output within `proc_macro` expansions.
// NB. the server can't do this because it may use a different libstd.
static HIDE_PANICS_DURING_EXPANSION: Once = Once::new();
HIDE_PANICS_DURING_EXPANSION.call_once(|| {
let prev = panic::take_hook();
panic::set_hook(Box::new(move |info| {
let show = BridgeState::with(|state| match state {
BridgeState::NotConnected => true,
BridgeState::Connected(_) | BridgeState::InUse => force_show_panics,
});
if show {
prev(info)
}
}));
});

BRIDGE_STATE.with(|state| state.set(BridgeState::Connected(self), f))
}

fn with<R>(f: impl FnOnce(&mut Bridge<'_>) -> R) -> R {
BridgeState::with(|state| match state {
BridgeState::NotConnected => {
Expand All @@ -346,6 +347,13 @@ impl Bridge<'_> {
}
}

pub(crate) fn is_available() -> bool {
BridgeState::with(|state| match state {
BridgeState::Connected(_) | BridgeState::InUse => true,
BridgeState::NotConnected => false,
})
}

/// A client-side RPC entry-point, which may be using a different `proc_macro`
/// from the one used by the server, but can be invoked compatibly.
///
Expand All @@ -363,7 +371,7 @@ pub struct Client<I, O> {
// a wrapper `fn` pointer, once `const fn` can reference `static`s.
pub(super) get_handle_counters: extern "C" fn() -> &'static HandleCounters,

pub(super) run: extern "C" fn(Bridge<'_>) -> Buffer,
pub(super) run: extern "C" fn(BridgeConfig<'_>) -> Buffer,

pub(super) _marker: PhantomData<fn(I) -> O>,
}
Expand All @@ -375,40 +383,62 @@ impl<I, O> Clone for Client<I, O> {
}
}

fn maybe_install_panic_hook(force_show_panics: bool) {
// Hide the default panic output within `proc_macro` expansions.
// NB. the server can't do this because it may use a different libstd.
static HIDE_PANICS_DURING_EXPANSION: Once = Once::new();
HIDE_PANICS_DURING_EXPANSION.call_once(|| {
let prev = panic::take_hook();
panic::set_hook(Box::new(move |info| {
let show = BridgeState::with(|state| match state {
BridgeState::NotConnected => true,
BridgeState::Connected(_) | BridgeState::InUse => force_show_panics,
});
if show {
prev(info)
}
}));
});
}

/// Client-side helper for handling client panics, entering the bridge,
/// deserializing input and serializing output.
// FIXME(eddyb) maybe replace `Bridge::enter` with this?
fn run_client<A: for<'a, 's> DecodeMut<'a, 's, ()>, R: Encode<()>>(
mut bridge: Bridge<'_>,
config: BridgeConfig<'_>,
f: impl FnOnce(A) -> R,
) -> Buffer {
// The initial `cached_buffer` contains the input.
let mut buf = bridge.cached_buffer.take();
let BridgeConfig { input: mut buf, dispatch, force_show_panics, .. } = config;

panic::catch_unwind(panic::AssertUnwindSafe(|| {
bridge.enter(|| {
let reader = &mut &buf[..];
let input = A::decode(reader, &mut ());

// Put the `cached_buffer` back in the `Bridge`, for requests.
Bridge::with(|bridge| bridge.cached_buffer = buf.take());

let output = f(input);

// Take the `cached_buffer` back out, for the output value.
buf = Bridge::with(|bridge| bridge.cached_buffer.take());

// HACK(eddyb) Separate encoding a success value (`Ok(output)`)
// from encoding a panic (`Err(e: PanicMessage)`) to avoid
// having handles outside the `bridge.enter(|| ...)` scope, and
// to catch panics that could happen while encoding the success.
//
// Note that panics should be impossible beyond this point, but
// this is defensively trying to avoid any accidental panicking
// reaching the `extern "C"` (which should `abort` but might not
// at the moment, so this is also potentially preventing UB).
buf.clear();
Ok::<_, ()>(output).encode(&mut buf, &mut ());
maybe_install_panic_hook(force_show_panics);

let reader = &mut &buf[..];
let (globals, input) = <(ExpnGlobals<Span>, A)>::decode(reader, &mut ());

// Put the buffer we used for input back in the `Bridge` for requests.
let new_state =
BridgeState::Connected(Bridge { cached_buffer: buf.take(), dispatch, globals });

BRIDGE_STATE.with(|state| {
state.set(new_state, || {
let output = f(input);

// Take the `cached_buffer` back out, for the output value.
buf = Bridge::with(|bridge| bridge.cached_buffer.take());

// HACK(eddyb) Separate encoding a success value (`Ok(output)`)
// from encoding a panic (`Err(e: PanicMessage)`) to avoid
// having handles outside the `bridge.enter(|| ...)` scope, and
// to catch panics that could happen while encoding the success.
//
// Note that panics should be impossible beyond this point, but
// this is defensively trying to avoid any accidental panicking
// reaching the `extern "C"` (which should `abort` but might not
// at the moment, so this is also potentially preventing UB).
buf.clear();
Ok::<_, ()>(output).encode(&mut buf, &mut ());
})
})
}))
.map_err(PanicMessage::from)
Expand Down
50 changes: 39 additions & 11 deletions library/proc_macro/src/bridge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,6 @@ macro_rules! with_api {
},
Span {
fn debug($self: $S::Span) -> String;
fn def_site() -> $S::Span;
fn call_site() -> $S::Span;
fn mixed_site() -> $S::Span;
fn source_file($self: $S::Span) -> $S::SourceFile;
fn parent($self: $S::Span) -> Option<$S::Span>;
fn source($self: $S::Span) -> $S::Span;
Expand Down Expand Up @@ -213,16 +210,15 @@ use buffer::Buffer;
pub use rpc::PanicMessage;
use rpc::{Decode, DecodeMut, Encode, Reader, Writer};

/// An active connection between a server and a client.
/// The server creates the bridge (`Bridge::run_server` in `server.rs`),
/// then passes it to the client through the function pointer in the `run`
/// field of `client::Client`. The client holds its copy of the `Bridge`
/// Configuration for establishing an active connection between a server and a
/// client. The server creates the bridge config (`run_server` in `server.rs`),
/// then passes it to the client through the function pointer in the `run` field
/// of `client::Client`. The client constructs a local `Bridge` from the config
/// in TLS during its execution (`Bridge::{enter, with}` in `client.rs`).
#[repr(C)]
pub struct Bridge<'a> {
/// Reusable buffer (only `clear`-ed, never shrunk), primarily
/// used for making requests, but also for passing input to client.
cached_buffer: Buffer,
pub struct BridgeConfig<'a> {
/// Buffer used to pass initial input to the client.
input: Buffer,

/// Server-side function that the client uses to make requests.
dispatch: closure::Closure<'a, Buffer, Buffer>,
Expand Down Expand Up @@ -379,6 +375,25 @@ rpc_encode_decode!(
);

macro_rules! mark_compound {
(struct $name:ident <$($T:ident),+> { $($field:ident),* $(,)? }) => {
impl<$($T: Mark),+> Mark for $name <$($T),+> {
type Unmarked = $name <$($T::Unmarked),+>;
fn mark(unmarked: Self::Unmarked) -> Self {
$name {
$($field: Mark::mark(unmarked.$field)),*
}
}
}

impl<$($T: Unmark),+> Unmark for $name <$($T),+> {
type Unmarked = $name <$($T::Unmarked),+>;
fn unmark(self) -> Self::Unmarked {
$name {
$($field: Unmark::unmark(self.$field)),*
}
}
}
};
(enum $name:ident <$($T:ident),+> { $($variant:ident $(($field:ident))?),* $(,)? }) => {
impl<$($T: Mark),+> Mark for $name <$($T),+> {
type Unmarked = $name <$($T::Unmarked),+>;
Expand Down Expand Up @@ -449,3 +464,16 @@ compound_traits!(
Literal(tt),
}
);

/// Globals provided alongside the initial inputs for a macro expansion.
/// Provides values such as spans which are used frequently to avoid RPC.
#[derive(Clone)]
pub struct ExpnGlobals<S> {
pub def_site: S,
pub call_site: S,
pub mixed_site: S,
}

compound_traits!(
struct ExpnGlobals<Sp> { def_site, call_site, mixed_site }
);
11 changes: 6 additions & 5 deletions library/proc_macro/src/bridge/selfless_reify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ macro_rules! define_reify_functions {
define_reify_functions! {
fn _reify_to_extern_c_fn_unary<A, R> for extern "C" fn(arg: A) -> R;

// HACK(eddyb) this abstraction is used with `for<'a> fn(Bridge<'a>) -> T`
// but that doesn't work with just `reify_to_extern_c_fn_unary` because of
// the `fn` pointer type being "higher-ranked" (i.e. the `for<'a>` binder).
// FIXME(eddyb) try to remove the lifetime from `Bridge`, that'd help.
fn reify_to_extern_c_fn_hrt_bridge<R> for extern "C" fn(bridge: super::Bridge<'_>) -> R;
// HACK(eddyb) this abstraction is used with `for<'a> fn(BridgeConfig<'a>)
// -> T` but that doesn't work with just `reify_to_extern_c_fn_unary`
// because of the `fn` pointer type being "higher-ranked" (i.e. the
// `for<'a>` binder).
// FIXME(eddyb) try to remove the lifetime from `BridgeConfig`, that'd help.
fn reify_to_extern_c_fn_hrt_bridge<R> for extern "C" fn(bridge: super::BridgeConfig<'_>) -> R;
}
Loading