diff --git a/src/coreclr/nativeaot/Test.CoreLib/src/System/RuntimeTypeHandle.cs b/src/coreclr/nativeaot/Test.CoreLib/src/System/RuntimeTypeHandle.cs
index f0401dc09e07a9..9adaed61c4cc86 100644
--- a/src/coreclr/nativeaot/Test.CoreLib/src/System/RuntimeTypeHandle.cs
+++ b/src/coreclr/nativeaot/Test.CoreLib/src/System/RuntimeTypeHandle.cs
@@ -34,5 +34,10 @@ private static RuntimeTypeHandle GetRuntimeTypeHandle(IntPtr pEEType)
{
return new RuntimeTypeHandle(pEEType);
}
+
+ private static Type GetRuntimeType(IntPtr pEEType)
+ {
+ return Type.GetTypeFromHandle(new RuntimeTypeHandle(pEEType));
+ }
}
}
diff --git a/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs b/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs
index e4c4ddb42d3180..26bc612b8262db 100644
--- a/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs
+++ b/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs
@@ -114,8 +114,9 @@ private void FindJumpTargets()
MarkInstructionBoundary();
ILOpcode opCode = (ILOpcode)ReadILByte();
+ if (opCode == ILOpcode.prefix1)
+ opCode = (ILOpcode)(0x100 + ReadILByte());
- again:
switch (opCode)
{
case ILOpcode.ldarg_s:
@@ -179,9 +180,6 @@ private void FindJumpTargets()
case ILOpcode.sizeof_:
SkipIL(4);
break;
- case ILOpcode.prefix1:
- opCode = (ILOpcode)(0x100 + ReadILByte());
- goto again;
case ILOpcode.br_s:
case ILOpcode.leave_s:
{
@@ -314,6 +312,8 @@ private void MarkBasicBlock(BasicBlock basicBlock)
}
}
+ partial void StartImportingInstruction(ILOpcode opcode);
+
private void ImportBasicBlock(BasicBlock basicBlock)
{
_currentBasicBlock = basicBlock;
@@ -324,8 +324,11 @@ private void ImportBasicBlock(BasicBlock basicBlock)
StartImportingInstruction();
ILOpcode opCode = (ILOpcode)ReadILByte();
+ if (opCode == ILOpcode.prefix1)
+ opCode = (ILOpcode)(0x100 + ReadILByte());
+
+ StartImportingInstruction(opCode);
- again:
switch (opCode)
{
case ILOpcode.nop:
@@ -814,9 +817,6 @@ private void ImportBasicBlock(BasicBlock basicBlock)
case ILOpcode.conv_u:
ImportConvert(WellKnownType.UIntPtr, false, true);
break;
- case ILOpcode.prefix1:
- opCode = (ILOpcode)(0x100 + ReadILByte());
- goto again;
case ILOpcode.arglist:
ImportArgList();
break;
diff --git a/src/coreclr/tools/Common/TypeSystem/IL/ILReader.cs b/src/coreclr/tools/Common/TypeSystem/IL/ILReader.cs
index 3e2b5cd7aa0251..8c462a8ac2281b 100644
--- a/src/coreclr/tools/Common/TypeSystem/IL/ILReader.cs
+++ b/src/coreclr/tools/Common/TypeSystem/IL/ILReader.cs
@@ -100,7 +100,7 @@ public ILOpcode ReadILOpcode()
return opcode;
}
- public ILOpcode PeekILOpcode()
+ public readonly ILOpcode PeekILOpcode()
{
ILOpcode opcode = (ILOpcode)_ilBytes[_currentOffset];
if (opcode == ILOpcode.prefix1)
@@ -113,6 +113,14 @@ public ILOpcode PeekILOpcode()
return opcode;
}
+ public readonly int PeekILToken()
+ {
+ if (!BinaryPrimitives.TryReadInt32LittleEndian(_ilBytes.Slice(_currentOffset), out int value))
+ ThrowHelper.ThrowInvalidProgramException();
+
+ return value;
+ }
+
public void Skip(ILOpcode opcode)
{
if (!opcode.IsValid())
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs
index 04db2b136d4bc1..db8bec02ff2b9c 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs
@@ -252,6 +252,8 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method)
if ((flags[offset] & OpcodeFlags.Mark) != 0)
continue;
+ TypeEqualityPatternAnalyzer typeEqualityAnalyzer = default;
+
ILReader reader = new ILReader(methodBytes, offset);
while (reader.HasNext)
{
@@ -259,6 +261,8 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method)
flags[offset] |= OpcodeFlags.Mark;
ILOpcode opcode = reader.ReadILOpcode();
+ typeEqualityAnalyzer.Advance(opcode, reader, method);
+
// Mark any applicable EH blocks
foreach (ILExceptionRegion ehRegion in ehRegions)
{
@@ -297,7 +301,8 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method)
|| opcode == ILOpcode.brtrue || opcode == ILOpcode.brtrue_s)
{
int destination = reader.ReadBranchDestination(opcode);
- if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int constant))
+ if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int constant)
+ && !TryExpandTypeEquality(typeEqualityAnalyzer, method, out constant))
{
// Can't get the constant - both branches are live.
offsetsToVisit.Push(destination);
@@ -681,13 +686,6 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[
constant = (int)substitution.Value;
return true;
}
- else if (method.IsIntrinsic && method.Name is "op_Inequality" or "op_Equality"
- && method.OwningType is MetadataType mdType
- && mdType.Name == "Type" && mdType.Namespace == "System" && mdType.Module == mdType.Context.SystemModule
- && TryExpandTypeEquality(methodIL, body, flags, currentOffset, method.Name, out constant))
- {
- return true;
- }
else if (method.IsIntrinsic && method.Name is "get_IsValueType" or "get_IsEnum"
&& method.OwningType is MetadataType mdt
&& mdt.Name == "Type" && mdt.Namespace == "System" && mdt.Module == mdt.Context.SystemModule
@@ -873,175 +871,63 @@ private static bool TryExpandTypeIs(MethodIL methodIL, byte[] body, OpcodeFlags[
return true;
}
- private bool TryExpandTypeEquality(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, string op, out int constant)
- {
- if (TryExpandTypeEquality_TokenToken(methodIL, body, flags, offset, out constant)
- || TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 1, expectGetType: false, out constant)
- || TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 2, expectGetType: false, out constant)
- || TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 3, expectGetType: false, out constant)
- || TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 1, expectGetType: true, out constant)
- || TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 2, expectGetType: true, out constant)
- || TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 3, expectGetType: true, out constant))
- {
- if (op == "op_Inequality")
- constant ^= 1;
-
- return true;
- }
-
- return false;
- }
-
- private static bool TryExpandTypeEquality_TokenToken(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, out int constant)
+ private bool TryExpandTypeEquality(in TypeEqualityPatternAnalyzer analyzer, MethodIL methodIL, out int constant)
{
- // We expect to see a sequence:
- // ldtoken Foo
- // call GetTypeFromHandle
- // ldtoken Bar
- // call GetTypeFromHandle
- // -> offset points here
constant = 0;
- const int SequenceLength = 20;
- if (offset < SequenceLength)
- return false;
-
- if ((flags[offset - SequenceLength] & OpcodeFlags.InstructionStart) == 0)
+ if (!analyzer.IsTypeEqualityBranch)
return false;
- ILReader reader = new ILReader(body, offset - SequenceLength);
-
- TypeDesc type1 = ReadLdToken(ref reader, methodIL, flags);
- if (type1 == null)
- return false;
-
- if (!ReadGetTypeFromHandle(ref reader, methodIL, flags))
- return false;
-
- TypeDesc type2 = ReadLdToken(ref reader, methodIL, flags);
- if (type2 == null)
- return false;
-
- if (!ReadGetTypeFromHandle(ref reader, methodIL, flags))
- return false;
-
- // No value in making this work for definitions
- if (type1.IsGenericDefinition || type2.IsGenericDefinition)
- return false;
-
- // Dataflow runs on top of uninstantiated IL and we can't answer some questions there.
- // Unfortunately this means dataflow will still see code that the rest of the system
- // might have optimized away. It should not be a problem in practice.
- if (type1.ContainsSignatureVariables() || type2.ContainsSignatureVariables())
- return false;
-
- bool? equality = TypeExtensions.CompareTypesForEquality(type1, type2);
- if (!equality.HasValue)
- return false;
-
- constant = equality.Value ? 1 : 0;
-
- return true;
- }
-
- private bool TryExpandTypeEquality_TokenOther(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, int ldInstructionSize, bool expectGetType, out int constant)
- {
- // We expect to see a sequence:
- // ldtoken Foo
- // call GetTypeFromHandle
- // ldloc.X/ldloc_s X/ldarg.X/ldarg_s X
- // [optional] call Object.GetType
- // -> offset points here
- //
- // The ldtoken part can potentially be in the second argument position
-
- constant = 0;
- int sequenceLength = 5 + 5 + ldInstructionSize + (expectGetType ? 5 : 0);
- if (offset < sequenceLength)
- return false;
-
- if ((flags[offset - sequenceLength] & OpcodeFlags.InstructionStart) == 0)
- return false;
-
- ILReader reader = new ILReader(body, offset - sequenceLength);
+ if (analyzer.IsTwoTokens)
+ {
+ var type1 = (TypeDesc)methodIL.GetObject(analyzer.Token1);
+ var type2 = (TypeDesc)methodIL.GetObject(analyzer.Token2);
- TypeDesc knownType = null;
+ // No value in making this work for definitions
+ if (type1.IsGenericDefinition || type2.IsGenericDefinition)
+ return false;
- // Is the ldtoken in the first position?
- if (reader.PeekILOpcode() == ILOpcode.ldtoken)
- {
- knownType = ReadLdToken(ref reader, methodIL, flags);
- if (knownType == null)
+ // Dataflow runs on top of uninstantiated IL and we can't answer some questions there.
+ // Unfortunately this means dataflow will still see code that the rest of the system
+ // might have optimized away. It should not be a problem in practice.
+ if (type1.ContainsSignatureVariables() || type2.ContainsSignatureVariables())
return false;
- if (!ReadGetTypeFromHandle(ref reader, methodIL, flags))
+ bool? equality = TypeExtensions.CompareTypesForEquality(type1, type2);
+ if (!equality.HasValue)
return false;
- }
- ILOpcode opcode = reader.ReadILOpcode();
- if (ldInstructionSize == 1 && opcode is (>= ILOpcode.ldloc_0 and <= ILOpcode.ldloc_3) or (>= ILOpcode.ldarg_0 and <= ILOpcode.ldarg_3))
- {
- // Nothing to read
- }
- else if (ldInstructionSize == 2 && opcode is ILOpcode.ldloc_s or ILOpcode.ldarg_s)
- {
- reader.ReadILByte();
- }
- else if (ldInstructionSize == 3 && opcode is ILOpcode.ldloc or ILOpcode.ldarg)
- {
- reader.ReadILUInt16();
+ constant = equality.Value ? 1 : 0;
}
else
{
- return false;
- }
-
- if ((flags[reader.Offset] & OpcodeFlags.BasicBlockStart) != 0)
- return false;
+ var knownType = (TypeDesc)methodIL.GetObject(analyzer.Token1);
- if (expectGetType)
- {
- if (reader.ReadILOpcode() is not ILOpcode.callvirt and not ILOpcode.call)
+ // No value in making this work for definitions
+ if (knownType.IsGenericDefinition)
return false;
- // We don't actually mind if this is not Object.GetType
- reader.ReadILToken();
-
- if ((flags[reader.Offset] & OpcodeFlags.BasicBlockStart) != 0)
+ // Dataflow runs on top of uninstantiated IL and we can't answer some questions there.
+ // Unfortunately this means dataflow will still see code that the rest of the system
+ // might have optimized away. It should not be a problem in practice.
+ if (knownType.ContainsSignatureVariables())
return false;
- }
- // If the ldtoken wasn't in the first position, it must be in the other
- if (knownType == null)
- {
- knownType = ReadLdToken(ref reader, methodIL, flags);
- if (knownType == null)
+ if (knownType.IsCanonicalDefinitionType(CanonicalFormKind.Any))
return false;
- if (!ReadGetTypeFromHandle(ref reader, methodIL, flags))
+ // We don't track types without a constructed MethodTable very well.
+ if (!ConstructedEETypeNode.CreationAllowed(knownType))
return false;
- }
- // No value in making this work for definitions
- if (knownType.IsGenericDefinition)
- return false;
-
- // Dataflow runs on top of uninstantiated IL and we can't answer some questions there.
- // Unfortunately this means dataflow will still see code that the rest of the system
- // might have optimized away. It should not be a problem in practice.
- if (knownType.ContainsSignatureVariables())
- return false;
-
- if (knownType.IsCanonicalDefinitionType(CanonicalFormKind.Any))
- return false;
+ if (_devirtualizationManager.CanReferenceConstructedTypeOrCanonicalFormOfType(knownType.NormalizeInstantiation()))
+ return false;
- // We don't track types without a constructed MethodTable very well.
- if (!ConstructedEETypeNode.CreationAllowed(knownType))
- return false;
+ constant = 0;
+ }
- if (_devirtualizationManager.CanReferenceConstructedTypeOrCanonicalFormOfType(knownType.NormalizeInstantiation()))
- return false;
+ if (analyzer.IsInequality)
+ constant ^= 1;
- constant = 0;
return true;
}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs
index b124fe824333c2..9952aedd18d409 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs
@@ -32,6 +32,8 @@ internal partial class ILImporter
private readonly byte[] _ilBytes;
+ private TypeEqualityPatternAnalyzer _typeEqualityPatternAnalyzer;
+
private sealed class BasicBlock
{
// Common fields
@@ -227,6 +229,13 @@ private void StartImportingBasicBlock(BasicBlock basicBlock)
}
}
}
+
+ _typeEqualityPatternAnalyzer = default;
+ }
+
+ partial void StartImportingInstruction(ILOpcode opcode)
+ {
+ _typeEqualityPatternAnalyzer.Advance(opcode, new ILReader(_ilBytes, _currentOffset), _methodIL);
}
private void EndImportingInstruction()
@@ -867,57 +876,25 @@ private void ImportLdToken(int token)
if (obj is TypeDesc type)
{
- // If this is a ldtoken Type / Type.GetTypeFromHandle sequence, we need one more helper.
// We might also be able to optimize this a little if this is a ldtoken/GetTypeFromHandle/Equals sequence.
bool isTypeEquals = false;
- BasicBlock nextBasicBlock = _basicBlocks[_currentOffset];
- if (nextBasicBlock == null)
+ TypeEqualityPatternAnalyzer analyzer = _typeEqualityPatternAnalyzer;
+ ILReader reader = new ILReader(_ilBytes, _currentOffset);
+ while (!analyzer.IsDefault)
{
- if ((ILOpcode)_ilBytes[_currentOffset] == ILOpcode.call)
- {
- int methodToken = ReadILTokenAt(_currentOffset + 1);
- var method = (MethodDesc)_methodIL.GetObject(methodToken);
- if (IsTypeGetTypeFromHandle(method))
- {
- // Codegen will swap this one for GetRuntimeTypeHandle when optimizing
- _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeType), "ldtoken");
+ ILOpcode opcode = reader.ReadILOpcode();
+ analyzer.Advance(opcode, reader, _methodIL);
+ reader.Skip(opcode);
- // Is the next instruction a call to Type::Equals?
- nextBasicBlock = _basicBlocks[_currentOffset + 5];
- if (nextBasicBlock == null)
- {
- // We expect pattern:
- //
- // ldtoken Foo
- // call GetTypeFromHandle
- // ldtoken Bar
- // call GetTypeFromHandle
- // call Equals
- //
- // We check for both ldtoken cases
- if ((ILOpcode)_ilBytes[_currentOffset + 5] == ILOpcode.call)
- {
- methodToken = ReadILTokenAt(_currentOffset + 6);
- method = (MethodDesc)_methodIL.GetObject(methodToken);
- isTypeEquals = IsTypeEquals(method);
- }
- else if ((ILOpcode)_ilBytes[_currentOffset + 5] == ILOpcode.ldtoken
- && _basicBlocks[_currentOffset + 10] == null
- && (ILOpcode)_ilBytes[_currentOffset + 10] == ILOpcode.call
- && methodToken == ReadILTokenAt(_currentOffset + 11)
- && _basicBlocks[_currentOffset + 15] == null
- && (ILOpcode)_ilBytes[_currentOffset + 15] == ILOpcode.call)
- {
- methodToken = ReadILTokenAt(_currentOffset + 16);
- method = (MethodDesc)_methodIL.GetObject(methodToken);
- isTypeEquals = IsTypeEquals(method);
- }
- }
- }
+ if (analyzer.IsTypeEqualityCheck)
+ {
+ isTypeEquals = true;
+ break;
}
}
_dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeTypeHandle), "ldtoken");
+ _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeType), "ldtoken");
ISymbolNode reference;
if (type.IsRuntimeDeterminedSubtype)
@@ -1348,20 +1325,6 @@ private static bool IsTypeGetTypeFromHandle(MethodDesc method)
return false;
}
- private static bool IsTypeEquals(MethodDesc method)
- {
- if (method.IsIntrinsic && method.Name == "op_Equality")
- {
- MetadataType owningType = method.OwningType as MetadataType;
- if (owningType != null)
- {
- return owningType.Name == "Type" && owningType.Namespace == "System";
- }
- }
-
- return false;
- }
-
private static bool IsActivatorDefaultConstructorOf(MethodDesc method)
{
if (method.IsIntrinsic && method.Name == "DefaultConstructorOf" && method.Instantiation.Length == 1)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs
new file mode 100644
index 00000000000000..e260ea338ed25f
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs
@@ -0,0 +1,195 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+using Internal.IL;
+using Internal.TypeSystem;
+
+namespace ILCompiler
+{
+ ///
+ /// Simple state machine to analyze IL sequences that represent runtime type equality checks.
+ ///
+ internal struct TypeEqualityPatternAnalyzer
+ {
+ // Captures following sequence:
+ //
+ // ldtoken Foo
+ // call GetTypeFromHandle
+ // One Of:
+ // ldtoken Bar
+ // call GetTypeFromHandle
+ // Or:
+ // ldarg/ldloc X
+ // Optional:
+ // call object.GetType()
+ // Or:
+ // (nothing)
+ // End One Of
+ // call op_Equality/op_Inequality
+ // Optional:
+ // stloc X
+ // ldloc X
+ // brtrue/brfalse
+
+ private enum State : byte
+ {
+ LdToken = 1,
+ TypeOf,
+
+ TypeOf_LdToken,
+ TypeOf_TypeOf,
+ TypeOf_PushedOne,
+
+ TypeEqualityCheck,
+ TypeEqualityCheck_StlocLdloc,
+
+ Branch,
+ }
+
+ private enum Flags : byte
+ {
+ TwoTokens = 1,
+ Inequality = 2,
+ }
+
+ private State _state;
+ private Flags _flags;
+ private int _token1;
+ private int _token2;
+
+ public readonly int Token1 => IsTypeEqualityBranch ? _token1 : throw new UnreachableException();
+ public readonly int Token2 => IsTwoTokens ? _token2 : throw new UnreachableException();
+
+ public readonly bool IsDefault => _state == default;
+ public readonly bool IsTypeEqualityCheck => _state is State.TypeEqualityCheck;
+ public readonly bool IsTypeEqualityBranch => _state is State.Branch;
+ public readonly bool IsTwoTokens => (_flags & Flags.TwoTokens) != 0;
+ public readonly bool IsInequality => (_flags & Flags.Inequality) != 0;
+
+ public void Advance(ILOpcode opcode, in ILReader reader, MethodIL methodIL)
+ {
+ switch (_state)
+ {
+ case 0:
+ if (opcode == ILOpcode.ldtoken)
+ (_state, _token1) = (State.LdToken, reader.PeekILToken());
+ return;
+ case State.LdToken:
+ if (IsTypeGetTypeFromHandle(opcode, reader, methodIL))
+ _state = State.TypeOf;
+ else
+ break;
+ return;
+ case State.TypeOf:
+ if (opcode == ILOpcode.ldtoken)
+ (_state, _token2) = (State.TypeOf_LdToken, reader.PeekILToken());
+ else if (IsArgumentOrLocalLoad(opcode))
+ _state = State.TypeOf_PushedOne;
+ else if (IsTypeEquals(opcode, reader, methodIL))
+ _state = State.TypeEqualityCheck;
+ else if (IsTypeInequals(opcode, reader, methodIL))
+ (_state, _flags) = (State.TypeEqualityCheck, _flags | Flags.Inequality);
+ else
+ break;
+ return;
+ case State.TypeOf_LdToken:
+ if (IsTypeGetTypeFromHandle(opcode, reader, methodIL))
+ _state = State.TypeOf_TypeOf;
+ else
+ break;
+ return;
+ case State.TypeOf_PushedOne:
+ if (IsObjectGetType(opcode, reader, methodIL))
+ {
+ // Nothing, state stays the same
+ }
+ else if (IsTypeEquals(opcode, reader, methodIL))
+ _state = State.TypeEqualityCheck;
+ else if (IsTypeInequals(opcode, reader, methodIL))
+ (_state, _flags) = (State.TypeEqualityCheck, _flags | Flags.Inequality);
+ else
+ break;
+ return;
+ case State.TypeOf_TypeOf:
+ if (IsTypeEquals(opcode, reader, methodIL))
+ (_state, _flags) = (State.TypeEqualityCheck, _flags | Flags.TwoTokens);
+ else if (IsTypeInequals(opcode, reader, methodIL))
+ (_state, _flags) = (State.TypeEqualityCheck, _flags | Flags.TwoTokens | Flags.Inequality);
+ else
+ {
+ _token1 = _token2;
+ goto case State.TypeOf;
+ }
+ return;
+ case State.TypeEqualityCheck:
+ if (opcode is ILOpcode.brfalse or ILOpcode.brfalse_s or ILOpcode.brtrue or ILOpcode.brtrue_s)
+ _state = State.Branch;
+ else if (IsStlocLdlocSequence(opcode, reader))
+ _state = State.TypeEqualityCheck_StlocLdloc;
+ else
+ break;
+ return;
+ case State.TypeEqualityCheck_StlocLdloc:
+ if (opcode == ILOpcode.ldloc || opcode == ILOpcode.ldloc_s || (opcode >= ILOpcode.ldloc_0 && opcode <= ILOpcode.ldloc_3))
+ _state = State.TypeEqualityCheck;
+ else
+ throw new UnreachableException();
+ return;
+ default:
+ throw new UnreachableException();
+ }
+
+ static bool IsTypeGetTypeFromHandle(ILOpcode opcode, in ILReader reader, MethodIL methodIL)
+ => opcode == ILOpcode.call && methodIL.GetObject(reader.PeekILToken()) is MethodDesc method
+ && method.IsIntrinsic && method.Name == "GetTypeFromHandle"
+ && method.OwningType is MetadataType { Name: "Type", Namespace: "System" };
+
+ static bool IsTypeEquals(ILOpcode opcode, in ILReader reader, MethodIL methodIL)
+ => opcode == ILOpcode.call && methodIL.GetObject(reader.PeekILToken()) is MethodDesc method
+ && method.IsIntrinsic && method.Name is "op_Equality"
+ && method.OwningType is MetadataType { Name: "Type", Namespace: "System" };
+
+ static bool IsTypeInequals(ILOpcode opcode, in ILReader reader, MethodIL methodIL)
+ => opcode == ILOpcode.call && methodIL.GetObject(reader.PeekILToken()) is MethodDesc method
+ && method.IsIntrinsic && method.Name is "op_Inequality"
+ && method.OwningType is MetadataType { Name: "Type", Namespace: "System" };
+
+ static bool IsObjectGetType(ILOpcode opcode, in ILReader reader, MethodIL methodIL)
+ => opcode is ILOpcode.call or ILOpcode.callvirt && methodIL.GetObject(reader.PeekILToken()) is MethodDesc method
+ && method.IsIntrinsic && method.Name is "GetType" && method.OwningType.IsObject;
+
+ static bool IsArgumentOrLocalLoad(ILOpcode opcode)
+ => opcode is (>= ILOpcode.ldloc_0 and <= ILOpcode.ldloc_3) or (>= ILOpcode.ldarg_0 and <= ILOpcode.ldarg_3);
+
+ static bool IsStlocLdlocSequence(ILOpcode opcode, in ILReader reader)
+ {
+ if (opcode == ILOpcode.stloc || opcode == ILOpcode.stloc_s || (opcode >= ILOpcode.stloc_0 && opcode <= ILOpcode.stloc_3))
+ {
+ ILReader nestedReader = reader;
+ int locIndex = opcode switch
+ {
+ ILOpcode.stloc => nestedReader.ReadILUInt16(),
+ ILOpcode.stloc_s => nestedReader.ReadILByte(),
+ _ => opcode - ILOpcode.stloc_0,
+ };
+ ILOpcode otherOpcode = nestedReader.ReadILOpcode();
+ return (otherOpcode == ILOpcode.ldloc || otherOpcode == ILOpcode.ldloc_s || (otherOpcode >= ILOpcode.ldloc_0 && otherOpcode <= ILOpcode.ldloc_3))
+ && otherOpcode switch
+ {
+ ILOpcode.ldloc => nestedReader.ReadILUInt16(),
+ ILOpcode.ldloc_s => nestedReader.ReadILByte(),
+ _ => otherOpcode - ILOpcode.ldloc_0,
+ } == locIndex;
+ }
+ return false;
+ }
+
+ _state = default;
+ _flags = default;
+
+ Advance(opcode, reader, methodIL);
+ }
+ }
+}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
index 51d70dc7a34479..4e9ac41dc1df28 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
@@ -671,6 +671,7 @@
+
diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs
index 9cdeab80e8e36f..cae60553668734 100644
--- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs
+++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs
@@ -350,6 +350,8 @@ class Never2 { }
class Canary2 { }
class Never3 { }
class Canary3 { }
+ class Never4 { }
+ class Canary4 { }
class Maybe1 { }
@@ -405,6 +407,21 @@ static void RunCheck(object o)
ThrowIfPresentWithUsableMethodTable(typeof(TestTypeEquals), nameof(Canary3));
}
+ {
+
+ RunCheck(GetTheObject());
+
+ static void RunCheck(object o)
+ {
+ if (typeof(Never4) == o.GetType())
+ {
+ s_sink = new Canary4();
+ }
+ }
+
+ ThrowIfPresentWithUsableMethodTable(typeof(TestTypeEquals), nameof(Canary4));
+ }
+
{
RunCheck(GetThePointerType());