1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 
7 using Internal.TypeSystem;
8 using ILCompiler.DependencyAnalysisFramework;
9 
10 using Debug = System.Diagnostics.Debug;
11 
12 namespace ILCompiler.DependencyAnalysis
13 {
14     /// <summary>
15     /// Represents the VTable for a type's slice. For example, System.String's VTableSliceNode includes virtual
16     /// slots added by System.String itself, System.Object's VTableSliceNode contains the virtuals it defines.
17     /// </summary>
18     public abstract class VTableSliceNode : DependencyNodeCore<NodeFactory>
19     {
20         protected TypeDesc _type;
21 
VTableSliceNode(TypeDesc type)22         public VTableSliceNode(TypeDesc type)
23         {
24             Debug.Assert(!type.IsArray, "Wanted to call GetClosestDefType?");
25             Debug.Assert(!type.IsCanonicalSubtype(CanonicalFormKind.Any) ||
26                 type.ConvertToCanonForm(CanonicalFormKind.Specific) == type);
27             _type = type;
28         }
29 
30         public abstract IReadOnlyList<MethodDesc> Slots
31         {
32             get;
33         }
34 
35         public TypeDesc Type => _type;
36 
37         /// <summary>
38         /// Gets a value indicating whether the slots are assigned at the beginning of the compilation.
39         /// </summary>
40         public abstract bool HasFixedSlots
41         {
42             get;
43         }
44 
45         protected override string GetName(NodeFactory factory) => $"__vtable_{factory.NameMangler.GetMangledTypeName(_type).ToString()}";
46 
47         public override bool StaticDependenciesAreComputed => true;
48 
GetStaticDependencies(NodeFactory factory)49         public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
50         {
51             DefType baseType = _type.NormalizedBaseType();
52             if (baseType != null)
53             {
54                 return new[] { new DependencyListEntry(factory.VTable(baseType), "Base type VTable") };
55             }
56 
57             return null;
58         }
59 
60         public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory) => null;
SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory factory)61         public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory factory) => null;
62 
63         public override bool InterestingForDynamicDependencyAnalysis => false;
64         public override bool HasDynamicDependencies => false;
65         public override bool HasConditionalStaticDependencies => false;
66 
GetAllVirtualMethods(TypeDesc type)67         protected static IEnumerable<MethodDesc> GetAllVirtualMethods(TypeDesc type)
68         {
69             foreach (MethodDesc method in type.GetAllMethods())
70             {
71                 if (method.IsVirtual)
72                     yield return method;
73             }
74         }
75     }
76 
77     /// <summary>
78     /// Represents a VTable slice with fixed slots whose assignment was determined at the time the slice was allocated.
79     /// </summary>
80     internal class PrecomputedVTableSliceNode : VTableSliceNode
81     {
82         private readonly IReadOnlyList<MethodDesc> _slots;
83 
PrecomputedVTableSliceNode(TypeDesc type, IReadOnlyList<MethodDesc> slots)84         public PrecomputedVTableSliceNode(TypeDesc type, IReadOnlyList<MethodDesc> slots)
85             : base(type)
86         {
87             _slots = slots;
88         }
89 
90         public override IReadOnlyList<MethodDesc> Slots
91         {
92             get
93             {
94                 return _slots;
95             }
96         }
97 
98         public override bool HasFixedSlots
99         {
100             get
101             {
102                 return true;
103             }
104         }
105     }
106 
107     /// <summary>
108     /// Represents a VTable slice for a complete type - a type with all virtual method slots generated,
109     /// irrespective of whether they are used.
110     /// </summary>
111     internal sealed class EagerlyBuiltVTableSliceNode : PrecomputedVTableSliceNode
112     {
EagerlyBuiltVTableSliceNode(TypeDesc type)113         public EagerlyBuiltVTableSliceNode(TypeDesc type)
114             : base(type, ComputeSlots(type))
115         {
116         }
117 
ComputeSlots(TypeDesc type)118         private static IReadOnlyList<MethodDesc> ComputeSlots(TypeDesc type)
119         {
120             var slots = new ArrayBuilder<MethodDesc>();
121 
122             bool isObjectType = type.IsObject;
123             DefType defType = type.GetClosestDefType();
124 
125             IEnumerable<MethodDesc> allSlots = type.IsInterface ?
126                 GetAllVirtualMethods(type) : defType.EnumAllVirtualSlots();
127 
128             foreach (var method in allSlots)
129             {
130                 // GVMs are not emitted in the type's vtable.
131                 if (method.HasInstantiation)
132                     continue;
133 
134                 // Finalizers are called via a field on the EEType, not through the VTable
135                 if (isObjectType && method.Name == "Finalize")
136                     continue;
137 
138                 // Current type doesn't define this slot.
139                 if (method.OwningType != defType)
140                     continue;
141 
142                 slots.Add(method);
143             }
144 
145             return slots.ToArray();
146         }
147     }
148 
149     /// <summary>
150     /// Represents a VTable slice where slots are built on demand. Only the slots that are actually used
151     /// will be generated.
152     /// </summary>
153     internal sealed class LazilyBuiltVTableSliceNode : VTableSliceNode
154     {
155         private HashSet<MethodDesc> _usedMethods = new HashSet<MethodDesc>();
156         private MethodDesc[] _slots;
157 
LazilyBuiltVTableSliceNode(TypeDesc type)158         public LazilyBuiltVTableSliceNode(TypeDesc type)
159             : base(type)
160         {
161         }
162 
163         public override IReadOnlyList<MethodDesc> Slots
164         {
165             get
166             {
167                 if (_slots == null)
168                 {
169                     // Sort the lazily populated slots in metadata order (the order in which they show up
170                     // in GetAllMethods()).
171                     // This ensures that Foo<string> and Foo<object> will end up with the same vtable
172                     // no matter the order in which VirtualMethodUse nodes populated it.
173                     ArrayBuilder<MethodDesc> slotsBuilder = new ArrayBuilder<MethodDesc>();
174                     DefType defType = _type.GetClosestDefType();
175                     foreach (var method in defType.GetAllMethods())
176                     {
177                         if (_usedMethods.Contains(method))
178                             slotsBuilder.Add(method);
179                     }
180                     Debug.Assert(_usedMethods.Count == slotsBuilder.Count);
181                     _slots = slotsBuilder.ToArray();
182 
183                     // Null out used methods so that we AV if someone tries to add now.
184                     _usedMethods = null;
185                 }
186 
187                 return _slots;
188             }
189         }
190 
191         public override bool HasFixedSlots
192         {
193             get
194             {
195                 return false;
196             }
197         }
198 
AddEntry(NodeFactory factory, MethodDesc virtualMethod)199         public void AddEntry(NodeFactory factory, MethodDesc virtualMethod)
200         {
201             // GVMs are not emitted in the type's vtable.
202             Debug.Assert(!virtualMethod.HasInstantiation);
203             Debug.Assert(virtualMethod.IsVirtual);
204             Debug.Assert(_slots == null && _usedMethods != null);
205 
206             _usedMethods.Add(virtualMethod);
207         }
208 
209         public override bool HasConditionalStaticDependencies
210         {
211             get
212             {
213                 return _type.ConvertToCanonForm(CanonicalFormKind.Specific) != _type;
214             }
215         }
216 
GetConditionalStaticDependencies(NodeFactory factory)217         public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory)
218         {
219             // VirtualMethodUse of Foo<SomeType>.Method will bring in VirtualMethodUse
220             // of Foo<__Canon>.Method. This in turn should bring in Foo<OtherType>.Method.
221             DefType defType = _type.GetClosestDefType();
222 
223             IEnumerable<MethodDesc> allSlots = _type.IsInterface ?
224                 GetAllVirtualMethods(_type) : defType.EnumAllVirtualSlots();
225 
226             foreach (var method in allSlots)
227             {
228                 // Generic virtual methods are tracked by an orthogonal mechanism.
229                 if (method.HasInstantiation)
230                     continue;
231 
232                 // Current type doesn't define this slot. Another VTableSlice will take care of this.
233                 if (method.OwningType != defType)
234                     continue;
235 
236                 if (defType.Context.SupportsCanon)
237                     yield return new CombinedDependencyListEntry(
238                         factory.VirtualMethodUse(method),
239                         factory.VirtualMethodUse(method.GetCanonMethodTarget(CanonicalFormKind.Specific)),
240                         "Canonically equivalent virtual method use");
241 
242                 if (defType.Context.SupportsUniversalCanon)
243                     yield return new CombinedDependencyListEntry(
244                         factory.VirtualMethodUse(method),
245                         factory.VirtualMethodUse(method.GetCanonMethodTarget(CanonicalFormKind.Universal)),
246                         "Universal Canonically equivalent virtual method use");
247             }
248         }
249     }
250 }
251