From 8997f558177ac2f09b516207414d89a1d62b5b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 9 Aug 2022 11:42:48 +0900 Subject: [PATCH 1/3] Fix preinit of types placing the same value in two fields We had a problem where types that put the same object instance in two different fields would see two different object instances at runtime due to two frozen objects being created for what should have been just one instance. (See the test.) Frozen objects were deriving their identity from the field to which they were assigned to so the problem fell out from this awkward design. The fix is actually a simplification - stop deriving object identity from field and use a "Allocation site ID" instead. The Allocation Site ID is a tuple of "Type whose cctor we were interpreting" + "instruction counter at the time of allocation". That way we can uniquely identify object instances and keep referring to objects allocated in different cctors. I've also lifted the limitation that instance delegates can only point to objects that were assigned to some fields in a different cctor because it's no longer required to limit it. --- .../DependencyAnalysis/FrozenObjectNode.cs | 21 ++- .../GCStaticsPreInitDataNode.cs | 2 +- .../DependencyAnalysis/NodeFactory.cs | 19 +-- .../DependencyAnalysis/NonGCStaticsNode.cs | 2 +- .../Compiler/TypePreinit.cs | 129 +++++++++++------- .../Preinitialization/Preinitialization.cs | 43 ++++++ 6 files changed, 154 insertions(+), 62 deletions(-) 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 496cb7841d6ffb..c109c97dd44c3c 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 Comparer.Default.Compare(otherFrozenObjectNode._allocationSiteId, _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 423854c113f93f..d84317c8002e20 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 7e77c5803d1b30..9905dcdc255e2c 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 2bb181ef816989..09ef4979526233 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 7ee229c690a613..c6076c1dd2a11a 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); + int baseInstructionCounter = instructionCounter; Status status = nestedPreinit.TryScanMethod(field.OwningType.GetStaticConstructor(), null, recursionProtect, ref instructionCounter, out Value _); if (!status.IsSuccessful) { @@ -350,7 +352,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 +1755,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 +1840,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 +1889,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 +1916,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 +1961,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 +1978,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; + } + } + + /// + /// 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 : ReferenceTypeValue, ISerializableReference + 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 +2066,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 +2079,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 +2143,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 +2171,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 +2206,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 +2227,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 +2238,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 +2263,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 8261ef90910450..c2230c65d64bb7 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", From a45eb63c090146c057b2c7ccf46499ea319ee614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 10 Aug 2022 15:01:15 +0900 Subject: [PATCH 2/3] Update src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs --- .../aot/ILCompiler.Compiler/Compiler/TypePreinit.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs index c6076c1dd2a11a..15baf60300503e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs @@ -341,6 +341,16 @@ 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) From beaf8d07cda84fe90db19c8abf5cf0ad399ad76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 10 Aug 2022 15:01:24 +0900 Subject: [PATCH 3/3] Update src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenObjectNode.cs Co-authored-by: Jan Kotas --- .../Compiler/DependencyAnalysis/FrozenObjectNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c109c97dd44c3c..567ecb7595c513 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenObjectNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/FrozenObjectNode.cs @@ -90,7 +90,7 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer if (result != 0) return result; - return Comparer.Default.Compare(otherFrozenObjectNode._allocationSiteId, _allocationSiteId); + return _allocationSiteId.CompareTo(otherFrozenObjectNode._allocationSiteId); } } }