| use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; | |
| use rustc_data_structures::unord::UnordSet; | |
| use rustc_hir::def_id::{DefId, LocalDefId}; | |
| use rustc_middle::mir::TerminatorKind; | |
| use rustc_middle::ty::{self, GenericArgsRef, InstanceKind, ShimKind, TyCtxt, TypeVisitableExt}; | |
| use rustc_span::sym; | |
| use rustc_structures::Limit; | |
| use tracing::{instrument, trace}; | |
| #[instrument(level = "debug", skip(tcx), ret)] | |
| fn should_recurse<'tcx>(tcx: TyCtxt<'tcx>, callee: ty::Instance<'tcx>) -> bool { | |
| match callee.def { | |
| // If there is no MIR available (either because it was not in metadata or | |
| // because it has no MIR because it's an extern function), then the inliner | |
| // won't cause cycles on this. | |
| InstanceKind::Item(_) => { | |
| if !tcx.is_mir_available(callee.def_id()) { | |
| return false; | |
| } | |
| } | |
| // These have no own callable MIR. | |
| InstanceKind::Intrinsic(_) | InstanceKind::LlvmIntrinsic(_) | InstanceKind::Virtual(..) => { | |
| return false; | |
| } | |
| // These have MIR and if that MIR is inlined, instantiated and then inlining is run | |
| // again, a function item can end up getting inlined. Thus we'll be able to cause | |
| // a cycle that way | |
| InstanceKind::Shim(ShimKind::VTable(_)) | |
| | InstanceKind::Shim(ShimKind::Reify(..)) | |
| | InstanceKind::Shim(ShimKind::FnPtr(..)) | |
| | InstanceKind::Shim(ShimKind::ClosureOnce { .. }) | |
| | InstanceKind::Shim(ShimKind::ConstructCoroutineInClosure { .. }) | |
| | InstanceKind::Shim(ShimKind::ThreadLocal { .. }) | |
| | InstanceKind::Shim(ShimKind::Clone(..)) => {} | |
| // This shim does not call any other functions, thus there can be no recursion. | |
| InstanceKind::Shim(ShimKind::FnPtrAsPtr(..) | ShimKind::FnPtrFromPtr(..)) => return false, | |
| // FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to | |
| // have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this | |
| // needs some more analysis. | |
| InstanceKind::Shim(ShimKind::DropGlue(..)) | |
| | InstanceKind::Shim(ShimKind::FutureDropPoll(..)) | |
| | InstanceKind::Shim(ShimKind::AsyncDropGlue(..)) | |
| | InstanceKind::Shim(ShimKind::AsyncDropGlueCtor(..)) => { | |
| if callee.has_param() { | |
| return false; | |
| } | |
| } | |
| } | |
| crate::pm::should_run_pass( | |
| &crate::inline::Inline, | |
| &crate::pm::PassCtx::for_body(tcx, callee.def_id()), | |
| ) || crate::inline::ForceInline::should_run_pass_for_callee(tcx, callee.def.def_id()) | |
| } | |
| #[instrument( | |
| level = "debug", | |
| skip(tcx, typing_env, seen, involved, recursion_limiter, recursion_limit), | |
| ret | |
| )] | |
| fn process<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| typing_env: ty::TypingEnv<'tcx>, | |
| caller: ty::Instance<'tcx>, | |
| target: LocalDefId, | |
| seen: &mut FxHashMap<ty::Instance<'tcx>, bool>, | |
| involved: &mut FxHashSet<LocalDefId>, | |
| recursion_limiter: &mut FxHashMap<DefId, usize>, | |
| recursion_limit: Limit, | |
| ) -> Option<bool> { | |
| trace!(%caller); | |
| let mut reaches_root = false; | |
| for &(callee_def_id, args) in tcx.mir_inliner_callees(caller.def) { | |
| let Ok(args) = caller.try_instantiate_mir_and_normalize_erasing_regions( | |
| tcx, | |
| typing_env, | |
| ty::EarlyBinder::bind(tcx, args), | |
| ) else { | |
| trace!(?caller, ?typing_env, ?args, "cannot normalize, skipping"); | |
| continue; | |
| }; | |
| let Ok(Some(callee)) = ty::Instance::try_resolve(tcx, typing_env, callee_def_id, args) | |
| else { | |
| trace!(?callee_def_id, "cannot resolve, skipping"); | |
| continue; | |
| }; | |
| // Found a path. | |
| if callee.def_id() == target.to_def_id() { | |
| reaches_root = true; | |
| seen.insert(callee, true); | |
| continue; | |
| } | |
| if tcx.is_constructor(callee.def_id()) { | |
| trace!("constructors always have MIR"); | |
| // Constructor functions cannot cause a query cycle. | |
| continue; | |
| } | |
| if !should_recurse(tcx, callee) { | |
| continue; | |
| } | |
| let callee_reaches_root = if let Some(&c) = seen.get(&callee) { | |
| // Even if we have seen this callee before, and thus don't need | |
| // to recurse into it, we still need to propagate whether it reaches | |
| // the root so that we can mark all the involved callers, in case we | |
| // end up reaching that same recursive callee through some *other* cycle. | |
| c | |
| } else { | |
| seen.insert(callee, false); | |
| let recursion = recursion_limiter.entry(callee.def_id()).or_default(); | |
| trace!(?callee, recursion = *recursion); | |
| let callee_reaches_root = if recursion_limit.value_within_limit(*recursion) { | |
| *recursion += 1; | |
| process( | |
| tcx, | |
| typing_env, | |
| callee, | |
| target, | |
| seen, | |
| involved, | |
| recursion_limiter, | |
| recursion_limit, | |
| )? | |
| } else { | |
| return None; | |
| }; | |
| seen.insert(callee, callee_reaches_root); | |
| callee_reaches_root | |
| }; | |
| if callee_reaches_root { | |
| if let Some(callee_def_id) = callee.def_id().as_local() { | |
| // Calling `optimized_mir` of a non-local definition cannot cycle. | |
| involved.insert(callee_def_id); | |
| } | |
| reaches_root = true; | |
| } | |
| } | |
| Some(reaches_root) | |
| } | |
| #[instrument(level = "debug", skip(tcx), ret)] | |
| pub(crate) fn mir_callgraph_cyclic<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| root: LocalDefId, | |
| ) -> Option<&'tcx UnordSet<LocalDefId>> { | |
| assert!( | |
| !tcx.is_constructor(root.to_def_id()), | |
| "you should not call `mir_callgraph_reachable` on enum/struct constructor functions" | |
| ); | |
| // FIXME(-Znext-solver=no): Remove this hack when trait solver overflow can return an error. | |
| // In code like that pointed out in #128887, the type complexity we ask the solver to deal with | |
| // grows as we recurse into the call graph. If we use the same recursion limit here and in the | |
| // solver, the solver hits the limit first and emits a fatal error. But if we use a reduced | |
| // limit, we will hit the limit first and give up on looking for inlining. And in any case, | |
| // the default recursion limits are quite generous for us. If we need to recurse 64 times | |
| // into the call graph, we're probably not going to find any useful MIR inlining. | |
| let recursion_limit = tcx.recursion_limit() / 8; | |
| let mut involved = FxHashSet::default(); | |
| let typing_env = ty::TypingEnv::post_analysis(tcx, root); | |
| let root_instance = | |
| ty::Instance::new_raw(root.to_def_id(), ty::GenericArgs::identity_for_item(tcx, root)); | |
| if !should_recurse(tcx, root_instance) { | |
| trace!("cannot walk, skipping"); | |
| return Some(tcx.arena.alloc(involved.into())); | |
| } | |
| match process( | |
| tcx, | |
| typing_env, | |
| root_instance, | |
| root, | |
| &mut FxHashMap::default(), | |
| &mut involved, | |
| &mut FxHashMap::default(), | |
| recursion_limit, | |
| ) { | |
| Some(_) => Some(tcx.arena.alloc(involved.into())), | |
| _ => None, | |
| } | |
| } | |
| pub(crate) fn mir_inliner_callees<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| instance: ty::InstanceKind<'tcx>, | |
| ) -> &'tcx [(DefId, GenericArgsRef<'tcx>)] { | |
| let steal; | |
| let guard; | |
| let body = match (instance, instance.def_id().as_local()) { | |
| (InstanceKind::Item(_), Some(def_id)) => { | |
| steal = tcx.mir_promoted(def_id).0; | |
| guard = steal.borrow(); | |
| &*guard | |
| } | |
| // Functions from other crates and MIR shims | |
| _ => tcx.instance_mir(instance), | |
| }; | |
| let mut calls = FxIndexSet::default(); | |
| for bb_data in body.basic_blocks.iter() { | |
| let terminator = bb_data.terminator(); | |
| if let TerminatorKind::Call { func, args: call_args, .. } = &terminator.kind { | |
| let ty = func.ty(&body.local_decls, tcx); | |
| let ty::FnDef(def_id, generic_args) = ty.kind() else { | |
| continue; | |
| }; | |
| let call = if tcx.is_intrinsic(*def_id, sym::const_eval_select) { | |
| let func = &call_args[2].node; | |
| let ty = func.ty(&body.local_decls, tcx); | |
| let ty::FnDef(def_id, generic_args) = ty.kind() else { | |
| continue; | |
| }; | |
| (*def_id, *generic_args) | |
| } else { | |
| (*def_id, *generic_args) | |
| }; | |
| calls.insert(call); | |
| } | |
| } | |
| tcx.arena.alloc_from_iter(calls.iter().map(|(did, args)| (*did, args.no_bound_vars().unwrap()))) | |
| } |