diff options
Diffstat (limited to 'src')
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); |