66using System . Diagnostics . CodeAnalysis ;
77using ILLink . Shared ;
88using Mono . Cecil ;
9+ using Mono . Cecil . Cil ;
910
1011namespace Mono . Linker
1112{
@@ -14,8 +15,7 @@ public class CompilerGeneratedState
1415 {
1516 readonly LinkContext _context ;
1617 readonly Dictionary < TypeDefinition , MethodDefinition > _compilerGeneratedTypeToUserCodeMethod ;
17- // TODO: fix accessibility
18- internal readonly Dictionary < MethodDefinition , MethodDefinition > _compilerGeneratedMethodToUserCodeMethod ;
18+ readonly Dictionary < MethodDefinition , MethodDefinition > _compilerGeneratedMethodToUserCodeMethod ;
1919 readonly HashSet < TypeDefinition > _typesWithPopulatedCache ;
2020
2121 public CompilerGeneratedState ( LinkContext context )
@@ -27,119 +27,44 @@ public CompilerGeneratedState (LinkContext context)
2727 }
2828
2929 static bool HasRoslynCompilerGeneratedName ( TypeDefinition type ) =>
30- GeneratedNames . IsGeneratedMemberName ( type . Name ) || ( type . DeclaringType != null && HasRoslynCompilerGeneratedName ( type . DeclaringType ) ) ;
30+ CompilerGeneratedNames . IsGeneratedMemberName ( type . Name ) || ( type . DeclaringType != null && HasRoslynCompilerGeneratedName ( type . DeclaringType ) ) ;
3131
3232
33- public void TrackCallToLambdaOrLocalFunction ( MethodDefinition caller , MethodDefinition lambdaOrLocalFunction )
34- {
35- // The declaring type check makes sure we don't treat MoveNext as a normal method. It should be treated as compiler-generated,
36- // mapping to the state machine user method. TODO: check that this doesn't cause problems for global type, etc.
37- bool callerIsStateMachineMethod = GeneratedNames . IsGeneratedMemberName ( caller . DeclaringType . Name ) && ! GeneratedNames . IsLambdaDisplayClass ( caller . DeclaringType . Name ) ;
38- if ( callerIsStateMachineMethod )
39- return ;
40-
41- bool callerIsLambdaOrLocal = GeneratedNames . IsGeneratedMemberName ( caller . Name ) && ! callerIsStateMachineMethod ;
42-
43- if ( ! callerIsLambdaOrLocal ) {
44- // Caller is a normal method...
45- bool added = _compilerGeneratedMethodToUserCodeMethod . TryAdd ( lambdaOrLocalFunction , caller ) ;
46- // There should only be one non-compiler-generated caller of a lambda or local function.
47- Debug . Assert ( added || _compilerGeneratedMethodToUserCodeMethod [ lambdaOrLocalFunction ] == caller ) ;
48- return ;
49- }
50-
51- Debug . Assert ( GeneratedNames . TryParseLambdaMethodName ( caller . Name , out _ ) || GeneratedNames . TryParseLocalFunctionMethodName ( caller . Name , out _ , out _ ) ) ;
52- // Caller is a lambda or local function. This means the lambda or local function is contained within the scope of the caller's user-defined method.
53-
54- if ( _compilerGeneratedMethodToUserCodeMethod . TryGetValue ( caller , out MethodDefinition ? userCodeMethod ) ) {
55- // This lambda/localfn is in the same user code as the caller.
56- bool added = _compilerGeneratedMethodToUserCodeMethod . TryAdd ( lambdaOrLocalFunction , userCodeMethod ) ;
57- Debug . Assert ( added || _compilerGeneratedMethodToUserCodeMethod [ lambdaOrLocalFunction ] == caller ) ;
58- } else {
59- // Haven't tracked any calls to the caller yet.
60- throw new System . Exception ( "Not yet handled! Need to postpone marking of such methods until we can identify a caller, or bail out." ) ;
61- }
62- }
63-
6433 void PopulateCacheForType ( TypeDefinition type )
6534 {
6635 // Avoid repeat scans of the same type
6736 if ( ! _typesWithPopulatedCache . Add ( type ) )
6837 return ;
6938
70- Dictionary < string , List < MethodDefinition > > ? lambdaMethods = null ;
71- Dictionary < string , List < MethodDefinition > > ? localFunctions = null ;
39+ var callGraph = new CallGraph ( ) ;
40+ var callingMethods = new HashSet < MethodDefinition > ( ) ;
7241
73- foreach ( TypeDefinition nested in type . NestedTypes ) {
74- if ( ! GeneratedNames . IsLambdaDisplayClass ( nested . Name ) )
75- continue ;
76-
77- // Lambdas and local functions may be generated into a display class which holds
78- // the closure environment. Lambdas are always generated into such a class.
79-
80- // Local functions typically get emitted outside of the
81- // display class (which is a struct in this case), but when any of the captured state
82- // is used by a state machine local function, the local function is emitted into a
83- // display class holding that captured state.
84- lambdaMethods ??= new Dictionary < string , List < MethodDefinition > > ( ) ;
85- localFunctions ??= new Dictionary < string , List < MethodDefinition > > ( ) ;
86-
87- foreach ( var lambdaMethod in nested . Methods ) {
88- if ( ! GeneratedNames . TryParseLambdaMethodName ( lambdaMethod . Name , out string ? userMethodName ) )
89- continue ;
90- if ( ! lambdaMethods . TryGetValue ( userMethodName , out List < MethodDefinition > ? lambdaMethodsForName ) ) {
91- lambdaMethodsForName = new List < MethodDefinition > ( ) ;
92- lambdaMethods . Add ( userMethodName , lambdaMethodsForName ) ;
93- }
94- lambdaMethodsForName . Add ( lambdaMethod ) ;
42+ void ProcessMethod ( MethodDefinition method )
43+ {
44+ if ( ! CompilerGeneratedNames . IsLambdaOrLocalFunction ( method . Name ) ) {
45+ // If it's not a nested function, track as an entry point to the call graph.
46+ var added = callingMethods . Add ( method ) ;
47+ Debug . Assert ( added ) ;
9548 }
9649
97- foreach ( var localFunction in nested . Methods ) {
98- if ( ! GeneratedNames . TryParseLocalFunctionMethodName ( localFunction . Name , out string ? userMethodName , out string ? localFunctionName ) )
50+ // Discover calls to lambdas or local functions.
51+ foreach ( var instruction in method . Body . Instructions ) {
52+ if ( instruction . OpCode . OperandType != OperandType . InlineMethod )
9953 continue ;
100- if ( ! localFunctions . TryGetValue ( userMethodName , out List < MethodDefinition > ? localFunctionsForName ) ) {
101- localFunctionsForName = new List < MethodDefinition > ( ) ;
102- localFunctions . Add ( userMethodName , localFunctionsForName ) ;
103- }
104- localFunctionsForName . Add ( localFunction ) ;
105- }
106- }
107-
10854
109- foreach ( MethodDefinition localFunction in type . Methods ) {
110- if ( ! GeneratedNames . TryParseLocalFunctionMethodName ( localFunction . Name , out string ? userMethodName , out string ? localFunctionName ) )
111- continue ;
112-
113- // Local functions may be generated into the same type as its declaring method,
114- // alongside a displayclass which holds the captured state.
115- // Or it may not have a displayclass, if there is no captured state.
116-
117- localFunctions ??= new Dictionary < string , List < MethodDefinition > > ( ) ;
118-
119- if ( ! localFunctions . TryGetValue ( userMethodName , out List < MethodDefinition > ? localFunctionsForName ) ) {
120- localFunctionsForName = new List < MethodDefinition > ( ) ;
121- localFunctions . Add ( userMethodName , localFunctionsForName ) ;
122- }
123- localFunctionsForName . Add ( localFunction ) ;
124- }
125-
126- foreach ( MethodDefinition method in type . Methods ) {
127- // TODO: combine into one thing?
55+ MethodDefinition ? lambdaOrLocalFunction = _context . TryResolve ( ( MethodReference ) instruction . Operand ) ;
56+ if ( lambdaOrLocalFunction == null )
57+ continue ;
12858
129- if ( lambdaMethods ? . TryGetValue ( method . Name , out List < MethodDefinition > ? lambdaMethodsForName ) == true ) {
130- foreach ( var lambdaMethod in lambdaMethodsForName )
131- // TODO: change back to add
132- _compilerGeneratedMethodToUserCodeMethod . TryAdd ( lambdaMethod , method ) ;
133- }
59+ if ( ! CompilerGeneratedNames . IsLambdaOrLocalFunction ( lambdaOrLocalFunction . Name ) )
60+ continue ;
13461
135- if ( localFunctions ? . TryGetValue ( method . Name , out List < MethodDefinition > ? localFunctionsForName ) == true ) {
136- foreach ( var localFunction in localFunctionsForName )
137- // TODO: change back to add
138- _compilerGeneratedMethodToUserCodeMethod . TryAdd ( localFunction , method ) ;
62+ callGraph . TrackCall ( method , lambdaOrLocalFunction ) ;
13963 }
14064
65+ // Discover state machine methods.
14166 if ( ! method . HasCustomAttributes )
142- continue ;
67+ return ;
14368
14469 foreach ( var attribute in method . CustomAttributes ) {
14570 if ( attribute . AttributeType . Namespace != "System.Runtime.CompilerServices" )
@@ -162,6 +87,38 @@ void PopulateCacheForType (TypeDefinition type)
16287 }
16388 }
16489 }
90+
91+ // Look for state machine methods, and methods which call local functions.
92+ foreach ( MethodDefinition method in type . Methods )
93+ ProcessMethod ( method ) ;
94+
95+ // Also scan compiler-generated state machine methods (in case they have calls to nested functions),
96+ // and nested functions inside compiler-generated closures (in case they call other nested functions).
97+ foreach ( var nestedType in type . NestedTypes ) {
98+ if ( ! CompilerGeneratedNames . IsGeneratedMemberName ( nestedType . Name ) )
99+ continue ;
100+
101+ // TODO: state machine types shouldn't contain state machine methods. Assert this?
102+ foreach ( var method in nestedType . Methods )
103+ ProcessMethod ( method ) ;
104+ }
105+
106+ // Now we've discovered the call graphs for calls to nested functions.
107+ // Use this to map back from nested functions to the declaring user methods.
108+
109+ // Note: This maps all nested functions back to the user code, not to the immediately
110+ // declaring local function. The IL doesn't contain enough information in general for
111+ // us to determine the nesting of local functions and lambdas.
112+
113+ // Note: this only discovers nested functions which are referenced from the user
114+ // code or its referenced nested functions. There is no reliable way to determine from
115+ // IL which user code an unused nested function belongs to.
116+ foreach ( var userDefinedMethod in callingMethods ) {
117+ foreach ( var nestedFunction in callGraph . GetReachableMethods ( userDefinedMethod ) ) {
118+ Debug . Assert ( CompilerGeneratedNames . IsLambdaOrLocalFunction ( nestedFunction . Name ) ) ;
119+ _compilerGeneratedMethodToUserCodeMethod . Add ( nestedFunction , userDefinedMethod ) ;
120+ }
121+ }
165122 }
166123
167124 static TypeDefinition ? GetFirstConstructorArgumentAsType ( CustomAttribute attribute )
@@ -172,6 +129,9 @@ void PopulateCacheForType (TypeDefinition type)
172129 return attribute . ConstructorArguments [ 0 ] . Value as TypeDefinition ;
173130 }
174131
132+ // For state machine types/members, maps back to the state machine method.
133+ // For local functions and lambdas, maps back to the owning method in user code (not the declaring
134+ // lambda or local function, because the IL doesn't contain enough information to figure this out).
175135 public bool TryGetOwningMethodForCompilerGeneratedMember ( IMemberDefinition sourceMember , [ NotNullWhen ( true ) ] out MethodDefinition ? owningMethod )
176136 {
177137 owningMethod = null ;
0 commit comments