1use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
2use rustc_data_structures::stack::ensure_sufficient_stack;
3use rustc_hir::def_id::{DefId, LocalDefId};
4use rustc_middle::mir::TerminatorKind;
5use rustc_middle::ty::{self, GenericArgsRef, InstanceKind, TyCtxt, TypeVisitableExt};
6use rustc_session::Limit;
7use rustc_span::sym;
8use tracing::{instrument, trace};
910// 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))]
13pub(crate) fn mir_callgraph_reachable<'tcx>(
14 tcx: TyCtxt<'tcx>,
15 (root, target): (ty::Instance<'tcx>, LocalDefId),
16) -> bool {
17trace!(%root, target = %tcx.def_path_str(target));
18assert_ne!(
19 root.def_id().expect_local(),
20 target,
21"you should not call `mir_callgraph_reachable` on immediate self recursion"
22);
23assert!(
24matches!(root.def, InstanceKind::Item(_)),
25"you should not call `mir_callgraph_reachable` on shims"
26);
27assert!(
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 )]
35fn 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 {
45trace!(%caller);
46for &(callee, args) in tcx.mir_inliner_callees(caller.def) {
47let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions(
48 tcx,
49 typing_env,
50 ty::EarlyBinder::bind(args),
51 ) else {
52trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping");
53continue;
54 };
55let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee, args) else {
56trace!(?callee, "cannot resolve, skipping");
57continue;
58 };
5960// Found a path.
61if callee.def_id() == target.to_def_id() {
62return true;
63 }
6465if tcx.is_constructor(callee.def_id()) {
66trace!("constructors always have MIR");
67// Constructor functions cannot cause a query cycle.
68continue;
69 }
7071match 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.
76if !tcx.is_mir_available(callee.def_id()) {
77trace!(?callee, "no mir available, skipping");
78continue;
79 }
80 }
81// These have no own callable MIR.
82InstanceKind::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
86InstanceKind::VTableShim(_)
87 | InstanceKind::ReifyShim(..)
88 | InstanceKind::FnPtrShim(..)
89 | InstanceKind::ClosureOnceShim { .. }
90 | InstanceKind::ConstructCoroutineInClosureShim { .. }
91 | InstanceKind::ThreadLocalShim { .. }
92 | InstanceKind::CloneShim(..) => {}
9394// This shim does not call any other functions, thus there can be no recursion.
95InstanceKind::FnPtrAddrShim(..) => {
96continue;
97 }
98 InstanceKind::DropGlue(..) | InstanceKind::AsyncDropGlueCtorShim(..) => {
99// FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to
100 // have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this
101 // needs some more analysis.
102if callee.has_param() {
103continue;
104 }
105 }
106 }
107108if seen.insert(callee) {
109let recursion = recursion_limiter.entry(callee.def_id()).or_default();
110trace!(?callee, recursion = *recursion);
111if recursion_limit.value_within_limit(*recursion) {
112*recursion += 1;
113 stack.push(callee);
114let found_recursion = ensure_sufficient_stack(|| {
115 process(
116 tcx,
117 typing_env,
118 callee,
119 target,
120 stack,
121 seen,
122 recursion_limiter,
123 recursion_limit,
124 )
125 });
126if found_recursion {
127return true;
128 }
129 stack.pop();
130 } else {
131// Pessimistically assume that there could be recursion.
132return true;
133 }
134 }
135 }
136false
137}
138// FIXME(-Znext-solver): Remove this hack when trait solver overflow can return an error.
139 // In code like that pointed out in #128887, the type complexity we ask the solver to deal with
140 // grows as we recurse into the call graph. If we use the same recursion limit here and in the
141 // solver, the solver hits the limit first and emits a fatal error. But if we use a reduced
142 // limit, we will hit the limit first and give up on looking for inlining. And in any case,
143 // the default recursion limits are quite generous for us. If we need to recurse 64 times
144 // into the call graph, we're probably not going to find any useful MIR inlining.
145let recursion_limit = tcx.recursion_limit() / 2;
146 process(
147 tcx,
148 ty::TypingEnv::post_analysis(tcx, target),
149 root,
150 target,
151&mut Vec::new(),
152&mut FxHashSet::default(),
153&mut FxHashMap::default(),
154 recursion_limit,
155 )
156}
157158pub(crate) fn mir_inliner_callees<'tcx>(
159 tcx: TyCtxt<'tcx>,
160 instance: ty::InstanceKind<'tcx>,
161) -> &'tcx [(DefId, GenericArgsRef<'tcx>)] {
162let steal;
163let guard;
164let body = match (instance, instance.def_id().as_local()) {
165 (InstanceKind::Item(_), Some(def_id)) => {
166steal = tcx.mir_promoted(def_id).0;
167guard = steal.borrow();
168&*guard169 }
170// Functions from other crates and MIR shims
171_ => tcx.instance_mir(instance),
172 };
173let mut calls = FxIndexSet::default();
174for bb_data in body.basic_blocks.iter() {
175let terminator = bb_data.terminator();
176if let TerminatorKind::Call { func, args: call_args, .. } = &terminator.kind {
177let ty = func.ty(&body.local_decls, tcx);
178let ty::FnDef(def_id, generic_args) = ty.kind() else {
179continue;
180 };
181let call = if tcx.is_intrinsic(*def_id, sym::const_eval_select) {
182let func = &call_args[2].node;
183let ty = func.ty(&body.local_decls, tcx);
184let ty::FnDef(def_id, generic_args) = ty.kind() else {
185continue;
186 };
187 (*def_id, *generic_args)
188 } else {
189 (*def_id, *generic_args)
190 };
191 calls.insert(call);
192 }
193 }
194tcx.arena.alloc_from_iter(calls.iter().copied())
195}