-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Experimental: Add Derive Proc-Macro Caching #129102
base: master
Are you sure you want to change the base?
Changes from all commits
d5bc711
de3349c
461c7a7
f2acfe5
7ff4676
035d859
7d6ce77
74dd646
10657b6
6296fcb
14cb620
ae18144
5528931
267a5c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,12 +14,14 @@ | |
//! ownership of the original. | ||
|
||
use std::borrow::Cow; | ||
use std::hash::Hash; | ||
use std::{cmp, fmt, iter}; | ||
|
||
use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; | ||
use rustc_data_structures::sync::{self, Lrc}; | ||
use rustc_macros::{Decodable, Encodable, HashStable_Generic}; | ||
use rustc_serialize::{Decodable, Encodable}; | ||
use rustc_serialize::{Decodable, Encodable, Encoder}; | ||
use rustc_span::def_id::{CrateNum, DefIndex}; | ||
use rustc_span::{sym, Span, SpanDecoder, SpanEncoder, Symbol, DUMMY_SP}; | ||
|
||
use crate::ast::{AttrStyle, StmtKind}; | ||
|
@@ -140,6 +142,11 @@ impl fmt::Debug for LazyAttrTokenStream { | |
|
||
impl<S: SpanEncoder> Encodable<S> for LazyAttrTokenStream { | ||
fn encode(&self, _s: &mut S) { | ||
// FIXME(pr-time): Just a reminder that this exists/was tried out, | ||
// but probably not necessary anymore (see below). | ||
// self.to_attr_token_stream().encode(s) | ||
// We should not need to anymore, now that we `flatten`? | ||
// Yep, that seems to be true! :) | ||
panic!("Attempted to encode LazyAttrTokenStream"); | ||
} | ||
} | ||
|
@@ -296,6 +303,96 @@ pub struct AttrsTarget { | |
#[derive(Clone, Debug, Default, Encodable, Decodable)] | ||
pub struct TokenStream(pub(crate) Lrc<Vec<TokenTree>>); | ||
|
||
struct HashEncoder<H: std::hash::Hasher> { | ||
hasher: H, | ||
} | ||
|
||
impl<H: std::hash::Hasher> Encoder for HashEncoder<H> { | ||
fn emit_usize(&mut self, v: usize) { | ||
self.hasher.write_usize(v) | ||
} | ||
|
||
fn emit_u128(&mut self, v: u128) { | ||
self.hasher.write_u128(v) | ||
} | ||
|
||
fn emit_u64(&mut self, v: u64) { | ||
self.hasher.write_u64(v) | ||
} | ||
|
||
fn emit_u32(&mut self, v: u32) { | ||
self.hasher.write_u32(v) | ||
} | ||
|
||
fn emit_u16(&mut self, v: u16) { | ||
self.hasher.write_u16(v) | ||
} | ||
|
||
fn emit_u8(&mut self, v: u8) { | ||
self.hasher.write_u8(v) | ||
} | ||
|
||
fn emit_isize(&mut self, v: isize) { | ||
self.hasher.write_isize(v) | ||
} | ||
|
||
fn emit_i128(&mut self, v: i128) { | ||
self.hasher.write_i128(v) | ||
} | ||
|
||
fn emit_i64(&mut self, v: i64) { | ||
self.hasher.write_i64(v) | ||
} | ||
|
||
fn emit_i32(&mut self, v: i32) { | ||
self.hasher.write_i32(v) | ||
} | ||
|
||
fn emit_i16(&mut self, v: i16) { | ||
self.hasher.write_i16(v) | ||
} | ||
|
||
fn emit_raw_bytes(&mut self, s: &[u8]) { | ||
self.hasher.write(s) | ||
} | ||
} | ||
|
||
impl<H: std::hash::Hasher> SpanEncoder for HashEncoder<H> { | ||
fn encode_span(&mut self, span: Span) { | ||
span.hash(&mut self.hasher) | ||
} | ||
|
||
fn encode_symbol(&mut self, symbol: Symbol) { | ||
symbol.hash(&mut self.hasher) | ||
} | ||
|
||
fn encode_expn_id(&mut self, expn_id: rustc_span::ExpnId) { | ||
expn_id.hash(&mut self.hasher) | ||
} | ||
|
||
fn encode_syntax_context(&mut self, syntax_context: rustc_span::SyntaxContext) { | ||
syntax_context.hash(&mut self.hasher) | ||
} | ||
|
||
fn encode_crate_num(&mut self, crate_num: CrateNum) { | ||
crate_num.hash(&mut self.hasher) | ||
} | ||
|
||
fn encode_def_index(&mut self, def_index: DefIndex) { | ||
def_index.hash(&mut self.hasher) | ||
} | ||
|
||
fn encode_def_id(&mut self, def_id: rustc_span::def_id::DefId) { | ||
def_id.hash(&mut self.hasher) | ||
} | ||
} | ||
|
||
impl Hash for TokenStream { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is implementation of unstable hash required for (Also the use of |
||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { | ||
Encodable::encode(self, &mut HashEncoder { hasher: state }); | ||
} | ||
} | ||
|
||
/// Indicates whether a token can join with the following token to form a | ||
/// compound token. Used for conversions to `proc_macro::Spacing`. Also used to | ||
/// guide pretty-printing, which is where the `JointHidden` value (which isn't | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
use std::cell::Cell; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's probably better to merge this file into |
||
use std::ptr::{self, NonNull}; | ||
|
||
use rustc_ast::tokenstream::TokenStream; | ||
use rustc_data_structures::svh::Svh; | ||
use rustc_middle::ty::TyCtxt; | ||
use rustc_span::profiling::SpannedEventArgRecorder; | ||
use rustc_span::LocalExpnId; | ||
|
||
use crate::base::ExtCtxt; | ||
use crate::errors; | ||
|
||
pub(super) fn provide_derive_macro_expansion<'tcx>( | ||
tcx: TyCtxt<'tcx>, | ||
key: (LocalExpnId, Svh, &'tcx TokenStream), | ||
) -> Result<&'tcx TokenStream, ()> { | ||
let (invoc_id, _macro_crate_hash, input) = key; | ||
|
||
let res = with_context(|(ecx, client)| { | ||
let span = invoc_id.expn_data().call_site; | ||
let _timer = ecx.sess.prof.generic_activity_with_arg_recorder( | ||
"expand_derive_proc_macro_inner", | ||
|recorder| { | ||
recorder.record_arg_with_span(ecx.sess.source_map(), ecx.expansion_descr(), span); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
}, | ||
); | ||
let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace; | ||
let strategy = crate::proc_macro::exec_strategy(ecx); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't need |
||
let server = crate::proc_macro_server::Rustc::new(ecx); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of the logic in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is why I didn't bother replacing the other uses of |
||
let res = match client.run(&strategy, server, input.clone(), proc_macro_backtrace) { | ||
// FIXME(pr-time): without flattened some (weird) tests fail, but no idea if it's correct/enough | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Flattening again? |
||
Ok(stream) => Ok(tcx.arena.alloc(stream.flattened()) as &TokenStream), | ||
Err(e) => { | ||
ecx.dcx().emit_err({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anything that can use |
||
errors::ProcMacroDerivePanicked { | ||
span, | ||
message: e.as_str().map(|message| errors::ProcMacroDerivePanickedHelp { | ||
message: message.into(), | ||
}), | ||
} | ||
}); | ||
Err(()) | ||
} | ||
}; | ||
res | ||
}); | ||
|
||
res | ||
} | ||
|
||
type CLIENT = pm::bridge::client::Client<pm::TokenStream, pm::TokenStream>; | ||
|
||
// based on rust/compiler/rustc_middle/src/ty/context/tls.rs | ||
thread_local! { | ||
/// A thread local variable that stores a pointer to the current `CONTEXT`. | ||
static TLV: Cell<(*mut (), Option<CLIENT>)> = const { Cell::new((ptr::null_mut(), None)) }; | ||
} | ||
|
||
/// Sets `context` as the new current `CONTEXT` for the duration of the function `f`. | ||
#[inline] | ||
pub(crate) fn enter_context<'a, F, R>(context: (&mut ExtCtxt<'a>, CLIENT), f: F) -> R | ||
where | ||
F: FnOnce() -> R, | ||
{ | ||
let (ectx, client) = context; | ||
let erased = (ectx as *mut _ as *mut (), Some(client)); | ||
TLV.with(|tlv| { | ||
let old = tlv.replace(erased); | ||
let _reset = rustc_data_structures::defer(move || tlv.set(old)); | ||
f() | ||
}) | ||
} | ||
|
||
/// Allows access to the current `CONTEXT`. | ||
/// Panics if there is no `CONTEXT` available. | ||
#[inline] | ||
#[track_caller] | ||
fn with_context<F, R>(f: F) -> R | ||
where | ||
F: for<'a, 'b> FnOnce(&'b mut (&mut ExtCtxt<'a>, CLIENT)) -> R, | ||
{ | ||
let (ectx, client_opt) = TLV.get(); | ||
let ectx = NonNull::new(ectx).expect("no CONTEXT stored in tls"); | ||
|
||
// We could get an `CONTEXT` pointer from another thread. | ||
// Ensure that `CONTEXT` is `DynSync`. | ||
// FIXME(pr-time): we should not be able to? | ||
// sync::assert_dyn_sync::<CONTEXT<'_>>(); | ||
|
||
// prevent double entering, as that would allow creating two `&mut ExtCtxt`s | ||
// FIXME(pr-time): probably use a RefCell instead (which checks this properly)? | ||
TLV.with(|tlv| { | ||
let old = tlv.replace((ptr::null_mut(), None)); | ||
let _reset = rustc_data_structures::defer(move || tlv.set(old)); | ||
let ectx = { | ||
let mut casted = ectx.cast::<ExtCtxt<'_>>(); | ||
unsafe { casted.as_mut() } | ||
}; | ||
|
||
f(&mut (ectx, client_opt.unwrap())) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ use rustc_ast as ast; | |
use rustc_ast::ptr::P; | ||
use rustc_ast::tokenstream::TokenStream; | ||
use rustc_errors::ErrorGuaranteed; | ||
use rustc_middle::ty; | ||
use rustc_parse::parser::{ForceCollect, Parser}; | ||
use rustc_session::config::ProcMacroExecutionStrategy; | ||
use rustc_span::profiling::SpannedEventArgRecorder; | ||
|
@@ -31,7 +32,7 @@ impl<T> pm::bridge::server::MessagePipe<T> for MessagePipe<T> { | |
} | ||
} | ||
|
||
fn exec_strategy(ecx: &ExtCtxt<'_>) -> impl pm::bridge::server::ExecutionStrategy { | ||
pub fn exec_strategy(ecx: &ExtCtxt<'_>) -> impl pm::bridge::server::ExecutionStrategy { | ||
pm::bridge::server::MaybeCrossThread::<MessagePipe<_>>::new( | ||
ecx.sess.opts.unstable_opts.proc_macro_execution_strategy | ||
== ProcMacroExecutionStrategy::CrossThread, | ||
|
@@ -114,6 +115,13 @@ impl MultiItemModifier for DeriveProcMacro { | |
item: Annotatable, | ||
_is_derive_const: bool, | ||
) -> ExpandResult<Vec<Annotatable>, Annotatable> { | ||
let _timer = ecx.sess.prof.generic_activity_with_arg_recorder( | ||
"expand_derive_proc_macro_outer", | ||
|recorder| { | ||
recorder.record_arg_with_span(ecx.sess.source_map(), ecx.expansion_descr(), span); | ||
}, | ||
); | ||
|
||
// We need special handling for statement items | ||
// (e.g. `fn foo() { #[derive(Debug)] struct Bar; }`) | ||
let is_stmt = matches!(item, Annotatable::Stmt(..)); | ||
|
@@ -124,36 +132,38 @@ impl MultiItemModifier for DeriveProcMacro { | |
// altogether. See #73345. | ||
crate::base::ann_pretty_printing_compatibility_hack(&item, &ecx.sess); | ||
let input = item.to_tokens(); | ||
let stream = { | ||
let _timer = | ||
ecx.sess.prof.generic_activity_with_arg_recorder("expand_proc_macro", |recorder| { | ||
recorder.record_arg_with_span( | ||
ecx.sess.source_map(), | ||
ecx.expansion_descr(), | ||
span, | ||
); | ||
}); | ||
let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace; | ||
let strategy = exec_strategy(ecx); | ||
let server = proc_macro_server::Rustc::new(ecx); | ||
match self.client.run(&strategy, server, input, proc_macro_backtrace) { | ||
Ok(stream) => stream, | ||
Err(e) => { | ||
ecx.dcx().emit_err({ | ||
errors::ProcMacroDerivePanicked { | ||
span, | ||
message: e.as_str().map(|message| { | ||
errors::ProcMacroDerivePanickedHelp { message: message.into() } | ||
}), | ||
} | ||
}); | ||
return ExpandResult::Ready(vec![]); | ||
let res = ty::tls::with(|tcx| { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This shouldn't be necessary, |
||
// FIXME(pr-time): without flattened some (weird) tests fail, but no idea if it's correct/enough | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In theory flattening should always be correct (proc macro APIs will perform flattening lazily when necessary), but in practice there may be issues with badly written proc macros that pretty-print token streams and then parse the result as a text. |
||
let input = tcx.arena.alloc(input.flattened()) as &TokenStream; | ||
let invoc_id = ecx.current_expansion.id; | ||
|
||
// FIXME(pr-time): Just using the crate hash to notice when the proc-macro code has | ||
// changed. How to *correctly* depend on exactly the macro definition? | ||
// I.e., depending on the crate hash is just a HACK (and leaves garbage in the | ||
// incremental compilation dir). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which garbage? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought that the incremental cache entries with old crate hashes would lead to garbage in the incremental dir. But now that mention it, this seems like it's actually the normal case for incremental comp, so it should probably be covered by whatever mechanisms are already in play. Is that the case? |
||
let macro_def_id = invoc_id.expn_data().macro_def_id.unwrap(); | ||
let proc_macro_crate_hash = tcx.crate_hash(macro_def_id.krate); | ||
|
||
assert_eq!(invoc_id.expn_data().call_site, span); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Operations on global hygiene data like |
||
|
||
let res = crate::derive_macro_expansion::enter_context((ecx, self.client), move || { | ||
let key = (invoc_id, proc_macro_crate_hash, input); | ||
if tcx.sess.opts.unstable_opts.cache_all_derive_macros { | ||
tcx.derive_macro_expansion(key).cloned() | ||
} else { | ||
crate::derive_macro_expansion::provide_derive_macro_expansion(tcx, key).cloned() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need to setup any global context in this case, we could just pass arguments, the context only needs to be setup around the query call. |
||
} | ||
} | ||
}); | ||
|
||
res | ||
}); | ||
let Ok(output) = res else { | ||
// error will already have been emitted | ||
return ExpandResult::Ready(vec![]); | ||
}; | ||
|
||
let error_count_before = ecx.dcx().err_count(); | ||
let mut parser = Parser::new(&ecx.sess.psess, stream, Some("proc-macro derive")); | ||
let mut parser = Parser::new(&ecx.sess.psess, output, Some("proc-macro derive")); | ||
let mut items = vec![]; | ||
|
||
loop { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment can be removed now.