From ff448cfcee92cf507488d6335c68ca98a70cf7cc Mon Sep 17 00:00:00 2001 From: b-naber Date: Fri, 26 Nov 2021 17:41:22 +0100 Subject: [PATCH] implement version of normalize_erasing_regions that doesn't assume value is normalizable --- compiler/rustc_lint/src/types.rs | 4 +- .../rustc_middle/src/mir/interpret/error.rs | 2 +- compiler/rustc_middle/src/query/mod.rs | 14 ++ compiler/rustc_middle/src/ty/layout.rs | 19 ++- .../src/ty/normalize_erasing_regions.rs | 127 ++++++++++++++++++ .../src/normalize_erasing_regions.rs | 40 ++++++ 6 files changed, 203 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 708cd56e068..b20f7357b35 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -1337,7 +1337,9 @@ impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences { let layout = match cx.layout_of(ty) { Ok(layout) => layout, Err( - ty::layout::LayoutError::Unknown(_) | ty::layout::LayoutError::SizeOverflow(_), + ty::layout::LayoutError::Unknown(_) + | ty::layout::LayoutError::SizeOverflow(_) + | ty::layout::LayoutError::NormalizationFailure(_, _), ) => return, }; let (variants, tag) = match layout.variants { diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index 7a51bb4a1f3..3e307979f2d 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -493,7 +493,7 @@ impl dyn MachineStopType { } #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] -static_assert_size!(InterpError<'_>, 64); +static_assert_size!(InterpError<'_>, 88); pub enum InterpError<'tcx> { /// The program caused undefined behavior. diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 268a66b9926..12665be3354 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1658,6 +1658,20 @@ rustc_queries! { desc { "normalizing `{}`", goal.value } } + /// Do not call this query directly: invoke `try_normalize_erasing_regions` instead. + query try_normalize_generic_arg_after_erasing_regions( + goal: ParamEnvAnd<'tcx, GenericArg<'tcx>> + ) -> Result, NoSolution> { + desc { "trying to normalize `{}`", goal.value } + } + + /// Do not call this query directly: invoke `try_normalize_erasing_regions` instead. + query try_normalize_mir_const_after_erasing_regions( + goal: ParamEnvAnd<'tcx, mir::ConstantKind<'tcx>> + ) -> Result, NoSolution> { + desc { "trying to normalize `{}`", goal.value } + } + query implied_outlives_bounds( goal: CanonicalTyGoal<'tcx> ) -> Result< diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index b87e23af72b..9c1407de812 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -1,5 +1,6 @@ use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags; use crate::mir::{GeneratorLayout, GeneratorSavedLocal}; +use crate::ty::normalize_erasing_regions::NormalizationError; use crate::ty::subst::Subst; use crate::ty::{self, subst::SubstsRef, ReprOptions, Ty, TyCtxt, TypeFoldable}; use rustc_ast as ast; @@ -199,6 +200,7 @@ pub const MAX_SIMD_LANES: u64 = 1 << 0xF; pub enum LayoutError<'tcx> { Unknown(Ty<'tcx>), SizeOverflow(Ty<'tcx>), + NormalizationFailure(Ty<'tcx>, NormalizationError<'tcx>), } impl<'tcx> fmt::Display for LayoutError<'tcx> { @@ -208,16 +210,24 @@ impl<'tcx> fmt::Display for LayoutError<'tcx> { LayoutError::SizeOverflow(ty) => { write!(f, "values of the type `{}` are too big for the current architecture", ty) } + LayoutError::NormalizationFailure(t, e) => write!( + f, + "unable to determine layout for `{}` because `{}` cannot be normalized", + t, + e.get_type_for_failure() + ), } } } +#[instrument(skip(tcx, query), level = "debug")] fn layout_of<'tcx>( tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>, ) -> Result, LayoutError<'tcx>> { ty::tls::with_related_context(tcx, move |icx| { let (param_env, ty) = query.into_parts(); + debug!(?ty); if !tcx.recursion_limit().value_within_limit(icx.layout_depth) { tcx.sess.fatal(&format!("overflow representing the type `{}`", ty)); @@ -229,7 +239,14 @@ fn layout_of<'tcx>( ty::tls::enter_context(&icx, |_| { let param_env = param_env.with_reveal_all_normalized(tcx); let unnormalized_ty = ty; - let ty = tcx.normalize_erasing_regions(param_env, ty); + + let ty = match tcx.try_normalize_erasing_regions(param_env, ty) { + Ok(t) => t, + Err(normalization_error) => { + return Err(LayoutError::NormalizationFailure(ty, normalization_error)); + } + }; + if ty != unnormalized_ty { // Ensure this layout is also cached for the normalized type. return tcx.layout_of(param_env.and(ty)); diff --git a/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs index e6f67adae93..0915228d070 100644 --- a/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs +++ b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs @@ -8,10 +8,28 @@ //! or constant found within. (This underlying query is what is cached.) use crate::mir; +use crate::traits::query::NoSolution; use crate::ty::fold::{TypeFoldable, TypeFolder}; use crate::ty::subst::{Subst, SubstsRef}; use crate::ty::{self, Ty, TyCtxt}; +#[derive(Debug, Copy, Clone, HashStable, TyEncodable, TyDecodable)] +pub enum NormalizationError<'tcx> { + Type(Ty<'tcx>), + Const(ty::Const<'tcx>), + ConstantKind(mir::ConstantKind<'tcx>), +} + +impl<'tcx> NormalizationError<'tcx> { + pub fn get_type_for_failure(&self) -> String { + match self { + NormalizationError::Type(t) => format!("{}", t), + NormalizationError::Const(c) => format!("{}", c), + NormalizationError::ConstantKind(ck) => format!("{}", ck), + } + } +} + impl<'tcx> TyCtxt<'tcx> { /// Erase the regions in `value` and then fully normalize all the /// types found within. The result will also have regions erased. @@ -32,6 +50,8 @@ impl<'tcx> TyCtxt<'tcx> { // Erase first before we do the real query -- this keeps the // cache from being too polluted. let value = self.erase_regions(value); + debug!(?value); + if !value.has_projections() { value } else { @@ -41,6 +61,44 @@ impl<'tcx> TyCtxt<'tcx> { } } + /// Tries to erase the regions in `value` and then fully normalize all the + /// types found within. The result will also have regions erased. + /// + /// Contrary to `normalize_erasing_regions` this function does not assume that normalization + /// succeeds. + pub fn try_normalize_erasing_regions( + self, + param_env: ty::ParamEnv<'tcx>, + value: T, + ) -> Result> + where + T: TypeFoldable<'tcx>, + { + debug!( + "try_normalize_erasing_regions::<{}>(value={:?}, param_env={:?})", + std::any::type_name::(), + value, + param_env, + ); + + // Erase first before we do the real query -- this keeps the + // cache from being too polluted. + let value = self.erase_regions(value); + debug!(?value); + + if !value.has_projections() { + Ok(value) + } else { + let mut folder = TryNormalizeAfterErasingRegionsFolder::new(self, param_env); + let result = value.fold_with(&mut folder); + + match folder.found_normalization_error() { + Some(e) => Err(e), + None => Ok(result), + } + } + } + /// If you have a `Binder<'tcx, T>`, you can do this to strip out the /// late-bound regions and then normalize the result, yielding up /// a `T` (with regions erased). This is appropriate when the @@ -91,11 +149,14 @@ struct NormalizeAfterErasingRegionsFolder<'tcx> { } impl<'tcx> NormalizeAfterErasingRegionsFolder<'tcx> { + #[instrument(skip(self), level = "debug")] fn normalize_generic_arg_after_erasing_regions( &self, arg: ty::GenericArg<'tcx>, ) -> ty::GenericArg<'tcx> { let arg = self.param_env.and(arg); + debug!(?arg); + self.tcx.normalize_generic_arg_after_erasing_regions(arg) } } @@ -126,3 +187,69 @@ impl TypeFolder<'tcx> for NormalizeAfterErasingRegionsFolder<'tcx> { Ok(self.tcx.normalize_mir_const_after_erasing_regions(arg)) } } + +struct TryNormalizeAfterErasingRegionsFolder<'tcx> { + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + normalization_error: Option>, +} + +impl<'tcx> TryNormalizeAfterErasingRegionsFolder<'tcx> { + fn new(tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Self { + TryNormalizeAfterErasingRegionsFolder { tcx, param_env, normalization_error: None } + } + + #[instrument(skip(self), level = "debug")] + fn try_normalize_generic_arg_after_erasing_regions( + &self, + arg: ty::GenericArg<'tcx>, + ) -> Result, NoSolution> { + let arg = self.param_env.and(arg); + debug!(?arg); + + self.tcx.try_normalize_generic_arg_after_erasing_regions(arg) + } + + pub fn found_normalization_error(&self) -> Option> { + self.normalization_error + } +} + +impl TypeFolder<'tcx> for TryNormalizeAfterErasingRegionsFolder<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { + match self.try_normalize_generic_arg_after_erasing_regions(ty.into()) { + Ok(t) => t.expect_ty(), + Err(_) => { + self.normalization_error = Some(NormalizationError::Type(ty)); + ty + } + } + } + + fn fold_const(&mut self, c: &'tcx ty::Const<'tcx>) -> &'tcx ty::Const<'tcx> { + match self.try_normalize_generic_arg_after_erasing_regions(c.into()) { + Ok(t) => t.expect_const(), + Err(_) => { + self.normalization_error = Some(NormalizationError::Const(*c)); + c + } + } + } + + #[inline] + fn fold_mir_const(&mut self, c: mir::ConstantKind<'tcx>) -> mir::ConstantKind<'tcx> { + // FIXME: This *probably* needs canonicalization too! + let arg = self.param_env.and(c); + match self.tcx.try_normalize_mir_const_after_erasing_regions(arg) { + Ok(c) => c, + Err(_) => { + self.normalization_error = Some(NormalizationError::ConstantKind(c)); + c + } + } + } +} diff --git a/compiler/rustc_traits/src/normalize_erasing_regions.rs b/compiler/rustc_traits/src/normalize_erasing_regions.rs index 61ab5e28b67..4f35909df7f 100644 --- a/compiler/rustc_traits/src/normalize_erasing_regions.rs +++ b/compiler/rustc_traits/src/normalize_erasing_regions.rs @@ -20,6 +20,14 @@ crate fn provide(p: &mut Providers) { normalize_mir_const_after_erasing_regions: |tcx, goal| { normalize_after_erasing_regions(tcx, goal) }, + try_normalize_generic_arg_after_erasing_regions: |tcx, goal| { + debug!("try_normalize_generic_arg_after_erasing_regions(goal={:#?}", goal); + + try_normalize_after_erasing_regions(tcx, goal) + }, + try_normalize_mir_const_after_erasing_regions: |tcx, goal| { + try_normalize_after_erasing_regions(tcx, goal) + }, ..*p }; } @@ -56,6 +64,38 @@ fn normalize_after_erasing_regions<'tcx, T: TypeFoldable<'tcx> + PartialEq + Cop }) } +#[instrument(level = "debug", skip(tcx))] +fn try_normalize_after_erasing_regions<'tcx, T: TypeFoldable<'tcx> + PartialEq + Copy>( + tcx: TyCtxt<'tcx>, + goal: ParamEnvAnd<'tcx, T>, +) -> Result { + let ParamEnvAnd { param_env, value } = goal; + tcx.infer_ctxt().enter(|infcx| { + let cause = ObligationCause::dummy(); + match infcx.at(&cause, param_env).normalize(value) { + Ok(Normalized { value: normalized_value, obligations: normalized_obligations }) => { + // We don't care about the `obligations`; they are + // always only region relations, and we are about to + // erase those anyway: + debug_assert_eq!( + normalized_obligations.iter().find(|p| not_outlives_predicate(&p.predicate)), + None, + ); + + let resolved_value = infcx.resolve_vars_if_possible(normalized_value); + // It's unclear when `resolve_vars` would have an effect in a + // fresh `InferCtxt`. If this assert does trigger, it will give + // us a test case. + debug_assert_eq!(normalized_value, resolved_value); + let erased = infcx.tcx.erase_regions(resolved_value); + debug_assert!(!erased.needs_infer(), "{:?}", erased); + Ok(erased) + } + Err(NoSolution) => Err(NoSolution), + } + }) +} + fn not_outlives_predicate(p: &ty::Predicate<'tcx>) -> bool { match p.kind().skip_binder() { ty::PredicateKind::RegionOutlives(..) | ty::PredicateKind::TypeOutlives(..) => false,