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

Erase trivial caller-bounds when typechecking MIR after optimizations #94238

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions compiler/rustc_const_eval/src/transform/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Predicate<'tcx>>) -> 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
Expand Down
47 changes: 47 additions & 0 deletions compiler/rustc_ty_utils/src/ty.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions src/test/ui/mir/mir-inlining/caller-with-trivial-bound.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// check-pass
// compile-flags: -Zmir-opt-level=3 --crate-type=lib

pub trait Factory<T> {
type Item;
}

pub struct IntFactory;

impl<T> Factory<T> for IntFactory {
type Item = usize;
}

pub fn foo<T>() where IntFactory: Factory<T> {
let mut x: <IntFactory as Factory<T>>::Item = bar::<T>();
}

#[inline]
pub fn bar<T>() -> <IntFactory as Factory<T>>::Item {
0usize
}