Skip to content

Commit 2848c2e

Browse files
committed
Auto merge of #147361 - saethlin:fallible-cycle-detection, r=oli-obk
Make inliner cycle detection a fallible process The query `mir_callgraph_cyclic` is supposed to find _all_ callees that _may_ lead to a recursive call back to the given `LocalDefId`. But that query was built using a function which recurses through the call graph and tries to locally handle hitting the recursion limit during the walk. That is wrong. If the recursion limit is encountered, the set may be incomplete and thus useless. If we hit the recursion limit the only correct thing to do is bail. Some benchmarks improve because for some functions we will bail out of the call graph walk faster. Some benchmarks regress because we do less inlining, but that is quite rare with the default recursion depth. Originally I thought this might be a fix for #131960, but it turns out that it is _actually_ a fix for #146998.
2 parents 629b092 + cee7f5e commit 2848c2e

File tree

5 files changed

+84
-15
lines changed

5 files changed

+84
-15
lines changed

compiler/rustc_middle/src/query/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,8 +1312,9 @@ rustc_queries! {
13121312
return_result_from_ensure_ok
13131313
}
13141314

1315-
/// Return the set of (transitive) callees that may result in a recursive call to `key`.
1316-
query mir_callgraph_cyclic(key: LocalDefId) -> &'tcx UnordSet<LocalDefId> {
1315+
/// Return the set of (transitive) callees that may result in a recursive call to `key`,
1316+
/// if we were able to walk all callees.
1317+
query mir_callgraph_cyclic(key: LocalDefId) -> &'tcx Option<UnordSet<LocalDefId>> {
13171318
fatal_cycle
13181319
arena_cache
13191320
desc { |tcx|

compiler/rustc_mir_transform/src/inline.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -775,8 +775,11 @@ fn check_mir_is_available<'tcx, I: Inliner<'tcx>>(
775775
{
776776
// If we know for sure that the function we're calling will itself try to
777777
// call us, then we avoid inlining that function.
778-
if inliner.tcx().mir_callgraph_cyclic(caller_def_id.expect_local()).contains(&callee_def_id)
779-
{
778+
let Some(cyclic_callees) = inliner.tcx().mir_callgraph_cyclic(caller_def_id.expect_local())
779+
else {
780+
return Err("call graph cycle detection bailed due to recursion limit");
781+
};
782+
if cyclic_callees.contains(&callee_def_id) {
780783
debug!("query cycle avoidance");
781784
return Err("caller might be reachable from callee");
782785
}

compiler/rustc_mir_transform/src/inline/cycle.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ fn process<'tcx>(
6868
involved: &mut FxHashSet<LocalDefId>,
6969
recursion_limiter: &mut FxHashMap<DefId, usize>,
7070
recursion_limit: Limit,
71-
) -> bool {
71+
) -> Option<bool> {
7272
trace!(%caller);
7373
let mut reaches_root = false;
7474

@@ -127,10 +127,9 @@ fn process<'tcx>(
127127
recursion_limiter,
128128
recursion_limit,
129129
)
130-
})
130+
})?
131131
} else {
132-
// Pessimistically assume that there could be recursion.
133-
true
132+
return None;
134133
};
135134
seen.insert(callee, callee_reaches_root);
136135
callee_reaches_root
@@ -144,14 +143,14 @@ fn process<'tcx>(
144143
}
145144
}
146145

147-
reaches_root
146+
Some(reaches_root)
148147
}
149148

150149
#[instrument(level = "debug", skip(tcx), ret)]
151150
pub(crate) fn mir_callgraph_cyclic<'tcx>(
152151
tcx: TyCtxt<'tcx>,
153152
root: LocalDefId,
154-
) -> UnordSet<LocalDefId> {
153+
) -> Option<UnordSet<LocalDefId>> {
155154
assert!(
156155
!tcx.is_constructor(root.to_def_id()),
157156
"you should not call `mir_callgraph_reachable` on enum/struct constructor functions"
@@ -164,16 +163,16 @@ pub(crate) fn mir_callgraph_cyclic<'tcx>(
164163
// limit, we will hit the limit first and give up on looking for inlining. And in any case,
165164
// the default recursion limits are quite generous for us. If we need to recurse 64 times
166165
// into the call graph, we're probably not going to find any useful MIR inlining.
167-
let recursion_limit = tcx.recursion_limit() / 2;
166+
let recursion_limit = tcx.recursion_limit() / 8;
168167
let mut involved = FxHashSet::default();
169168
let typing_env = ty::TypingEnv::post_analysis(tcx, root);
170169
let root_instance =
171170
ty::Instance::new_raw(root.to_def_id(), ty::GenericArgs::identity_for_item(tcx, root));
172171
if !should_recurse(tcx, root_instance) {
173172
trace!("cannot walk, skipping");
174-
return involved.into();
173+
return Some(involved.into());
175174
}
176-
process(
175+
match process(
177176
tcx,
178177
typing_env,
179178
root_instance,
@@ -182,8 +181,10 @@ pub(crate) fn mir_callgraph_cyclic<'tcx>(
182181
&mut involved,
183182
&mut FxHashMap::default(),
184183
recursion_limit,
185-
);
186-
involved.into()
184+
) {
185+
Some(_) => Some(involved.into()),
186+
_ => None,
187+
}
187188
}
188189

189190
pub(crate) fn mir_inliner_callees<'tcx>(
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//@ no-prefer-dynamic
2+
//@ compile-flags: -O
3+
4+
pub trait Compare {
5+
fn eq(self);
6+
}
7+
8+
pub fn wrap<A: Compare>(a: A) {
9+
Compare::eq(a);
10+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//@ aux-build: wrapper.rs
2+
//@ compile-flags: -Zmir-opt-level=2 -Zinline-mir
3+
// skip-filecheck
4+
5+
// This is a regression test for https://github.com/rust-lang/rust/issues/146998
6+
7+
extern crate wrapper;
8+
use wrapper::{Compare, wrap};
9+
10+
pub struct BundleInner;
11+
12+
impl Compare for BundleInner {
13+
fn eq(self) {
14+
lots_of_calls();
15+
wrap(Resource::ExtensionValue);
16+
}
17+
}
18+
19+
pub enum Resource {
20+
Bundle,
21+
ExtensionValue,
22+
}
23+
24+
impl Compare for Resource {
25+
fn eq(self) {
26+
match self {
27+
Self::Bundle => wrap(BundleInner),
28+
Self::ExtensionValue => lots_of_calls(),
29+
}
30+
}
31+
}
32+
33+
macro_rules! units {
34+
($($n: ident)*) => {
35+
$(
36+
struct $n;
37+
38+
impl Compare for $n {
39+
fn eq(self) { }
40+
}
41+
42+
wrap($n);
43+
)*
44+
};
45+
}
46+
47+
fn lots_of_calls() {
48+
units! {
49+
O1 O2 O3 O4 O5 O6 O7 O8 O9 O10 O11 O12 O13 O14 O15 O16 O17 O18 O19 O20
50+
O21 O22 O23 O24 O25 O26 O27 O28 O29 O30 O31 O32 O33 O34 O35 O36 O37 O38 O39 O40
51+
O41 O42 O43 O44 O45 O46 O47 O48 O49 O50 O51 O52 O53 O54 O55 O56 O57 O58 O59 O60
52+
O61 O62 O63 O64 O65
53+
}
54+
}

0 commit comments

Comments
 (0)