forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of rust-lang#122568 - RalfJung:mentioned-items, r=<try>
recursively evaluate the constants in everything that is 'mentioned' This is another attempt at fixing rust-lang#107503. The previous attempt at rust-lang#112879 seems stuck in figuring out where the [perf regression](https://perf.rust-lang.org/compare.html?start=c55d1ee8d4e3162187214692229a63c2cc5e0f31&end=ec8de1ebe0d698b109beeaaac83e60f4ef8bb7d1&stat=instructions:u) comes from. In rust-lang#122258 I learned some things, which informed the approach this PR is taking. Quoting from the new collector docs, which explain the high-level idea: ```rust //! One important role of collection is to evaluate all constants that are used by all the items //! which are being collected. Codegen can then rely on only encountering constants that evaluate //! successfully, and if a constant fails to evaluate, the collector has much better context to be //! able to show where this constant comes up. //! //! However, the exact set of "used" items (collected as described above), and therefore the exact //! set of used constants, can depend on optimizations. Optimizing away dead code may optimize away //! a function call that uses a failing constant, so an unoptimized build may fail where an //! optimized build succeeds. This is undesirable. //! //! To fix this, the collector has the concept of "mentioned" items. Some time during the MIR //! pipeline, before any optimization-level-dependent optimizations, we compute a list of all items //! that syntactically appear in the code. These are considered "mentioned", and even if they are in //! dead code and get optimized away (which makes them no longer "used"), they are still //! "mentioned". For every used item, the collector ensures that all mentioned items, recursively, //! do not use a failing constant. This is reflected via the [`CollectionMode`], which determines //! whether we are visiting a used item or merely a mentioned item. //! //! The collector and "mentioned items" gathering (which lives in `rustc_mir_transform::mentioned_items`) //! need to stay in sync in the following sense: //! //! - For every item that the collector gather that could eventually lead to build failure (most //! likely due to containing a constant that fails to evaluate), a corresponding mentioned item //! must be added. This should use the exact same strategy as the ecollector to make sure they are //! in sync. However, while the collector works on monomorphized types, mentioned items are //! collected on generic MIR -- so any time the collector checks for a particular type (such as //! `ty::FnDef`), we have to just onconditionally add this as a mentioned item. //! - In `visit_mentioned_item`, we then do with that mentioned item exactly what the collector //! would have done during regular MIR visiting. Basically you can think of the collector having //! two stages, a pre-monomorphization stage and a post-monomorphization stage (usually quite //! literally separated by a call to `self.monomorphize`); the pre-monomorphizationn stage is //! duplicated in mentioned items gathering and the post-monomorphization stage is duplicated in //! `visit_mentioned_item`. //! - Finally, as a performance optimization, the collector should fill `used_mentioned_item` during //! its MIR traversal with exactly what mentioned item gathering would have added in the same //! situation. This detects mentioned items that have *not* been optimized away and hence don't //! need a dedicated traversal. enum CollectionMode { /// Collect items that are used, i.e., actually needed for codegen. /// /// Which items are used can depend on optimization levels, as MIR optimizations can remove /// uses. UsedItems, /// Collect items that are mentioned. The goal of this mode is that it is independent of /// optimizations: the set of "mentioned" items is computed before optimizations are run. /// /// The exact contents of this set are *not* a stable guarantee. (For instance, it is currently /// computed after drop-elaboration. If we ever do some optimizations even in debug builds, we /// might decide to run them before computing mentioned items.) The key property of this set is /// that it is optimization-independent. MentionedItems, } ``` And the `mentioned_items` MIR body field docs: ```rust /// Further items that were mentioned in this function and hence *may* become monomorphized, /// depending on optimizations. We use this to avoid optimization-dependent compile errors: the /// collector recursively traverses all "mentioned" items and evaluates all their /// `required_consts`. /// /// This is *not* soundness-critical and the contents of this list are *not* a stable guarantee. /// All that's relevant is that this set is optimization-level-independent, and that it includes /// everything that the collector would consider "used". (For example, we currently compute this /// set after drop elaboration, so some drop calls that can never be reached are not considered /// "mentioned".) See the documentation of `CollectionMode` in /// `compiler/rustc_monomorphize/src/collector.rs` for more context. pub mentioned_items: Vec<Spanned<MentionedItem<'tcx>>>, ``` Fixes rust-lang#107503
- Loading branch information
Showing
59 changed files
with
1,582 additions
and
400 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
use rustc_middle::mir::visit::Visitor; | ||
use rustc_middle::mir::{self, Location, MentionedItem, MirPass}; | ||
use rustc_middle::ty::{self, adjustment::PointerCoercion, TyCtxt}; | ||
use rustc_session::Session; | ||
use rustc_span::source_map::Spanned; | ||
|
||
pub struct MentionedItems; | ||
|
||
struct MentionedItemsVisitor<'a, 'tcx> { | ||
tcx: TyCtxt<'tcx>, | ||
body: &'a mir::Body<'tcx>, | ||
mentioned_items: &'a mut Vec<Spanned<MentionedItem<'tcx>>>, | ||
} | ||
|
||
impl<'tcx> MirPass<'tcx> for MentionedItems { | ||
fn is_enabled(&self, _sess: &Session) -> bool { | ||
// If this pass is skipped the collector assume that nothing got mentioned! We could | ||
// potentially skip it in opt-level 0 if we are sure that opt-level will never *remove* uses | ||
// of anything, but that still seems fragile. Furthermore, even debug builds use level 1, so | ||
// special-casing level 0 is just not worth it. | ||
true | ||
} | ||
|
||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) { | ||
debug_assert!(body.mentioned_items.is_empty()); | ||
let mut mentioned_items = Vec::new(); | ||
MentionedItemsVisitor { tcx, body, mentioned_items: &mut mentioned_items }.visit_body(body); | ||
body.mentioned_items = mentioned_items; | ||
} | ||
} | ||
|
||
// This visitor is carefully in sync with the one in `rustc_monomorphize::collector`. We are | ||
// visiting the exact same places but then instead of monomorphizing and creating `MonoItems`, we | ||
// have to remain generic and just recording the relevant information in `mentioned_items`, where it | ||
// will then be monomorphized later during "mentioned items" collection. | ||
impl<'tcx> Visitor<'tcx> for MentionedItemsVisitor<'_, 'tcx> { | ||
fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) { | ||
self.super_terminator(terminator, location); | ||
let span = || self.body.source_info(location).span; | ||
match &terminator.kind { | ||
mir::TerminatorKind::Call { func, .. } => { | ||
let callee_ty = func.ty(self.body, self.tcx); | ||
self.mentioned_items | ||
.push(Spanned { node: MentionedItem::Fn(callee_ty), span: span() }); | ||
} | ||
mir::TerminatorKind::Drop { place, .. } => { | ||
let ty = place.ty(self.body, self.tcx).ty; | ||
self.mentioned_items.push(Spanned { node: MentionedItem::Drop(ty), span: span() }); | ||
} | ||
mir::TerminatorKind::InlineAsm { ref operands, .. } => { | ||
for op in operands { | ||
match *op { | ||
mir::InlineAsmOperand::SymFn { ref value } => { | ||
self.mentioned_items.push(Spanned { | ||
node: MentionedItem::Fn(value.const_.ty()), | ||
span: span(), | ||
}); | ||
} | ||
_ => {} | ||
} | ||
} | ||
} | ||
_ => {} | ||
} | ||
} | ||
|
||
fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) { | ||
self.super_rvalue(rvalue, location); | ||
let span = || self.body.source_info(location).span; | ||
match *rvalue { | ||
// We need to detect unsizing casts that required vtables. | ||
mir::Rvalue::Cast( | ||
mir::CastKind::PointerCoercion(PointerCoercion::Unsize), | ||
ref operand, | ||
target_ty, | ||
) | ||
| mir::Rvalue::Cast(mir::CastKind::DynStar, ref operand, target_ty) => { | ||
// This isn't monomorphized yet so we can't tell what the actual types are -- just | ||
// add everything that may involve a vtable. | ||
let source_ty = operand.ty(self.body, self.tcx); | ||
let may_involve_vtable = match ( | ||
source_ty.builtin_deref(true).map(|t| t.ty.kind()), | ||
target_ty.builtin_deref(true).map(|t| t.ty.kind()), | ||
) { | ||
(Some(ty::Array(..)), Some(ty::Str | ty::Slice(..))) => false, // &str/&[T] unsizing | ||
_ => true, | ||
}; | ||
if may_involve_vtable { | ||
self.mentioned_items.push(Spanned { | ||
node: MentionedItem::UnsizeCast { source_ty, target_ty }, | ||
span: span(), | ||
}); | ||
} | ||
} | ||
// Similarly, record closures that are turned into function pointers. | ||
mir::Rvalue::Cast( | ||
mir::CastKind::PointerCoercion(PointerCoercion::ClosureFnPointer(_)), | ||
ref operand, | ||
_, | ||
) => { | ||
let source_ty = operand.ty(self.body, self.tcx); | ||
self.mentioned_items | ||
.push(Spanned { node: MentionedItem::Closure(source_ty), span: span() }); | ||
} | ||
// And finally, function pointer reification casts. | ||
mir::Rvalue::Cast( | ||
mir::CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer), | ||
ref operand, | ||
_, | ||
) => { | ||
let fn_ty = operand.ty(self.body, self.tcx); | ||
self.mentioned_items.push(Spanned { node: MentionedItem::Fn(fn_ty), span: span() }); | ||
} | ||
_ => {} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.