From 74e0a9aa8147cc640120d73ec868507e37b29db1 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Sat, 4 Oct 2025 23:35:19 -0400 Subject: [PATCH 1/2] Make inliner cycle detection a fallible process --- compiler/rustc_middle/src/query/mod.rs | 5 ++-- compiler/rustc_mir_transform/src/inline.rs | 7 ++++-- .../rustc_mir_transform/src/inline/cycle.rs | 23 ++++++++++--------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index b2473052b442..08db16ba8ecb 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1312,8 +1312,9 @@ rustc_queries! { return_result_from_ensure_ok } - /// Return the set of (transitive) callees that may result in a recursive call to `key`. - query mir_callgraph_cyclic(key: LocalDefId) -> &'tcx UnordSet { + /// Return the set of (transitive) callees that may result in a recursive call to `key`, + /// if we were able to walk all callees. + query mir_callgraph_cyclic(key: LocalDefId) -> &'tcx Option> { fatal_cycle arena_cache desc { |tcx| diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index 9e3ca9b30d53..1e9665f4337d 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -775,8 +775,11 @@ fn check_mir_is_available<'tcx, I: Inliner<'tcx>>( { // If we know for sure that the function we're calling will itself try to // call us, then we avoid inlining that function. - if inliner.tcx().mir_callgraph_cyclic(caller_def_id.expect_local()).contains(&callee_def_id) - { + let Some(cyclic_callees) = inliner.tcx().mir_callgraph_cyclic(caller_def_id.expect_local()) + else { + return Err("call graph cycle detection bailed due to recursion limit"); + }; + if cyclic_callees.contains(&callee_def_id) { debug!("query cycle avoidance"); return Err("caller might be reachable from callee"); } diff --git a/compiler/rustc_mir_transform/src/inline/cycle.rs b/compiler/rustc_mir_transform/src/inline/cycle.rs index 25a9baffe582..9d031b654802 100644 --- a/compiler/rustc_mir_transform/src/inline/cycle.rs +++ b/compiler/rustc_mir_transform/src/inline/cycle.rs @@ -68,7 +68,7 @@ fn process<'tcx>( involved: &mut FxHashSet, recursion_limiter: &mut FxHashMap, recursion_limit: Limit, -) -> bool { +) -> Option { trace!(%caller); let mut reaches_root = false; @@ -127,10 +127,9 @@ fn process<'tcx>( recursion_limiter, recursion_limit, ) - }) + })? } else { - // Pessimistically assume that there could be recursion. - true + return None; }; seen.insert(callee, callee_reaches_root); callee_reaches_root @@ -144,14 +143,14 @@ fn process<'tcx>( } } - reaches_root + Some(reaches_root) } #[instrument(level = "debug", skip(tcx), ret)] pub(crate) fn mir_callgraph_cyclic<'tcx>( tcx: TyCtxt<'tcx>, root: LocalDefId, -) -> UnordSet { +) -> Option> { assert!( !tcx.is_constructor(root.to_def_id()), "you should not call `mir_callgraph_reachable` on enum/struct constructor functions" @@ -164,16 +163,16 @@ pub(crate) fn mir_callgraph_cyclic<'tcx>( // 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() / 2; + 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 involved.into(); + return Some(involved.into()); } - process( + match process( tcx, typing_env, root_instance, @@ -182,8 +181,10 @@ pub(crate) fn mir_callgraph_cyclic<'tcx>( &mut involved, &mut FxHashMap::default(), recursion_limit, - ); - involved.into() + ) { + Some(_) => Some(involved.into()), + _ => None, + } } pub(crate) fn mir_inliner_callees<'tcx>( From cee7f5ed318e0717e2d86d94c8d08bc0a69a3355 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Tue, 30 Dec 2025 22:21:03 -0500 Subject: [PATCH 2/2] Add a regression test --- tests/mir-opt/inline/auxiliary/wrapper.rs | 10 ++++ ...ecursion_limit_prevents_cycle_discovery.rs | 54 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 tests/mir-opt/inline/auxiliary/wrapper.rs create mode 100644 tests/mir-opt/inline/recursion_limit_prevents_cycle_discovery.rs diff --git a/tests/mir-opt/inline/auxiliary/wrapper.rs b/tests/mir-opt/inline/auxiliary/wrapper.rs new file mode 100644 index 000000000000..35d3ae788239 --- /dev/null +++ b/tests/mir-opt/inline/auxiliary/wrapper.rs @@ -0,0 +1,10 @@ +//@ no-prefer-dynamic +//@ compile-flags: -O + +pub trait Compare { + fn eq(self); +} + +pub fn wrap(a: A) { + Compare::eq(a); +} diff --git a/tests/mir-opt/inline/recursion_limit_prevents_cycle_discovery.rs b/tests/mir-opt/inline/recursion_limit_prevents_cycle_discovery.rs new file mode 100644 index 000000000000..866d6fbd9e97 --- /dev/null +++ b/tests/mir-opt/inline/recursion_limit_prevents_cycle_discovery.rs @@ -0,0 +1,54 @@ +//@ aux-build: wrapper.rs +//@ compile-flags: -Zmir-opt-level=2 -Zinline-mir +// skip-filecheck + +// This is a regression test for https://github.com/rust-lang/rust/issues/146998 + +extern crate wrapper; +use wrapper::{Compare, wrap}; + +pub struct BundleInner; + +impl Compare for BundleInner { + fn eq(self) { + lots_of_calls(); + wrap(Resource::ExtensionValue); + } +} + +pub enum Resource { + Bundle, + ExtensionValue, +} + +impl Compare for Resource { + fn eq(self) { + match self { + Self::Bundle => wrap(BundleInner), + Self::ExtensionValue => lots_of_calls(), + } + } +} + +macro_rules! units { + ($($n: ident)*) => { + $( + struct $n; + + impl Compare for $n { + fn eq(self) { } + } + + wrap($n); + )* + }; +} + +fn lots_of_calls() { + units! { + O1 O2 O3 O4 O5 O6 O7 O8 O9 O10 O11 O12 O13 O14 O15 O16 O17 O18 O19 O20 + O21 O22 O23 O24 O25 O26 O27 O28 O29 O30 O31 O32 O33 O34 O35 O36 O37 O38 O39 O40 + O41 O42 O43 O44 O45 O46 O47 O48 O49 O50 O51 O52 O53 O54 O55 O56 O57 O58 O59 O60 + O61 O62 O63 O64 O65 + } +}