Skip to content

Commit 0f7f3f4

Browse files
Re-implement a type-size based limit
1 parent 9dc129a commit 0f7f3f4

25 files changed

+206
-160
lines changed

compiler/rustc_middle/messages.ftl

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ middle_cannot_be_normalized =
4141
middle_conflict_types =
4242
this expression supplies two conflicting concrete types for the same opaque type
4343
44+
middle_consider_type_length_limit =
45+
consider adding a `#![type_length_limit="{$type_length}"]` attribute to your crate
46+
4447
middle_const_eval_non_int =
4548
constant evaluation of enum discriminant resulted in non-integer
4649
@@ -94,8 +97,11 @@ middle_strict_coherence_needs_negative_coherence =
9497
to use `strict_coherence` on this trait, the `with_negative_coherence` feature must be enabled
9598
.label = due to this attribute
9699
100+
middle_type_length_limit = reached the type-length limit while instantiating `{$shrunk}`
101+
97102
middle_unknown_layout =
98103
the type `{$ty}` has an unknown layout
99104
100105
middle_values_too_big =
101106
values of the type `{$ty}` are too big for the current architecture
107+
middle_written_to_path = the full type name has been written to '{$path}'

compiler/rustc_middle/src/error.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::fmt;
2+
use std::path::PathBuf;
23

34
use rustc_errors::{codes::*, DiagArgName, DiagArgValue, DiagMessage};
45
use rustc_macros::{Diagnostic, Subdiagnostic};
@@ -149,3 +150,16 @@ pub struct ErroneousConstant {
149150

150151
/// Used by `rustc_const_eval`
151152
pub use crate::fluent_generated::middle_adjust_for_foreign_abi_error;
153+
154+
#[derive(Diagnostic)]
155+
#[diag(middle_type_length_limit)]
156+
#[help(middle_consider_type_length_limit)]
157+
pub struct TypeLengthLimit {
158+
#[primary_span]
159+
pub span: Span,
160+
pub shrunk: String,
161+
#[note(middle_written_to_path)]
162+
pub was_written: Option<()>,
163+
pub path: PathBuf,
164+
pub type_length: usize,
165+
}

compiler/rustc_middle/src/middle/limits.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub fn provide(providers: &mut Providers) {
3030
tcx.hir().krate_attrs(),
3131
tcx.sess,
3232
sym::type_length_limit,
33-
1048576,
33+
2usize.pow(24),
3434
),
3535
}
3636
}

compiler/rustc_middle/src/ty/instance.rs

+73-9
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
1+
use crate::error;
12
use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags;
2-
use crate::ty::print::{FmtPrinter, Printer};
3-
use crate::ty::{self, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable};
4-
use crate::ty::{EarlyBinder, GenericArgs, GenericArgsRef, TypeVisitableExt};
3+
use crate::ty::print::{shrunk_instance_name, FmtPrinter, Printer};
4+
use crate::ty::{
5+
self, EarlyBinder, GenericArgs, GenericArgsRef, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable,
6+
TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
7+
};
58
use rustc_errors::ErrorGuaranteed;
69
use rustc_hir as hir;
710
use rustc_hir::def::Namespace;
811
use rustc_hir::def_id::{CrateNum, DefId};
912
use rustc_hir::lang_items::LangItem;
1013
use rustc_index::bit_set::FiniteBitSet;
11-
use rustc_macros::{
12-
Decodable, Encodable, HashStable, Lift, TyDecodable, TyEncodable, TypeVisitable,
13-
};
14+
use rustc_macros::{Decodable, Encodable, HashStable, Lift, TyDecodable, TyEncodable};
1415
use rustc_middle::ty::normalize_erasing_regions::NormalizationError;
1516
use rustc_span::def_id::LOCAL_CRATE;
16-
use rustc_span::{Span, Symbol};
17+
use rustc_span::{Span, Symbol, DUMMY_SP};
1718
use tracing::{debug, instrument};
1819

1920
use std::assert_matches::assert_matches;
2021
use std::fmt;
22+
use std::path::PathBuf;
2123

2224
/// An `InstanceKind` along with the args that are needed to substitute the instance.
2325
///
@@ -385,7 +387,28 @@ impl<'tcx> InstanceKind<'tcx> {
385387
}
386388
}
387389

388-
fn fmt_instance(
390+
fn type_length<'tcx>(item: impl TypeVisitable<TyCtxt<'tcx>>) -> usize {
391+
struct Visitor {
392+
type_length: usize,
393+
}
394+
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for Visitor {
395+
fn visit_ty(&mut self, t: Ty<'tcx>) {
396+
self.type_length += 1;
397+
t.super_visit_with(self);
398+
}
399+
400+
fn visit_const(&mut self, ct: ty::Const<'tcx>) {
401+
self.type_length += 1;
402+
ct.super_visit_with(self);
403+
}
404+
}
405+
let mut visitor = Visitor { type_length: 0 };
406+
item.visit_with(&mut visitor);
407+
408+
visitor.type_length
409+
}
410+
411+
pub fn fmt_instance(
389412
f: &mut fmt::Formatter<'_>,
390413
instance: Instance<'_>,
391414
type_length: Option<rustc_session::Limit>,
@@ -485,7 +508,8 @@ impl<'tcx> Instance<'tcx> {
485508
///
486509
/// Presuming that coherence and type-check have succeeded, if this method is invoked
487510
/// in a monomorphic context (i.e., like during codegen), then it is guaranteed to return
488-
/// `Ok(Some(instance))`.
511+
/// `Ok(Some(instance))`, **except** for when the instance's inputs hit the type size limit,
512+
/// in which case it may bail out and return `Ok(None)`.
489513
///
490514
/// Returns `Err(ErrorGuaranteed)` when the `Instance` resolution process
491515
/// couldn't complete due to errors elsewhere - this is distinct
@@ -498,6 +522,16 @@ impl<'tcx> Instance<'tcx> {
498522
def_id: DefId,
499523
args: GenericArgsRef<'tcx>,
500524
) -> Result<Option<Instance<'tcx>>, ErrorGuaranteed> {
525+
// Rust code can easily create exponentially-long types using only a
526+
// polynomial recursion depth. Even with the default recursion
527+
// depth, you can easily get cases that take >2^60 steps to run,
528+
// which means that rustc basically hangs.
529+
//
530+
// Bail out in these cases to avoid that bad user experience.
531+
if !tcx.type_length_limit().value_within_limit(type_length(args)) {
532+
return Ok(None);
533+
}
534+
501535
// All regions in the result of this query are erased, so it's
502536
// fine to erase all of the input regions.
503537

@@ -517,6 +551,36 @@ impl<'tcx> Instance<'tcx> {
517551
) -> Instance<'tcx> {
518552
match ty::Instance::resolve(tcx, param_env, def_id, args) {
519553
Ok(Some(instance)) => instance,
554+
Ok(None) => {
555+
let type_length = type_length(args);
556+
if !tcx.type_length_limit().value_within_limit(type_length) {
557+
let (shrunk, written_to_path) =
558+
shrunk_instance_name(tcx, Instance::new(def_id, args));
559+
let mut path = PathBuf::new();
560+
let was_written = if let Some(path2) = written_to_path {
561+
path = path2;
562+
Some(())
563+
} else {
564+
None
565+
};
566+
tcx.dcx().emit_fatal(error::TypeLengthLimit {
567+
// We don't use `def_span(def_id)` so that diagnostics point
568+
// to the crate root during mono instead of to foreign items.
569+
// This is arguably better.
570+
span: span.unwrap_or(DUMMY_SP),
571+
shrunk,
572+
was_written,
573+
path,
574+
type_length,
575+
});
576+
} else {
577+
span_bug!(
578+
span.unwrap_or(tcx.def_span(def_id)),
579+
"failed to resolve instance for {}",
580+
tcx.def_path_str_with_args(def_id, args)
581+
)
582+
}
583+
}
520584
instance => span_bug!(
521585
span.unwrap_or(tcx.def_span(def_id)),
522586
"failed to resolve instance for {}: {instance:#?}",

compiler/rustc_middle/src/ty/print/mod.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use std::path::PathBuf;
2+
13
use crate::ty::GenericArg;
2-
use crate::ty::{self, Ty, TyCtxt};
4+
use crate::ty::{self, ShortInstance, Ty, TyCtxt};
35

46
use hir::def::Namespace;
57
use rustc_data_structures::fx::FxHashSet;
@@ -356,3 +358,31 @@ where
356358
with_no_trimmed_paths!(Self::print(t, fmt))
357359
}
358360
}
361+
362+
/// Format instance name that is already known to be too long for rustc.
363+
/// Show only the first 2 types if it is longer than 32 characters to avoid blasting
364+
/// the user's terminal with thousands of lines of type-name.
365+
///
366+
/// If the type name is longer than before+after, it will be written to a file.
367+
pub fn shrunk_instance_name<'tcx>(
368+
tcx: TyCtxt<'tcx>,
369+
instance: ty::Instance<'tcx>,
370+
) -> (String, Option<PathBuf>) {
371+
let s = instance.to_string();
372+
373+
// Only use the shrunk version if it's really shorter.
374+
// This also avoids the case where before and after slices overlap.
375+
if s.chars().nth(33).is_some() {
376+
let shrunk = format!("{}", ShortInstance(instance, 4));
377+
if shrunk == s {
378+
return (s, None);
379+
}
380+
381+
let path = tcx.output_filenames(()).temp_path_ext("long-type.txt", None);
382+
let written_to_path = std::fs::write(&path, s).ok().map(|_| path);
383+
384+
(shrunk, written_to_path)
385+
} else {
386+
(s, None)
387+
}
388+
}

compiler/rustc_monomorphize/messages.ftl

-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
monomorphize_consider_type_length_limit =
2-
consider adding a `#![type_length_limit="{$type_length}"]` attribute to your crate
3-
41
monomorphize_couldnt_dump_mono_stats =
52
unexpected error occurred while dumping monomorphization stats: {$error}
63
@@ -25,8 +22,6 @@ monomorphize_start_not_found = using `fn main` requires the standard library
2522
2623
monomorphize_symbol_already_defined = symbol `{$symbol}` is already defined
2724
28-
monomorphize_type_length_limit = reached the type-length limit while instantiating `{$shrunk}`
29-
3025
monomorphize_unknown_cgu_collection_mode =
3126
unknown codegen-item collection mode '{$mode}', falling back to 'lazy' mode
3227

compiler/rustc_monomorphize/src/collector.rs

+3-66
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,12 @@ use rustc_middle::mir::{self, Location, MentionedItem};
222222
use rustc_middle::query::TyCtxtAt;
223223
use rustc_middle::ty::adjustment::{CustomCoerceUnsized, PointerCoercion};
224224
use rustc_middle::ty::layout::ValidityRequirement;
225-
use rustc_middle::ty::print::with_no_trimmed_paths;
225+
use rustc_middle::ty::print::{shrunk_instance_name, with_no_trimmed_paths};
226+
use rustc_middle::ty::GenericArgs;
226227
use rustc_middle::ty::{
227228
self, AssocKind, GenericParamDefKind, Instance, InstanceKind, Ty, TyCtxt, TypeFoldable,
228229
TypeVisitableExt, VtblEntry,
229230
};
230-
use rustc_middle::ty::{GenericArgKind, GenericArgs};
231231
use rustc_middle::{bug, span_bug};
232232
use rustc_session::config::EntryFnType;
233233
use rustc_session::Limit;
@@ -238,9 +238,7 @@ use rustc_target::abi::Size;
238238
use std::path::PathBuf;
239239
use tracing::{debug, instrument, trace};
240240

241-
use crate::errors::{
242-
self, EncounteredErrorWhileInstantiating, NoOptimizedMir, RecursionLimit, TypeLengthLimit,
243-
};
241+
use crate::errors::{self, EncounteredErrorWhileInstantiating, NoOptimizedMir, RecursionLimit};
244242
use move_check::MoveCheckState;
245243

246244
#[derive(PartialEq)]
@@ -443,7 +441,6 @@ fn collect_items_rec<'tcx>(
443441
recursion_depths,
444442
recursion_limit,
445443
));
446-
check_type_length_limit(tcx, instance);
447444

448445
rustc_data_structures::stack::ensure_sufficient_stack(|| {
449446
collect_items_of_instance(
@@ -554,34 +551,6 @@ fn collect_items_rec<'tcx>(
554551
}
555552
}
556553

557-
/// Format instance name that is already known to be too long for rustc.
558-
/// Show only the first 2 types if it is longer than 32 characters to avoid blasting
559-
/// the user's terminal with thousands of lines of type-name.
560-
///
561-
/// If the type name is longer than before+after, it will be written to a file.
562-
fn shrunk_instance_name<'tcx>(
563-
tcx: TyCtxt<'tcx>,
564-
instance: Instance<'tcx>,
565-
) -> (String, Option<PathBuf>) {
566-
let s = instance.to_string();
567-
568-
// Only use the shrunk version if it's really shorter.
569-
// This also avoids the case where before and after slices overlap.
570-
if s.chars().nth(33).is_some() {
571-
let shrunk = format!("{}", ty::ShortInstance(instance, 4));
572-
if shrunk == s {
573-
return (s, None);
574-
}
575-
576-
let path = tcx.output_filenames(()).temp_path_ext("long-type.txt", None);
577-
let written_to_path = std::fs::write(&path, s).ok().map(|_| path);
578-
579-
(shrunk, written_to_path)
580-
} else {
581-
(s, None)
582-
}
583-
}
584-
585554
fn check_recursion_limit<'tcx>(
586555
tcx: TyCtxt<'tcx>,
587556
instance: Instance<'tcx>,
@@ -630,38 +599,6 @@ fn check_recursion_limit<'tcx>(
630599
(def_id, recursion_depth)
631600
}
632601

633-
fn check_type_length_limit<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
634-
let type_length = instance
635-
.args
636-
.iter()
637-
.flat_map(|arg| arg.walk())
638-
.filter(|arg| match arg.unpack() {
639-
GenericArgKind::Type(_) | GenericArgKind::Const(_) => true,
640-
GenericArgKind::Lifetime(_) => false,
641-
})
642-
.count();
643-
debug!(" => type length={}", type_length);
644-
645-
// Rust code can easily create exponentially-long types using only a
646-
// polynomial recursion depth. Even with the default recursion
647-
// depth, you can easily get cases that take >2^60 steps to run,
648-
// which means that rustc basically hangs.
649-
//
650-
// Bail out in these cases to avoid that bad user experience.
651-
if !tcx.type_length_limit().value_within_limit(type_length) {
652-
let (shrunk, written_to_path) = shrunk_instance_name(tcx, instance);
653-
let span = tcx.def_span(instance.def_id());
654-
let mut path = PathBuf::new();
655-
let was_written = if let Some(path2) = written_to_path {
656-
path = path2;
657-
Some(())
658-
} else {
659-
None
660-
};
661-
tcx.dcx().emit_fatal(TypeLengthLimit { span, shrunk, was_written, path, type_length });
662-
}
663-
}
664-
665602
struct MirUsedCollector<'a, 'tcx> {
666603
tcx: TyCtxt<'tcx>,
667604
body: &'a mir::Body<'tcx>,

compiler/rustc_monomorphize/src/errors.rs

-13
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,6 @@ pub struct RecursionLimit {
1919
pub path: PathBuf,
2020
}
2121

22-
#[derive(Diagnostic)]
23-
#[diag(monomorphize_type_length_limit)]
24-
#[help(monomorphize_consider_type_length_limit)]
25-
pub struct TypeLengthLimit {
26-
#[primary_span]
27-
pub span: Span,
28-
pub shrunk: String,
29-
#[note(monomorphize_written_to_path)]
30-
pub was_written: Option<()>,
31-
pub path: PathBuf,
32-
pub type_length: usize,
33-
}
34-
3522
#[derive(Diagnostic)]
3623
#[diag(monomorphize_no_optimized_mir)]
3724
pub struct NoOptimizedMir {

tests/ui/closures/issue-72408-nested-closures-exponential.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
//@ build-pass
2-
//@ ignore-compare-mode-next-solver (hangs)
1+
//@ build-fail
32

43
// Closures include captured types twice in a type tree.
54
//
@@ -46,6 +45,7 @@ fn main() {
4645

4746
let f = dup(f);
4847
let f = dup(f);
48+
//~^ ERROR reached the type-length limit
4949
let f = dup(f);
5050
let f = dup(f);
5151
let f = dup(f);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: reached the type-length limit while instantiating `dup::<{closure@$DIR/issue-72408-nested-closures-exponential.rs:13:5: 13:13}>`
2+
--> $DIR/issue-72408-nested-closures-exponential.rs:47:13
3+
|
4+
LL | let f = dup(f);
5+
| ^^^^^^
6+
|
7+
= help: consider adding a `#![type_length_limit="29360121"]` attribute to your crate
8+
9+
error: aborting due to 1 previous error
10+

tests/ui/codegen/overflow-during-mono.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
//@ build-fail
2-
//~^ ERROR overflow evaluating the requirement
32

43
#![recursion_limit = "32"]
54

0 commit comments

Comments
 (0)