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

(Big performance change) Do not run lints that cannot emit #125116

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4034,6 +4034,7 @@ dependencies = [
"rustc_hir",
"rustc_hir_pretty",
"rustc_index",
"rustc_lint_defs",
"rustc_macros",
"rustc_query_system",
"rustc_serialize",
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_ast/src/attr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ impl AttrItem {
self.args.span().map_or(self.path.span, |args_span| self.path.span.to(args_span))
}

fn meta_item_list(&self) -> Option<ThinVec<NestedMetaItem>> {
pub fn meta_item_list(&self) -> Option<ThinVec<NestedMetaItem>> {
match &self.args {
AttrArgs::Delimited(args) if args.delim == Delimiter::Parenthesis => {
MetaItemKind::list_from_tokens(args.tokens.clone())
Expand Down
13 changes: 10 additions & 3 deletions compiler/rustc_lint/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
//! If you define a new `LateLintPass`, you will also need to add it to the
//! `late_lint_methods!` invocation in `lib.rs`.

use std::default::Default;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this in prelude?

use std::fmt::Write;

use ast::token::TokenKind;
Expand Down Expand Up @@ -73,6 +74,10 @@ use crate::{
fluent_generated as fluent, EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level,
LintContext,
};
// use std::fmt::Write;

// hardwired lints from rustc_lint_defs
// pub use rustc_session::lint::builtin::*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented-out code.


declare_lint! {
/// The `while_true` lint detects `while true { }`.
Expand Down Expand Up @@ -242,7 +247,8 @@ declare_lint! {
/// behavior.
UNSAFE_CODE,
Allow,
"usage of `unsafe` code and other potentially unsound constructs"
"usage of `unsafe` code and other potentially unsound constructs",
[eval_always: true]
}

declare_lint_pass!(UnsafeCode => [UNSAFE_CODE]);
Expand Down Expand Up @@ -390,6 +396,7 @@ declare_lint! {
report_in_external_macro
}

#[derive(Default)]
pub struct MissingDoc;

impl_lint_pass!(MissingDoc => [MISSING_DOCS]);
Expand Down Expand Up @@ -830,8 +837,8 @@ pub struct DeprecatedAttr {

impl_lint_pass!(DeprecatedAttr => []);

impl DeprecatedAttr {
pub fn new() -> DeprecatedAttr {
impl Default for DeprecatedAttr {
fn default() -> Self {
DeprecatedAttr { depr_attrs: deprecated_attributes() }
}
}
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_lint/src/early.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ impl LintPass for RuntimeCombinedEarlyLintPass<'_> {
fn name(&self) -> &'static str {
panic!()
}
fn get_lints(&self) -> crate::LintVec {
panic!()
}
}

macro_rules! impl_early_lint_pass {
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_lint/src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,8 @@ declare_tool_lint! {
pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
Deny,
"prevent creation of diagnostics which cannot be translated",
report_in_external_macro: true
report_in_external_macro: true,
[eval_always: true]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the syntax be closer to other modifiers?

}

declare_tool_lint! {
Expand All @@ -452,7 +453,8 @@ declare_tool_lint! {
pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
Deny,
"prevent diagnostic creation outside of `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls",
report_in_external_macro: true
report_in_external_macro: true,
[eval_always: true]
}

declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL]);
Expand Down
49 changes: 41 additions & 8 deletions compiler/rustc_lint/src/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ use rustc_hir::def_id::{LocalDefId, LocalModDefId};
use rustc_hir::{intravisit as hir_visit, HirId};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, TyCtxt};
use rustc_session::lint::builtin::HardwiredLints;
use rustc_session::lint::LintPass;
use rustc_session::Session;
use rustc_span::Span;
use tracing::debug;

use crate::passes::LateLintPassObject;
use crate::{LateContext, LateLintPass, LintStore};
use crate::{LateContext, LateLintPass, LintId, LintStore};

/// Extract the [`LintStore`] from [`Session`].
///
Expand Down Expand Up @@ -327,6 +328,9 @@ impl LintPass for RuntimeCombinedLateLintPass<'_, '_> {
fn name(&self) -> &'static str {
panic!()
}
fn get_lints(&self) -> crate::LintVec {
panic!()
}
}

macro_rules! impl_late_lint_pass {
Expand Down Expand Up @@ -362,13 +366,20 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>(
// Note: `passes` is often empty. In that case, it's faster to run
// `builtin_lints` directly rather than bundling it up into the
// `RuntimeCombinedLateLintPass`.
let late_module_passes = &unerased_lint_store(tcx.sess).late_module_passes;
if late_module_passes.is_empty() {
let store = unerased_lint_store(tcx.sess);

if store.late_module_passes.is_empty() {
late_lint_mod_inner(tcx, module_def_id, context, builtin_lints);
} else {
let mut passes: Vec<_> = late_module_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect();
passes.push(Box::new(builtin_lints));
let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] };
let chained_box = Box::new(builtin_lints) as Box<dyn LateLintPass<'tcx>>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let chained_box = Box::new(builtin_lints) as Box<dyn LateLintPass<'tcx>>;
let builtin_lints = Box::new(builtin_lints) as Box<dyn LateLintPass<'tcx>>;

?

let mut binding = store
.late_module_passes
.iter()
.map(|mk_pass| (mk_pass)(tcx))
.chain(std::iter::once(chained_box))
.collect::<Vec<_>>();

let pass = RuntimeCombinedLateLintPass { passes: binding.as_mut_slice() };
late_lint_mod_inner(tcx, module_def_id, context, pass);
}
}
Expand Down Expand Up @@ -399,7 +410,7 @@ fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>(

fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) {
// Note: `passes` is often empty.
let mut passes: Vec<_> =
let passes: Vec<_> =
unerased_lint_store(tcx.sess).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect();

if passes.is_empty() {
Expand All @@ -417,7 +428,29 @@ fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) {
only_module: false,
};

let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] };
let lints_that_dont_need_to_run = tcx.lints_that_dont_need_to_run(());

let mut filtered_passes: Vec<Box<dyn LateLintPass<'tcx>>> = passes
.into_iter()
.filter(|pass| {
let lints = LintPass::get_lints(pass);
!lints.iter().all(|lint| lints_that_dont_need_to_run.contains(&LintId::of(lint)))
})
.collect();

filtered_passes.push(Box::new(HardwiredLints));

// let mut filtered_passes: Vec<Box<dyn LateLintPass<'tcx>>> = passes
// .into_iter()
// .filter(|pass| {
// let lints = LintPass::get_lints(pass);
// lints.iter()
// .any(|lint|
// !lints_that_dont_need_to_run.contains(&LintId::of(lint)))
// }).collect();
//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented-out code.


let pass = RuntimeCombinedLateLintPass { passes: &mut filtered_passes[..] };
late_lint_crate_inner(tcx, context, pass);
}

Expand Down
103 changes: 101 additions & 2 deletions compiler/rustc_lint/src/levels.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_errors::{Diag, LintDiagnostic, MultiSpan};
use rustc_feature::{Features, GateIssue};
use rustc_hir::intravisit::{self, Visitor};
Expand Down Expand Up @@ -115,6 +115,28 @@ impl LintLevelSets {
}
}

fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> FxIndexSet<LintId> {
let store = unerased_lint_store(&tcx.sess);

let dont_need_to_run: FxIndexSet<LintId> = store
.get_lints()
.into_iter()
.filter_map(|lint| {
if !lint.eval_always && lint.default_level(tcx.sess.edition()) == Level::Allow {
Some(LintId::of(lint))
} else {
None
}
})
.collect();

let mut visitor = LintLevelMaximum { tcx, dont_need_to_run };
visitor.process_opts();
tcx.hir().walk_attributes(&mut visitor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be implemented by calling shallow_lint_levels_on and looking at the lint specs stored inside it? That would avoid re-parsing all lints.


visitor.dont_need_to_run
}

#[instrument(level = "trace", skip(tcx), ret)]
fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLevelMap {
let store = unerased_lint_store(tcx.sess);
Expand Down Expand Up @@ -303,6 +325,83 @@ impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> {
}
}

/// Visitor with the only function of visiting every item-like in a crate and
/// computing the highest level that every lint gets put to.
///
/// E.g., if a crate has a global #![allow(lint)] attribute, but a single item
/// uses #[warn(lint)], this visitor will set that lint level as `Warn`
struct LintLevelMaximum<'tcx> {
tcx: TyCtxt<'tcx>,
/// The actual list of detected lints.
dont_need_to_run: FxIndexSet<LintId>,
}

impl<'tcx> LintLevelMaximum<'tcx> {
fn process_opts(&mut self) {
let store = unerased_lint_store(self.tcx.sess);
for (lint_group, level) in &self.tcx.sess.opts.lint_opts {
if *level != Level::Allow {
let Ok(lints) = store.find_lints(lint_group) else {
return;
};
for lint in lints {
self.dont_need_to_run.swap_remove(&lint);
}
}
}
}
}

impl<'tcx> Visitor<'tcx> for LintLevelMaximum<'tcx> {
type NestedFilter = nested_filter::All;

fn nested_visit_map(&mut self) -> Self::Map {
self.tcx.hir()
}

/// FIXME(blyxyas): In a future revision, we should also graph #![allow]s,
/// but that is handled with more care
fn visit_attribute(&mut self, attribute: &'tcx ast::Attribute) {
if matches!(
Level::from_attr(attribute),
Some(
Level::Warn
| Level::Deny
| Level::Forbid
| Level::Expect(..)
| Level::ForceWarn(..),
)
) {
let store = unerased_lint_store(self.tcx.sess);
let Some(meta) = attribute.meta() else { return };
// SAFETY: Lint attributes are always a metalist inside a
// metalist (even with just one lint).
let Some(meta_item_list) = meta.meta_item_list() else { return };

for meta_list in meta_item_list {
// Convert Path to String
let Some(meta_item) = meta_list.meta_item() else { return };
let ident: &str = &meta_item
.path
.segments
.iter()
.map(|segment| segment.ident.as_str())
.collect::<Vec<&str>>()
.join("::");
let Ok(lints) = store.find_lints(
// SAFETY: Lint attributes can only have literals
ident,
) else {
return;
};
for lint in lints {
self.dont_need_to_run.swap_remove(&lint);
}
}
}
}
}

pub struct LintLevelsBuilder<'s, P> {
sess: &'s Session,
features: &'s Features,
Expand Down Expand Up @@ -933,7 +1032,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
}

pub(crate) fn provide(providers: &mut Providers) {
*providers = Providers { shallow_lint_levels_on, ..*providers };
*providers = Providers { shallow_lint_levels_on, lints_that_dont_need_to_run, ..*providers };
}

pub(crate) fn parse_lint_and_tool_name(lint_name: &str) -> (Option<Symbol>, &str) {
Expand Down
27 changes: 14 additions & 13 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#![doc(rust_logo)]
#![feature(array_windows)]
#![feature(assert_matches)]
#![feature(box_into_inner)]
#![feature(box_patterns)]
#![feature(control_flow_enum)]
#![feature(extract_if)]
Expand Down Expand Up @@ -165,15 +166,15 @@ early_lint_methods!(
[
pub BuiltinCombinedEarlyLintPass,
[
UnusedParens: UnusedParens::new(),
UnusedParens: UnusedParens::default(),
UnusedBraces: UnusedBraces,
UnusedImportBraces: UnusedImportBraces,
UnsafeCode: UnsafeCode,
SpecialModuleName: SpecialModuleName,
AnonymousParameters: AnonymousParameters,
EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(),
NonCamelCaseTypes: NonCamelCaseTypes,
DeprecatedAttr: DeprecatedAttr::new(),
DeprecatedAttr: DeprecatedAttr::default(),
WhileTrue: WhileTrue,
NonAsciiIdents: NonAsciiIdents,
HiddenUnicodeCodepoints: HiddenUnicodeCodepoints,
Expand All @@ -194,7 +195,6 @@ late_lint_methods!(
ForLoopsOverFallibles: ForLoopsOverFallibles,
DerefIntoDynSupertrait: DerefIntoDynSupertrait,
DropForgetUseless: DropForgetUseless,
HardwiredLints: HardwiredLints,
ImproperCTypesDeclarations: ImproperCTypesDeclarations,
ImproperCTypesDefinitions: ImproperCTypesDefinitions,
InvalidFromUtf8: InvalidFromUtf8,
Expand Down Expand Up @@ -272,6 +272,7 @@ fn register_builtins(store: &mut LintStore) {
store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints());
store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints());
store.register_lints(&foreign_modules::get_lints());
store.register_lints(&HardwiredLints::default().get_lints());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
store.register_lints(&HardwiredLints::default().get_lints());
store.register_lints(&HardwiredLints::get_lints());

?


add_lint_group!(
"nonstandard_style",
Expand Down Expand Up @@ -579,25 +580,25 @@ fn register_builtins(store: &mut LintStore) {
}

fn register_internals(store: &mut LintStore) {
store.register_lints(&LintPassImpl::get_lints());
store.register_lints(&LintPassImpl::default().get_lints());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
store.register_lints(&LintPassImpl::default().get_lints());
store.register_lints(&LintPassImpl::get_lints());

and everywhere below.

store.register_early_pass(|| Box::new(LintPassImpl));
store.register_lints(&DefaultHashTypes::get_lints());
store.register_lints(&DefaultHashTypes::default().get_lints());
store.register_late_mod_pass(|_| Box::new(DefaultHashTypes));
store.register_lints(&QueryStability::get_lints());
store.register_lints(&QueryStability::default().get_lints());
store.register_late_mod_pass(|_| Box::new(QueryStability));
store.register_lints(&ExistingDocKeyword::get_lints());
store.register_lints(&ExistingDocKeyword::default().get_lints());
store.register_late_mod_pass(|_| Box::new(ExistingDocKeyword));
store.register_lints(&TyTyKind::get_lints());
store.register_lints(&TyTyKind::default().get_lints());
store.register_late_mod_pass(|_| Box::new(TyTyKind));
store.register_lints(&TypeIr::get_lints());
store.register_lints(&TypeIr::default().get_lints());
store.register_late_mod_pass(|_| Box::new(TypeIr));
store.register_lints(&Diagnostics::get_lints());
store.register_lints(&Diagnostics::default().get_lints());
store.register_late_mod_pass(|_| Box::new(Diagnostics));
store.register_lints(&BadOptAccess::get_lints());
store.register_lints(&BadOptAccess::default().get_lints());
store.register_late_mod_pass(|_| Box::new(BadOptAccess));
store.register_lints(&PassByValue::get_lints());
store.register_lints(&PassByValue::default().get_lints());
store.register_late_mod_pass(|_| Box::new(PassByValue));
store.register_lints(&SpanUseEqCtxt::get_lints());
store.register_lints(&SpanUseEqCtxt::default().get_lints());
store.register_late_mod_pass(|_| Box::new(SpanUseEqCtxt));
// FIXME(davidtwco): deliberately do not include `UNTRANSLATABLE_DIAGNOSTIC` and
// `DIAGNOSTIC_OUTSIDE_OF_IMPL` here because `-Wrustc::internal` is provided to every crate and
Expand Down
Loading
Loading