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