From 244a73c549d1468908bfd4efcb0f568a3b4ae557 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sun, 20 Feb 2022 14:49:57 -0800 Subject: [PATCH] Erase trivial caller bounds when typechecking MIR after optimizations --- .../src/transform/validate.rs | 7 +-- compiler/rustc_middle/src/query/mod.rs | 10 ++++ compiler/rustc_middle/src/ty/mod.rs | 6 +++ compiler/rustc_ty_utils/src/ty.rs | 47 +++++++++++++++++++ .../mir-inlining/caller-with-trivial-bound.rs | 21 +++++++++ 5 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 src/test/ui/mir/mir-inlining/caller-with-trivial-bound.rs diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs index cf15fc4ddc3a5..b6ec4f04a38d2 100644 --- a/compiler/rustc_const_eval/src/transform/validate.rs +++ b/compiler/rustc_const_eval/src/transform/validate.rs @@ -171,9 +171,10 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { return true; } // Normalize projections and things like that. - // FIXME: We need to reveal_all, as some optimizations change types in ways - // that require unfolding opaque types. - let param_env = self.param_env.with_reveal_all_normalized(self.tcx); + // FIXME: We need to reveal_all and erase any trivial caller bounds (see query description) + // due to the fact that certain optimizations (e.g. inlining) change the way that types are + // normalized. + let param_env = self.tcx.reveal_all_and_erase_trivial_caller_bounds(self.param_env); let src = self.tcx.normalize_erasing_regions(param_env, src); let dest = self.tcx.normalize_erasing_regions(param_env, dest); diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index c299dacfc927a..c4f67c0d1ce86 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1152,6 +1152,16 @@ rustc_queries! { desc { |tcx| "computing revealed normalized predicates of `{}`", tcx.def_path_str(def_id) } } + /// Like `ty::ParamEnv::reveal_all_normalized`, but returns the `ParamEnv` with any + /// where clause bounds removed if they hold "trivially" -- that is, if they are + /// satisfied by the input param-env with that bound removed, such as if they match a + /// global `impl` or are a duplicated bound in the where clause. + /// + /// This is a query because it is computationally expensive, and we want to cache it. + query reveal_all_and_erase_trivial_caller_bounds(key: ty::ParamEnv<'tcx>) -> ty::ParamEnv<'tcx> { + desc { |tcx| "normalizing and erasing trivial predicates of `{:?}`", key } + } + /// Trait selection queries. These are best used by invoking `ty.is_copy_modulo_regions()`, /// `ty.is_copy()`, etc, since that will prune the environment where possible. query is_copy_raw(env: ty::ParamEnvAnd<'tcx, Ty<'tcx>>) -> bool { diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index f0b7f2a653f45..c6b4837d2dc73 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -1455,6 +1455,12 @@ impl<'tcx> ParamEnv<'tcx> { Self::new(List::empty(), self.reveal(), self.constness()) } + /// Returns this same environment but with new caller bounds. + #[inline] + pub fn with_caller_bounds(self, caller_bounds: &'tcx List>) -> Self { + Self::new(caller_bounds, self.reveal(), self.constness()) + } + /// Creates a suitable environment in which to perform trait /// queries on the given value. When type-checking, this is simply /// the pair of the environment plus value. But when reveal is set to diff --git a/compiler/rustc_ty_utils/src/ty.rs b/compiler/rustc_ty_utils/src/ty.rs index e6ce3447548de..7692cd906a471 100644 --- a/compiler/rustc_ty_utils/src/ty.rs +++ b/compiler/rustc_ty_utils/src/ty.rs @@ -1,6 +1,8 @@ use rustc_data_structures::fx::FxIndexSet; use rustc_hir as hir; use rustc_hir::def_id::DefId; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_infer::traits::TraitEngine; use rustc_middle::ty::subst::Subst; use rustc_middle::ty::{self, Binder, Predicate, PredicateKind, ToPredicate, Ty, TyCtxt}; use rustc_span::{sym, Span}; @@ -356,6 +358,50 @@ fn param_env_reveal_all_normalized(tcx: TyCtxt<'_>, def_id: DefId) -> ty::ParamE tcx.param_env(def_id).with_reveal_all_normalized(tcx) } +fn reveal_all_and_erase_trivial_caller_bounds<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, +) -> ty::ParamEnv<'tcx> { + let mut old_caller_bounds = param_env.caller_bounds(); + loop { + // The caller bounds that we must continue to include in our ParamEnv, + // because they cause errors without them... + let mut needed_caller_bounds = vec![]; + for (idx, bound) in old_caller_bounds.iter().enumerate() { + let new_param_env = param_env.with_caller_bounds(tcx.mk_predicates( + needed_caller_bounds.iter().chain(old_caller_bounds[idx + 1..].iter()).copied(), + )); + // a bound is trivial if it does not have const projections + // (which might induce cycles), and if we can prove that bound + // given a copy of our param-env that has the bound removed. + let is_bound_trivial = tcx.infer_ctxt().enter(|infcx| { + let mut fulfillcx = traits::FulfillmentContext::new_in_snapshot(); + fulfillcx.register_predicate_obligation( + &infcx, + traits::Obligation::new(traits::ObligationCause::dummy(), new_param_env, bound), + ); + let errors = fulfillcx.select_all_or_error(&infcx); + if !errors.is_empty() { + info!("{:?} is NOT trivial: {:?}", bound, errors); + } else { + info!("{:?} is trivial", bound); + } + // is trivial iff there are no errors when fulfilling this bound + errors.is_empty() + }); + if !is_bound_trivial { + needed_caller_bounds.push(bound); + } + } + let new_caller_bounds = tcx.mk_predicates(needed_caller_bounds.into_iter()); + if new_caller_bounds == old_caller_bounds { + return param_env.with_caller_bounds(new_caller_bounds).with_reveal_all_normalized(tcx); + } else { + old_caller_bounds = new_caller_bounds; + } + } +} + fn instance_def_size_estimate<'tcx>( tcx: TyCtxt<'tcx>, instance_def: ty::InstanceDef<'tcx>, @@ -495,6 +541,7 @@ pub fn provide(providers: &mut ty::query::Providers) { def_ident_span, param_env, param_env_reveal_all_normalized, + reveal_all_and_erase_trivial_caller_bounds, instance_def_size_estimate, issue33140_self_ty, impl_defaultness, diff --git a/src/test/ui/mir/mir-inlining/caller-with-trivial-bound.rs b/src/test/ui/mir/mir-inlining/caller-with-trivial-bound.rs new file mode 100644 index 0000000000000..bcc1289ef2205 --- /dev/null +++ b/src/test/ui/mir/mir-inlining/caller-with-trivial-bound.rs @@ -0,0 +1,21 @@ +// check-pass +// compile-flags: -Zmir-opt-level=3 --crate-type=lib + +pub trait Factory { + type Item; +} + +pub struct IntFactory; + +impl Factory for IntFactory { + type Item = usize; +} + +pub fn foo() where IntFactory: Factory { + let mut x: >::Item = bar::(); +} + +#[inline] +pub fn bar() -> >::Item { + 0usize +}