Skip to content

Commit abbadb1

Browse files
Be a bit more careful around exotic cycles in in the inliner
1 parent 6b3ae3f commit abbadb1

File tree

4 files changed

+166
-16
lines changed

4 files changed

+166
-16
lines changed

compiler/rustc_mir_transform/src/inline/cycle.rs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,15 @@ fn process<'tcx>(
6464
typing_env: ty::TypingEnv<'tcx>,
6565
caller: ty::Instance<'tcx>,
6666
target: LocalDefId,
67-
seen: &mut FxHashSet<ty::Instance<'tcx>>,
67+
seen: &mut FxHashMap<ty::Instance<'tcx>, bool>,
6868
involved: &mut FxHashSet<LocalDefId>,
6969
recursion_limiter: &mut FxHashMap<DefId, usize>,
7070
recursion_limit: Limit,
7171
) -> bool {
7272
trace!(%caller);
73-
let mut cycle_found = false;
73+
let mut reaches_root = false;
7474

75-
for &(callee, args) in tcx.mir_inliner_callees(caller.def) {
75+
for &(callee_def_id, args) in tcx.mir_inliner_callees(caller.def) {
7676
let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions(
7777
tcx,
7878
typing_env,
@@ -81,14 +81,17 @@ fn process<'tcx>(
8181
trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping");
8282
continue;
8383
};
84-
let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else {
85-
trace!(?callee, "cannot resolve, skipping");
84+
let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee_def_id, args)
85+
else {
86+
trace!(?callee_def_id, "cannot resolve, skipping");
8687
continue;
8788
};
8889

8990
// Found a path.
9091
if callee.def_id() == target.to_def_id() {
91-
cycle_found = true;
92+
reaches_root = true;
93+
seen.insert(callee, true);
94+
continue;
9295
}
9396

9497
if tcx.is_constructor(callee.def_id()) {
@@ -101,10 +104,17 @@ fn process<'tcx>(
101104
continue;
102105
}
103106

104-
if seen.insert(callee) {
107+
let callee_reaches_root = if let Some(&c) = seen.get(&callee) {
108+
// Even if we have seen this callee before, and thus don't need
109+
// to recurse into it, we still need to propagate whether it reaches
110+
// the root so that we can mark all the involved callers, in case we
111+
// end up reaching that same recursive callee through some *other* cycle.
112+
c
113+
} else {
114+
seen.insert(callee, false);
105115
let recursion = recursion_limiter.entry(callee.def_id()).or_default();
106116
trace!(?callee, recursion = *recursion);
107-
let found_recursion = if recursion_limit.value_within_limit(*recursion) {
117+
let callee_reaches_root = if recursion_limit.value_within_limit(*recursion) {
108118
*recursion += 1;
109119
ensure_sufficient_stack(|| {
110120
process(
@@ -122,17 +132,19 @@ fn process<'tcx>(
122132
// Pessimistically assume that there could be recursion.
123133
true
124134
};
125-
if found_recursion {
126-
if let Some(callee) = callee.def_id().as_local() {
127-
// Calling `optimized_mir` of a non-local definition cannot cycle.
128-
involved.insert(callee);
129-
}
130-
cycle_found = true;
135+
seen.insert(callee, callee_reaches_root);
136+
callee_reaches_root
137+
};
138+
if callee_reaches_root {
139+
if let Some(callee_def_id) = callee.def_id().as_local() {
140+
// Calling `optimized_mir` of a non-local definition cannot cycle.
141+
involved.insert(callee_def_id);
131142
}
143+
reaches_root = true;
132144
}
133145
}
134146

135-
cycle_found
147+
reaches_root
136148
}
137149

138150
#[instrument(level = "debug", skip(tcx), ret)]
@@ -166,7 +178,7 @@ pub(crate) fn mir_callgraph_cyclic<'tcx>(
166178
typing_env,
167179
root_instance,
168180
root,
169-
&mut FxHashSet::default(),
181+
&mut FxHashMap::default(),
170182
&mut involved,
171183
&mut FxHashMap::default(),
172184
recursion_limit,
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
- // MIR for `a` before Inline
2+
+ // MIR for `a` after Inline
3+
4+
fn a() -> () {
5+
let mut _0: ();
6+
let _1: ();
7+
let mut _2: &fn() {a};
8+
let _3: fn() {a};
9+
let mut _4: ();
10+
let _5: ();
11+
let mut _6: &fn() {b};
12+
let _7: fn() {b};
13+
let mut _8: ();
14+
let mut _9: &fn() {b};
15+
let mut _10: &fn() {a};
16+
+ scope 1 (inlined ops::function::impls::<impl FnOnce<()> for &fn() {a}>::call_once) {
17+
+ scope 2 (inlined <fn() {a} as Fn<()>>::call - shim(fn() {a})) {
18+
+ }
19+
+ }
20+
+ scope 3 (inlined ops::function::impls::<impl FnOnce<()> for &fn() {b}>::call_once) {
21+
+ scope 4 (inlined <fn() {b} as Fn<()>>::call - shim(fn() {b})) {
22+
+ }
23+
+ }
24+
25+
bb0: {
26+
StorageLive(_1);
27+
StorageLive(_2);
28+
_10 = const a::promoted[1];
29+
_2 = &(*_10);
30+
StorageLive(_4);
31+
_4 = ();
32+
- _1 = <&fn() {a} as FnOnce<()>>::call_once(move _2, move _4) -> [return: bb1, unwind continue];
33+
+ _1 = move (*_2)() -> [return: bb1, unwind continue];
34+
}
35+
36+
bb1: {
37+
StorageDead(_4);
38+
StorageDead(_2);
39+
StorageDead(_1);
40+
StorageLive(_5);
41+
StorageLive(_6);
42+
_9 = const a::promoted[0];
43+
_6 = &(*_9);
44+
StorageLive(_8);
45+
_8 = ();
46+
- _5 = <&fn() {b} as FnOnce<()>>::call_once(move _6, move _8) -> [return: bb2, unwind continue];
47+
+ _5 = move (*_6)() -> [return: bb2, unwind continue];
48+
}
49+
50+
bb2: {
51+
StorageDead(_8);
52+
StorageDead(_6);
53+
StorageDead(_5);
54+
_0 = const ();
55+
return;
56+
}
57+
}
58+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
- // MIR for `b` before Inline
2+
+ // MIR for `b` after Inline
3+
4+
fn b() -> () {
5+
let mut _0: ();
6+
let _1: ();
7+
let mut _2: &fn() {b};
8+
let _3: fn() {b};
9+
let mut _4: ();
10+
let _5: ();
11+
let mut _6: &fn() {a};
12+
let _7: fn() {a};
13+
let mut _8: ();
14+
let mut _9: &fn() {a};
15+
let mut _10: &fn() {b};
16+
+ scope 1 (inlined ops::function::impls::<impl FnOnce<()> for &fn() {b}>::call_once) {
17+
+ scope 2 (inlined <fn() {b} as Fn<()>>::call - shim(fn() {b})) {
18+
+ }
19+
+ }
20+
+ scope 3 (inlined ops::function::impls::<impl FnOnce<()> for &fn() {a}>::call_once) {
21+
+ scope 4 (inlined <fn() {a} as Fn<()>>::call - shim(fn() {a})) {
22+
+ }
23+
+ }
24+
25+
bb0: {
26+
StorageLive(_1);
27+
StorageLive(_2);
28+
_10 = const b::promoted[1];
29+
_2 = &(*_10);
30+
StorageLive(_4);
31+
_4 = ();
32+
- _1 = <&fn() {b} as FnOnce<()>>::call_once(move _2, move _4) -> [return: bb1, unwind continue];
33+
+ _1 = move (*_2)() -> [return: bb1, unwind continue];
34+
}
35+
36+
bb1: {
37+
StorageDead(_4);
38+
StorageDead(_2);
39+
StorageDead(_1);
40+
StorageLive(_5);
41+
StorageLive(_6);
42+
_9 = const b::promoted[0];
43+
_6 = &(*_9);
44+
StorageLive(_8);
45+
_8 = ();
46+
- _5 = <&fn() {a} as FnOnce<()>>::call_once(move _6, move _8) -> [return: bb2, unwind continue];
47+
+ _5 = move (*_6)() -> [return: bb2, unwind continue];
48+
}
49+
50+
bb2: {
51+
StorageDead(_8);
52+
StorageDead(_6);
53+
StorageDead(_5);
54+
_0 = const ();
55+
return;
56+
}
57+
}
58+

tests/mir-opt/inline_double_cycle.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
2+
// skip-filecheck
3+
//@ test-mir-pass: Inline
4+
//@ edition: 2021
5+
//@ compile-flags: -Zinline-mir --crate-type=lib
6+
7+
// EMIT_MIR inline_double_cycle.a.Inline.diff
8+
// EMIT_MIR inline_double_cycle.b.Inline.diff
9+
10+
#![feature(fn_traits)]
11+
12+
#[inline]
13+
pub fn a() {
14+
FnOnce::call_once(&a, ());
15+
FnOnce::call_once(&b, ());
16+
}
17+
18+
#[inline]
19+
pub fn b() {
20+
FnOnce::call_once(&b, ());
21+
FnOnce::call_once(&a, ());
22+
}

0 commit comments

Comments
 (0)