From a96f6da94733c4c317bb491a2667bb3d12e90ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 19 Jun 2024 16:23:58 +0200 Subject: [PATCH 01/13] Extract shared IL pattern analysis to a class 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. --- .../Compiler/SubstitutedILProvider.cs | 182 +++--------- .../IL/ILImporter.Scanner.cs | 81 ++---- .../IL/ILPatternAnalyzer.cs | 274 ++++++++++++++++++ .../ILCompiler.Compiler.csproj | 1 + .../TrimmingBehaviors/DeadCodeElimination.cs | 17 ++ 5 files changed, 357 insertions(+), 198 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs index 04db2b136d4bc1..bb1ee11683643b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs @@ -235,6 +235,8 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method) } } + var analyzer = new ILPatternAnalyzer(new ILPatternAnalyzerState(flags), method, methodBytes); + bool hasGetResourceStringCall = false; // Mark all reachable basic blocks @@ -297,7 +299,7 @@ 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(analyzer, offset, 0, out int constant)) { // Can't get the constant - both branches are live. offsetsToVisit.Push(destination); @@ -323,8 +325,8 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method) || opcode == ILOpcode.bne_un || opcode == ILOpcode.bne_un_s) { int destination = reader.ReadBranchDestination(opcode); - if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int left) - || !TryGetConstantArgument(method, methodBytes, flags, offset, 1, out int right)) + if (!TryGetConstantArgument(analyzer, offset, 0, out int left) + || !TryGetConstantArgument(analyzer, offset, 1, out int right)) { // Can't get the constant - both branches are live. offsetsToVisit.Push(destination); @@ -654,9 +656,9 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method) return new SubstitutedMethodIL(method.GetMethodILDefinition(), newBody, newEHRegions.ToArray(), debugInfo, newStrings.ToArray()); } - private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, int argIndex, out int constant) + private bool TryGetConstantArgument(ILPatternAnalyzer analyzer, int offset, int argIndex, out int constant) { - if ((flags[offset] & OpcodeFlags.BasicBlockStart) != 0) + if (analyzer.State.IsBasicBlockStart(offset)) { constant = 0; return false; @@ -664,14 +666,14 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[ for (int currentOffset = offset - 1; currentOffset >= 0; currentOffset--) { - if ((flags[currentOffset] & OpcodeFlags.InstructionStart) == 0) + if (!analyzer.State.IsInstructionStart(currentOffset)) continue; - ILReader reader = new ILReader(body, currentOffset); + ILReader reader = new ILReader(analyzer.ILBytes, currentOffset); ILOpcode opcode = reader.ReadILOpcode(); if (opcode == ILOpcode.call || opcode == ILOpcode.callvirt) { - MethodDesc method = (MethodDesc)methodIL.GetObject(reader.ReadILToken()); + MethodDesc method = (MethodDesc)analyzer.Method.GetObject(reader.ReadILToken()); if (argIndex == 0) { BodySubstitution substitution = _substitutionProvider.GetSubstitution(method); @@ -684,14 +686,14 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[ 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)) + && TryExpandTypeEquality(analyzer, 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 - && TryExpandTypeIs(methodIL, body, flags, currentOffset, method.Name, out constant)) + && TryExpandTypeIs(analyzer, currentOffset, method.Name, out constant)) { return true; } @@ -712,7 +714,7 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[ } else if (opcode == ILOpcode.ldsfld) { - FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken()); + FieldDesc field = (FieldDesc)analyzer.Method.GetObject(reader.ReadILToken()); if (argIndex == 0) { object substitution = _substitutionProvider.GetSubstitution(field); @@ -762,7 +764,7 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[ } else if ((opcode == ILOpcode.ldloc || opcode == ILOpcode.ldloc_s || (opcode >= ILOpcode.ldloc_0 && opcode <= ILOpcode.ldloc_3)) && - ((flags[currentOffset] & OpcodeFlags.BasicBlockStart) == 0)) + !analyzer.State.IsBasicBlockStart(currentOffset)) { // Paired stloc/ldloc that the C# compiler generates in debug code? int locIndex = opcode switch @@ -774,10 +776,10 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[ for (int potentialStlocOffset = currentOffset - 1; potentialStlocOffset >= 0; potentialStlocOffset--) { - if ((flags[potentialStlocOffset] & OpcodeFlags.InstructionStart) == 0) + if (!analyzer.State.IsInstructionStart(potentialStlocOffset)) continue; - ILReader nestedReader = new ILReader(body, potentialStlocOffset); + ILReader nestedReader = new ILReader(analyzer.ILBytes, potentialStlocOffset); ILOpcode otherOpcode = nestedReader.ReadILOpcode(); if ((otherOpcode == ILOpcode.stloc || otherOpcode == ILOpcode.stloc_s || (otherOpcode >= ILOpcode.stloc_0 && otherOpcode <= ILOpcode.stloc_3)) @@ -803,8 +805,8 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[ { if (argIndex == 0) { - if (!TryGetConstantArgument(methodIL, body, flags, currentOffset, 0, out int left) - || !TryGetConstantArgument(methodIL, body, flags, currentOffset, 1, out int right)) + if (!TryGetConstantArgument(analyzer, currentOffset, 0, out int left) + || !TryGetConstantArgument(analyzer, currentOffset, 1, out int right)) { constant = 0; return false; @@ -826,7 +828,7 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[ return false; } - if ((flags[currentOffset] & OpcodeFlags.BasicBlockStart) != 0) + if (analyzer.State.IsBasicBlockStart(currentOffset)) break; } @@ -834,7 +836,7 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[ return false; } - private static bool TryExpandTypeIs(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, string name, out int constant) + private static bool TryExpandTypeIs(ILPatternAnalyzer analyzer, int offset, string name, out int constant) { // We expect to see a sequence: // ldtoken Foo @@ -845,16 +847,16 @@ private static bool TryExpandTypeIs(MethodIL methodIL, byte[] body, OpcodeFlags[ if (offset < SequenceLength) return false; - if ((flags[offset - SequenceLength] & OpcodeFlags.InstructionStart) == 0) + if (!analyzer.State.IsInstructionStart(offset - SequenceLength)) return false; - ILReader reader = new ILReader(body, offset - SequenceLength); + ILReader reader = new ILReader(analyzer.ILBytes, offset - SequenceLength); - TypeDesc type = ReadLdToken(ref reader, methodIL, flags); + TypeDesc type = ReadLdToken(ref reader, analyzer); if (type == null) return false; - if (!ReadGetTypeFromHandle(ref reader, methodIL, flags)) + if (!ReadGetTypeFromHandle(ref reader, analyzer)) return false; // Dataflow runs on top of uninstantiated IL and we can't answer some questions there. @@ -873,15 +875,10 @@ 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) + private bool TryExpandTypeEquality(ILPatternAnalyzer analyzer, 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 (TryExpandTypeEquality_TokenToken(analyzer, offset, out constant) + || TryExpandTypeEquality_TokenOther(analyzer, offset, out constant)) { if (op == "op_Inequality") constant ^= 1; @@ -892,36 +889,11 @@ private bool TryExpandTypeEquality(MethodIL methodIL, byte[] body, OpcodeFlags[] return false; } - private static bool TryExpandTypeEquality_TokenToken(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, out int constant) + private static bool TryExpandTypeEquality_TokenToken(ILPatternAnalyzer analyzer, int offset, 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) - 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)) + if (!analyzer.TryAnalyzeTypeEquality_TokenToken(offset, offsetIsAtTypeEquals: true, out TypeDesc type1, out TypeDesc type2)) return false; // No value in making this work for definitions @@ -943,84 +915,13 @@ private static bool TryExpandTypeEquality_TokenToken(MethodIL methodIL, byte[] b return true; } - private bool TryExpandTypeEquality_TokenOther(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, int ldInstructionSize, bool expectGetType, out int constant) + private bool TryExpandTypeEquality_TokenOther(ILPatternAnalyzer analyzer, int offset, 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); - - TypeDesc knownType = null; - - // Is the ldtoken in the first position? - if (reader.PeekILOpcode() == ILOpcode.ldtoken) - { - knownType = ReadLdToken(ref reader, methodIL, flags); - if (knownType == null) - return false; - if (!ReadGetTypeFromHandle(ref reader, methodIL, flags)) - 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(); - } - else - { - return false; - } - - if ((flags[reader.Offset] & OpcodeFlags.BasicBlockStart) != 0) + if (!analyzer.TryAnalyzeTypeEquality_TokenOther(offset, offsetIsAtTypeEquals: true, out TypeDesc knownType)) return false; - if (expectGetType) - { - if (reader.ReadILOpcode() is not ILOpcode.callvirt and not ILOpcode.call) - return false; - - // We don't actually mind if this is not Object.GetType - reader.ReadILToken(); - - if ((flags[reader.Offset] & OpcodeFlags.BasicBlockStart) != 0) - 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) - return false; - - if (!ReadGetTypeFromHandle(ref reader, methodIL, flags)) - return false; - } - // No value in making this work for definitions if (knownType.IsGenericDefinition) return false; @@ -1045,32 +946,32 @@ private bool TryExpandTypeEquality_TokenOther(MethodIL methodIL, byte[] body, Op return true; } - private static TypeDesc ReadLdToken(ref ILReader reader, MethodIL methodIL, OpcodeFlags[] flags) + private static TypeDesc ReadLdToken(ref ILReader reader, ILPatternAnalyzer analyzer) { ILOpcode opcode = reader.ReadILOpcode(); if (opcode != ILOpcode.ldtoken) return null; - TypeDesc t = (TypeDesc)methodIL.GetObject(reader.ReadILToken()); + TypeDesc t = (TypeDesc)analyzer.Method.GetObject(reader.ReadILToken()); - if ((flags[reader.Offset] & OpcodeFlags.BasicBlockStart) != 0) + if (analyzer.State.IsBasicBlockStart(reader.Offset)) return null; return t; } - private static bool ReadGetTypeFromHandle(ref ILReader reader, MethodIL methodIL, OpcodeFlags[] flags) + private static bool ReadGetTypeFromHandle(ref ILReader reader, ILPatternAnalyzer analyzer) { ILOpcode opcode = reader.ReadILOpcode(); if (opcode != ILOpcode.call) return false; - MethodDesc method = (MethodDesc)methodIL.GetObject(reader.ReadILToken()); + MethodDesc method = (MethodDesc)analyzer.Method.GetObject(reader.ReadILToken()); if (!method.IsIntrinsic || method.Name != "GetTypeFromHandle") return false; - if ((flags[reader.Offset] & OpcodeFlags.BasicBlockStart) != 0) + if (analyzer.State.IsBasicBlockStart(reader.Offset)) return false; return true; @@ -1135,6 +1036,15 @@ public SubstitutedDebugInformation(MethodDebugInformation originalDebugInformati public override IEnumerable GetSequencePoints() => _sequencePoints; } + private struct ILPatternAnalyzerState : ILPatternAnalyzerTraits + { + private readonly OpcodeFlags[] _flags; + public ILPatternAnalyzerState(OpcodeFlags[] flags) => _flags = flags; + + public bool IsBasicBlockStart(int offset) => (_flags[offset] & OpcodeFlags.BasicBlockStart) != 0; + public bool IsInstructionStart(int offset) => (_flags[offset] & OpcodeFlags.InstructionStart) != 0; + } + private const int TokenTypeString = 0x70; // CorTokenType for strings } } 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..860c2a88e9e6da 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 ILPatternAnalyzer _patternAnalyzer; + private sealed class BasicBlock { // Common fields @@ -51,6 +53,16 @@ public enum ImportState : byte public bool HandlerStart; } + private struct ILPatternAnalyzerState : ILPatternAnalyzerTraits + { + public readonly BasicBlock[] BasicBlocks; + + public ILPatternAnalyzerState(BasicBlock[] basicBlocks) => BasicBlocks = basicBlocks; + + public bool IsBasicBlockStart(int offset) => BasicBlocks[offset] != null; + public bool IsInstructionStart(int offset) => throw new System.NotImplementedException(); + } + private bool _isReadOnly; private TypeDesc _constrained; @@ -168,6 +180,9 @@ public DependencyList Import() } FindBasicBlocks(); + + _patternAnalyzer = new ILPatternAnalyzer(new ILPatternAnalyzerState(_basicBlocks), _methodIL, _ilBytes); + ImportBasicBlocks(); CodeBasedDependencyAlgorithm.AddDependenciesDueToMethodCodePresence(ref _dependencies, _factory, _canonMethod, _canonMethodIL); @@ -867,57 +882,13 @@ 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) - { - 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"); + int offsetOfLdtoken = _currentOffset - 5; - // 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); - } - } - } - } - } + // We might also be able to optimize this a little if this is a ldtoken/GetTypeFromHandle/Equals sequence. + bool isTypeEquals = _patternAnalyzer.IsLdTokenConsumedByTypeEqualityCheck(offsetOfLdtoken); _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeTypeHandle), "ldtoken"); + _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeType), "ldtoken"); ISymbolNode reference; if (type.IsRuntimeDeterminedSubtype) @@ -1348,20 +1319,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/ILPatternAnalyzer.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs new file mode 100644 index 00000000000000..6c7a45fa80e58d --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs @@ -0,0 +1,274 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.IL; +using Internal.TypeSystem; + +using Debug = System.Diagnostics.Debug; + +namespace ILCompiler +{ + internal class ILPatternAnalyzer where T : ILPatternAnalyzerTraits + { + public readonly MethodIL Method; + public T State; + public readonly byte[] ILBytes; + + public ILPatternAnalyzer(T state, MethodIL method, byte[] methodBytes) + { + Method = method; + State = state; + ILBytes = methodBytes; + } + + public bool IsLdTokenConsumedByTypeEqualityCheck(int offset) + { + // 'offset' points to an ldtoken instruction. + // + // This could be ldtoken in the first parameter position of a type equality check + // or in the second parameter position. Check for the second parameter position + // first since it's the cheapest. + + var reader = new ILReader(ILBytes, offset); + + // If it's not a typeof() it cannot be any of the patterns below, bail + if (!TryReadTypeOf(ref reader, out _)) + return false; + + // If it's typeof followed by a call to op_Equality/op_Inequality, we're done. + if (TryReadTypeEqualityOrInequalityOperation(ref reader)) + return true; + + // This might still be ldtoken associated with the first parameter of the equality + // check. This checks for a couple patterns that we know how to analyze such as: + // typeof(X) == typeof(Y) + // typeof(X) == local + // typeof(X) == local.GetType + if (TryAnalyzeTypeEquality_TokenToken(offset, offsetIsAtTypeEquals: false, out int _, out _) + || TryAnalyzeTypeEquality_TokenOther(offset, offsetIsAtTypeEquals: false, out int _)) + { + return true; + } + + return false; + } + + public bool TryAnalyzeTypeEquality_TokenToken(int offset, bool offsetIsAtTypeEquals, out TypeDesc type1, out TypeDesc type2) + { + if (TryAnalyzeTypeEquality_TokenToken(offset, offsetIsAtTypeEquals, out int type1tok, out int type2tok)) + { + type1 = (TypeDesc)Method.GetObject(type1tok); + type2 = (TypeDesc)Method.GetObject(type2tok); + return true; + } + type1 = type2 = null; + return false; + } + + public bool TryAnalyzeTypeEquality_TokenToken(int offset, bool offsetIsAtTypeEquals, out int type1, out int type2) + { + // We expect to see a sequence: + // -> offset may point to here + // ldtoken Foo + // call GetTypeFromHandle + // ldtoken Bar + // call GetTypeFromHandle + // -> or offset may point to here + + type1 = 0; + type2 = 0; + + if (offsetIsAtTypeEquals) + { + const int SequenceLength = 20; + if (offset < SequenceLength) + return false; + + offset -= SequenceLength; + + if (!State.IsInstructionStart(offset)) + return false; + } + + ILReader reader = new ILReader(ILBytes, offset); + + if (!TryReadTypeOf(ref reader, out type1)) + return false; + + if (!TryReadTypeOf(ref reader, out type2)) + return false; + + if (!offsetIsAtTypeEquals) + { + if (!TryReadTypeEqualityOrInequalityOperation(ref reader)) + return false; + } + else + { + Debug.Assert(TryReadTypeEqualityOrInequalityOperation(ref reader)); + } + + return true; + } + + public bool TryAnalyzeTypeEquality_TokenOther(int offset, bool offsetIsAtTypeEquals, out TypeDesc type) + { + if (TryAnalyzeTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, out int typeTok)) + { + type = (TypeDesc)Method.GetObject(typeTok); + return true; + } + type = null; + return false; + } + + public bool TryAnalyzeTypeEquality_TokenOther(int offset, bool offsetIsAtTypeEquals, out int type) + { + return TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 1, expectGetType: false, out type) + || TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 2, expectGetType: false, out type) + || TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 3, expectGetType: false, out type) + || TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 1, expectGetType: true, out type) + || TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 2, expectGetType: true, out type) + || TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 3, expectGetType: true, out type); + } + + private bool TryExpandTypeEquality_TokenOther(int offset, bool offsetIsAtTypeEquals, int ldInstructionSize, bool expectGetType, out int type) + { + // We expect to see a sequence: + // -> offset may point to here + // ldtoken Foo + // call GetTypeFromHandle + // ldloc.X/ldloc_s X/ldarg.X/ldarg_s X + // [optional] call Object.GetType + // -> or offset may point to here + // + // The ldtoken part can potentially be in the second argument position + + type = 0; + + if (offsetIsAtTypeEquals) + { + int sequenceLength = 5 + 5 + ldInstructionSize + (expectGetType ? 5 : 0); + if (offset < sequenceLength) + return false; + + offset -= sequenceLength; + + if (!State.IsInstructionStart(offset)) + return false; + } + + ILReader reader = new ILReader(ILBytes, offset); + + // Is the ldtoken in the first position? + if (reader.PeekILOpcode() == ILOpcode.ldtoken) + { + if (!TryReadTypeOf(ref reader, out type)) + 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(); + } + else + { + return false; + } + + if (State.IsBasicBlockStart(reader.Offset)) + return false; + + if (expectGetType) + { + if (reader.ReadILOpcode() is not ILOpcode.callvirt and not ILOpcode.call) + return false; + + // We don't actually mind if this is not Object.GetType + reader.ReadILToken(); + + if (State.IsBasicBlockStart(reader.Offset)) + return false; + } + + // If the ldtoken wasn't in the first position, it must be in the other + if (type == 0) + { + if (!TryReadTypeOf(ref reader, out type)) + return false; + } + + if (!offsetIsAtTypeEquals) + { + if (!TryReadTypeEqualityOrInequalityOperation(ref reader)) + return false; + } + else + { + Debug.Assert(TryReadTypeEqualityOrInequalityOperation(ref reader)); + } + + return true; + } + + private bool TryReadTypeOf(ref ILReader reader, out int token) + { + token = 0; + + if (reader.ReadILOpcode() != ILOpcode.ldtoken) + return false; + + token = reader.ReadILToken(); + + if (State.IsBasicBlockStart(reader.Offset)) + return false; + + if (reader.ReadILOpcode() != ILOpcode.call) + return false; + + MethodDesc method = (MethodDesc)Method.GetObject(reader.ReadILToken()); + + if (!method.IsIntrinsic || method.Name != "GetTypeFromHandle") + return false; + + if (State.IsBasicBlockStart(reader.Offset)) + return false; + + return true; + } + + private bool TryReadTypeEqualityOrInequalityOperation(ref ILReader reader) + { + ILOpcode opcode = reader.ReadILOpcode(); + if (opcode != ILOpcode.call) + return false; + + var method = (MethodDesc)Method.GetObject(reader.ReadILToken()); + if (method.IsIntrinsic && method.Name is "op_Equality" or "op_Inequality") + { + MetadataType owningType = method.OwningType as MetadataType; + if (owningType != null) + { + return owningType.Name == "Type" && owningType.Namespace == "System"; + } + } + + return false; + } + } + + internal interface ILPatternAnalyzerTraits + { + bool IsInstructionStart(int offset); + bool IsBasicBlockStart(int offset); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 51d70dc7a34479..dcd944b8e432a4 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..e6f78e2082dedf 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(Canary3)); + } + { RunCheck(GetThePointerType()); From 1d1ddace23172602ab73f0ea5fc5a013fc64b912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 19 Jun 2024 20:19:22 +0200 Subject: [PATCH 02/13] Update ILPatternAnalyzer.cs --- .../tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs index 6c7a45fa80e58d..3a512bf63c64d0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs @@ -8,7 +8,7 @@ namespace ILCompiler { - internal class ILPatternAnalyzer where T : ILPatternAnalyzerTraits + internal sealed class ILPatternAnalyzer where T : ILPatternAnalyzerTraits { public readonly MethodIL Method; public T State; From 37c7639edf4f56bef800e477f4726edf78531776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 19 Jun 2024 21:15:20 +0200 Subject: [PATCH 03/13] Update RuntimeTypeHandle.cs --- .../nativeaot/Test.CoreLib/src/System/RuntimeTypeHandle.cs | 5 +++++ 1 file changed, 5 insertions(+) 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)); + } } } From e84d58c036b4b5a6669a0e0988d32518d562568d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 21 Jun 2024 11:59:07 +0200 Subject: [PATCH 04/13] wip --- .../tools/Common/TypeSystem/IL/ILImporter.cs | 4 + .../tools/Common/TypeSystem/IL/ILReader.cs | 10 +- .../Compiler/SubstitutedILProvider.cs | 170 ++++---- .../IL/ILImporter.Scanner.cs | 39 +- .../IL/ILPatternAnalyzer.cs | 385 +++++++----------- 5 files changed, 246 insertions(+), 362 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs b/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs index e4c4ddb42d3180..73897d1a0b2419 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs @@ -314,6 +314,8 @@ private void MarkBasicBlock(BasicBlock basicBlock) } } + partial void StartImportingInstruction(ILOpcode opcode); + private void ImportBasicBlock(BasicBlock basicBlock) { _currentBasicBlock = basicBlock; @@ -325,6 +327,8 @@ private void ImportBasicBlock(BasicBlock basicBlock) ILOpcode opCode = (ILOpcode)ReadILByte(); + StartImportingInstruction(opCode); + again: switch (opCode) { 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 bb1ee11683643b..49f23d71d6873f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs @@ -235,8 +235,6 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method) } } - var analyzer = new ILPatternAnalyzer(new ILPatternAnalyzerState(flags), method, methodBytes); - bool hasGetResourceStringCall = false; // Mark all reachable basic blocks @@ -254,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) { @@ -261,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) { @@ -299,7 +301,8 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method) || opcode == ILOpcode.brtrue || opcode == ILOpcode.brtrue_s) { int destination = reader.ReadBranchDestination(opcode); - if (!TryGetConstantArgument(analyzer, 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); @@ -325,8 +328,8 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method) || opcode == ILOpcode.bne_un || opcode == ILOpcode.bne_un_s) { int destination = reader.ReadBranchDestination(opcode); - if (!TryGetConstantArgument(analyzer, offset, 0, out int left) - || !TryGetConstantArgument(analyzer, offset, 1, out int right)) + if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int left) + || !TryGetConstantArgument(method, methodBytes, flags, offset, 1, out int right)) { // Can't get the constant - both branches are live. offsetsToVisit.Push(destination); @@ -656,9 +659,9 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method) return new SubstitutedMethodIL(method.GetMethodILDefinition(), newBody, newEHRegions.ToArray(), debugInfo, newStrings.ToArray()); } - private bool TryGetConstantArgument(ILPatternAnalyzer analyzer, int offset, int argIndex, out int constant) + private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, int argIndex, out int constant) { - if (analyzer.State.IsBasicBlockStart(offset)) + if ((flags[offset] & OpcodeFlags.BasicBlockStart) != 0) { constant = 0; return false; @@ -666,14 +669,14 @@ private bool TryGetConstantArgument(ILPatternAnalyzer an for (int currentOffset = offset - 1; currentOffset >= 0; currentOffset--) { - if (!analyzer.State.IsInstructionStart(currentOffset)) + if ((flags[currentOffset] & OpcodeFlags.InstructionStart) == 0) continue; - ILReader reader = new ILReader(analyzer.ILBytes, currentOffset); + ILReader reader = new ILReader(body, currentOffset); ILOpcode opcode = reader.ReadILOpcode(); if (opcode == ILOpcode.call || opcode == ILOpcode.callvirt) { - MethodDesc method = (MethodDesc)analyzer.Method.GetObject(reader.ReadILToken()); + MethodDesc method = (MethodDesc)methodIL.GetObject(reader.ReadILToken()); if (argIndex == 0) { BodySubstitution substitution = _substitutionProvider.GetSubstitution(method); @@ -683,17 +686,10 @@ private bool TryGetConstantArgument(ILPatternAnalyzer an 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(analyzer, 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 - && TryExpandTypeIs(analyzer, currentOffset, method.Name, out constant)) + && TryExpandTypeIs(methodIL, body, flags, currentOffset, method.Name, out constant)) { return true; } @@ -714,7 +710,7 @@ private bool TryGetConstantArgument(ILPatternAnalyzer an } else if (opcode == ILOpcode.ldsfld) { - FieldDesc field = (FieldDesc)analyzer.Method.GetObject(reader.ReadILToken()); + FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken()); if (argIndex == 0) { object substitution = _substitutionProvider.GetSubstitution(field); @@ -764,7 +760,7 @@ private bool TryGetConstantArgument(ILPatternAnalyzer an } else if ((opcode == ILOpcode.ldloc || opcode == ILOpcode.ldloc_s || (opcode >= ILOpcode.ldloc_0 && opcode <= ILOpcode.ldloc_3)) && - !analyzer.State.IsBasicBlockStart(currentOffset)) + ((flags[currentOffset] & OpcodeFlags.BasicBlockStart) == 0)) { // Paired stloc/ldloc that the C# compiler generates in debug code? int locIndex = opcode switch @@ -776,10 +772,10 @@ private bool TryGetConstantArgument(ILPatternAnalyzer an for (int potentialStlocOffset = currentOffset - 1; potentialStlocOffset >= 0; potentialStlocOffset--) { - if (!analyzer.State.IsInstructionStart(potentialStlocOffset)) + if ((flags[potentialStlocOffset] & OpcodeFlags.InstructionStart) == 0) continue; - ILReader nestedReader = new ILReader(analyzer.ILBytes, potentialStlocOffset); + ILReader nestedReader = new ILReader(body, potentialStlocOffset); ILOpcode otherOpcode = nestedReader.ReadILOpcode(); if ((otherOpcode == ILOpcode.stloc || otherOpcode == ILOpcode.stloc_s || (otherOpcode >= ILOpcode.stloc_0 && otherOpcode <= ILOpcode.stloc_3)) @@ -805,8 +801,8 @@ private bool TryGetConstantArgument(ILPatternAnalyzer an { if (argIndex == 0) { - if (!TryGetConstantArgument(analyzer, currentOffset, 0, out int left) - || !TryGetConstantArgument(analyzer, currentOffset, 1, out int right)) + if (!TryGetConstantArgument(methodIL, body, flags, currentOffset, 0, out int left) + || !TryGetConstantArgument(methodIL, body, flags, currentOffset, 1, out int right)) { constant = 0; return false; @@ -828,7 +824,7 @@ private bool TryGetConstantArgument(ILPatternAnalyzer an return false; } - if (analyzer.State.IsBasicBlockStart(currentOffset)) + if ((flags[currentOffset] & OpcodeFlags.BasicBlockStart) != 0) break; } @@ -836,7 +832,7 @@ private bool TryGetConstantArgument(ILPatternAnalyzer an return false; } - private static bool TryExpandTypeIs(ILPatternAnalyzer analyzer, int offset, string name, out int constant) + private static bool TryExpandTypeIs(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, string name, out int constant) { // We expect to see a sequence: // ldtoken Foo @@ -847,16 +843,16 @@ private static bool TryExpandTypeIs(ILPatternAnalyzer an if (offset < SequenceLength) return false; - if (!analyzer.State.IsInstructionStart(offset - SequenceLength)) + if ((flags[offset - SequenceLength] & OpcodeFlags.InstructionStart) == 0) return false; - ILReader reader = new ILReader(analyzer.ILBytes, offset - SequenceLength); + ILReader reader = new ILReader(body, offset - SequenceLength); - TypeDesc type = ReadLdToken(ref reader, analyzer); + TypeDesc type = ReadLdToken(ref reader, methodIL, flags); if (type == null) return false; - if (!ReadGetTypeFromHandle(ref reader, analyzer)) + if (!ReadGetTypeFromHandle(ref reader, methodIL, flags)) return false; // Dataflow runs on top of uninstantiated IL and we can't answer some questions there. @@ -875,103 +871,92 @@ private static bool TryExpandTypeIs(ILPatternAnalyzer an return true; } - private bool TryExpandTypeEquality(ILPatternAnalyzer analyzer, int offset, string op, out int constant) - { - if (TryExpandTypeEquality_TokenToken(analyzer, offset, out constant) - || TryExpandTypeEquality_TokenOther(analyzer, offset, out constant)) - { - if (op == "op_Inequality") - constant ^= 1; - - return true; - } - - return false; - } - - private static bool TryExpandTypeEquality_TokenToken(ILPatternAnalyzer analyzer, int offset, out int constant) + private bool TryExpandTypeEquality(in TypeEqualityPatternAnalyzer analyzer, MethodIL methodIL, out int constant) { constant = 0; - - if (!analyzer.TryAnalyzeTypeEquality_TokenToken(offset, offsetIsAtTypeEquals: true, out TypeDesc type1, out TypeDesc type2)) + if (!analyzer.IsTypeEqualityCheck) return false; - // No value in making this work for definitions - if (type1.IsGenericDefinition || type2.IsGenericDefinition) - return false; + if (analyzer.IsTwoTokens) + { + var type1 = (TypeDesc)methodIL.GetObject(analyzer.Token1); + var type2 = (TypeDesc)methodIL.GetObject(analyzer.Token2); - // 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; + // No value in making this work for definitions + if (type1.IsGenericDefinition || type2.IsGenericDefinition) + return false; - bool? equality = TypeExtensions.CompareTypesForEquality(type1, type2); - if (!equality.HasValue) - 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; - constant = equality.Value ? 1 : 0; + bool? equality = TypeExtensions.CompareTypesForEquality(type1, type2); + if (!equality.HasValue) + return false; - return true; - } + constant = equality.Value ? 1 : 0; + } + else + { + var knownType = (TypeDesc)methodIL.GetObject(analyzer.Token1); - private bool TryExpandTypeEquality_TokenOther(ILPatternAnalyzer analyzer, int offset, out int constant) - { - constant = 0; + // No value in making this work for definitions + if (knownType.IsGenericDefinition) + return false; - if (!analyzer.TryAnalyzeTypeEquality_TokenOther(offset, offsetIsAtTypeEquals: true, out TypeDesc knownType)) - 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; - // No value in making this work for definitions - if (knownType.IsGenericDefinition) - return false; + if (knownType.IsCanonicalDefinitionType(CanonicalFormKind.Any)) + 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; + // We don't track types without a constructed MethodTable very well. + if (!ConstructedEETypeNode.CreationAllowed(knownType)) + 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; } - private static TypeDesc ReadLdToken(ref ILReader reader, ILPatternAnalyzer analyzer) + private static TypeDesc ReadLdToken(ref ILReader reader, MethodIL methodIL, OpcodeFlags[] flags) { ILOpcode opcode = reader.ReadILOpcode(); if (opcode != ILOpcode.ldtoken) return null; - TypeDesc t = (TypeDesc)analyzer.Method.GetObject(reader.ReadILToken()); + TypeDesc t = (TypeDesc)methodIL.GetObject(reader.ReadILToken()); - if (analyzer.State.IsBasicBlockStart(reader.Offset)) + if ((flags[reader.Offset] & OpcodeFlags.BasicBlockStart) != 0) return null; return t; } - private static bool ReadGetTypeFromHandle(ref ILReader reader, ILPatternAnalyzer analyzer) + private static bool ReadGetTypeFromHandle(ref ILReader reader, MethodIL methodIL, OpcodeFlags[] flags) { ILOpcode opcode = reader.ReadILOpcode(); if (opcode != ILOpcode.call) return false; - MethodDesc method = (MethodDesc)analyzer.Method.GetObject(reader.ReadILToken()); + MethodDesc method = (MethodDesc)methodIL.GetObject(reader.ReadILToken()); if (!method.IsIntrinsic || method.Name != "GetTypeFromHandle") return false; - if (analyzer.State.IsBasicBlockStart(reader.Offset)) + if ((flags[reader.Offset] & OpcodeFlags.BasicBlockStart) != 0) return false; return true; @@ -1036,15 +1021,6 @@ public SubstitutedDebugInformation(MethodDebugInformation originalDebugInformati public override IEnumerable GetSequencePoints() => _sequencePoints; } - private struct ILPatternAnalyzerState : ILPatternAnalyzerTraits - { - private readonly OpcodeFlags[] _flags; - public ILPatternAnalyzerState(OpcodeFlags[] flags) => _flags = flags; - - public bool IsBasicBlockStart(int offset) => (_flags[offset] & OpcodeFlags.BasicBlockStart) != 0; - public bool IsInstructionStart(int offset) => (_flags[offset] & OpcodeFlags.InstructionStart) != 0; - } - private const int TokenTypeString = 0x70; // CorTokenType for strings } } 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 860c2a88e9e6da..1db29c3de1dfd1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -32,7 +32,7 @@ internal partial class ILImporter private readonly byte[] _ilBytes; - private ILPatternAnalyzer _patternAnalyzer; + private TypeEqualityPatternAnalyzer _typeEqualityPatternAnalyzer; private sealed class BasicBlock { @@ -53,16 +53,6 @@ public enum ImportState : byte public bool HandlerStart; } - private struct ILPatternAnalyzerState : ILPatternAnalyzerTraits - { - public readonly BasicBlock[] BasicBlocks; - - public ILPatternAnalyzerState(BasicBlock[] basicBlocks) => BasicBlocks = basicBlocks; - - public bool IsBasicBlockStart(int offset) => BasicBlocks[offset] != null; - public bool IsInstructionStart(int offset) => throw new System.NotImplementedException(); - } - private bool _isReadOnly; private TypeDesc _constrained; @@ -181,8 +171,6 @@ public DependencyList Import() FindBasicBlocks(); - _patternAnalyzer = new ILPatternAnalyzer(new ILPatternAnalyzerState(_basicBlocks), _methodIL, _ilBytes); - ImportBasicBlocks(); CodeBasedDependencyAlgorithm.AddDependenciesDueToMethodCodePresence(ref _dependencies, _factory, _canonMethod, _canonMethodIL); @@ -242,6 +230,13 @@ private void StartImportingBasicBlock(BasicBlock basicBlock) } } } + + _typeEqualityPatternAnalyzer = default; + } + + partial void StartImportingInstruction(ILOpcode opcode) + { + _typeEqualityPatternAnalyzer.Advance(opcode, new ILReader(_ilBytes, _currentOffset), _methodIL); } private void EndImportingInstruction() @@ -882,10 +877,22 @@ private void ImportLdToken(int token) if (obj is TypeDesc type) { - int offsetOfLdtoken = _currentOffset - 5; - // We might also be able to optimize this a little if this is a ldtoken/GetTypeFromHandle/Equals sequence. - bool isTypeEquals = _patternAnalyzer.IsLdTokenConsumedByTypeEqualityCheck(offsetOfLdtoken); + bool isTypeEquals = false; + TypeEqualityPatternAnalyzer analyzer = _typeEqualityPatternAnalyzer; + ILReader reader = new ILReader(_ilBytes, _currentOffset); + while (!analyzer.IsDefault) + { + ILOpcode opcode = reader.ReadILOpcode(); + analyzer.Advance(opcode, reader, _methodIL); + reader.Skip(opcode); + + if (analyzer.IsTypeEqualityCheCCCC) + { + isTypeEquals = true; + break; + } + } _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeTypeHandle), "ldtoken"); _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeType), "ldtoken"); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs index 3a512bf63c64d0..5b1f2863adc624 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs @@ -1,274 +1,163 @@ // 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; -using Debug = System.Diagnostics.Debug; - namespace ILCompiler { - internal sealed class ILPatternAnalyzer where T : ILPatternAnalyzerTraits + internal struct TypeEqualityPatternAnalyzer { - public readonly MethodIL Method; - public T State; - public readonly byte[] ILBytes; - - public ILPatternAnalyzer(T state, MethodIL method, byte[] methodBytes) - { - Method = method; - State = state; - ILBytes = methodBytes; - } - - public bool IsLdTokenConsumedByTypeEqualityCheck(int offset) + private enum State : byte { - // 'offset' points to an ldtoken instruction. - // - // This could be ldtoken in the first parameter position of a type equality check - // or in the second parameter position. Check for the second parameter position - // first since it's the cheapest. + LdToken = 1, + TypeOf, + PushedOne, - var reader = new ILReader(ILBytes, offset); + TypeOf_LdToken, + TypeOf_TypeOf, + TypeOf_PushedOne, - // If it's not a typeof() it cannot be any of the patterns below, bail - if (!TryReadTypeOf(ref reader, out _)) - return false; + PushedOne_LdToken, + PushedOne_TypeOf, - // If it's typeof followed by a call to op_Equality/op_Inequality, we're done. - if (TryReadTypeEqualityOrInequalityOperation(ref reader)) - return true; + TypeEqualityCheck, - // This might still be ldtoken associated with the first parameter of the equality - // check. This checks for a couple patterns that we know how to analyze such as: - // typeof(X) == typeof(Y) - // typeof(X) == local - // typeof(X) == local.GetType - if (TryAnalyzeTypeEquality_TokenToken(offset, offsetIsAtTypeEquals: false, out int _, out _) - || TryAnalyzeTypeEquality_TokenOther(offset, offsetIsAtTypeEquals: false, out int _)) - { - return true; - } - - return false; + Branch, } - public bool TryAnalyzeTypeEquality_TokenToken(int offset, bool offsetIsAtTypeEquals, out TypeDesc type1, out TypeDesc type2) + private enum Flags : byte { - if (TryAnalyzeTypeEquality_TokenToken(offset, offsetIsAtTypeEquals, out int type1tok, out int type2tok)) - { - type1 = (TypeDesc)Method.GetObject(type1tok); - type2 = (TypeDesc)Method.GetObject(type2tok); - return true; - } - type1 = type2 = null; - return false; + TwoTokens = 1, + Inequality = 2, } - public bool TryAnalyzeTypeEquality_TokenToken(int offset, bool offsetIsAtTypeEquals, out int type1, out int type2) - { - // We expect to see a sequence: - // -> offset may point to here - // ldtoken Foo - // call GetTypeFromHandle - // ldtoken Bar - // call GetTypeFromHandle - // -> or offset may point to here + private State _state; + private Flags _flags; + private int _token1; + private int _token2; - type1 = 0; - type2 = 0; + public readonly int Token1 => IsTypeEqualityCheck ? _token1 : throw new UnreachableException(); + public readonly int Token2 => IsTwoTokens ? _token2 : throw new UnreachableException(); - if (offsetIsAtTypeEquals) - { - const int SequenceLength = 20; - if (offset < SequenceLength) - return false; - - offset -= SequenceLength; - - if (!State.IsInstructionStart(offset)) - return false; - } - - ILReader reader = new ILReader(ILBytes, offset); - - if (!TryReadTypeOf(ref reader, out type1)) - return false; + public readonly bool IsDefault => _state == default; + public readonly bool IsTypeEqualityCheCCCC => _state is State.TypeEqualityCheck; + public readonly bool IsTypeEqualityCheck => _state is State.Branch; + public readonly bool IsTwoTokens => (_flags & Flags.TwoTokens) != 0; + public readonly bool IsInequality => (_flags & Flags.Inequality) != 0; - if (!TryReadTypeOf(ref reader, out type2)) - return false; - - if (!offsetIsAtTypeEquals) - { - if (!TryReadTypeEqualityOrInequalityOperation(ref reader)) - return false; - } - else - { - Debug.Assert(TryReadTypeEqualityOrInequalityOperation(ref reader)); - } - - return true; - } - - public bool TryAnalyzeTypeEquality_TokenOther(int offset, bool offsetIsAtTypeEquals, out TypeDesc type) + public void Advance(ILOpcode opcode, in ILReader reader, MethodIL methodIL) { - if (TryAnalyzeTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, out int typeTok)) - { - type = (TypeDesc)Method.GetObject(typeTok); - return true; - } - type = null; - return false; + switch (_state) + { + case 0: + if (opcode == ILOpcode.ldtoken) + (_state, _token1) = (State.LdToken, reader.PeekILToken()); + else if (IsArgumentOrLocalLoad(opcode)) + _state = State.PushedOne; + 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 + break; + return; + case State.PushedOne: + if (opcode == ILOpcode.ldtoken) + (_state, _token1) = (State.PushedOne_LdToken, reader.PeekILToken()); + else if (IsObjectGetType(opcode, reader, methodIL)) + { + // Nothing, state stays PushedOne + } + 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 + goto case State.PushedOne; + 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 + goto case State.TypeOf; + return; + case State.PushedOne_TypeOf: + if (IsTypeEquals(opcode, reader, methodIL)) + _state = State.TypeEqualityCheck; + else if (IsTypeInequals(opcode, reader, methodIL)) + (_state, _flags) = (State.TypeEqualityCheck, _flags | Flags.Inequality); + else + goto case State.TypeOf; + return; + case State.PushedOne_LdToken: + if (IsTypeGetTypeFromHandle(opcode, reader, methodIL)) + _state = State.PushedOne_TypeOf; + else + break; + return; + case State.TypeEqualityCheck: + if (opcode is ILOpcode.brfalse or ILOpcode.brfalse_s or ILOpcode.brtrue or ILOpcode.brtrue_s) + _state = State.Branch; + else + break; + 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 == ILOpcode.call && 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); + + _state = default; + _flags = default; + + Advance(opcode, reader, methodIL); } - - public bool TryAnalyzeTypeEquality_TokenOther(int offset, bool offsetIsAtTypeEquals, out int type) - { - return TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 1, expectGetType: false, out type) - || TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 2, expectGetType: false, out type) - || TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 3, expectGetType: false, out type) - || TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 1, expectGetType: true, out type) - || TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 2, expectGetType: true, out type) - || TryExpandTypeEquality_TokenOther(offset, offsetIsAtTypeEquals, 3, expectGetType: true, out type); - } - - private bool TryExpandTypeEquality_TokenOther(int offset, bool offsetIsAtTypeEquals, int ldInstructionSize, bool expectGetType, out int type) - { - // We expect to see a sequence: - // -> offset may point to here - // ldtoken Foo - // call GetTypeFromHandle - // ldloc.X/ldloc_s X/ldarg.X/ldarg_s X - // [optional] call Object.GetType - // -> or offset may point to here - // - // The ldtoken part can potentially be in the second argument position - - type = 0; - - if (offsetIsAtTypeEquals) - { - int sequenceLength = 5 + 5 + ldInstructionSize + (expectGetType ? 5 : 0); - if (offset < sequenceLength) - return false; - - offset -= sequenceLength; - - if (!State.IsInstructionStart(offset)) - return false; - } - - ILReader reader = new ILReader(ILBytes, offset); - - // Is the ldtoken in the first position? - if (reader.PeekILOpcode() == ILOpcode.ldtoken) - { - if (!TryReadTypeOf(ref reader, out type)) - 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(); - } - else - { - return false; - } - - if (State.IsBasicBlockStart(reader.Offset)) - return false; - - if (expectGetType) - { - if (reader.ReadILOpcode() is not ILOpcode.callvirt and not ILOpcode.call) - return false; - - // We don't actually mind if this is not Object.GetType - reader.ReadILToken(); - - if (State.IsBasicBlockStart(reader.Offset)) - return false; - } - - // If the ldtoken wasn't in the first position, it must be in the other - if (type == 0) - { - if (!TryReadTypeOf(ref reader, out type)) - return false; - } - - if (!offsetIsAtTypeEquals) - { - if (!TryReadTypeEqualityOrInequalityOperation(ref reader)) - return false; - } - else - { - Debug.Assert(TryReadTypeEqualityOrInequalityOperation(ref reader)); - } - - return true; - } - - private bool TryReadTypeOf(ref ILReader reader, out int token) - { - token = 0; - - if (reader.ReadILOpcode() != ILOpcode.ldtoken) - return false; - - token = reader.ReadILToken(); - - if (State.IsBasicBlockStart(reader.Offset)) - return false; - - if (reader.ReadILOpcode() != ILOpcode.call) - return false; - - MethodDesc method = (MethodDesc)Method.GetObject(reader.ReadILToken()); - - if (!method.IsIntrinsic || method.Name != "GetTypeFromHandle") - return false; - - if (State.IsBasicBlockStart(reader.Offset)) - return false; - - return true; - } - - private bool TryReadTypeEqualityOrInequalityOperation(ref ILReader reader) - { - ILOpcode opcode = reader.ReadILOpcode(); - if (opcode != ILOpcode.call) - return false; - - var method = (MethodDesc)Method.GetObject(reader.ReadILToken()); - if (method.IsIntrinsic && method.Name is "op_Equality" or "op_Inequality") - { - MetadataType owningType = method.OwningType as MetadataType; - if (owningType != null) - { - return owningType.Name == "Type" && owningType.Namespace == "System"; - } - } - - return false; - } - } - - internal interface ILPatternAnalyzerTraits - { - bool IsInstructionStart(int offset); - bool IsBasicBlockStart(int offset); } } From 4a656eda2fa9f439173e44894c72a1c079a44271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 21 Jun 2024 12:00:22 +0200 Subject: [PATCH 05/13] Update src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs --- .../SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs index e6f78e2082dedf..cae60553668734 100644 --- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/DeadCodeElimination.cs @@ -419,7 +419,7 @@ static void RunCheck(object o) } } - ThrowIfPresentWithUsableMethodTable(typeof(TestTypeEquals), nameof(Canary3)); + ThrowIfPresentWithUsableMethodTable(typeof(TestTypeEquals), nameof(Canary4)); } { From 1ca52af94d655e095d187ef16cbb2c0e40e25aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 21 Jun 2024 16:49:02 +0200 Subject: [PATCH 06/13] Fixes --- .../Compiler/SubstitutedILProvider.cs | 2 +- .../IL/ILImporter.Scanner.cs | 2 +- .../IL/ILPatternAnalyzer.cs | 44 ++++--------------- 3 files changed, 11 insertions(+), 37 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs index 49f23d71d6873f..db8bec02ff2b9c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs @@ -874,7 +874,7 @@ private static bool TryExpandTypeIs(MethodIL methodIL, byte[] body, OpcodeFlags[ private bool TryExpandTypeEquality(in TypeEqualityPatternAnalyzer analyzer, MethodIL methodIL, out int constant) { constant = 0; - if (!analyzer.IsTypeEqualityCheck) + if (!analyzer.IsTypeEqualityBranch) return false; if (analyzer.IsTwoTokens) 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 1db29c3de1dfd1..9f5f70869b2114 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -887,7 +887,7 @@ private void ImportLdToken(int token) analyzer.Advance(opcode, reader, _methodIL); reader.Skip(opcode); - if (analyzer.IsTypeEqualityCheCCCC) + if (analyzer.IsTypeEqualityCheck) { isTypeEquals = true; break; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs index 5b1f2863adc624..cc5b01b08d01c3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs @@ -14,15 +14,11 @@ private enum State : byte { LdToken = 1, TypeOf, - PushedOne, TypeOf_LdToken, TypeOf_TypeOf, TypeOf_PushedOne, - PushedOne_LdToken, - PushedOne_TypeOf, - TypeEqualityCheck, Branch, @@ -39,12 +35,12 @@ private enum Flags : byte private int _token1; private int _token2; - public readonly int Token1 => IsTypeEqualityCheck ? _token1 : throw new UnreachableException(); + 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 IsTypeEqualityCheCCCC => _state is State.TypeEqualityCheck; - public readonly bool IsTypeEqualityCheck => _state is State.Branch; + 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; @@ -55,8 +51,6 @@ public void Advance(ILOpcode opcode, in ILReader reader, MethodIL methodIL) case 0: if (opcode == ILOpcode.ldtoken) (_state, _token1) = (State.LdToken, reader.PeekILToken()); - else if (IsArgumentOrLocalLoad(opcode)) - _state = State.PushedOne; return; case State.LdToken: if (IsTypeGetTypeFromHandle(opcode, reader, methodIL)) @@ -69,16 +63,10 @@ public void Advance(ILOpcode opcode, in ILReader reader, MethodIL methodIL) (_state, _token2) = (State.TypeOf_LdToken, reader.PeekILToken()); else if (IsArgumentOrLocalLoad(opcode)) _state = State.TypeOf_PushedOne; - else - break; - return; - case State.PushedOne: - if (opcode == ILOpcode.ldtoken) - (_state, _token1) = (State.PushedOne_LdToken, reader.PeekILToken()); - else if (IsObjectGetType(opcode, reader, methodIL)) - { - // Nothing, state stays 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; @@ -98,7 +86,7 @@ public void Advance(ILOpcode opcode, in ILReader reader, MethodIL methodIL) else if (IsTypeInequals(opcode, reader, methodIL)) (_state, _flags) = (State.TypeEqualityCheck, _flags | Flags.Inequality); else - goto case State.PushedOne; + break; return; case State.TypeOf_TypeOf: if (IsTypeEquals(opcode, reader, methodIL)) @@ -108,20 +96,6 @@ public void Advance(ILOpcode opcode, in ILReader reader, MethodIL methodIL) else goto case State.TypeOf; return; - case State.PushedOne_TypeOf: - if (IsTypeEquals(opcode, reader, methodIL)) - _state = State.TypeEqualityCheck; - else if (IsTypeInequals(opcode, reader, methodIL)) - (_state, _flags) = (State.TypeEqualityCheck, _flags | Flags.Inequality); - else - goto case State.TypeOf; - return; - case State.PushedOne_LdToken: - if (IsTypeGetTypeFromHandle(opcode, reader, methodIL)) - _state = State.PushedOne_TypeOf; - else - break; - return; case State.TypeEqualityCheck: if (opcode is ILOpcode.brfalse or ILOpcode.brfalse_s or ILOpcode.brtrue or ILOpcode.brtrue_s) _state = State.Branch; @@ -148,7 +122,7 @@ static bool IsTypeInequals(ILOpcode opcode, in ILReader reader, MethodIL methodI && method.OwningType is MetadataType { Name: "Type", Namespace: "System" }; static bool IsObjectGetType(ILOpcode opcode, in ILReader reader, MethodIL methodIL) - => opcode == ILOpcode.call && methodIL.GetObject(reader.PeekILToken()) is MethodDesc method + => 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) From 6ae7b724480cbc9f7bbb4d2842dbf0b78358a325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Sat, 22 Jun 2024 12:50:29 +0200 Subject: [PATCH 07/13] Update ILPatternAnalyzer.cs --- .../IL/ILPatternAnalyzer.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs index cc5b01b08d01c3..7b402ca892250d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs @@ -20,6 +20,7 @@ private enum State : byte TypeOf_PushedOne, TypeEqualityCheck, + TypeEqualityCheck_StlocLdloc, Branch, } @@ -99,9 +100,17 @@ public void Advance(ILOpcode opcode, in ILReader reader, MethodIL methodIL) 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, methodIL)) + _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(); } @@ -128,6 +137,29 @@ static bool IsObjectGetType(ILOpcode opcode, in ILReader reader, MethodIL method 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, MethodIL methodIL) + { + 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; From 2d7f3b7146950cd0d671a967b0588d6041dbb433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Sat, 22 Jun 2024 20:50:34 +0900 Subject: [PATCH 08/13] Update ILPatternAnalyzer.cs --- .../tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs index 7b402ca892250d..e245b49f8585d1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs @@ -100,7 +100,7 @@ public void Advance(ILOpcode opcode, in ILReader reader, MethodIL methodIL) 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, methodIL)) + else if (IsStlocLdlocSequence(opcode, reader)) _state = State.TypeEqualityCheck_StlocLdloc; else break; @@ -137,7 +137,7 @@ static bool IsObjectGetType(ILOpcode opcode, in ILReader reader, MethodIL method 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, MethodIL methodIL) + static bool IsStlocLdlocSequence(ILOpcode opcode, in ILReader reader) { if (opcode == ILOpcode.stloc || opcode == ILOpcode.stloc_s || (opcode >= ILOpcode.stloc_0 && opcode <= ILOpcode.stloc_3)) { From 958c6b00e68132faf3f8c1538564a9a4602850a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 24 Jun 2024 09:21:05 +0200 Subject: [PATCH 09/13] Nits --- ...yzer.cs => TypeEqualityPatternAnalyzer.cs} | 23 +++++++++++++++++++ .../ILCompiler.Compiler.csproj | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) rename src/coreclr/tools/aot/ILCompiler.Compiler/IL/{ILPatternAnalyzer.cs => TypeEqualityPatternAnalyzer.cs} (92%) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs similarity index 92% rename from src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs rename to src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs index e245b49f8585d1..a2975637aba96c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILPatternAnalyzer.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs @@ -8,8 +8,31 @@ 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, diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index dcd944b8e432a4..4e9ac41dc1df28 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -671,7 +671,7 @@ - + From 1e4b33b5402e9c12dbbefa524b3cbd8191ebca7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 28 Jun 2024 07:52:00 +0200 Subject: [PATCH 10/13] Update ILImporter.cs --- src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs b/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs index 73897d1a0b2419..ce4ab243260c0e 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs @@ -326,10 +326,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: @@ -818,9 +819,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; From 03b9f1fc1ce89b91896459de4ef80d8127102ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 28 Jun 2024 07:57:42 +0200 Subject: [PATCH 11/13] Update ILImporter.cs --- src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs b/src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs index ce4ab243260c0e..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: { From bee8ee1243c44daf2d42f88ea5d716b6c0b3e251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 28 Jun 2024 07:59:16 +0200 Subject: [PATCH 12/13] nit --- .../tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs | 1 - 1 file changed, 1 deletion(-) 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 9f5f70869b2114..9952aedd18d409 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs @@ -170,7 +170,6 @@ public DependencyList Import() } FindBasicBlocks(); - ImportBasicBlocks(); CodeBasedDependencyAlgorithm.AddDependenciesDueToMethodCodePresence(ref _dependencies, _factory, _canonMethod, _canonMethodIL); From 1cbdd9653686c2552edd07df76988000a48c9de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 1 Jul 2024 07:11:56 +0200 Subject: [PATCH 13/13] Update src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs --- .../aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs index a2975637aba96c..e260ea338ed25f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/TypeEqualityPatternAnalyzer.cs @@ -118,7 +118,10 @@ public void Advance(ILOpcode opcode, in ILReader reader, MethodIL methodIL) 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)