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