rustc_mir_transform/inline/
cycle.rs1use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
2use rustc_data_structures::unord::UnordSet;
3use rustc_hir::def_id::{DefId, LocalDefId};
4use rustc_middle::mir::TerminatorKind;
5use rustc_middle::ty::{self, GenericArgsRef, InstanceKind, ShimKind, TyCtxt, TypeVisitableExt};
6use rustc_span::sym;
7use rustc_structures::Limit;
8use tracing::{instrument, trace};
9
10#[instrument(level = "debug", skip(tcx), ret)]
11fn should_recurse<'tcx>(tcx: TyCtxt<'tcx>, callee: ty::Instance<'tcx>) -> bool {
12 match callee.def {
13 InstanceKind::Item(_) => {
17 if !tcx.is_mir_available(callee.def_id()) {
18 return false;
19 }
20 }
21
22 InstanceKind::Intrinsic(_) | InstanceKind::LlvmIntrinsic(_) | InstanceKind::Virtual(..) => {
24 return false;
25 }
26
27 InstanceKind::Shim(ShimKind::VTable(_))
31 | InstanceKind::Shim(ShimKind::Reify(..))
32 | InstanceKind::Shim(ShimKind::FnPtr(..))
33 | InstanceKind::Shim(ShimKind::ClosureOnce { .. })
34 | InstanceKind::Shim(ShimKind::ConstructCoroutineInClosure { .. })
35 | InstanceKind::Shim(ShimKind::ThreadLocal { .. })
36 | InstanceKind::Shim(ShimKind::Clone(..)) => {}
37
38 InstanceKind::Shim(ShimKind::FnPtrAsPtr(..) | ShimKind::FnPtrFromPtr(..)) => return false,
40
41 InstanceKind::Shim(ShimKind::DropGlue(..))
45 | InstanceKind::Shim(ShimKind::FutureDropPoll(..))
46 | InstanceKind::Shim(ShimKind::AsyncDropGlue(..))
47 | InstanceKind::Shim(ShimKind::AsyncDropGlueCtor(..)) => {
48 if callee.has_param() {
49 return false;
50 }
51 }
52 }
53
54 crate::pm::should_run_pass(
55 &crate::inline::Inline,
56 &crate::pm::PassCtx::for_body(tcx, callee.def_id()),
57 ) || crate::inline::ForceInline::should_run_pass_for_callee(tcx, callee.def.def_id())
58}
59
60#[instrument(
61 level = "debug",
62 skip(tcx, typing_env, seen, involved, recursion_limiter, recursion_limit),
63 ret
64)]
65fn process<'tcx>(
66 tcx: TyCtxt<'tcx>,
67 typing_env: ty::TypingEnv<'tcx>,
68 caller: ty::Instance<'tcx>,
69 target: LocalDefId,
70 seen: &mut FxHashMap<ty::Instance<'tcx>, bool>,
71 involved: &mut FxHashSet<LocalDefId>,
72 recursion_limiter: &mut FxHashMap<DefId, usize>,
73 recursion_limit: Limit,
74) -> Option<bool> {
75 trace!(%caller);
76 let mut reaches_root = false;
77
78 for &(callee_def_id, args) in tcx.mir_inliner_callees(caller.def) {
79 let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions(
80 tcx,
81 typing_env,
82 ty::EarlyBinder::bind(tcx, args),
83 ) else {
84 trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping");
85 continue;
86 };
87 let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee_def_id, args)
88 else {
89 trace!(?callee_def_id, "cannot resolve, skipping");
90 continue;
91 };
92
93 if callee.def_id() == target.to_def_id() {
95 reaches_root = true;
96 seen.insert(callee, true);
97 continue;
98 }
99
100 if tcx.is_constructor(callee.def_id()) {
101 trace!("constructors always have MIR");
102 continue;
104 }
105
106 if !should_recurse(tcx, callee) {
107 continue;
108 }
109
110 let callee_reaches_root = if let Some(&c) = seen.get(&callee) {
111 c
116 } else {
117 seen.insert(callee, false);
118 let recursion = recursion_limiter.entry(callee.def_id()).or_default();
119 trace!(?callee, recursion = *recursion);
120 let callee_reaches_root = if recursion_limit.value_within_limit(*recursion) {
121 *recursion += 1;
122
123 process(
124 tcx,
125 typing_env,
126 callee,
127 target,
128 seen,
129 involved,
130 recursion_limiter,
131 recursion_limit,
132 )?
133 } else {
134 return None;
135 };
136 seen.insert(callee, callee_reaches_root);
137 callee_reaches_root
138 };
139 if callee_reaches_root {
140 if let Some(callee_def_id) = callee.def_id().as_local() {
141 involved.insert(callee_def_id);
143 }
144 reaches_root = true;
145 }
146 }
147
148 Some(reaches_root)
149}
150
151#[instrument(level = "debug", skip(tcx), ret)]
152pub(crate) fn mir_callgraph_cyclic<'tcx>(
153 tcx: TyCtxt<'tcx>,
154 root: LocalDefId,
155) -> Option<&'tcx UnordSet<LocalDefId>> {
156 assert!(
157 !tcx.is_constructor(root.to_def_id()),
158 "you should not call `mir_callgraph_reachable` on enum/struct constructor functions"
159 );
160
161 let recursion_limit = tcx.recursion_limit() / 8;
169 let mut involved = FxHashSet::default();
170 let typing_env = ty::TypingEnv::post_analysis(tcx, root);
171 let root_instance =
172 ty::Instance::new_raw(root.to_def_id(), ty::GenericArgs::identity_for_item(tcx, root));
173 if !should_recurse(tcx, root_instance) {
174 trace!("cannot walk, skipping");
175 return Some(tcx.arena.alloc(involved.into()));
176 }
177 match process(
178 tcx,
179 typing_env,
180 root_instance,
181 root,
182 &mut FxHashMap::default(),
183 &mut involved,
184 &mut FxHashMap::default(),
185 recursion_limit,
186 ) {
187 Some(_) => Some(tcx.arena.alloc(involved.into())),
188 _ => None,
189 }
190}
191
192pub(crate) fn mir_inliner_callees<'tcx>(
193 tcx: TyCtxt<'tcx>,
194 instance: ty::InstanceKind<'tcx>,
195) -> &'tcx [(DefId, GenericArgsRef<'tcx>)] {
196 let steal;
197 let guard;
198 let body = match (instance, instance.def_id().as_local()) {
199 (InstanceKind::Item(_), Some(def_id)) => {
200 steal = tcx.mir_promoted(def_id).0;
201 guard = steal.borrow();
202 &*guard
203 }
204 _ => tcx.instance_mir(instance),
206 };
207 let mut calls = FxIndexSet::default();
208 for bb_data in body.basic_blocks.iter() {
209 let terminator = bb_data.terminator();
210 if let TerminatorKind::Call { func, args: call_args, .. } = &terminator.kind {
211 let ty = func.ty(&body.local_decls, tcx);
212 let ty::FnDef(def_id, generic_args) = ty.kind() else {
213 continue;
214 };
215 let call = if tcx.is_intrinsic(*def_id, sym::const_eval_select) {
216 let func = &call_args[2].node;
217 let ty = func.ty(&body.local_decls, tcx);
218 let ty::FnDef(def_id, generic_args) = ty.kind() else {
219 continue;
220 };
221 (*def_id, *generic_args)
222 } else {
223 (*def_id, *generic_args)
224 };
225 calls.insert(call);
226 }
227 }
228 tcx.arena.alloc_from_iter(calls.iter().map(|(did, args)| (*did, args.no_bound_vars().unwrap())))
229}