Skip to content

Commit 81d5a42

Browse files
committed
Auto merge of #122207 - Urgau:cfg-check-cfg-cache, r=<try>
Add cache-based fast path for cfgs and check-cfgs This PR add a fast path for testing if a cfg is enabled and is expected. This cache reduce the number of lookup from 2/3 to just 1 (in the good path). Currently: - when there are no expecteds cfgs, we do 2 lookup (one for finding if the cfg is enabled and the second for trying to get the expected values) - when there are some expecteds cfgs, we do 3 lookup in the good case (same as above + one to see if the value is in the expected values) and 2 in the "bad" case With this PR we will only do 1 lookup if the cfg is expected (aka the good case) no matter if the cfg is actually enabled or not. See the PR changes/commits for more details. The perf runs done below shows that there is no to a slightly improvement, which is expected as we do less work, but we also don't have any benchmark with many cfgs to be assertive.
2 parents 8cef37d + e6dcf99 commit 81d5a42

File tree

3 files changed

+78
-32
lines changed

3 files changed

+78
-32
lines changed

compiler/rustc_attr/src/builtin.rs

+33-31
Original file line numberDiff line numberDiff line change
@@ -526,38 +526,40 @@ pub fn cfg_matches(
526526
) -> bool {
527527
eval_condition(cfg, sess, features, &mut |cfg| {
528528
try_gate_cfg(cfg.name, cfg.span, sess, features);
529-
match sess.psess.check_config.expecteds.get(&cfg.name) {
530-
Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => {
531-
sess.psess.buffer_lint_with_diagnostic(
532-
UNEXPECTED_CFGS,
533-
cfg.span,
534-
lint_node_id,
535-
if let Some(value) = cfg.value {
536-
format!("unexpected `cfg` condition value: `{value}`")
537-
} else {
538-
format!("unexpected `cfg` condition value: (none)")
539-
},
540-
BuiltinLintDiag::UnexpectedCfgValue(
541-
(cfg.name, cfg.name_span),
542-
cfg.value.map(|v| (v, cfg.value_span.unwrap())),
543-
),
544-
);
545-
}
546-
None if sess.psess.check_config.exhaustive_names => {
547-
sess.psess.buffer_lint_with_diagnostic(
548-
UNEXPECTED_CFGS,
549-
cfg.span,
550-
lint_node_id,
551-
format!("unexpected `cfg` condition name: `{}`", cfg.name),
552-
BuiltinLintDiag::UnexpectedCfgName(
553-
(cfg.name, cfg.name_span),
554-
cfg.value.map(|v| (v, cfg.value_span.unwrap())),
555-
),
556-
);
529+
sess.psess.config_cache.get(&(cfg.name, cfg.value)).copied().unwrap_or_else(|| {
530+
match sess.psess.check_config.expecteds.get(&cfg.name) {
531+
Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => {
532+
sess.psess.buffer_lint_with_diagnostic(
533+
UNEXPECTED_CFGS,
534+
cfg.span,
535+
lint_node_id,
536+
if let Some(value) = cfg.value {
537+
format!("unexpected `cfg` condition value: `{value}`")
538+
} else {
539+
format!("unexpected `cfg` condition value: (none)")
540+
},
541+
BuiltinLintDiag::UnexpectedCfgValue(
542+
(cfg.name, cfg.name_span),
543+
cfg.value.map(|v| (v, cfg.value_span.unwrap())),
544+
),
545+
);
546+
}
547+
None if sess.psess.check_config.exhaustive_names => {
548+
sess.psess.buffer_lint_with_diagnostic(
549+
UNEXPECTED_CFGS,
550+
cfg.span,
551+
lint_node_id,
552+
format!("unexpected `cfg` condition name: `{}`", cfg.name),
553+
BuiltinLintDiag::UnexpectedCfgName(
554+
(cfg.name, cfg.name_span),
555+
cfg.value.map(|v| (v, cfg.value_span.unwrap())),
556+
),
557+
);
558+
}
559+
_ => { /* not unexpected */ }
557560
}
558-
_ => { /* not unexpected */ }
559-
}
560-
sess.psess.config.contains(&(cfg.name, cfg.value))
561+
sess.psess.config.contains(&(cfg.name, cfg.value))
562+
})
561563
})
562564
}
563565

compiler/rustc_interface/src/interface.rs

+32-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use rustc_query_impl::QueryCtxt;
2020
use rustc_query_system::query::print_query_stack;
2121
use rustc_session::config::{self, Cfg, CheckCfg, ExpectedValues, Input, OutFileName};
2222
use rustc_session::filesearch::{self, sysroot_candidates};
23-
use rustc_session::parse::ParseSess;
23+
use rustc_session::parse::{CfgCache, ParseSess};
2424
use rustc_session::{lint, CompilerIO, EarlyDiagCtxt, Session};
2525
use rustc_span::source_map::{FileLoader, RealFileLoader, SourceMapInputs};
2626
use rustc_span::symbol::sym;
@@ -297,6 +297,35 @@ pub(crate) fn parse_check_cfg(dcx: &DiagCtxt, specs: Vec<String>) -> CheckCfg {
297297
check_cfg
298298
}
299299

300+
/// Create the config cache of `--cfg` and `--check-cfg` for internal use.
301+
///
302+
/// The cache is used to reduce the number of hashmap lookups by
303+
/// pre-computing the maximum possible configs[^1] from `--check-cfg` and `--cfg`.
304+
///
305+
/// It heavily favours expected cfgs (if any are provided), since if the
306+
/// user provides a list of expected cfgs, they are likely to care more
307+
/// about expected cfgs than unexpected cfgs.
308+
///
309+
/// [^1]: See [`CfgCache`] for an explanation of "maximum possible".
310+
fn config_cache(config: &Cfg, check_cfg: &CheckCfg) -> CfgCache {
311+
let mut config_cache = CfgCache::default();
312+
313+
if check_cfg.expecteds.is_empty() {
314+
config_cache.extend(config.iter().map(|cfg| (*cfg, true)));
315+
} else {
316+
#[allow(rustc::potential_query_instability)]
317+
for (name, expected) in &check_cfg.expecteds {
318+
if let ExpectedValues::Some(values) = expected {
319+
config_cache.extend(
320+
values.iter().map(|value| ((*name, *value), config.contains(&(*name, *value)))),
321+
);
322+
}
323+
}
324+
}
325+
326+
config_cache
327+
}
328+
300329
/// The compiler configuration
301330
pub struct Config {
302331
/// Command line options
@@ -459,6 +488,8 @@ pub fn run_compiler<R: Send>(config: Config, f: impl FnOnce(&Compiler) -> R + Se
459488
check_cfg.fill_well_known(&sess.target);
460489
sess.psess.check_config = check_cfg;
461490

491+
sess.psess.config_cache = config_cache(&sess.psess.config, &sess.psess.check_config);
492+
462493
if let Some(psess_created) = config.psess_created {
463494
psess_created(&mut sess.psess);
464495
}

compiler/rustc_session/src/parse.rs

+13
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,24 @@ pub fn add_feature_diagnostics_for_issue<G: EmissionGuarantee>(
199199
}
200200
}
201201

202+
/// Config cache that tries to combine expected cfgs and enabled cfgs.
203+
///
204+
/// If a config is found, this means it is definitely an expected config
205+
/// and the value represents whenever that config is enabled.
206+
///
207+
/// If a config is not found, it doesn't imply anything! The config still may
208+
/// be enabled and it may also be expected. You should only rely on the
209+
/// cache having a config and not "NOT having it". You should always fallback
210+
/// to a manual lookup in `ParseSess::config` and `ParseSess::check_cfg`.
211+
pub type CfgCache = FxHashMap<(Symbol, Option<Symbol>), bool>;
212+
202213
/// Info about a parsing session.
203214
pub struct ParseSess {
204215
pub dcx: DiagCtxt,
205216
pub unstable_features: UnstableFeatures,
206217
pub config: Cfg,
207218
pub check_config: CheckCfg,
219+
pub config_cache: CfgCache,
208220
pub edition: Edition,
209221
/// Places where raw identifiers were used. This is used to avoid complaining about idents
210222
/// clashing with keywords in new editions.
@@ -253,6 +265,7 @@ impl ParseSess {
253265
unstable_features: UnstableFeatures::from_environment(None),
254266
config: Cfg::default(),
255267
check_config: CheckCfg::default(),
268+
config_cache: CfgCache::default(),
256269
edition: ExpnId::root().expn_data().edition,
257270
raw_identifier_spans: Default::default(),
258271
bad_unicode_identifiers: Lock::new(Default::default()),

0 commit comments

Comments
 (0)