11use rustc_data_structures:: fx:: { FxHashMap , FxHashSet , FxIndexSet } ;
22use rustc_data_structures:: stack:: ensure_sufficient_stack;
3+ use rustc_data_structures:: unord:: UnordSet ;
34use rustc_hir:: def_id:: { DefId , LocalDefId } ;
45use rustc_middle:: mir:: TerminatorKind ;
56use rustc_middle:: ty:: { self , GenericArgsRef , InstanceKind , TyCtxt , TypeVisitableExt } ;
67use rustc_session:: Limit ;
78use rustc_span:: sym;
89use tracing:: { instrument, trace} ;
910
10- // FIXME: check whether it is cheaper to precompute the entire call graph instead of invoking
11- // this query ridiculously often.
12- #[ instrument( level = "debug" , skip( tcx, root, target) ) ]
13- pub ( crate ) fn mir_callgraph_reachable < ' tcx > (
11+ #[ instrument( level = "debug" , skip( tcx) , ret) ]
12+ fn should_recurse < ' tcx > ( tcx : TyCtxt < ' tcx > , callee : ty:: Instance < ' tcx > ) -> bool {
13+ match callee. def {
14+ // If there is no MIR available (either because it was not in metadata or
15+ // because it has no MIR because it's an extern function), then the inliner
16+ // won't cause cycles on this.
17+ InstanceKind :: Item ( _) => {
18+ if !tcx. is_mir_available ( callee. def_id ( ) ) {
19+ return false ;
20+ }
21+ }
22+
23+ // These have no own callable MIR.
24+ InstanceKind :: Intrinsic ( _) | InstanceKind :: Virtual ( ..) => return false ,
25+
26+ // These have MIR and if that MIR is inlined, instantiated and then inlining is run
27+ // again, a function item can end up getting inlined. Thus we'll be able to cause
28+ // a cycle that way
29+ InstanceKind :: VTableShim ( _)
30+ | InstanceKind :: ReifyShim ( ..)
31+ | InstanceKind :: FnPtrShim ( ..)
32+ | InstanceKind :: ClosureOnceShim { .. }
33+ | InstanceKind :: ConstructCoroutineInClosureShim { .. }
34+ | InstanceKind :: ThreadLocalShim { .. }
35+ | InstanceKind :: CloneShim ( ..) => { }
36+
37+ // This shim does not call any other functions, thus there can be no recursion.
38+ InstanceKind :: FnPtrAddrShim ( ..) => return false ,
39+
40+ // FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to
41+ // have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this
42+ // needs some more analysis.
43+ InstanceKind :: DropGlue ( ..)
44+ | InstanceKind :: FutureDropPollShim ( ..)
45+ | InstanceKind :: AsyncDropGlue ( ..)
46+ | InstanceKind :: AsyncDropGlueCtorShim ( ..) => {
47+ if callee. has_param ( ) {
48+ return false ;
49+ }
50+ }
51+ }
52+
53+ crate :: pm:: should_run_pass ( tcx, & crate :: inline:: Inline , crate :: pm:: Optimizations :: Allowed )
54+ || crate :: inline:: ForceInline :: should_run_pass_for_callee ( tcx, callee. def . def_id ( ) )
55+ }
56+
57+ #[ instrument(
58+ level = "debug" ,
59+ skip( tcx, typing_env, seen, involved, recursion_limiter, recursion_limit) ,
60+ ret
61+ ) ]
62+ fn process < ' tcx > (
1463 tcx : TyCtxt < ' tcx > ,
15- ( root, target) : ( ty:: Instance < ' tcx > , LocalDefId ) ,
64+ typing_env : ty:: TypingEnv < ' tcx > ,
65+ caller : ty:: Instance < ' tcx > ,
66+ target : LocalDefId ,
67+ seen : & mut FxHashSet < ty:: Instance < ' tcx > > ,
68+ involved : & mut FxHashSet < LocalDefId > ,
69+ recursion_limiter : & mut FxHashMap < DefId , usize > ,
70+ recursion_limit : Limit ,
1671) -> bool {
17- trace ! ( %root, target = %tcx. def_path_str( target) ) ;
18- assert_ne ! (
19- root. def_id( ) . expect_local( ) ,
20- target,
21- "you should not call `mir_callgraph_reachable` on immediate self recursion"
22- ) ;
23- assert ! (
24- matches!( root. def, InstanceKind :: Item ( _) ) ,
25- "you should not call `mir_callgraph_reachable` on shims"
26- ) ;
27- assert ! (
28- !tcx. is_constructor( root. def_id( ) ) ,
29- "you should not call `mir_callgraph_reachable` on enum/struct constructor functions"
30- ) ;
31- #[ instrument(
32- level = "debug" ,
33- skip( tcx, typing_env, target, stack, seen, recursion_limiter, caller, recursion_limit)
34- ) ]
35- fn process < ' tcx > (
36- tcx : TyCtxt < ' tcx > ,
37- typing_env : ty:: TypingEnv < ' tcx > ,
38- caller : ty:: Instance < ' tcx > ,
39- target : LocalDefId ,
40- stack : & mut Vec < ty:: Instance < ' tcx > > ,
41- seen : & mut FxHashSet < ty:: Instance < ' tcx > > ,
42- recursion_limiter : & mut FxHashMap < DefId , usize > ,
43- recursion_limit : Limit ,
44- ) -> bool {
45- trace ! ( %caller) ;
46- for & ( callee, args) in tcx. mir_inliner_callees ( caller. def ) {
47- let Ok ( args) = caller. try_instantiate_mir_and_normalize_erasing_regions (
48- tcx,
49- typing_env,
50- ty:: EarlyBinder :: bind ( args) ,
51- ) else {
52- trace ! ( ?caller, ?typing_env, ?args, "cannot normalize, skipping" ) ;
53- continue ;
54- } ;
55- let Ok ( Some ( callee) ) = ty:: Instance :: try_resolve ( tcx, typing_env, callee, args) else {
56- trace ! ( ?callee, "cannot resolve, skipping" ) ;
57- continue ;
58- } ;
72+ trace ! ( %caller) ;
73+ let mut cycle_found = false ;
5974
60- // Found a path.
61- if callee. def_id ( ) == target. to_def_id ( ) {
62- return true ;
63- }
75+ for & ( callee, args) in tcx. mir_inliner_callees ( caller. def ) {
76+ let Ok ( args) = caller. try_instantiate_mir_and_normalize_erasing_regions (
77+ tcx,
78+ typing_env,
79+ ty:: EarlyBinder :: bind ( args) ,
80+ ) else {
81+ trace ! ( ?caller, ?typing_env, ?args, "cannot normalize, skipping" ) ;
82+ continue ;
83+ } ;
84+ let Ok ( Some ( callee) ) = ty:: Instance :: try_resolve ( tcx, typing_env, callee, args) else {
85+ trace ! ( ?callee, "cannot resolve, skipping" ) ;
86+ continue ;
87+ } ;
6488
65- if tcx. is_constructor ( callee. def_id ( ) ) {
66- trace ! ( "constructors always have MIR" ) ;
67- // Constructor functions cannot cause a query cycle.
68- continue ;
69- }
89+ // Found a path.
90+ if callee. def_id ( ) == target. to_def_id ( ) {
91+ cycle_found = true ;
92+ }
7093
71- match callee. def {
72- InstanceKind :: Item ( _) => {
73- // If there is no MIR available (either because it was not in metadata or
74- // because it has no MIR because it's an extern function), then the inliner
75- // won't cause cycles on this.
76- if !tcx. is_mir_available ( callee. def_id ( ) ) {
77- trace ! ( ?callee, "no mir available, skipping" ) ;
78- continue ;
79- }
80- }
81- // These have no own callable MIR.
82- InstanceKind :: Intrinsic ( _) | InstanceKind :: Virtual ( ..) => continue ,
83- // These have MIR and if that MIR is inlined, instantiated and then inlining is run
84- // again, a function item can end up getting inlined. Thus we'll be able to cause
85- // a cycle that way
86- InstanceKind :: VTableShim ( _)
87- | InstanceKind :: ReifyShim ( ..)
88- | InstanceKind :: FnPtrShim ( ..)
89- | InstanceKind :: ClosureOnceShim { .. }
90- | InstanceKind :: ConstructCoroutineInClosureShim { .. }
91- | InstanceKind :: ThreadLocalShim { .. }
92- | InstanceKind :: CloneShim ( ..) => { }
93-
94- // This shim does not call any other functions, thus there can be no recursion.
95- InstanceKind :: FnPtrAddrShim ( ..) => {
96- continue ;
97- }
98- InstanceKind :: DropGlue ( ..)
99- | InstanceKind :: FutureDropPollShim ( ..)
100- | InstanceKind :: AsyncDropGlue ( ..)
101- | InstanceKind :: AsyncDropGlueCtorShim ( ..) => {
102- // FIXME: A not fully instantiated drop shim can cause ICEs if one attempts to
103- // have its MIR built. Likely oli-obk just screwed up the `ParamEnv`s, so this
104- // needs some more analysis.
105- if callee. has_param ( ) {
106- continue ;
107- }
108- }
109- }
94+ if tcx. is_constructor ( callee. def_id ( ) ) {
95+ trace ! ( "constructors always have MIR" ) ;
96+ // Constructor functions cannot cause a query cycle.
97+ continue ;
98+ }
99+
100+ if !should_recurse ( tcx, callee) {
101+ continue ;
102+ }
110103
111- if seen. insert ( callee) {
112- let recursion = recursion_limiter. entry ( callee. def_id ( ) ) . or_default ( ) ;
113- trace ! ( ?callee, recursion = * recursion) ;
114- if recursion_limit. value_within_limit ( * recursion) {
115- * recursion += 1 ;
116- stack . push ( callee ) ;
117- let found_recursion = ensure_sufficient_stack ( || {
118- process (
119- tcx ,
120- typing_env ,
121- callee ,
122- target ,
123- stack ,
124- seen ,
125- recursion_limiter ,
126- recursion_limit ,
127- )
128- } ) ;
129- if found_recursion {
130- return true ;
131- }
132- stack . pop ( ) ;
133- } else {
134- // Pessimistically assume that there could be recursion .
135- return true ;
104+ if seen. insert ( callee) {
105+ let recursion = recursion_limiter. entry ( callee. def_id ( ) ) . or_default ( ) ;
106+ trace ! ( ?callee, recursion = * recursion) ;
107+ let found_recursion = if recursion_limit. value_within_limit ( * recursion) {
108+ * recursion += 1 ;
109+ ensure_sufficient_stack ( || {
110+ process (
111+ tcx ,
112+ typing_env ,
113+ callee ,
114+ target ,
115+ seen ,
116+ involved ,
117+ recursion_limiter ,
118+ recursion_limit ,
119+ )
120+ } )
121+ } else {
122+ // Pessimistically assume that there could be recursion.
123+ true
124+ } ;
125+ if found_recursion {
126+ if let Some ( callee ) = callee . def_id ( ) . as_local ( ) {
127+ // Calling `optimized_mir` of a non-local definition cannot cycle .
128+ involved . insert ( callee ) ;
136129 }
130+ cycle_found = true ;
137131 }
138132 }
139- false
140133 }
134+
135+ cycle_found
136+ }
137+
138+ #[ instrument( level = "debug" , skip( tcx) , ret) ]
139+ pub ( crate ) fn mir_callgraph_cyclic < ' tcx > (
140+ tcx : TyCtxt < ' tcx > ,
141+ root : LocalDefId ,
142+ ) -> UnordSet < LocalDefId > {
143+ assert ! (
144+ !tcx. is_constructor( root. to_def_id( ) ) ,
145+ "you should not call `mir_callgraph_reachable` on enum/struct constructor functions"
146+ ) ;
147+
141148 // FIXME(-Znext-solver=no): Remove this hack when trait solver overflow can return an error.
142149 // In code like that pointed out in #128887, the type complexity we ask the solver to deal with
143150 // grows as we recurse into the call graph. If we use the same recursion limit here and in the
@@ -146,16 +153,32 @@ pub(crate) fn mir_callgraph_reachable<'tcx>(
146153 // the default recursion limits are quite generous for us. If we need to recurse 64 times
147154 // into the call graph, we're probably not going to find any useful MIR inlining.
148155 let recursion_limit = tcx. recursion_limit ( ) / 2 ;
156+ let mut involved = FxHashSet :: default ( ) ;
157+ let typing_env = ty:: TypingEnv :: post_analysis ( tcx, root) ;
158+ let Ok ( Some ( root_instance) ) = ty:: Instance :: try_resolve (
159+ tcx,
160+ typing_env,
161+ root. to_def_id ( ) ,
162+ ty:: GenericArgs :: identity_for_item ( tcx, root. to_def_id ( ) ) ,
163+ ) else {
164+ trace ! ( "cannot resolve, skipping" ) ;
165+ return involved. into ( ) ;
166+ } ;
167+ if !should_recurse ( tcx, root_instance) {
168+ trace ! ( "cannot walk, skipping" ) ;
169+ return involved. into ( ) ;
170+ }
149171 process (
150172 tcx,
151- ty:: TypingEnv :: post_analysis ( tcx, target) ,
173+ typing_env,
174+ root_instance,
152175 root,
153- target,
154- & mut Vec :: new ( ) ,
155176 & mut FxHashSet :: default ( ) ,
177+ & mut involved,
156178 & mut FxHashMap :: default ( ) ,
157179 recursion_limit,
158- )
180+ ) ;
181+ involved. into ( )
159182}
160183
161184pub ( crate ) fn mir_inliner_callees < ' tcx > (
0 commit comments