Skip to content

Commit 71969a5

Browse files
futileKobzol
authored andcommitted
Implement incremental caching for derive macro expansions
1 parent b1b26b8 commit 71969a5

File tree

15 files changed

+356
-35
lines changed

15 files changed

+356
-35
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3833,6 +3833,7 @@ dependencies = [
38333833
"rustc_lexer",
38343834
"rustc_lint_defs",
38353835
"rustc_macros",
3836+
"rustc_middle",
38363837
"rustc_parse",
38373838
"rustc_proc_macro",
38383839
"rustc_serialize",

compiler/rustc_ast/src/tokenstream.rs

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@
1414
//! ownership of the original.
1515
1616
use std::borrow::Cow;
17+
use std::hash::Hash;
1718
use std::ops::Range;
1819
use std::sync::Arc;
1920
use std::{cmp, fmt, iter, mem};
2021

2122
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
2223
use rustc_data_structures::sync;
2324
use rustc_macros::{Decodable, Encodable, HashStable_Generic, Walkable};
24-
use rustc_serialize::{Decodable, Encodable};
25-
use rustc_span::{DUMMY_SP, Span, SpanDecoder, SpanEncoder, Symbol, sym};
25+
use rustc_serialize::{Decodable, Encodable, Encoder};
26+
use rustc_span::def_id::{CrateNum, DefIndex};
27+
use rustc_span::{ByteSymbol, DUMMY_SP, Span, SpanDecoder, SpanEncoder, Symbol, sym};
2628
use thin_vec::ThinVec;
2729

2830
use crate::ast::AttrStyle;
@@ -560,6 +562,100 @@ pub struct AttrsTarget {
560562
#[derive(Clone, Debug, Default, Encodable, Decodable)]
561563
pub struct TokenStream(pub(crate) Arc<Vec<TokenTree>>);
562564

565+
struct HashEncoder<H: std::hash::Hasher> {
566+
hasher: H,
567+
}
568+
569+
impl<H: std::hash::Hasher> Encoder for HashEncoder<H> {
570+
fn emit_usize(&mut self, v: usize) {
571+
self.hasher.write_usize(v)
572+
}
573+
574+
fn emit_u128(&mut self, v: u128) {
575+
self.hasher.write_u128(v)
576+
}
577+
578+
fn emit_u64(&mut self, v: u64) {
579+
self.hasher.write_u64(v)
580+
}
581+
582+
fn emit_u32(&mut self, v: u32) {
583+
self.hasher.write_u32(v)
584+
}
585+
586+
fn emit_u16(&mut self, v: u16) {
587+
self.hasher.write_u16(v)
588+
}
589+
590+
fn emit_u8(&mut self, v: u8) {
591+
self.hasher.write_u8(v)
592+
}
593+
594+
fn emit_isize(&mut self, v: isize) {
595+
self.hasher.write_isize(v)
596+
}
597+
598+
fn emit_i128(&mut self, v: i128) {
599+
self.hasher.write_i128(v)
600+
}
601+
602+
fn emit_i64(&mut self, v: i64) {
603+
self.hasher.write_i64(v)
604+
}
605+
606+
fn emit_i32(&mut self, v: i32) {
607+
self.hasher.write_i32(v)
608+
}
609+
610+
fn emit_i16(&mut self, v: i16) {
611+
self.hasher.write_i16(v)
612+
}
613+
614+
fn emit_raw_bytes(&mut self, s: &[u8]) {
615+
self.hasher.write(s)
616+
}
617+
}
618+
619+
impl<H: std::hash::Hasher> SpanEncoder for HashEncoder<H> {
620+
fn encode_span(&mut self, span: Span) {
621+
span.hash(&mut self.hasher)
622+
}
623+
624+
fn encode_symbol(&mut self, symbol: Symbol) {
625+
symbol.hash(&mut self.hasher)
626+
}
627+
628+
fn encode_byte_symbol(&mut self, byte_sym: ByteSymbol) {
629+
byte_sym.hash(&mut self.hasher);
630+
}
631+
632+
fn encode_expn_id(&mut self, expn_id: rustc_span::ExpnId) {
633+
expn_id.hash(&mut self.hasher)
634+
}
635+
636+
fn encode_syntax_context(&mut self, syntax_context: rustc_span::SyntaxContext) {
637+
syntax_context.hash(&mut self.hasher)
638+
}
639+
640+
fn encode_crate_num(&mut self, crate_num: CrateNum) {
641+
crate_num.hash(&mut self.hasher)
642+
}
643+
644+
fn encode_def_index(&mut self, def_index: DefIndex) {
645+
def_index.hash(&mut self.hasher)
646+
}
647+
648+
fn encode_def_id(&mut self, def_id: rustc_span::def_id::DefId) {
649+
def_id.hash(&mut self.hasher)
650+
}
651+
}
652+
653+
impl Hash for TokenStream {
654+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
655+
Encodable::encode(self, &mut HashEncoder { hasher: state });
656+
}
657+
}
658+
563659
/// Indicates whether a token can join with the following token to form a
564660
/// compound token. Used for conversions to `proc_macro::Spacing`. Also used to
565661
/// guide pretty-printing, which is where the `JointHidden` value (which isn't

compiler/rustc_expand/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ rustc_hir = { path = "../rustc_hir" }
2121
rustc_lexer = { path = "../rustc_lexer" }
2222
rustc_lint_defs = { path = "../rustc_lint_defs" }
2323
rustc_macros = { path = "../rustc_macros" }
24+
rustc_middle = { path = "../rustc_middle" }
2425
rustc_parse = { path = "../rustc_parse" }
2526
# We must use the proc_macro version that we will compile proc-macros against,
2627
# not the one from our own sysroot.

compiler/rustc_expand/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,8 @@ pub mod module;
3131
#[allow(rustc::untranslatable_diagnostic)]
3232
pub mod proc_macro;
3333

34+
pub fn provide(providers: &mut rustc_middle::util::Providers) {
35+
providers.derive_macro_expansion = proc_macro::provide_derive_macro_expansion;
36+
}
37+
3438
rustc_fluent_macro::fluent_messages! { "../messages.ftl" }

compiler/rustc_expand/src/proc_macro.rs

Lines changed: 146 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
use std::cell::Cell;
2+
use std::ptr::NonNull;
3+
14
use rustc_ast::tokenstream::TokenStream;
5+
use rustc_data_structures::svh::Svh;
26
use rustc_errors::ErrorGuaranteed;
7+
use rustc_middle::ty::{self, TyCtxt};
38
use rustc_parse::parser::{ForceCollect, Parser};
9+
use rustc_session::Session;
410
use rustc_session::config::ProcMacroExecutionStrategy;
5-
use rustc_span::Span;
611
use rustc_span::profiling::SpannedEventArgRecorder;
12+
use rustc_span::{LocalExpnId, Span};
713
use {rustc_ast as ast, rustc_proc_macro as pm};
814

915
use crate::base::{self, *};
@@ -30,9 +36,9 @@ impl<T> pm::bridge::server::MessagePipe<T> for MessagePipe<T> {
3036
}
3137
}
3238

33-
fn exec_strategy(ecx: &ExtCtxt<'_>) -> impl pm::bridge::server::ExecutionStrategy + 'static {
39+
pub fn exec_strategy(sess: &Session) -> impl pm::bridge::server::ExecutionStrategy + 'static {
3440
pm::bridge::server::MaybeCrossThread::<MessagePipe<_>>::new(
35-
ecx.sess.opts.unstable_opts.proc_macro_execution_strategy
41+
sess.opts.unstable_opts.proc_macro_execution_strategy
3642
== ProcMacroExecutionStrategy::CrossThread,
3743
)
3844
}
@@ -54,7 +60,7 @@ impl base::BangProcMacro for BangProcMacro {
5460
});
5561

5662
let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
57-
let strategy = exec_strategy(ecx);
63+
let strategy = exec_strategy(ecx.sess);
5864
let server = proc_macro_server::Rustc::new(ecx);
5965
self.client.run(&strategy, server, input, proc_macro_backtrace).map_err(|e| {
6066
ecx.dcx().emit_err(errors::ProcMacroPanicked {
@@ -85,7 +91,7 @@ impl base::AttrProcMacro for AttrProcMacro {
8591
});
8692

8793
let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
88-
let strategy = exec_strategy(ecx);
94+
let strategy = exec_strategy(ecx.sess);
8995
let server = proc_macro_server::Rustc::new(ecx);
9096
self.client.run(&strategy, server, annotation, annotated, proc_macro_backtrace).map_err(
9197
|e| {
@@ -113,6 +119,13 @@ impl MultiItemModifier for DeriveProcMacro {
113119
item: Annotatable,
114120
_is_derive_const: bool,
115121
) -> ExpandResult<Vec<Annotatable>, Annotatable> {
122+
let _timer = ecx.sess.prof.generic_activity_with_arg_recorder(
123+
"expand_derive_proc_macro_outer",
124+
|recorder| {
125+
recorder.record_arg_with_span(ecx.sess.source_map(), ecx.expansion_descr(), span);
126+
},
127+
);
128+
116129
// We need special handling for statement items
117130
// (e.g. `fn foo() { #[derive(Debug)] struct Bar; }`)
118131
let is_stmt = matches!(item, Annotatable::Stmt(..));
@@ -123,36 +136,39 @@ impl MultiItemModifier for DeriveProcMacro {
123136
// altogether. See #73345.
124137
crate::base::ann_pretty_printing_compatibility_hack(&item, &ecx.sess.psess);
125138
let input = item.to_tokens();
126-
let stream = {
127-
let _timer =
128-
ecx.sess.prof.generic_activity_with_arg_recorder("expand_proc_macro", |recorder| {
129-
recorder.record_arg_with_span(
130-
ecx.sess.source_map(),
131-
ecx.expansion_descr(),
132-
span,
133-
);
134-
});
135-
let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
136-
let strategy = exec_strategy(ecx);
137-
let server = proc_macro_server::Rustc::new(ecx);
138-
match self.client.run(&strategy, server, input, proc_macro_backtrace) {
139-
Ok(stream) => stream,
140-
Err(e) => {
141-
ecx.dcx().emit_err({
142-
errors::ProcMacroDerivePanicked {
143-
span,
144-
message: e.as_str().map(|message| {
145-
errors::ProcMacroDerivePanickedHelp { message: message.into() }
146-
}),
147-
}
148-
});
149-
return ExpandResult::Ready(vec![]);
150-
}
139+
let res = ty::tls::with(|tcx| {
140+
let input = tcx.arena.alloc(input) as &TokenStream;
141+
let invoc_id = ecx.current_expansion.id;
142+
let invoc_expn_data = invoc_id.expn_data();
143+
144+
assert_eq!(invoc_expn_data.call_site, span);
145+
146+
// FIXME(pr-time): Is this the correct way to check for incremental compilation (as
147+
// well as for `cache_proc_macros`)?
148+
if tcx.sess.opts.incremental.is_some() && tcx.sess.opts.unstable_opts.cache_proc_macros
149+
{
150+
// FIXME(pr-time): Just using the crate hash to notice when the proc-macro code has
151+
// changed. How to *correctly* depend on exactly the macro definition?
152+
// I.e., depending on the crate hash is just a HACK, and ideally the dependency would be
153+
// more narrow.
154+
let macro_def_id = invoc_expn_data.macro_def_id.unwrap();
155+
let proc_macro_crate_hash = tcx.crate_hash(macro_def_id.krate);
156+
157+
let key = (invoc_id, proc_macro_crate_hash, input);
158+
159+
enter_context((ecx, self.client), move || tcx.derive_macro_expansion(key).cloned())
160+
} else {
161+
expand_derive_macro(tcx, invoc_id, input, ecx, self.client).cloned()
151162
}
163+
});
164+
165+
let Ok(output) = res else {
166+
// error will already have been emitted
167+
return ExpandResult::Ready(vec![]);
152168
};
153169

154170
let error_count_before = ecx.dcx().err_count();
155-
let mut parser = Parser::new(&ecx.sess.psess, stream, Some("proc-macro derive"));
171+
let mut parser = Parser::new(&ecx.sess.psess, output, Some("proc-macro derive"));
156172
let mut items = vec![];
157173

158174
loop {
@@ -180,3 +196,102 @@ impl MultiItemModifier for DeriveProcMacro {
180196
ExpandResult::Ready(items)
181197
}
182198
}
199+
200+
pub(super) fn provide_derive_macro_expansion<'tcx>(
201+
tcx: TyCtxt<'tcx>,
202+
key: (LocalExpnId, Svh, &'tcx TokenStream),
203+
) -> Result<&'tcx TokenStream, ()> {
204+
let (invoc_id, _macro_crate_hash, input) = key;
205+
206+
with_context(|(ecx, client)| expand_derive_macro(tcx, invoc_id, input, ecx, *client))
207+
}
208+
209+
type CLIENT = pm::bridge::client::Client<pm::TokenStream, pm::TokenStream>;
210+
211+
fn expand_derive_macro<'tcx>(
212+
tcx: TyCtxt<'tcx>,
213+
invoc_id: LocalExpnId,
214+
input: &'tcx TokenStream,
215+
ecx: &mut ExtCtxt<'_>,
216+
client: CLIENT,
217+
) -> Result<&'tcx TokenStream, ()> {
218+
let invoc_expn_data = invoc_id.expn_data();
219+
let span = invoc_expn_data.call_site;
220+
let event_arg = invoc_expn_data.kind.descr();
221+
let _timer = tcx.sess.prof.generic_activity_with_arg_recorder(
222+
"expand_derive_proc_macro_inner",
223+
|recorder| {
224+
recorder.record_arg_with_span(tcx.sess.source_map(), event_arg.clone(), span);
225+
},
226+
);
227+
228+
let proc_macro_backtrace = ecx.ecfg.proc_macro_backtrace;
229+
let strategy = crate::proc_macro::exec_strategy(tcx.sess);
230+
let server = crate::proc_macro_server::Rustc::new(ecx);
231+
232+
match client.run(&strategy, server, input.clone(), proc_macro_backtrace) {
233+
Ok(stream) => Ok(tcx.arena.alloc(stream) as &TokenStream),
234+
Err(e) => {
235+
tcx.dcx().emit_err({
236+
errors::ProcMacroDerivePanicked {
237+
span,
238+
message: e.as_str().map(|message| errors::ProcMacroDerivePanickedHelp {
239+
message: message.into(),
240+
}),
241+
}
242+
});
243+
Err(())
244+
}
245+
}
246+
}
247+
248+
// based on rust/compiler/rustc_middle/src/ty/context/tls.rs
249+
thread_local! {
250+
/// A thread local variable that stores a pointer to the current `CONTEXT`.
251+
static TLV: Cell<(*mut (), Option<CLIENT>)> = const { Cell::new((std::ptr::null_mut(), None)) };
252+
}
253+
254+
/// Sets `context` as the new current `CONTEXT` for the duration of the function `f`.
255+
#[inline]
256+
pub(crate) fn enter_context<'a, F, R>(context: (&mut ExtCtxt<'a>, CLIENT), f: F) -> R
257+
where
258+
F: FnOnce() -> R,
259+
{
260+
let (ectx, client) = context;
261+
let erased = (ectx as *mut _ as *mut (), Some(client));
262+
TLV.with(|tlv| {
263+
let old = tlv.replace(erased);
264+
let _reset = rustc_data_structures::defer(move || tlv.set(old));
265+
f()
266+
})
267+
}
268+
269+
/// Allows access to the current `CONTEXT`.
270+
/// Panics if there is no `CONTEXT` available.
271+
#[inline]
272+
#[track_caller]
273+
fn with_context<F, R>(f: F) -> R
274+
where
275+
F: for<'a, 'b> FnOnce(&'b mut (&mut ExtCtxt<'a>, CLIENT)) -> R,
276+
{
277+
let (ectx, client_opt) = TLV.get();
278+
let ectx = NonNull::new(ectx).expect("no CONTEXT stored in tls");
279+
280+
// We could get an `CONTEXT` pointer from another thread.
281+
// Ensure that `CONTEXT` is `DynSync`.
282+
// FIXME(pr-time): we should not be able to?
283+
// sync::assert_dyn_sync::<CONTEXT<'_>>();
284+
285+
// prevent double entering, as that would allow creating two `&mut ExtCtxt`s
286+
// FIXME(pr-time): probably use a RefCell instead (which checks this properly)?
287+
TLV.with(|tlv| {
288+
let old = tlv.replace((std::ptr::null_mut(), None));
289+
let _reset = rustc_data_structures::defer(move || tlv.set(old));
290+
let ectx = {
291+
let mut casted = ectx.cast::<ExtCtxt<'_>>();
292+
unsafe { casted.as_mut() }
293+
};
294+
295+
f(&mut (ectx, client_opt.unwrap()))
296+
})
297+
}

compiler/rustc_interface/src/passes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,7 @@ pub static DEFAULT_QUERY_PROVIDERS: LazyLock<Providers> = LazyLock::new(|| {
881881
providers.env_var_os = env_var_os;
882882
limits::provide(providers);
883883
proc_macro_decls::provide(providers);
884+
rustc_expand::provide(providers);
884885
rustc_const_eval::provide(providers);
885886
rustc_middle::hir::provide(providers);
886887
rustc_borrowck::provide(providers);

compiler/rustc_middle/src/arena.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ macro_rules! arena_types {
116116
[decode] specialization_graph: rustc_middle::traits::specialization_graph::Graph,
117117
[] crate_inherent_impls: rustc_middle::ty::CrateInherentImpls,
118118
[] hir_owner_nodes: rustc_hir::OwnerNodes<'tcx>,
119+
[decode] token_stream: rustc_ast::tokenstream::TokenStream,
119120
]);
120121
)
121122
}

0 commit comments

Comments
 (0)