From 9f2693914db423bd55d562a1afa5ccb9fde54086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 1 Jul 2024 16:35:53 +0900 Subject: [PATCH] Extract shared IL pattern analysis to a class (#103701) This fixes the problem discussed at https://github.com/dotnet/runtime/pull/102248#issuecomment-2118604222. Now we call into the same code from both substitutions and scanner. --- .../src/System/RuntimeTypeHandle.cs | 5 + .../tools/Common/TypeSystem/IL/ILImporter.cs | 16 +- .../tools/Common/TypeSystem/IL/ILReader.cs | 10 +- .../Compiler/SubstitutedILProvider.cs | 190 ++++------------- .../IL/ILImporter.Scanner.cs | 77 ++----- .../IL/TypeEqualityPatternAnalyzer.cs | 195 ++++++++++++++++++ .../ILCompiler.Compiler.csproj | 1 + .../TrimmingBehaviors/DeadCodeElimination.cs | 17 ++ 8 files changed, 293 insertions(+), 218 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs diff --git a/src/coreclr/nativeaot/Test.CoreLib/src/System/RuntimeTypeHandle.cs b/src/coreclr/nativeaot/Test.CoreLib/src/System/RuntimeTypeHandle.cs index f0401dc09e07a..9adaed61c4cc8 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 e4c4ddb42d318..26bc612b8262d 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 3e2b5cd7aa025..8c462a8ac2281 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 04db2b136d4bc..db8bec02ff2b9 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 b124fe824333c..9952aedd18d40 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 0000000000000..e260ea338ed25 --- /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 3e26a51657e0f..c884192f582d4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -668,6 +668,7 @@ + diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs index 9cdeab80e8e36..cae6055366873 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());