Skip to content

Commit 76ce6e1

Browse files
authored
Merge 2074013 into 3129d37
2 parents 3129d37 + 2074013 commit 76ce6e1

File tree

4 files changed

+158
-134
lines changed

4 files changed

+158
-134
lines changed

compiler/rustc_middle/src/query/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,15 +1283,15 @@ rustc_queries! {
12831283
return_result_from_ensure_ok
12841284
}
12851285

1286-
/// Check whether the function has any recursion that could cause the inliner to trigger
1287-
/// a cycle.
1288-
query mir_callgraph_reachable(key: (ty::Instance<'tcx>, LocalDefId)) -> bool {
1286+
/// Return the set of (transitive) callees that may result in a recursive call to `key`.
1287+
query mir_callgraph_cyclic(key: LocalDefId) -> &'tcx UnordSet<LocalDefId> {
12891288
fatal_cycle
1289+
arena_cache
12901290
desc { |tcx|
1291-
"computing if `{}` (transitively) calls `{}`",
1292-
key.0,
1293-
tcx.def_path_str(key.1),
1291+
"computing (transitive) callees of `{}` that may recurse",
1292+
tcx.def_path_str(key),
12941293
}
1294+
cache_on_disk_if { true }
12951295
}
12961296

12971297
/// Obtain all the calls into other local functions

compiler/rustc_mir_transform/src/inline.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -770,14 +770,15 @@ fn check_mir_is_available<'tcx, I: Inliner<'tcx>>(
770770
return Ok(());
771771
}
772772

773-
if callee_def_id.is_local()
773+
if let Some(callee_def_id) = callee_def_id.as_local()
774774
&& !inliner
775775
.tcx()
776776
.is_lang_item(inliner.tcx().parent(caller_def_id), rustc_hir::LangItem::FnOnce)
777777
{
778778
// If we know for sure that the function we're calling will itself try to
779779
// call us, then we avoid inlining that function.
780-
if inliner.tcx().mir_callgraph_reachable((callee, caller_def_id.expect_local())) {
780+
if inliner.tcx().mir_callgraph_cyclic(caller_def_id.expect_local()).contains(&callee_def_id)
781+
{
781782
debug!("query cycle avoidance");
782783
return Err("caller might be reachable from callee");
783784
}

compiler/rustc_mir_transform/src/inline/cycle.rs

Lines changed: 148 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,150 @@
11
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
22
use rustc_data_structures::stack::ensure_sufficient_stack;
3+
use rustc_data_structures::unord::UnordSet;
34
use rustc_hir::def_id::{DefId, LocalDefId};
45
use rustc_middle::mir::TerminatorKind;
56
use rustc_middle::ty::{self, GenericArgsRef, InstanceKind, TyCtxt, TypeVisitableExt};
67
use rustc_session::Limit;
78
use rustc_span::sym;
89
use tracing::{instrument, trace};
910

10-
// FIXME: check whether it is cheaper to precompute the entire call graph instead of invoking
11-
// this query ridiculously often.
12-
#[instrument(level = "debug", skip(tcx, root, target))]
13-
pub(crate) fn mir_callgraph_reachable<'tcx>(
11+
#[instrument(level = "debug", skip(tcx), ret)]
12+
fn should_recurse<'tcx>(tcx: TyCtxt<'tcx>, callee: ty::Instance<'tcx>) -> bool {
13+
match callee.def {
14+
// If there is no MIR available (either because it was not in metadata or
15+
// because it has no MIR because it's an extern function), then the inliner
16+
// won't cause cycles on this.
17+
InstanceKind::Item(_) => {
18+
if !tcx.is_mir_available(callee.def_id()) {
19+
return false;
20+
}
21+
}
22+
23+
// These have no own callable MIR.
24+
InstanceKind::Intrinsic(_) | InstanceKind::Virtual(..) => return false,
25+
26+
// These have MIR and if that MIR is inlined, instantiated and then inlining is run
27+
// again, a function item can end up getting inlined. Thus we'll be able to cause
28+
// a cycle that way
29+
InstanceKind::VTableShim(_)
30+
| InstanceKind::ReifyShim(..)
31+
| InstanceKind::FnPtrShim(..)
32+
| InstanceKind::ClosureOnceShim { .. }
33+
| InstanceKind::ConstructCoroutineInClosureShim { .. }
34+
| InstanceKind::ThreadLocalShim { .. }
35+
| InstanceKind::CloneShim(..) => {}
36+
37+
// This shim does not call any other functions, thus there can be no recursion.
38+
InstanceKind::FnPtrAddrShim(..) => return false,
39+
40+
// FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to
41+
// have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this
42+
// needs some more analysis.
43+
InstanceKind::DropGlue(..)
44+
| InstanceKind::FutureDropPollShim(..)
45+
| InstanceKind::AsyncDropGlue(..)
46+
| InstanceKind::AsyncDropGlueCtorShim(..) => {
47+
if callee.has_param() {
48+
return false;
49+
}
50+
}
51+
}
52+
53+
crate::pm::should_run_pass(tcx, &crate::inline::Inline, crate::pm::Optimizations::Allowed)
54+
|| crate::inline::ForceInline::should_run_pass_for_callee(tcx, callee.def.def_id())
55+
}
56+
57+
#[instrument(
58+
level = "debug",
59+
skip(tcx, typing_env, seen, involved, recursion_limiter, recursion_limit),
60+
ret
61+
)]
62+
fn process<'tcx>(
1463
tcx: TyCtxt<'tcx>,
15-
(root, target): (ty::Instance<'tcx>, LocalDefId),
64+
typing_env: ty::TypingEnv<'tcx>,
65+
caller: ty::Instance<'tcx>,
66+
target: LocalDefId,
67+
seen: &mut FxHashSet<ty::Instance<'tcx>>,
68+
involved: &mut FxHashSet<LocalDefId>,
69+
recursion_limiter: &mut FxHashMap<DefId, usize>,
70+
recursion_limit: Limit,
1671
) -> bool {
17-
trace!(%root, target = %tcx.def_path_str(target));
18-
assert_ne!(
19-
root.def_id().expect_local(),
20-
target,
21-
"you should not call `mir_callgraph_reachable` on immediate self recursion"
22-
);
23-
assert!(
24-
matches!(root.def, InstanceKind::Item(_)),
25-
"you should not call `mir_callgraph_reachable` on shims"
26-
);
27-
assert!(
28-
!tcx.is_constructor(root.def_id()),
29-
"you should not call `mir_callgraph_reachable` on enum/struct constructor functions"
30-
);
31-
#[instrument(
32-
level = "debug",
33-
skip(tcx, typing_env, target, stack, seen, recursion_limiter, caller, recursion_limit)
34-
)]
35-
fn process<'tcx>(
36-
tcx: TyCtxt<'tcx>,
37-
typing_env: ty::TypingEnv<'tcx>,
38-
caller: ty::Instance<'tcx>,
39-
target: LocalDefId,
40-
stack: &mut Vec<ty::Instance<'tcx>>,
41-
seen: &mut FxHashSet<ty::Instance<'tcx>>,
42-
recursion_limiter: &mut FxHashMap<DefId, usize>,
43-
recursion_limit: Limit,
44-
) -> bool {
45-
trace!(%caller);
46-
for &(callee, args) in tcx.mir_inliner_callees(caller.def) {
47-
let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions(
48-
tcx,
49-
typing_env,
50-
ty::EarlyBinder::bind(args),
51-
) else {
52-
trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping");
53-
continue;
54-
};
55-
let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else {
56-
trace!(?callee, "cannot resolve, skipping");
57-
continue;
58-
};
72+
trace!(%caller);
73+
let mut cycle_found = false;
5974

60-
// Found a path.
61-
if callee.def_id() == target.to_def_id() {
62-
return true;
63-
}
75+
for &(callee, args) in tcx.mir_inliner_callees(caller.def) {
76+
let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions(
77+
tcx,
78+
typing_env,
79+
ty::EarlyBinder::bind(args),
80+
) else {
81+
trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping");
82+
continue;
83+
};
84+
let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else {
85+
trace!(?callee, "cannot resolve, skipping");
86+
continue;
87+
};
6488

65-
if tcx.is_constructor(callee.def_id()) {
66-
trace!("constructors always have MIR");
67-
// Constructor functions cannot cause a query cycle.
68-
continue;
69-
}
89+
// Found a path.
90+
if callee.def_id() == target.to_def_id() {
91+
cycle_found = true;
92+
}
7093

71-
match callee.def {
72-
InstanceKind::Item(_) => {
73-
// If there is no MIR available (either because it was not in metadata or
74-
// because it has no MIR because it's an extern function), then the inliner
75-
// won't cause cycles on this.
76-
if !tcx.is_mir_available(callee.def_id()) {
77-
trace!(?callee, "no mir available, skipping");
78-
continue;
79-
}
80-
}
81-
// These have no own callable MIR.
82-
InstanceKind::Intrinsic(_) | InstanceKind::Virtual(..) => continue,
83-
// These have MIR and if that MIR is inlined, instantiated and then inlining is run
84-
// again, a function item can end up getting inlined. Thus we'll be able to cause
85-
// a cycle that way
86-
InstanceKind::VTableShim(_)
87-
| InstanceKind::ReifyShim(..)
88-
| InstanceKind::FnPtrShim(..)
89-
| InstanceKind::ClosureOnceShim { .. }
90-
| InstanceKind::ConstructCoroutineInClosureShim { .. }
91-
| InstanceKind::ThreadLocalShim { .. }
92-
| InstanceKind::CloneShim(..) => {}
93-
94-
// This shim does not call any other functions, thus there can be no recursion.
95-
InstanceKind::FnPtrAddrShim(..) => {
96-
continue;
97-
}
98-
InstanceKind::DropGlue(..)
99-
| InstanceKind::FutureDropPollShim(..)
100-
| InstanceKind::AsyncDropGlue(..)
101-
| InstanceKind::AsyncDropGlueCtorShim(..) => {
102-
// FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to
103-
// have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this
104-
// needs some more analysis.
105-
if callee.has_param() {
106-
continue;
107-
}
108-
}
109-
}
94+
if tcx.is_constructor(callee.def_id()) {
95+
trace!("constructors always have MIR");
96+
// Constructor functions cannot cause a query cycle.
97+
continue;
98+
}
99+
100+
if !should_recurse(tcx, callee) {
101+
continue;
102+
}
110103

111-
if seen.insert(callee) {
112-
let recursion = recursion_limiter.entry(callee.def_id()).or_default();
113-
trace!(?callee, recursion = *recursion);
114-
if recursion_limit.value_within_limit(*recursion) {
115-
*recursion += 1;
116-
stack.push(callee);
117-
let found_recursion = ensure_sufficient_stack(|| {
118-
process(
119-
tcx,
120-
typing_env,
121-
callee,
122-
target,
123-
stack,
124-
seen,
125-
recursion_limiter,
126-
recursion_limit,
127-
)
128-
});
129-
if found_recursion {
130-
return true;
131-
}
132-
stack.pop();
133-
} else {
134-
// Pessimistically assume that there could be recursion.
135-
return true;
104+
if seen.insert(callee) {
105+
let recursion = recursion_limiter.entry(callee.def_id()).or_default();
106+
trace!(?callee, recursion = *recursion);
107+
let found_recursion = if recursion_limit.value_within_limit(*recursion) {
108+
*recursion += 1;
109+
ensure_sufficient_stack(|| {
110+
process(
111+
tcx,
112+
typing_env,
113+
callee,
114+
target,
115+
seen,
116+
involved,
117+
recursion_limiter,
118+
recursion_limit,
119+
)
120+
})
121+
} else {
122+
// Pessimistically assume that there could be recursion.
123+
true
124+
};
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);
136129
}
130+
cycle_found = true;
137131
}
138132
}
139-
false
140133
}
134+
135+
cycle_found
136+
}
137+
138+
#[instrument(level = "debug", skip(tcx), ret)]
139+
pub(crate) fn mir_callgraph_cyclic<'tcx>(
140+
tcx: TyCtxt<'tcx>,
141+
root: LocalDefId,
142+
) -> UnordSet<LocalDefId> {
143+
assert!(
144+
!tcx.is_constructor(root.to_def_id()),
145+
"you should not call `mir_callgraph_reachable` on enum/struct constructor functions"
146+
);
147+
141148
// FIXME(-Znext-solver=no): Remove this hack when trait solver overflow can return an error.
142149
// In code like that pointed out in #128887, the type complexity we ask the solver to deal with
143150
// grows as we recurse into the call graph. If we use the same recursion limit here and in the
@@ -146,16 +153,32 @@ pub(crate) fn mir_callgraph_reachable<'tcx>(
146153
// the default recursion limits are quite generous for us. If we need to recurse 64 times
147154
// into the call graph, we're probably not going to find any useful MIR inlining.
148155
let recursion_limit = tcx.recursion_limit() / 2;
156+
let mut involved = FxHashSet::default();
157+
let typing_env = ty::TypingEnv::post_analysis(tcx, root);
158+
let Ok(Some(root_instance)) = ty::Instance::try_resolve(
159+
tcx,
160+
typing_env,
161+
root.to_def_id(),
162+
ty::GenericArgs::identity_for_item(tcx, root.to_def_id()),
163+
) else {
164+
trace!("cannot resolve, skipping");
165+
return involved.into();
166+
};
167+
if !should_recurse(tcx, root_instance) {
168+
trace!("cannot walk, skipping");
169+
return involved.into();
170+
}
149171
process(
150172
tcx,
151-
ty::TypingEnv::post_analysis(tcx, target),
173+
typing_env,
174+
root_instance,
152175
root,
153-
target,
154-
&mut Vec::new(),
155176
&mut FxHashSet::default(),
177+
&mut involved,
156178
&mut FxHashMap::default(),
157179
recursion_limit,
158-
)
180+
);
181+
involved.into()
159182
}
160183

161184
pub(crate) fn mir_inliner_callees<'tcx>(

compiler/rustc_mir_transform/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ pub fn provide(providers: &mut Providers) {
215215
optimized_mir,
216216
is_mir_available,
217217
is_ctfe_mir_available: is_mir_available,
218-
mir_callgraph_reachable: inline::cycle::mir_callgraph_reachable,
218+
mir_callgraph_cyclic: inline::cycle::mir_callgraph_cyclic,
219219
mir_inliner_callees: inline::cycle::mir_inliner_callees,
220220
promoted_mir,
221221
deduced_param_attrs: deduce_param_attrs::deduced_param_attrs,

0 commit comments

Comments
 (0)