diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenObjectNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenObjectNode.cs index 496cb7841d6ff..567ecb7595c51 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenObjectNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenObjectNode.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using Internal.Text; @@ -14,19 +15,22 @@ namespace ILCompiler.DependencyAnalysis /// public class FrozenObjectNode : EmbeddedObjectNode, ISymbolDefinitionNode { - private readonly FieldDesc _field; + private readonly MetadataType _owningType; private readonly TypePreinit.ISerializableReference _data; + private readonly int _allocationSiteId; - public FrozenObjectNode(FieldDesc field, TypePreinit.ISerializableReference data) + public FrozenObjectNode(MetadataType owningType, int allocationSiteId, TypePreinit.ISerializableReference data) { - _field = field; + _owningType = owningType; + _allocationSiteId = allocationSiteId; _data = data; } public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append(nameMangler.CompilationUnitPrefix).Append("__FrozenObj_") - .Append(nameMangler.GetMangledFieldName(_field)); + .Append(nameMangler.GetMangledTypeName(_owningType)) + .Append(_allocationSiteId.ToStringInvariant()); } public override bool StaticDependenciesAreComputed => true; @@ -38,7 +42,7 @@ int ISymbolDefinitionNode.Offset get { // The frozen object symbol points at the MethodTable portion of the object, skipping over the sync block - return OffsetFromBeginningOfArray + _field.Context.Target.PointerSize; + return OffsetFromBeginningOfArray + _owningType.Context.Target.PointerSize; } } @@ -81,7 +85,12 @@ protected override void OnMarked(NodeFactory factory) public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) { - return comparer.Compare(((FrozenObjectNode)other)._field, _field); + var otherFrozenObjectNode = (FrozenObjectNode)other; + int result = comparer.Compare(otherFrozenObjectNode._owningType, _owningType); + if (result != 0) + return result; + + return _allocationSiteId.CompareTo(otherFrozenObjectNode._allocationSiteId); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticsPreInitDataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticsPreInitDataNode.cs index 423854c113f93..d84317c8002e2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticsPreInitDataNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GCStaticsPreInitDataNode.cs @@ -76,7 +76,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) TypePreinit.ISerializableValue val = _preinitializationInfo.GetFieldValue(field); int currentOffset = builder.CountBytes; if (val != null) - val.WriteFieldData(ref builder, field, factory); + val.WriteFieldData(ref builder, factory); else builder.EmitZeroPointer(); Debug.Assert(builder.CountBytes - currentOffset == field.FieldType.GetElementSize().AsInt); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 7e77c5803d1b3..9905dcdc255e2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -349,7 +349,7 @@ private void CreateNodeCaches() _frozenObjectNodes = new NodeCache(key => { - return new FrozenObjectNode(key.Owner, key.SerializableObject); + return new FrozenObjectNode(key.OwnerType, key.AllocationSiteId, key.SerializableObject); }); _interfaceDispatchCells = new NodeCache(callSiteCell => @@ -1063,9 +1063,9 @@ public FrozenStringNode SerializedStringObject(string data) private NodeCache _frozenObjectNodes; - public FrozenObjectNode SerializedFrozenObject(FieldDesc owningField, TypePreinit.ISerializableReference data) + public FrozenObjectNode SerializedFrozenObject(MetadataType owningType, int allocationSiteId, TypePreinit.ISerializableReference data) { - return _frozenObjectNodes.GetOrAdd(new SerializedFrozenObjectKey(owningField, data)); + return _frozenObjectNodes.GetOrAdd(new SerializedFrozenObjectKey(owningType, allocationSiteId, data)); } private NodeCache _eagerCctorIndirectionNodes; @@ -1306,18 +1306,21 @@ public UninitializedWritableDataBlobKey(Utf8String name, int size, int alignment protected struct SerializedFrozenObjectKey : IEquatable { - public readonly FieldDesc Owner; + public readonly MetadataType OwnerType; + public readonly int AllocationSiteId; public readonly TypePreinit.ISerializableReference SerializableObject; - public SerializedFrozenObjectKey(FieldDesc owner, TypePreinit.ISerializableReference obj) + public SerializedFrozenObjectKey(MetadataType ownerType, int allocationSiteId, TypePreinit.ISerializableReference obj) { - Owner = owner; + Debug.Assert(ownerType.HasStaticConstructor); + OwnerType = ownerType; + AllocationSiteId = allocationSiteId; SerializableObject = obj; } public override bool Equals(object obj) => obj is SerializedFrozenObjectKey && Equals((SerializedFrozenObjectKey)obj); - public bool Equals(SerializedFrozenObjectKey other) => Owner == other.Owner; - public override int GetHashCode() => Owner.GetHashCode(); + public bool Equals(SerializedFrozenObjectKey other) => OwnerType == other.OwnerType && AllocationSiteId == other.AllocationSiteId; + public override int GetHashCode() => HashCode.Combine(OwnerType.GetHashCode(), AllocationSiteId); } private struct MethodILKey : IEquatable diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NonGCStaticsNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NonGCStaticsNode.cs index 2bb181ef81698..09ef497952623 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NonGCStaticsNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NonGCStaticsNode.cs @@ -164,7 +164,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly) TypePreinit.ISerializableValue val = preinitInfo.GetFieldValue(field); int currentOffset = builder.CountBytes; - val.WriteFieldData(ref builder, field, factory); + val.WriteFieldData(ref builder, factory); Debug.Assert(builder.CountBytes - currentOffset == field.FieldType.GetElementSize().AsInt); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index 7ee229c690a61..15baf60300503 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -237,7 +237,8 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack(); recursionProtect.Push(methodIL.OwningMethod); + + // Since we don't reset the instruction counter as we interpret the nested cctor, + // remember the instruction counter before we start interpreting so that we can subtract + // the instructions later when we convert object instances allocated in the nested + // cctor to foreign instances in the currently analyzed cctor. + // E.g. if the nested cctor allocates a new object at the beginning of the cctor, + // we should treat it as a ForeignTypeInstance with allocation site ID 0, not allocation + // site ID of `instructionCounter + 0`. + // We could also reset the counter, but we use the instruction counter as a complexity cutoff + // and resetting it would lead to unpredictable analysis durations. + int baseInstructionCounter = instructionCounter; Status status = nestedPreinit.TryScanMethod(field.OwningType.GetStaticConstructor(), null, recursionProtect, ref instructionCounter, out Value _); if (!status.IsSuccessful) { @@ -350,7 +362,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack public interface ISerializableValue { - void WriteFieldData(ref ObjectDataBuilder builder, FieldDesc field, NodeFactory factory); + void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory); } /// @@ -1753,7 +1765,7 @@ public virtual bool TryCreateByRef(out Value value) return false; } - public abstract void WriteFieldData(ref ObjectDataBuilder builder, FieldDesc field, NodeFactory factory); + public abstract void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory); private T ThrowInvalidProgram() { @@ -1838,9 +1850,8 @@ public override bool Equals(Value value) return true; } - public override void WriteFieldData(ref ObjectDataBuilder builder, FieldDesc field, NodeFactory factory) + public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) { - Debug.Assert(field.FieldType.GetElementSize().AsInt == InstanceBytes.Length); builder.EmitBytes(InstanceBytes); } @@ -1888,7 +1899,7 @@ public override bool Equals(Value value) return Field == ((RuntimeFieldHandleValue)value).Field; } - public override void WriteFieldData(ref ObjectDataBuilder builder, FieldDesc field, NodeFactory factory) + public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) { throw new NotSupportedException(); } @@ -1915,7 +1926,7 @@ public override bool Equals(Value value) return PointedToMethod == ((MethodPointerValue)value).PointedToMethod; } - public override void WriteFieldData(ref ObjectDataBuilder builder, FieldDesc field, NodeFactory factory) + public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) { throw new NotSupportedException(); } @@ -1960,7 +1971,7 @@ public void Initialize(int size) } } - public override void WriteFieldData(ref ObjectDataBuilder builder, FieldDesc field, NodeFactory factory) + public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) { // This would imply we have a byref-typed static field. The layout algorithm should have blocked this. throw new NotImplementedException(); @@ -1977,15 +1988,49 @@ public override bool Equals(Value value) { return this == value; } + + public abstract ReferenceTypeValue ToForeignInstance(int baseInstructionCounter); + } + + private struct AllocationSite + { + public MetadataType OwningType { get; } + public int InstructionCounter { get; } + public AllocationSite(MetadataType type, int instructionCounter) + { + Debug.Assert(type.HasStaticConstructor); + OwningType = type; + InstructionCounter = instructionCounter; + } } - private class DelegateInstance : ReferenceTypeValue, ISerializableReference + /// + /// A reference type that is not a string literal. + /// + private abstract class AllocatedReferenceTypeValue : ReferenceTypeValue + { + protected AllocationSite AllocationSite { get; } + + public AllocatedReferenceTypeValue(TypeDesc type, AllocationSite allocationSite) + : base(type) + { + AllocationSite = allocationSite; + } + + public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter) => + new ForeignTypeInstance( + Type, + new AllocationSite(AllocationSite.OwningType, AllocationSite.InstructionCounter - baseInstructionCounter), + this); + } + + private class DelegateInstance : AllocatedReferenceTypeValue, ISerializableReference { private readonly MethodDesc _methodPointed; - private readonly ForeignTypeInstance _firstParameter; + private readonly ReferenceTypeValue _firstParameter; - public DelegateInstance(TypeDesc delegateType, MethodDesc methodPointed, ForeignTypeInstance firstParameter) - : base(delegateType) + public DelegateInstance(TypeDesc delegateType, MethodDesc methodPointed, ReferenceTypeValue firstParameter, AllocationSite allocationSite) + : base(delegateType, allocationSite) { _methodPointed = methodPointed; _firstParameter = firstParameter; @@ -2031,7 +2076,7 @@ public virtual void WriteContent(ref ObjectDataBuilder builder, ISymbolNode this Debug.Assert(creationInfo.Constructor.Method.Name == "InitializeClosedInstance"); // m_firstParameter - _firstParameter.WriteFieldData(ref builder, _firstParameter.ForeignField, factory); + _firstParameter.WriteFieldData(ref builder, factory); // m_helperObject builder.EmitZeroPointer(); @@ -2044,20 +2089,20 @@ public virtual void WriteContent(ref ObjectDataBuilder builder, ISymbolNode this } } - public override void WriteFieldData(ref ObjectDataBuilder builder, FieldDesc field, NodeFactory factory) + public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) { - builder.EmitPointerReloc(factory.SerializedFrozenObject(field, this)); + builder.EmitPointerReloc(factory.SerializedFrozenObject(AllocationSite.OwningType, AllocationSite.InstructionCounter, this)); } } - private class ArrayInstance : ReferenceTypeValue, ISerializableReference + private class ArrayInstance : AllocatedReferenceTypeValue, ISerializableReference { private readonly int _elementCount; private readonly int _elementSize; private readonly byte[] _data; - public ArrayInstance(ArrayType type, int elementCount) - : base(type) + public ArrayInstance(ArrayType type, int elementCount, AllocationSite allocationSite) + : base(type, allocationSite) { _elementCount = elementCount; _elementSize = type.ElementType.GetElementSize().AsInt; @@ -2108,9 +2153,9 @@ public bool TryLoadElement(int index, out Value value) return true; } - public override void WriteFieldData(ref ObjectDataBuilder builder, FieldDesc field, NodeFactory factory) + public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) { - builder.EmitPointerReloc(factory.SerializedFrozenObject(field, this)); + builder.EmitPointerReloc(factory.SerializedFrozenObject(AllocationSite.OwningType, AllocationSite.InstructionCounter, this)); } public virtual void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, NodeFactory factory) @@ -2136,29 +2181,29 @@ public virtual void WriteContent(ref ObjectDataBuilder builder, ISymbolNode this } } - private class ForeignTypeInstance : ReferenceTypeValue + private class ForeignTypeInstance : AllocatedReferenceTypeValue { - public FieldDesc ForeignField { get; } public ReferenceTypeValue Data { get; } - public ForeignTypeInstance(TypeDesc type, FieldDesc foreignField, ReferenceTypeValue data) - : base(type) + public ForeignTypeInstance(TypeDesc type, AllocationSite allocationSite, ReferenceTypeValue data) + : base(type, allocationSite) { - ForeignField = foreignField; Data = data; } - public override void WriteFieldData(ref ObjectDataBuilder builder, FieldDesc field, NodeFactory factory) + public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) { if (Data is ISerializableReference serializableReference) { - builder.EmitPointerReloc(factory.SerializedFrozenObject(ForeignField, serializableReference)); + builder.EmitPointerReloc(factory.SerializedFrozenObject(AllocationSite.OwningType, AllocationSite.InstructionCounter, serializableReference)); } else { - Data.WriteFieldData(ref builder, field, factory); + Data.WriteFieldData(ref builder, factory); } } + + public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter) => this; } private class StringInstance : ReferenceTypeValue @@ -2171,18 +2216,20 @@ public StringInstance(TypeDesc stringType, string value) _value = value; } - public override void WriteFieldData(ref ObjectDataBuilder builder, FieldDesc field, NodeFactory factory) + public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) { builder.EmitPointerReloc(factory.SerializedStringObject(_value)); } + + public override ReferenceTypeValue ToForeignInstance(int baseInstructionCounter) => this; } - private class ObjectInstance : ReferenceTypeValue, IHasInstanceFields, ISerializableReference + private class ObjectInstance : AllocatedReferenceTypeValue, IHasInstanceFields, ISerializableReference { private readonly byte[] _data; - public ObjectInstance(DefType type) - : base(type) + public ObjectInstance(DefType type, AllocationSite allocationSite) + : base(type, allocationSite) { int size = type.InstanceByteCount.AsInt; if (type.IsValueType) @@ -2190,7 +2237,7 @@ public ObjectInstance(DefType type) _data = new byte[size]; } - public static bool TryBox(DefType type, Value value, out ObjectInstance result) + public static bool TryBox(DefType type, Value value, AllocationSite allocationSite, out ObjectInstance result) { if (!(value is BaseValueTypeValue)) ThrowHelper.ThrowInvalidProgramException(); @@ -2201,7 +2248,7 @@ public static bool TryBox(DefType type, Value value, out ObjectInstance result) return false; } - result = new ObjectInstance(type); + result = new ObjectInstance(type, allocationSite); Array.Copy(valuetype.InstanceBytes, 0, result._data, type.Context.Target.PointerSize, valuetype.InstanceBytes.Length); return true; } @@ -2226,9 +2273,9 @@ public bool TryUnboxAny(TypeDesc type, out Value value) void IHasInstanceFields.SetField(FieldDesc field, Value value) => new FieldAccessor(_data).SetField(field, value); ByRefValue IHasInstanceFields.GetFieldAddress(FieldDesc field) => new FieldAccessor(_data).GetFieldAddress(field); - public override void WriteFieldData(ref ObjectDataBuilder builder, FieldDesc field, NodeFactory factory) + public override void WriteFieldData(ref ObjectDataBuilder builder, NodeFactory factory) { - builder.EmitPointerReloc(factory.SerializedFrozenObject(field, this)); + builder.EmitPointerReloc(factory.SerializedFrozenObject(AllocationSite.OwningType, AllocationSite.InstructionCounter, this)); } public virtual void WriteContent(ref ObjectDataBuilder builder, ISymbolNode thisNode, NodeFactory factory) diff --git a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs index 8261ef9091045..c2230c65d64bb 100644 --- a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs +++ b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs @@ -43,6 +43,8 @@ private static int Main() TestValueTypeDup.Run(); TestFunctionPointers.Run(); TestGCInteraction.Run(); + TestDuplicatedFields.Run(); + TestInstanceDelegate.Run(); #else Console.WriteLine("Preinitialization is disabled in multimodule builds for now. Skipping test."); #endif @@ -870,6 +872,47 @@ public static void Run() } } +class TestDuplicatedFields +{ + class WithSameFields + { + public static WithSameFields Field1a = new WithSameFields(); + public static WithSameFields Field1b = Field1a; + + public static int[] Field2a = new int[1]; + public static int[] Field2b = Field2a; + } + + public static void Run() + { + Assert.IsPreinitialized(typeof(WithSameFields)); + Assert.AreSame(WithSameFields.Field1a, WithSameFields.Field1b); + Assert.AreSame(WithSameFields.Field2a, WithSameFields.Field2b); + } +} + +class TestInstanceDelegate +{ + class ClassWithInstanceDelegate + { + public static Func Instance1 = new ClassWithInstanceDelegate(42).GetCookie; + public static ClassWithInstanceDelegate Target = new ClassWithInstanceDelegate(123); + public static Func Instance2 = Target.GetCookie; + + private int _cookie; + public ClassWithInstanceDelegate(int cookie) => _cookie = cookie; + public int GetCookie() => _cookie; + } + + public static void Run() + { + Assert.IsPreinitialized(typeof(ClassWithInstanceDelegate)); + Assert.AreEqual(42, ClassWithInstanceDelegate.Instance1()); + Assert.AreEqual(123, ClassWithInstanceDelegate.Instance2()); + Assert.AreSame(ClassWithInstanceDelegate.Target, ClassWithInstanceDelegate.Instance2.Target); + } +} + static class Assert { [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",