rustc_mir_transform/inline/
cycle.rs1use rustc_data_structures::Limit;
2use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
3use rustc_data_structures::stack::ensure_sufficient_stack;
4use rustc_data_structures::unord::UnordSet;
5use rustc_hir::def_id::{DefId, LocalDefId};
6use rustc_middle::mir::TerminatorKind;
7use rustc_middle::ty::{self, GenericArgsRef, InstanceKind, ShimKind, TyCtxt, TypeVisitableExt};
8use rustc_span::sym;
9use tracing::{instrument, trace};
10
11#[instrument(level = "debug", skip(tcx), ret)]
12fn should_recurse<'tcx>(tcx: TyCtxt<'tcx>, callee: ty::Instance<'tcx>) -> bool {
13 match callee.def {
14 InstanceKind::Item(_) => {
18 if !tcx.is_mir_available(callee.def_id()) {
19 return false;
20 }
21 }
22
23 InstanceKind::Intrinsic(_) | InstanceKind::LlvmIntrinsic(_) | InstanceKind::Virtual(..) => {
25 return false;
26 }
27
28 InstanceKind::Shim(ShimKind::VTable(_))
32 | InstanceKind::Shim(ShimKind::Reify(..))
33 | InstanceKind::Shim(ShimKind::FnPtr(..))
34 | InstanceKind::Shim(ShimKind::ClosureOnce { .. })
35 | InstanceKind::Shim(ShimKind::ConstructCoroutineInClosure { .. })
36 | InstanceKind::Shim(ShimKind::ThreadLocal { .. })
37 | InstanceKind::Shim(ShimKind::Clone(..)) => {}
38
39 InstanceKind::Shim(ShimKind::FnPtrAddr(..)) => return false,
41
42 InstanceKind::Shim(ShimKind::DropGlue(..))
46 | InstanceKind::Shim(ShimKind::FutureDropPoll(..))
47 | InstanceKind::Shim(ShimKind::AsyncDropGlue(..))
48 | InstanceKind::Shim(ShimKind::AsyncDropGlueCtor(..)) => {
49 if callee.has_param() {
50 return false;
51 }
52 }
53 }
54
55 crate::pm::should_run_pass(tcx, &crate::inline::Inline, crate::pm::Optimizations::Allowed)
56 || crate::inline::ForceInline::should_run_pass_for_callee(tcx, callee.def.def_id())
57}
58
59#[instrument(
60 level = "debug",
61 skip(tcx, typing_env, seen, involved, recursion_limiter, recursion_limit),
62 ret
63)]
64fn process<'tcx>(
65 tcx: TyCtxt<'tcx>,
66 typing_env: ty::TypingEnv<'tcx>,
67 caller: ty::Instance<'tcx>,
68 target: LocalDefId,
69 seen: &mut FxHashMap<ty::Instance<'tcx>, bool>,
70 involved: &mut FxHashSet<LocalDefId>,
71 recursion_limiter: &mut FxHashMap<DefId, usize>,
72 recursion_limit: Limit,
73) -> Option<bool> {
74 trace!(%caller);
75 let mut reaches_root = false;
76
77 for &(callee_def_id, args) in tcx.mir_inliner_callees(caller.def) {
78 let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions(
79 tcx,
80 typing_env,
81 ty::EarlyBinder::bind(tcx, args),
82 ) else {
83 trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping");
84 continue;
85 };
86 let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee_def_id, args)
87 else {
88 trace!(?callee_def_id, "cannot resolve, skipping");
89 continue;
90 };
91
92 if callee.def_id() == target.to_def_id() {
94 reaches_root = true;
95 seen.insert(callee, true);
96 continue;
97 }
98
99 if tcx.is_constructor(callee.def_id()) {
100 trace!("constructors always have MIR");
101 continue;
103 }
104
105 if !should_recurse(tcx, callee) {
106 continue;
107 }
108
109 let callee_reaches_root = if let Some(&c) = seen.get(&callee) {
110 c
115 } else {
116 seen.insert(callee, false);
117 let recursion = recursion_limiter.entry(callee.def_id()).or_default();
118 trace!(?callee, recursion = *recursion);
119 let callee_reaches_root = if recursion_limit.value_within_limit(*recursion) {
120 *recursion += 1;
121 ensure_sufficient_stack(|| {
122 process(
123 tcx,
124 typing_env,
125 callee,
126 target,
127 seen,
128 involved,
129 recursion_limiter,
130 recursion_limit,
131 )
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<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(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(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}