diff options
author | Fadi Hanna <fadim@microsoft.com> | 2018-03-02 21:55:13 +0300 |
---|---|---|
committer | Fadi Hanna <fadim@microsoft.com> | 2018-03-02 21:55:13 +0300 |
commit | f65acd118e398531c49086f46a7d89494a086547 (patch) | |
tree | 769e50f72d6bf7544524c4f55350679a569562d2 | |
parent | 87e4f1151a4be22ac6cbecf373a5dd31e42537f8 (diff) |
Sealed VTables implementation for CoreRT/ProjectX (tested on both). Changes include:
1) New node type for the sealed vtables
2) Logic in EETypeNode to emit the proper sealed vtable related bits, and not emit sealed newslot methods in the regular vtable
3) Changes in the algorithm that computes the virtual method slot to handle sealed vtable entries
4) Avoid creation of interface dispatch maps for special array types (mdarrays and arrays of pointers)
Current size gains we get with the shared library is about 3% for amd64 ret on ProjectX. Average app size reduction is about 5%
[tfs-changeset: 1690448]
19 files changed, 375 insertions, 62 deletions
diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/CanonicalEETypeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/CanonicalEETypeNode.cs index 2383df231..807f620b9 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/CanonicalEETypeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/CanonicalEETypeNode.cs @@ -46,7 +46,7 @@ namespace ILCompiler.DependencyAnalysis DefType closestDefType = _type.GetClosestDefType(); - if (_type.RuntimeInterfaces.Length > 0) + if (_type.RuntimeInterfaces.Length > 0 && !_type.IsArrayTypeWithoutGenericInterfaces()) dependencyList.Add(factory.InterfaceDispatchMap(_type), "Canonical interface dispatch map"); dependencyList.Add(factory.VTable(closestDefType), "VTable"); diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs index a835bc1f7..4dc900b3e 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ConstructedEETypeNode.cs @@ -80,7 +80,7 @@ namespace ILCompiler.DependencyAnalysis DefType closestDefType = _type.GetClosestDefType(); - if (_type.RuntimeInterfaces.Length > 0) + if (_type.RuntimeInterfaces.Length > 0 && !_type.IsArrayTypeWithoutGenericInterfaces()) { dependencyList.Add(factory.InterfaceDispatchMap(_type), "Interface dispatch map"); } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs index dc09feaf8..3eeb0909d 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/EETypeNode.cs @@ -385,6 +385,12 @@ namespace ILCompiler.DependencyAnalysis if (EmitVirtualSlotsAndInterfaces) { + if (!_type.IsArrayTypeWithoutGenericInterfaces()) + { + // Sealed vtables have relative pointers, so to minimize size, we build sealed vtables for the canonical types + dependencies.Add(new DependencyListEntry(factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific)), "Sealed Vtable")); + } + AddVirtualMethodUseDependencies(dependencies, factory); } @@ -470,6 +476,7 @@ namespace ILCompiler.DependencyAnalysis OutputFinalizerMethod(factory, ref objData); OutputOptionalFields(factory, ref objData); OutputNullableTypeParameter(factory, ref objData); + OutputSealedVTable(factory, relocsOnly, ref objData); OutputGenericInstantiationDetails(factory, ref objData); return objData.ToObjectData(); @@ -690,7 +697,7 @@ namespace ILCompiler.DependencyAnalysis } } - protected virtual void OutputVirtualSlots(NodeFactory factory, ref ObjectDataBuilder objData, TypeDesc implType, TypeDesc declType, TypeDesc templateType, bool relocsOnly) + private void OutputVirtualSlots(NodeFactory factory, ref ObjectDataBuilder objData, TypeDesc implType, TypeDesc declType, TypeDesc templateType, bool relocsOnly) { Debug.Assert(EmitVirtualSlotsAndInterfaces); @@ -759,6 +766,11 @@ namespace ILCompiler.DependencyAnalysis MethodDesc implMethod = implType.GetClosestDefType().FindVirtualFunctionTargetMethodOnObjectType(declMethod); + // Final NewSlot methods cannot be overridden, and therefore can be placed in the sealed-vtable to reduce the size of the vtable + // of this type and any type that inherits from it. + if (declMethod.CanMethodBeInSealedVTable() && !declType.IsArrayTypeWithoutGenericInterfaces() && !factory.IsCppCodegenTemporaryWorkaround) + continue; + if (!implMethod.IsAbstract) { MethodDesc canonImplMethod = implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); @@ -812,6 +824,18 @@ namespace ILCompiler.DependencyAnalysis } } + private void OutputSealedVTable(NodeFactory factory, bool relocsOnly, ref ObjectDataBuilder objData) + { + if (EmitVirtualSlotsAndInterfaces && !_type.IsArrayTypeWithoutGenericInterfaces()) + { + // Sealed vtables have relative pointers, so to minimize size, we build sealed vtables for the canonical types + SealedVTableNode sealedVTable = factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific)); + + if (sealedVTable.BuildSealedVTableSlots(factory, relocsOnly) && sealedVTable.NumSealedVTableEntries > 0) + objData.EmitReloc(sealedVTable, RelocType.IMAGE_REL_BASED_RELPTR32); + } + } + private void OutputGenericInstantiationDetails(NodeFactory factory, ref ObjectDataBuilder objData) { if (_type.HasInstantiation && !_type.IsTypeDefinition) @@ -843,19 +867,19 @@ namespace ILCompiler.DependencyAnalysis /// </summary> protected internal virtual void ComputeOptionalEETypeFields(NodeFactory factory, bool relocsOnly) { - if (!relocsOnly && _type.RuntimeInterfaces.Length > 0 && factory.InterfaceDispatchMap(_type).Marked) + if (!relocsOnly && _type.RuntimeInterfaces.Length > 0 && !_type.IsArrayTypeWithoutGenericInterfaces() && factory.InterfaceDispatchMap(_type).Marked) { _optionalFieldsBuilder.SetFieldValue(EETypeOptionalFieldTag.DispatchMap, checked((uint)factory.InterfaceDispatchMapIndirection(Type).IndexFromBeginningOfArray)); } - ComputeRareFlags(factory); + ComputeRareFlags(factory, relocsOnly); ComputeNullableValueOffset(); if (!relocsOnly) ComputeICastableVirtualMethodSlots(factory); ComputeValueTypeFieldPadding(); } - void ComputeRareFlags(NodeFactory factory) + void ComputeRareFlags(NodeFactory factory, bool relocsOnly) { uint flags = 0; @@ -897,6 +921,13 @@ namespace ILCompiler.DependencyAnalysis flags |= (uint)EETypeRareFlags.IsByRefLikeFlag; } + if (EmitVirtualSlotsAndInterfaces && !_type.IsArrayTypeWithoutGenericInterfaces()) + { + SealedVTableNode sealedVTable = factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific)); + if (sealedVTable.BuildSealedVTableSlots(factory, relocsOnly) && sealedVTable.NumSealedVTableEntries > 0) + flags |= (uint)EETypeRareFlags.HasSealedVTableEntriesFlag; + } + if (flags != 0) { _optionalFieldsBuilder.SetFieldValue(EETypeOptionalFieldTag.RareFlags, flags); @@ -945,13 +976,14 @@ namespace ILCompiler.DependencyAnalysis MethodDesc isInstMethodImpl = _type.ResolveInterfaceMethodTarget(isInstDecl); MethodDesc getImplTypeMethodImpl = _type.ResolveInterfaceMethodTarget(getImplTypeDecl); - int isInstMethodSlot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, isInstMethodImpl); - int getImplTypeMethodSlot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, getImplTypeMethodImpl); - - Debug.Assert(isInstMethodSlot != -1 && getImplTypeMethodSlot != -1); + int isInstMethodSlot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, isInstMethodImpl, _type); + int getImplTypeMethodSlot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, getImplTypeMethodImpl, _type); - _optionalFieldsBuilder.SetFieldValue(EETypeOptionalFieldTag.ICastableIsInstSlot, (uint)isInstMethodSlot); - _optionalFieldsBuilder.SetFieldValue(EETypeOptionalFieldTag.ICastableGetImplTypeSlot, (uint)getImplTypeMethodSlot); + // Slots are usually -1, since these methods are usually in the sealed vtable of the base type. + if (isInstMethodSlot != -1) + _optionalFieldsBuilder.SetFieldValue(EETypeOptionalFieldTag.ICastableIsInstSlot, (uint)isInstMethodSlot); + if (getImplTypeMethodSlot != -1) + _optionalFieldsBuilder.SetFieldValue(EETypeOptionalFieldTag.ICastableGetImplTypeSlot, (uint)getImplTypeMethodSlot); } } } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/InterfaceDispatchCellNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/InterfaceDispatchCellNode.cs index a5c49fc66..6779e0a59 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/InterfaceDispatchCellNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/InterfaceDispatchCellNode.cs @@ -108,7 +108,7 @@ namespace ILCompiler.DependencyAnalysis // Avoid consulting VTable slots until they're guaranteed complete during final data emission if (!relocsOnly) { - objData.EmitNaturalInt(VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, _targetMethod)); + objData.EmitNaturalInt(VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, _targetMethod, _targetMethod.OwningType)); } return objData.ToObjectData(); diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs index e2f6a6535..71966e20f 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/InterfaceDispatchMapNode.cs @@ -17,6 +17,11 @@ namespace ILCompiler.DependencyAnalysis public InterfaceDispatchMapNode(TypeDesc type) { + // Multidimensional arrays should not get a sealed vtable or a dispatch map. Runtime should use the + // sealed vtable and dispatch map of the System.Array basetype instead. + // Pointer arrays also follow the same path + Debug.Assert(!type.IsArrayTypeWithoutGenericInterfaces()); + _type = type; } @@ -80,7 +85,7 @@ namespace ILCompiler.DependencyAnalysis { builder.EmitShort(checked((short)interfaceIndex)); builder.EmitShort(checked((short)(interfaceMethodSlot + (interfaceType.HasGenericDictionarySlot() ? 1 : 0)))); - builder.EmitShort(checked((short)VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, implMethod))); + builder.EmitShort(checked((short)VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, implMethod, _type.GetClosestDefType()))); entryCount++; } } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index 2c9122d7c..d83ba988c 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -1059,7 +1059,7 @@ namespace ILCompiler.DependencyAnalysis List<NativeLayoutVertexNode> vtableSignatureNodeEntries = null; int currentVTableIndexUnused = 0; ProcessVTableEntriesForCallingConventionSignatureGeneration(context, VTableEntriesToProcess.AllOnTypesThatShouldProduceFullVTables, ref currentVTableIndexUnused, - (int vtableIndex, MethodDesc declMethod, MethodDesc implMethod) => + (int vtableIndex, bool isSealedVTableSlot, MethodDesc declMethod, MethodDesc implMethod) => { if (implMethod.IsAbstract) return; @@ -1096,7 +1096,7 @@ namespace ILCompiler.DependencyAnalysis int currentVTableIndexUnused = 0; ProcessVTableEntriesForCallingConventionSignatureGeneration(context, VTableEntriesToProcess.AllOnTypesThatProducePartialVTables, ref currentVTableIndexUnused, - (int vtableIndex, MethodDesc declMethod, MethodDesc implMethod) => + (int vtableIndex, bool isSealedVTableSlot, MethodDesc declMethod, MethodDesc implMethod) => { if (implMethod.IsAbstract) return; @@ -1236,6 +1236,16 @@ namespace ILCompiler.DependencyAnalysis if (typeFlags != default(Internal.NativeFormat.TypeFlags)) layoutInfo.AppendUnsigned(BagElementKind.TypeFlags, (uint)typeFlags); + if (!_type.IsArrayTypeWithoutGenericInterfaces() && ConstructedEETypeNode.CreationAllowed(_type)) + { + SealedVTableNode sealedVTable = factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific)); + + sealedVTable.BuildSealedVTableSlots(factory, relocsOnly: false /* This is the final emission phase */); + + if (sealedVTable.NumSealedVTableEntries > 0) + layoutInfo.AppendUnsigned(BagElementKind.SealedVTableEntries, (uint)sealedVTable.NumSealedVTableEntries); + } + if (_type.GetTypeDefinition().HasVariance || factory.TypeSystemContext.IsGenericArrayInterfaceType(_type)) { // Runtime casting logic relies on all interface types implemented on arrays @@ -1319,7 +1329,7 @@ namespace ILCompiler.DependencyAnalysis VertexSequence vtableSignaturesSequence = null; ProcessVTableEntriesForCallingConventionSignatureGeneration(factory, VTableEntriesToProcess.AllInVTable, ref currentVTableIndexUnused, - (int vtableIndex, MethodDesc declMethod, MethodDesc implMethod) => + (int vtableIndex, bool isSealedVTableSlot, MethodDesc declMethod, MethodDesc implMethod) => { if (implMethod.IsAbstract) return; @@ -1333,7 +1343,7 @@ namespace ILCompiler.DependencyAnalysis Vertex signatureVertex = GetNativeWriter(factory).GetRelativeOffsetSignature(methodSignature.WriteVertex(factory)); Vertex vtableSignatureEntry = writer.GetTuple( - writer.GetUnsignedConstant(((uint)vtableIndex) << 1), // We currently do not use sealed vtable entries yet. Update when that happens + writer.GetUnsignedConstant((uint)((vtableIndex << 1) | (isSealedVTableSlot ? 1 : 0))), factory.MetadataManager.NativeLayoutInfo.TemplatesSection.Place(signatureVertex)); vtableSignaturesSequence.Append(vtableSignatureEntry); @@ -1391,7 +1401,7 @@ namespace ILCompiler.DependencyAnalysis /// Do not adjust vtable index for generic dictionary slot /// The vtable index is only actually valid if whichEntries is set to VTableEntriesToProcess.AllInVTable /// </summary> - private void ProcessVTableEntriesForCallingConventionSignatureGeneration(NodeFactory factory, VTableEntriesToProcess whichEntries, ref int currentVTableIndex, Action<int, MethodDesc, MethodDesc> operation, TypeDesc implType, TypeDesc declType, TypeDesc templateType) + private void ProcessVTableEntriesForCallingConventionSignatureGeneration(NodeFactory factory, VTableEntriesToProcess whichEntries, ref int currentVTableIndex, Action<int, bool, MethodDesc, MethodDesc> operation, TypeDesc implType, TypeDesc declType, TypeDesc templateType) { if (implType.IsInterface) return; @@ -1454,6 +1464,8 @@ namespace ILCompiler.DependencyAnalysis if (declType.HasGenericDictionarySlot() || templateType.HasGenericDictionarySlot()) currentVTableIndex++; + int sealedVTableSlot = 0; + // Actual vtable slots follow foreach (MethodDesc declMethod in vtableEntriesToProcess) { @@ -1462,8 +1474,17 @@ namespace ILCompiler.DependencyAnalysis MethodDesc implMethod = implType.GetClosestDefType().FindVirtualFunctionTargetMethodOnObjectType(declMethod); - operation(currentVTableIndex, declMethod, implMethod); - currentVTableIndex++; + if (implMethod.CanMethodBeInSealedVTable() && !implType.IsArrayTypeWithoutGenericInterfaces()) + { + // Sealed vtable entries on other types in the hierarchy should not be reported (types read entries + // from their own sealed vtables, and not from the sealed vtables of base types). + if (implMethod.OwningType == implType) + operation(sealedVTableSlot++, true, declMethod, implMethod); + } + else + { + operation(currentVTableIndex++, false, declMethod, implMethod); + } } } } @@ -1714,7 +1735,7 @@ namespace ILCompiler.DependencyAnalysis if (method.IsRuntimeDeterminedExactMethod) method = method.GetCanonMethodTarget(CanonicalFormKind.Specific); - int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, method); + int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, method, method.OwningType); return writer.GetMethodSlotSignature(_signature.WriteVertex(factory), checked((uint)slot)); } @@ -1870,8 +1891,8 @@ namespace ILCompiler.DependencyAnalysis protected sealed override Vertex WriteSignatureVertex(NativeWriter writer, NodeFactory factory) { NativeWriter nativeWriter = GetNativeWriter(factory); - - int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, _slotDefiningMethod); + + int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, _slotDefiningMethod, _slotDefiningMethod.OwningType); Vertex typeVertex = factory.NativeLayout.TypeSignatureVertex(_slotDefiningMethod.OwningType).WriteVertex(factory); return nativeWriter.GetTuple(typeVertex, nativeWriter.GetUnsignedConstant((uint)slot)); } @@ -2024,7 +2045,8 @@ namespace ILCompiler.DependencyAnalysis { Debug.Assert((SignatureKind == FixupSignatureKind.NonGenericConstrainedMethod) || (SignatureKind == FixupSignatureKind.NonGenericDirectConstrainedMethod)); Vertex methodType = factory.NativeLayout.TypeSignatureVertex(_constrainedMethod.OwningType).WriteVertex(factory); - int interfaceSlot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, _constrainedMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)); + var canonConstrainedMethod = _constrainedMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); + int interfaceSlot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, canonConstrainedMethod, canonConstrainedMethod.OwningType); Vertex interfaceSlotVertex = writer.GetUnsignedConstant(checked((uint)interfaceSlot)); return writer.GetTuple(constraintType, methodType, interfaceSlotVertex); } diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs index 6bfbf947d..597b2d55a 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs @@ -382,6 +382,11 @@ namespace ILCompiler.DependencyAnalysis return new InterfaceDispatchMapNode(type); }); + _sealedVtableNodes = new NodeCache<TypeDesc, SealedVTableNode>((TypeDesc type) => + { + return new SealedVTableNode(type); + }); + _runtimeMethodHandles = new NodeCache<MethodDesc, RuntimeMethodHandleNode>((MethodDesc method) => { return new RuntimeMethodHandleNode(method); @@ -641,6 +646,13 @@ namespace ILCompiler.DependencyAnalysis return _readOnlyDataBlobs.GetOrAdd(new ReadOnlyDataBlobKey(name, blobData, alignment)); } + private NodeCache<TypeDesc, SealedVTableNode> _sealedVtableNodes; + + internal SealedVTableNode SealedVTable(TypeDesc type) + { + return _sealedVtableNodes.GetOrAdd(type); + } + private NodeCache<TypeDesc, InterfaceDispatchMapNode> _interfaceDispatchMaps; internal InterfaceDispatchMapNode InterfaceDispatchMap(TypeDesc type) diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReflectionVirtualInvokeMapNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReflectionVirtualInvokeMapNode.cs index 2b5f45090..cc9f6a98a 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReflectionVirtualInvokeMapNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReflectionVirtualInvokeMapNode.cs @@ -218,7 +218,7 @@ namespace ILCompiler.DependencyAnalysis else { // Get the declaring method for slot on the instantiated declaring type - int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, declaringMethodForSlot, factory.Target.Abi != TargetAbi.ProjectN); + int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, declaringMethodForSlot, declaringMethodForSlot.OwningType, factory.Target.Abi != TargetAbi.ProjectN); Debug.Assert(slot != -1); vertex = writer.GetTuple( diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/SealedVtableNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/SealedVtableNode.cs new file mode 100644 index 000000000..4f7e5ebe3 --- /dev/null +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/SealedVtableNode.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using Internal.Text; +using Internal.TypeSystem; + +namespace ILCompiler.DependencyAnalysis +{ + public class SealedVTableNode : ObjectNode, ISymbolDefinitionNode + { + private readonly TypeDesc _type; + private List<MethodDesc> _sealedVTableEntries; + + public SealedVTableNode(TypeDesc type) + { + // Multidimensional arrays should not get a sealed vtable or a dispatch map. Runtime should use the + // sealed vtable and dispatch map of the System.Array basetype instead. + // Pointer arrays also follow the same path + Debug.Assert(!type.IsArrayTypeWithoutGenericInterfaces()); + Debug.Assert(!type.IsRuntimeDeterminedSubtype); + + _type = type; + } + + protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler); + + public override ObjectNodeSection Section => _type.Context.Target.IsWindows ? ObjectNodeSection.FoldableReadOnlyDataSection : ObjectNodeSection.DataSection; + + public virtual void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(nameMangler.CompilationUnitPrefix + "__SealedVTable_" + nameMangler.NodeMangler.EEType(_type)); + } + + int ISymbolNode.Offset => 0; + int ISymbolDefinitionNode.Offset => 0; + public override bool IsShareable => EETypeNode.IsTypeNodeShareable(_type); + public override bool StaticDependenciesAreComputed => true; + + /// <summary> + /// Returns the number of sealed vtable slots on the type. This API should only be called after successfully + /// building the sealed vtable slots. + /// </summary> + public int NumSealedVTableEntries + { + get + { + if (_sealedVTableEntries == null) + throw new NotSupportedException(); + + return _sealedVTableEntries.Count; + } + } + + /// <summary> + /// Returns the slot of a method in the sealed vtable, or -1 if not found. This API should only be called after + /// successfully building the sealed vtable slots. + /// </summary> + public int ComputeSealedVTableSlot(MethodDesc method) + { + if (_sealedVTableEntries == null) + throw new NotSupportedException(); + + for (int i = 0; i < _sealedVTableEntries.Count; i++) + { + if (_sealedVTableEntries[i] == method) + return i; + } + + return -1; + } + + public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly) + { + // Sealed vtable already built + if (_sealedVTableEntries != null) + return true; + + TypeDesc declType = _type.GetClosestDefType(); + + // It's only okay to touch the actual list of slots if we're in the final emission phase + // or the vtable is not built lazily. + if (relocsOnly && !factory.VTable(declType).HasFixedSlots) + return false; + + _sealedVTableEntries = new List<MethodDesc>(); + + // Cpp codegen does not support sealed vtables + if (factory.IsCppCodegenTemporaryWorkaround) + return true; + + IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(declType).Slots; + + for (int i = 0; i < virtualSlots.Count; i++) + { + MethodDesc implMethod = _type.GetClosestDefType().FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]); + + if (implMethod.CanMethodBeInSealedVTable()) + _sealedVTableEntries.Add(implMethod); + } + + for (int interfaceIndex = 0; interfaceIndex < _type.RuntimeInterfaces.Length; interfaceIndex++) + { + var interfaceType = _type.RuntimeInterfaces[interfaceIndex]; + + virtualSlots = factory.VTable(interfaceType).Slots; + + for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++) + { + MethodDesc declMethod = virtualSlots[interfaceMethodSlot]; + var implMethod = _type.GetClosestDefType().ResolveInterfaceMethodToVirtualMethodOnType(declMethod); + + // Interface methods first implemented by a base type in the hierarchy will return null for the implMethod (runtime interface + // dispatch will walk the inheritance chain). + if (implMethod != null && implMethod.CanMethodBeInSealedVTable() && implMethod.OwningType != _type) + _sealedVTableEntries.Add(implMethod); + } + } + + return true; + } + + public override ObjectData GetData(NodeFactory factory, bool relocsOnly) + { + ObjectDataBuilder objData = new ObjectDataBuilder(factory, relocsOnly); + objData.RequireInitialPointerAlignment(); + objData.AddSymbol(this); + + if (BuildSealedVTableSlots(factory, relocsOnly)) + { + for (int i = 0; i < _sealedVTableEntries.Count; i++) + { + MethodDesc canonImplMethod = _sealedVTableEntries[i].GetCanonMethodTarget(CanonicalFormKind.Specific); + objData.EmitReloc(factory.MethodEntrypoint(canonImplMethod, _sealedVTableEntries[i].OwningType.IsValueType), RelocType.IMAGE_REL_BASED_RELPTR32); + } + } + + return objData.ToObjectData(); + } + + protected internal override int ClassCode => 1632890252; + protected internal override int CompareToImpl(SortableDependencyNode other, CompilerComparer comparer) + { + return comparer.Compare(_type, ((SealedVTableNode)other)._type); + } + } +} diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunHelperNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunHelperNode.cs index d4bf28b31..a05365715 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunHelperNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_ARM/ARMReadyToRunHelperNode.cs @@ -33,13 +33,14 @@ namespace ILCompiler.DependencyAnalysis MethodDesc targetMethod = (MethodDesc)Target; Debug.Assert(!targetMethod.OwningType.IsInterface); + Debug.Assert(!targetMethod.CanMethodBeInSealedVTable()); int pointerSize = factory.Target.PointerSize; int slot = 0; if (!relocsOnly) { - slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod); + slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, targetMethod.OwningType); Debug.Assert(slot != -1); } @@ -162,11 +163,13 @@ namespace ILCompiler.DependencyAnalysis if (target.TargetNeedsVTableLookup) { + Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable()); + encoder.EmitLDR(encoder.TargetRegister.Arg2, encoder.TargetRegister.Arg1); int slot = 0; if (!relocsOnly) - slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, target.TargetMethod); + slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, target.TargetMethod, target.TargetMethod.OwningType); Debug.Assert(slot != -1); encoder.EmitLDR(encoder.TargetRegister.Arg2, encoder.TargetRegister.Arg2, @@ -212,7 +215,9 @@ namespace ILCompiler.DependencyAnalysis encoder.EmitLDR(encoder.TargetRegister.Result, encoder.TargetRegister.Arg0); - int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod); + Debug.Assert(!targetMethod.CanMethodBeInSealedVTable()); + + int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, targetMethod.OwningType); Debug.Assert(slot != -1); encoder.EmitLDR(encoder.TargetRegister.Result, encoder.TargetRegister.Result, ((short)(EETypeNode.GetVTableOffset(factory.Target.PointerSize) + (slot * factory.Target.PointerSize)))); diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs index 614c171e6..026367d85 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs @@ -32,6 +32,7 @@ namespace ILCompiler.DependencyAnalysis MethodDesc targetMethod = (MethodDesc)Target; Debug.Assert(!targetMethod.OwningType.IsInterface); + Debug.Assert(!targetMethod.CanMethodBeInSealedVTable()); AddrMode loadFromThisPtr = new AddrMode(encoder.TargetRegister.Arg0, null, 0, 0, AddrModeSize.Int64); encoder.EmitMOV(encoder.TargetRegister.Result, ref loadFromThisPtr); @@ -41,7 +42,7 @@ namespace ILCompiler.DependencyAnalysis int slot = 0; if (!relocsOnly) { - slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod); + slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, targetMethod.OwningType); Debug.Assert(slot != -1); } Debug.Assert(((INodeWithDebugInfo)this).DebugLocInfos[1].NativeOffset == encoder.Builder.CountBytes); @@ -167,12 +168,14 @@ namespace ILCompiler.DependencyAnalysis if (target.TargetNeedsVTableLookup) { + Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable()); + AddrMode loadFromThisPtr = new AddrMode(encoder.TargetRegister.Arg1, null, 0, 0, AddrModeSize.Int64); encoder.EmitMOV(encoder.TargetRegister.Arg2, ref loadFromThisPtr); int slot = 0; if (!relocsOnly) - slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, target.TargetMethod); + slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, target.TargetMethod, target.TargetMethod.OwningType); Debug.Assert(slot != -1); AddrMode loadFromSlot = new AddrMode(encoder.TargetRegister.Arg2, null, EETypeNode.GetVTableOffset(factory.Target.PointerSize) + (slot * factory.Target.PointerSize), 0, AddrModeSize.Int64); @@ -214,7 +217,9 @@ namespace ILCompiler.DependencyAnalysis AddrMode loadFromThisPtr = new AddrMode(encoder.TargetRegister.Arg0, null, 0, 0, AddrModeSize.Int64); encoder.EmitMOV(encoder.TargetRegister.Result, ref loadFromThisPtr); - int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod); + Debug.Assert(!targetMethod.CanMethodBeInSealedVTable()); + + int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, targetMethod.OwningType); Debug.Assert(slot != -1); AddrMode loadFromSlot = new AddrMode(encoder.TargetRegister.Result, null, EETypeNode.GetVTableOffset(factory.Target.PointerSize) + (slot * factory.Target.PointerSize), 0, AddrModeSize.Int64); encoder.EmitMOV(encoder.TargetRegister.Result, ref loadFromSlot); diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/WindowsDebugMethodInfoSection.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/WindowsDebugMethodInfoSection.cs index 7d4396fc7..03488c594 100644 --- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/WindowsDebugMethodInfoSection.cs +++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/WindowsDebugMethodInfoSection.cs @@ -102,7 +102,7 @@ namespace ILCompiler.DependencyAnalysis // find virtual method slot. MethodDesc declaringMethodForSlot = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(md.GetTypicalMethodDefinition()); - int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, declaringMethodForSlot); + int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, declaringMethodForSlot, type); if (slot != -1 && !methodList.ContainsKey(methodToken)) methodList.Add(methodToken, slot); } diff --git a/src/ILCompiler.Compiler/src/Compiler/MethodExtensions.cs b/src/ILCompiler.Compiler/src/Compiler/MethodExtensions.cs index 15070a4b4..4b72c15d8 100644 --- a/src/ILCompiler.Compiler/src/Compiler/MethodExtensions.cs +++ b/src/ILCompiler.Compiler/src/Compiler/MethodExtensions.cs @@ -113,5 +113,19 @@ namespace ILCompiler return false; } + + /// <summary> + /// Determine whether a method can go into the sealed vtable of a type. Such method must be a sealed virtual + /// method that is not overriding any method on a base type. + /// Given that such methods can never be overridden in any derived type, we can + /// save space in the vtable of a type, and all of its derived types by not emitting these methods in their vtables, + /// and storing them in a separate table on the side. This is especially beneficial for all array types, + /// since all of their collection interface methods are sealed and implemented on the System.Array and + /// System.Array<T> base types, and therefore we can minimize the vtable sizes of all derived array types. + /// </summary> + public static bool CanMethodBeInSealedVTable(this MethodDesc method) + { + return method.IsFinal && method.IsNewSlot; + } } } diff --git a/src/ILCompiler.Compiler/src/Compiler/TypeExtensions.cs b/src/ILCompiler.Compiler/src/Compiler/TypeExtensions.cs index 5cf56d172..99b0de66c 100644 --- a/src/ILCompiler.Compiler/src/Compiler/TypeExtensions.cs +++ b/src/ILCompiler.Compiler/src/Compiler/TypeExtensions.cs @@ -31,14 +31,11 @@ namespace ILCompiler { if (type.IsArray) { - var arrayType = (ArrayType)type; - TypeDesc elementType = arrayType.ElementType; - if (arrayType.IsSzArray && !elementType.IsPointer && !elementType.IsFunctionPointer) - { - MetadataType arrayShadowType = type.Context.SystemModule.GetKnownType("System", "Array`1"); - return arrayShadowType.MakeInstantiatedType(elementType); - } - return type.Context.GetWellKnownType(WellKnownType.Array); + if (type.IsArrayTypeWithoutGenericInterfaces()) + return type.Context.GetWellKnownType(WellKnownType.Array); + + MetadataType arrayShadowType = type.Context.SystemModule.GetKnownType("System", "Array`1"); + return arrayShadowType.MakeInstantiatedType(((ArrayType)type).ElementType); } Debug.Assert(type is DefType); @@ -183,5 +180,19 @@ namespace ILCompiler return false; } + + /// <summary> + /// Determines whether an array type does implements the generic collection interfaces. This is the case + /// for multi-dimensional arrays, and arrays of pointers. + /// </summary> + public static bool IsArrayTypeWithoutGenericInterfaces(this TypeDesc type) + { + if (!type.IsArray) + return false; + + var arrayType = (ArrayType)type; + TypeDesc elementType = arrayType.ElementType; + return type.IsMdArray || elementType.IsPointer || elementType.IsFunctionPointer; + } } } diff --git a/src/ILCompiler.Compiler/src/Compiler/VirtualMethodCallHelper.cs b/src/ILCompiler.Compiler/src/Compiler/VirtualMethodCallHelper.cs index 27b1198c6..8266ed8df 100644 --- a/src/ILCompiler.Compiler/src/Compiler/VirtualMethodCallHelper.cs +++ b/src/ILCompiler.Compiler/src/Compiler/VirtualMethodCallHelper.cs @@ -16,29 +16,76 @@ namespace ILCompiler /// Given a virtual method decl, return its VTable slot if the method is used on its containing type. /// Return -1 if the virtual method is not used. /// </summary> - public static int GetVirtualMethodSlot(NodeFactory factory, MethodDesc method, bool countDictionarySlots = true) + public static int GetVirtualMethodSlot(NodeFactory factory, MethodDesc method, TypeDesc implType, bool countDictionarySlots = true) { - // TODO: More efficient lookup of the slot - TypeDesc owningType = method.OwningType; - int baseSlots = GetNumberOfBaseSlots(factory, owningType, countDictionarySlots); - - // For types that have a generic dictionary, the introduced virtual method slots are - // prefixed with a pointer to the generic dictionary. - if (owningType.HasGenericDictionarySlot() && countDictionarySlots) - baseSlots++; - - IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(owningType).Slots; - int methodSlot = -1; - for (int slot = 0; slot < virtualSlots.Count; slot++) + // CppCodegen does not yet support sealed vtables. + + if (method.CanMethodBeInSealedVTable() && !factory.IsCppCodegenTemporaryWorkaround) { - if (virtualSlots[slot] == method) + // If the method is a sealed newslot method, it will be put in the sealed vtable instead of the type's vtable. In this + // case, the slot index return should be the index in the sealed vtable, plus the total number of vtable slots. + + // First, make sure we are not attempting to resolve the slot of a sealed vtable method on a special array type, which + // does not get any sealed vtable entries + Debug.Assert(!implType.IsArrayTypeWithoutGenericInterfaces()); + + // Ensure the sealed vtable is built before computing the slot + factory.SealedVTable(implType).BuildSealedVTableSlots(factory, relocsOnly: false /* GetVirtualMethodSlot is called in the final emission phase */); + + int sealedVTableSlot = factory.SealedVTable(implType).ComputeSealedVTableSlot(method); + if (sealedVTableSlot == -1) + return -1; + + // Now compute the total number of vtable slots that would exist on the type + int baseSlots = GetNumberOfBaseSlots(factory, implType, countDictionarySlots); + + // For types that have a generic dictionary, the introduced virtual method slots are + // prefixed with a pointer to the generic dictionary. + if (implType.HasGenericDictionarySlot() && countDictionarySlots) + baseSlots++; + + int numVTableSlots = baseSlots; + IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(implType).Slots; + for (int slot = 0; slot < virtualSlots.Count; slot++) { - methodSlot = slot; - break; + if (virtualSlots[slot].CanMethodBeInSealedVTable()) + continue; + numVTableSlots++; } + + return numVTableSlots + sealedVTableSlot; } + else + { + // TODO: More efficient lookup of the slot + TypeDesc owningType = method.OwningType; + int baseSlots = GetNumberOfBaseSlots(factory, owningType, countDictionarySlots); - return methodSlot == -1 ? -1 : baseSlots + methodSlot; + // For types that have a generic dictionary, the introduced virtual method slots are + // prefixed with a pointer to the generic dictionary. + if (owningType.HasGenericDictionarySlot() && countDictionarySlots) + baseSlots++; + + IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(owningType).Slots; + int methodSlot = -1; + int numSealedVTableEntries = 0; + for (int slot = 0; slot < virtualSlots.Count; slot++) + { + if (virtualSlots[slot].CanMethodBeInSealedVTable() && !factory.IsCppCodegenTemporaryWorkaround) + { + numSealedVTableEntries++; + continue; + } + + if (virtualSlots[slot] == method) + { + methodSlot = slot; + break; + } + } + + return methodSlot == -1 ? -1 : baseSlots + methodSlot - numSealedVTableEntries; + } } private static int GetNumberOfBaseSlots(NodeFactory factory, TypeDesc owningType, bool countDictionarySlots) @@ -83,7 +130,13 @@ namespace ILCompiler baseSlots++; IReadOnlyList<MethodDesc> baseVirtualSlots = factory.VTable(baseType).Slots; - baseSlots += baseVirtualSlots.Count; + foreach (var vtableMethod in baseVirtualSlots) + { + // Methods in the sealed vtable should be excluded from the count + if (vtableMethod.CanMethodBeInSealedVTable() && !factory.IsCppCodegenTemporaryWorkaround) + continue; + baseSlots++; + } baseType = baseType.BaseType; templateBaseType = templateBaseType.BaseType; diff --git a/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj b/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj index a29a5cd10..b799fb700 100644 --- a/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj +++ b/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj @@ -283,6 +283,7 @@ <Compile Include="Compiler\DependencyAnalysis\UtcDictionaryLayoutNode.cs" /> <Compile Include="Compiler\DependencyAnalysis\UtcNodeFactory.cs" /> <Compile Include="Compiler\DependencyAnalysis\VirtualMethodUseNode.cs" /> + <Compile Include="Compiler\DependencyAnalysis\SealedVTableNode.cs" /> <Compile Include="Compiler\DependencyAnalysis\StackTraceMethodMappingNode.cs" /> <Compile Include="Compiler\DependencyAnalysis\StackTraceEmbeddedMetadataNode.cs" /> <Compile Include="Compiler\DependencyAnalysis\Target_X64\Register.cs" /> diff --git a/src/ILCompiler.WebAssembly/src/Compiler/DependencyAnalysis/WebAssemblyVTableSlotNode.cs b/src/ILCompiler.WebAssembly/src/Compiler/DependencyAnalysis/WebAssemblyVTableSlotNode.cs index 6f7897a8f..e48163267 100644 --- a/src/ILCompiler.WebAssembly/src/Compiler/DependencyAnalysis/WebAssemblyVTableSlotNode.cs +++ b/src/ILCompiler.WebAssembly/src/Compiler/DependencyAnalysis/WebAssemblyVTableSlotNode.cs @@ -62,7 +62,7 @@ namespace ILCompiler.DependencyAnalysis if (!relocsOnly) { var tableOffset = EETypeNode.GetVTableOffset(factory.Target.PointerSize) / factory.Target.PointerSize; - objData.EmitInt(tableOffset + VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, _targetMethod)); + objData.EmitInt(tableOffset + VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, _targetMethod, _targetMethod.OwningType)); } return objData.ToObjectData(); } diff --git a/src/JitInterface/src/CorInfoImpl.cs b/src/JitInterface/src/CorInfoImpl.cs index a9eaceaab..535ac1cb7 100644 --- a/src/JitInterface/src/CorInfoImpl.cs +++ b/src/JitInterface/src/CorInfoImpl.cs @@ -859,8 +859,9 @@ namespace Internal.JitInterface // Normalize to the slot defining method. We don't have slot information for the overrides. methodDesc = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(methodDesc); + Debug.Assert(!methodDesc.CanMethodBeInSealedVTable()); - int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(_compilation.NodeFactory, methodDesc); + int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(_compilation.NodeFactory, methodDesc, methodDesc.OwningType); Debug.Assert(slot != -1); offsetAfterIndirection = (uint)(EETypeNode.GetVTableOffset(pointerSize) + slot * pointerSize); diff --git a/src/System.Private.Jit/src/Internal/Runtime/JitSupport/VirtualMethodSlotHelper.cs b/src/System.Private.Jit/src/Internal/Runtime/JitSupport/VirtualMethodSlotHelper.cs index 10e3466bd..ab82c2d6f 100644 --- a/src/System.Private.Jit/src/Internal/Runtime/JitSupport/VirtualMethodSlotHelper.cs +++ b/src/System.Private.Jit/src/Internal/Runtime/JitSupport/VirtualMethodSlotHelper.cs @@ -23,7 +23,7 @@ namespace ILCompiler /// Given a virtual method decl, return its VTable slot if the method is used on its containing type. /// Return -1 if the virtual method is not used. /// </summary> - public static int GetVirtualMethodSlot(NodeFactory factory, MethodDesc method) + public static int GetVirtualMethodSlot(NodeFactory factory, MethodDesc method, TypeDesc implType) { Debug.Assert(method.IsVirtual); |