Skip to content

Commit 36e4f5d

Browse files
committed
Auto merge of rust-lang#146096 - adwinwhite:handle_normalization_overflow_in_mono1, r=saethlin
Fix normalization overflow ICEs in monomorphization Fixes rust-lang#92004 Fixes rust-lang#92470 Fixes rust-lang#95134 Fixes rust-lang#105275 Fixes rust-lang#105937 Fixes rust-lang#117696-2 Fixes rust-lang#118590 Fixes rust-lang#122823 Fixes rust-lang#131342 Fixes rust-lang#139659 ## Analysis: The causes of these issues are similar. They contain generic recursive functions that can be instantiated with different args infinitely at monomorphization stage. Ideally this should be caught by the [`check_recursion_limit`](https://github.com/rust-lang/rust/blob/c0bb3b98bb7aac24a37635e5d36d961e0b14f435/compiler/rustc_monomorphize/src/collector.rs#L468) function. The reality is that normalization can reach recursion limit earlier than monomorphization's check because they calculate depths in different ways. Since normalization is called everywhere, ICEs appear in different locations. ## Fix: If we abort on overflow with `TypingMode::PostAnalysis` in the trait solver, it would also catch these errors. The main challenge is providing good diagnostics for them. So it's quite natural to put the check right before these normalization happening. I first tried to check the whole MIR body's normalization and `references_error`. (As elaborate_drop handles normalization failure by [returning `ty::Error`](https://github.com/rust-lang/rust/blob/c0bb3b98bb7aac24a37635e5d36d961e0b14f435/compiler/rustc_mir_transform/src/elaborate_drop.rs#L514-L519).) It turns out that checking all `Local`s seems sufficient. These types are gonna be normalized anyway. So with cache, these checks shouldn't be expensive. This fixes these ICEs for both the next and old solver, though I'm not sure the change I made to the old solver is proper. Its overflow handling looks convoluted thus I didn't try to fix it more "upstream".
2 parents 2300c2a + 08f16a9 commit 36e4f5d

27 files changed

+266
-24
lines changed

compiler/rustc_middle/src/mir/mono.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ pub enum InstantiationMode {
4747
LocalCopy,
4848
}
4949

50+
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash, HashStable, TyEncodable, TyDecodable)]
51+
pub struct NormalizationErrorInMono;
52+
5053
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash, HashStable, TyEncodable, TyDecodable)]
5154
pub enum MonoItem<'tcx> {
5255
Fn(Instance<'tcx>),

compiler/rustc_middle/src/query/erase.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ use std::intrinsics::transmute_unchecked;
33
use std::mem::MaybeUninit;
44

55
use rustc_span::ErrorGuaranteed;
6+
use rustc_span::source_map::Spanned;
67

78
use crate::mir::interpret::EvalToValTreeResult;
9+
use crate::mir::mono::{MonoItem, NormalizationErrorInMono};
810
use crate::query::CyclePlaceholder;
911
use crate::traits::solve;
1012
use crate::ty::adjustment::CoerceUnsizedInfo;
@@ -171,6 +173,17 @@ impl EraseType for Result<ty::EarlyBinder<'_, Ty<'_>>, CyclePlaceholder> {
171173
type Result = [u8; size_of::<Result<ty::EarlyBinder<'static, Ty<'_>>, CyclePlaceholder>>()];
172174
}
173175

176+
impl EraseType
177+
for Result<(&'_ [Spanned<MonoItem<'_>>], &'_ [Spanned<MonoItem<'_>>]), NormalizationErrorInMono>
178+
{
179+
type Result = [u8; size_of::<
180+
Result<
181+
(&'static [Spanned<MonoItem<'static>>], &'static [Spanned<MonoItem<'static>>]),
182+
NormalizationErrorInMono,
183+
>,
184+
>()];
185+
}
186+
174187
impl<T> EraseType for Option<&'_ T> {
175188
type Result = [u8; size_of::<Option<&'static ()>>()];
176189
}

compiler/rustc_middle/src/query/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ use crate::mir::interpret::{
116116
EvalStaticInitializerRawResult, EvalToAllocationRawResult, EvalToConstValueResult,
117117
EvalToValTreeResult, GlobalId, LitToConstInput,
118118
};
119-
use crate::mir::mono::{CodegenUnit, CollectionMode, MonoItem, MonoItemPartitions};
119+
use crate::mir::mono::{
120+
CodegenUnit, CollectionMode, MonoItem, MonoItemPartitions, NormalizationErrorInMono,
121+
};
120122
use crate::query::erase::{Erase, erase, restore};
121123
use crate::query::plumbing::{CyclePlaceholder, DynamicQuery};
122124
use crate::traits::query::{
@@ -2702,7 +2704,7 @@ rustc_queries! {
27022704
desc { "functions to skip for move-size check" }
27032705
}
27042706

2705-
query items_of_instance(key: (ty::Instance<'tcx>, CollectionMode)) -> (&'tcx [Spanned<MonoItem<'tcx>>], &'tcx [Spanned<MonoItem<'tcx>>]) {
2707+
query items_of_instance(key: (ty::Instance<'tcx>, CollectionMode)) -> Result<(&'tcx [Spanned<MonoItem<'tcx>>], &'tcx [Spanned<MonoItem<'tcx>>]), NormalizationErrorInMono> {
27062708
desc { "collecting items used by `{}`", key.0 }
27072709
cache_on_disk_if { true }
27082710
}

compiler/rustc_monomorphize/src/collector.rs

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@
208208
mod autodiff;
209209

210210
use std::cell::OnceCell;
211+
use std::ops::ControlFlow;
211212

212213
use rustc_data_structures::fx::FxIndexMap;
213214
use rustc_data_structures::sync::{MTLock, par_for_each_in};
@@ -220,15 +221,17 @@ use rustc_hir::lang_items::LangItem;
220221
use rustc_hir::limit::Limit;
221222
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
222223
use rustc_middle::mir::interpret::{AllocId, ErrorHandled, GlobalAlloc, Scalar};
223-
use rustc_middle::mir::mono::{CollectionMode, InstantiationMode, MonoItem};
224+
use rustc_middle::mir::mono::{
225+
CollectionMode, InstantiationMode, MonoItem, NormalizationErrorInMono,
226+
};
224227
use rustc_middle::mir::visit::Visitor as MirVisitor;
225-
use rustc_middle::mir::{self, Location, MentionedItem, traversal};
228+
use rustc_middle::mir::{self, Body, Location, MentionedItem, traversal};
226229
use rustc_middle::query::TyCtxtAt;
227230
use rustc_middle::ty::adjustment::{CustomCoerceUnsized, PointerCoercion};
228231
use rustc_middle::ty::layout::ValidityRequirement;
229232
use rustc_middle::ty::{
230233
self, GenericArgs, GenericParamDefKind, Instance, InstanceKind, Ty, TyCtxt, TypeFoldable,
231-
TypeVisitableExt, VtblEntry,
234+
TypeVisitable, TypeVisitableExt, TypeVisitor, VtblEntry,
232235
};
233236
use rustc_middle::util::Providers;
234237
use rustc_middle::{bug, span_bug};
@@ -474,7 +477,22 @@ fn collect_items_rec<'tcx>(
474477
));
475478

476479
rustc_data_structures::stack::ensure_sufficient_stack(|| {
477-
let (used, mentioned) = tcx.items_of_instance((instance, mode));
480+
let Ok((used, mentioned)) = tcx.items_of_instance((instance, mode)) else {
481+
// Normalization errors here are usually due to trait solving overflow.
482+
// FIXME: I assume that there are few type errors at post-analysis stage, but not
483+
// entirely sure.
484+
// We have to emit the error outside of `items_of_instance` to access the
485+
// span of the `starting_item`.
486+
let def_id = instance.def_id();
487+
let def_span = tcx.def_span(def_id);
488+
let def_path_str = tcx.def_path_str(def_id);
489+
tcx.dcx().emit_fatal(RecursionLimit {
490+
span: starting_item.span,
491+
instance,
492+
def_span,
493+
def_path_str,
494+
});
495+
};
478496
used_items.extend(used.into_iter().copied());
479497
mentioned_items.extend(mentioned.into_iter().copied());
480498
});
@@ -603,6 +621,35 @@ fn collect_items_rec<'tcx>(
603621
}
604622
}
605623

624+
// Check whether we can normalize every type in the instantiated MIR body.
625+
fn check_normalization_error<'tcx>(
626+
tcx: TyCtxt<'tcx>,
627+
instance: Instance<'tcx>,
628+
body: &Body<'tcx>,
629+
) -> Result<(), NormalizationErrorInMono> {
630+
struct NormalizationChecker<'tcx> {
631+
tcx: TyCtxt<'tcx>,
632+
instance: Instance<'tcx>,
633+
}
634+
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for NormalizationChecker<'tcx> {
635+
type Result = ControlFlow<()>;
636+
637+
fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
638+
match self.instance.try_instantiate_mir_and_normalize_erasing_regions(
639+
self.tcx,
640+
ty::TypingEnv::fully_monomorphized(),
641+
ty::EarlyBinder::bind(t),
642+
) {
643+
Ok(_) => ControlFlow::Continue(()),
644+
Err(_) => ControlFlow::Break(()),
645+
}
646+
}
647+
}
648+
649+
let mut checker = NormalizationChecker { tcx, instance };
650+
if body.visit_with(&mut checker).is_break() { Err(NormalizationErrorInMono) } else { Ok(()) }
651+
}
652+
606653
fn check_recursion_limit<'tcx>(
607654
tcx: TyCtxt<'tcx>,
608655
instance: Instance<'tcx>,
@@ -1259,11 +1306,15 @@ fn collect_items_of_instance<'tcx>(
12591306
tcx: TyCtxt<'tcx>,
12601307
instance: Instance<'tcx>,
12611308
mode: CollectionMode,
1262-
) -> (MonoItems<'tcx>, MonoItems<'tcx>) {
1309+
) -> Result<(MonoItems<'tcx>, MonoItems<'tcx>), NormalizationErrorInMono> {
12631310
// This item is getting monomorphized, do mono-time checks.
1311+
let body = tcx.instance_mir(instance.def);
1312+
// Plenty of code paths later assume that everything can be normalized. So we have to check
1313+
// normalization first.
1314+
// We choose to emit the error outside to provide helpful diagnostics.
1315+
check_normalization_error(tcx, instance, body)?;
12641316
tcx.ensure_ok().check_mono_item(instance);
12651317

1266-
let body = tcx.instance_mir(instance.def);
12671318
// Naively, in "used" collection mode, all functions get added to *both* `used_items` and
12681319
// `mentioned_items`. Mentioned items processing will then notice that they have already been
12691320
// visited, but at that point each mentioned item has been monomorphized, added to the
@@ -1313,19 +1364,22 @@ fn collect_items_of_instance<'tcx>(
13131364
}
13141365
}
13151366

1316-
(used_items, mentioned_items)
1367+
Ok((used_items, mentioned_items))
13171368
}
13181369

13191370
fn items_of_instance<'tcx>(
13201371
tcx: TyCtxt<'tcx>,
13211372
(instance, mode): (Instance<'tcx>, CollectionMode),
1322-
) -> (&'tcx [Spanned<MonoItem<'tcx>>], &'tcx [Spanned<MonoItem<'tcx>>]) {
1323-
let (used_items, mentioned_items) = collect_items_of_instance(tcx, instance, mode);
1373+
) -> Result<
1374+
(&'tcx [Spanned<MonoItem<'tcx>>], &'tcx [Spanned<MonoItem<'tcx>>]),
1375+
NormalizationErrorInMono,
1376+
> {
1377+
let (used_items, mentioned_items) = collect_items_of_instance(tcx, instance, mode)?;
13241378

13251379
let used_items = tcx.arena.alloc_from_iter(used_items);
13261380
let mentioned_items = tcx.arena.alloc_from_iter(mentioned_items);
13271381

1328-
(used_items, mentioned_items)
1382+
Ok((used_items, mentioned_items))
13291383
}
13301384

13311385
/// `item` must be already monomorphized.

compiler/rustc_traits/src/normalize_erasing_regions.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,18 @@ fn try_normalize_after_erasing_regions<'tcx, T: TypeFoldable<TyCtxt<'tcx>> + Par
4242
// us a test case.
4343
debug_assert_eq!(normalized_value, resolved_value);
4444
let erased = infcx.tcx.erase_and_anonymize_regions(resolved_value);
45-
debug_assert!(!erased.has_infer(), "{erased:?}");
45+
if infcx.next_trait_solver() {
46+
debug_assert!(!erased.has_infer(), "{erased:?}");
47+
} else {
48+
// The old solver returns an ty var with the failed obligation in case of
49+
// selection error. And when the obligation is re-tried, the error should be
50+
// reported. However in case of overflow error, the obligation may be fulfilled
51+
// due to the original depth being dropped.
52+
// In conclusion, overflow results in an unconstrained ty var.
53+
if erased.has_infer() {
54+
return Err(NoSolution);
55+
}
56+
}
4657
Ok(erased)
4758
}
4859
Err(NoSolution) => Err(NoSolution),

tests/crashes/105275.rs renamed to tests/ui/codegen/normalization-overflow/recursion-issue-105275.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
//@ known-bug: #105275
1+
//@ build-fail
22
//@ compile-flags: -Copt-level=0
33

44
pub fn encode_num<Writer: ExampleWriter>(n: u32, mut writer: Writer) -> Result<(), Writer::Error> {
55
if n > 15 {
66
encode_num(n / 16, &mut writer)?;
7+
//~^ ERROR: reached the recursion limit while instantiating
78
}
89
Ok(())
910
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: reached the recursion limit while instantiating `encode_num::<&mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut Error>`
2+
--> $DIR/recursion-issue-105275.rs:6:9
3+
|
4+
LL | encode_num(n / 16, &mut writer)?;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
note: `encode_num` defined here
8+
--> $DIR/recursion-issue-105275.rs:4:1
9+
|
10+
LL | pub fn encode_num<Writer: ExampleWriter>(n: u32, mut writer: Writer) -> Result<(), Writer::Error> {
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to 1 previous error
14+

tests/crashes/105937.rs renamed to tests/ui/codegen/normalization-overflow/recursion-issue-105937.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
//@ known-bug: #105937
1+
//@ build-fail
22
//@ compile-flags: -Copt-level=0
33

44
pub fn encode_num<Writer: ExampleWriter>(n: u32, mut writer: Writer) -> Result<(), Writer::Error> {
55
if n > 15 {
66
encode_num(n / 16, &mut writer)?;
7+
//~^ ERROR: reached the recursion limit while instantiating
78
}
89
Ok(())
910
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: reached the recursion limit while instantiating `encode_num::<&mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut &mut Error>`
2+
--> $DIR/recursion-issue-105937.rs:6:9
3+
|
4+
LL | encode_num(n / 16, &mut writer)?;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
note: `encode_num` defined here
8+
--> $DIR/recursion-issue-105937.rs:4:1
9+
|
10+
LL | pub fn encode_num<Writer: ExampleWriter>(n: u32, mut writer: Writer) -> Result<(), Writer::Error> {
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to 1 previous error
14+

tests/crashes/117696-1.rs renamed to tests/ui/codegen/normalization-overflow/recursion-issue-117696-1.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@ known-bug: #117696
1+
//@ build-fail
22
fn main() {
33
let mut it = (Empty);
44
rec(&mut it);
@@ -23,6 +23,7 @@ where
2323
{
2424
if () == () {
2525
T::count(it);
26+
//~^ ERROR: reached the recursion limit while instantiating
2627
} else {
2728
rec(identity(&mut it))
2829
}

0 commit comments

Comments
 (0)