Skip to content

Commit 9f26939

Browse files
Extract shared IL pattern analysis to a class (#103701)
This fixes the problem discussed at #102248 (comment). Now we call into the same code from both substitutions and scanner.
1 parent b3ee47d commit 9f26939

File tree

8 files changed

+293
-218
lines changed

8 files changed

+293
-218
lines changed

src/coreclr/nativeaot/Test.CoreLib/src/System/RuntimeTypeHandle.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,10 @@ private static RuntimeTypeHandle GetRuntimeTypeHandle(IntPtr pEEType)
3434
{
3535
return new RuntimeTypeHandle(pEEType);
3636
}
37+
38+
private static Type GetRuntimeType(IntPtr pEEType)
39+
{
40+
return Type.GetTypeFromHandle(new RuntimeTypeHandle(pEEType));
41+
}
3742
}
3843
}

src/coreclr/tools/Common/TypeSystem/IL/ILImporter.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,9 @@ private void FindJumpTargets()
114114
MarkInstructionBoundary();
115115

116116
ILOpcode opCode = (ILOpcode)ReadILByte();
117+
if (opCode == ILOpcode.prefix1)
118+
opCode = (ILOpcode)(0x100 + ReadILByte());
117119

118-
again:
119120
switch (opCode)
120121
{
121122
case ILOpcode.ldarg_s:
@@ -179,9 +180,6 @@ private void FindJumpTargets()
179180
case ILOpcode.sizeof_:
180181
SkipIL(4);
181182
break;
182-
case ILOpcode.prefix1:
183-
opCode = (ILOpcode)(0x100 + ReadILByte());
184-
goto again;
185183
case ILOpcode.br_s:
186184
case ILOpcode.leave_s:
187185
{
@@ -314,6 +312,8 @@ private void MarkBasicBlock(BasicBlock basicBlock)
314312
}
315313
}
316314

315+
partial void StartImportingInstruction(ILOpcode opcode);
316+
317317
private void ImportBasicBlock(BasicBlock basicBlock)
318318
{
319319
_currentBasicBlock = basicBlock;
@@ -324,8 +324,11 @@ private void ImportBasicBlock(BasicBlock basicBlock)
324324
StartImportingInstruction();
325325

326326
ILOpcode opCode = (ILOpcode)ReadILByte();
327+
if (opCode == ILOpcode.prefix1)
328+
opCode = (ILOpcode)(0x100 + ReadILByte());
329+
330+
StartImportingInstruction(opCode);
327331

328-
again:
329332
switch (opCode)
330333
{
331334
case ILOpcode.nop:
@@ -814,9 +817,6 @@ private void ImportBasicBlock(BasicBlock basicBlock)
814817
case ILOpcode.conv_u:
815818
ImportConvert(WellKnownType.UIntPtr, false, true);
816819
break;
817-
case ILOpcode.prefix1:
818-
opCode = (ILOpcode)(0x100 + ReadILByte());
819-
goto again;
820820
case ILOpcode.arglist:
821821
ImportArgList();
822822
break;

src/coreclr/tools/Common/TypeSystem/IL/ILReader.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public ILOpcode ReadILOpcode()
100100
return opcode;
101101
}
102102

103-
public ILOpcode PeekILOpcode()
103+
public readonly ILOpcode PeekILOpcode()
104104
{
105105
ILOpcode opcode = (ILOpcode)_ilBytes[_currentOffset];
106106
if (opcode == ILOpcode.prefix1)
@@ -113,6 +113,14 @@ public ILOpcode PeekILOpcode()
113113
return opcode;
114114
}
115115

116+
public readonly int PeekILToken()
117+
{
118+
if (!BinaryPrimitives.TryReadInt32LittleEndian(_ilBytes.Slice(_currentOffset), out int value))
119+
ThrowHelper.ThrowInvalidProgramException();
120+
121+
return value;
122+
}
123+
116124
public void Skip(ILOpcode opcode)
117125
{
118126
if (!opcode.IsValid())

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs

Lines changed: 38 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,17 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method)
252252
if ((flags[offset] & OpcodeFlags.Mark) != 0)
253253
continue;
254254

255+
TypeEqualityPatternAnalyzer typeEqualityAnalyzer = default;
256+
255257
ILReader reader = new ILReader(methodBytes, offset);
256258
while (reader.HasNext)
257259
{
258260
offset = reader.Offset;
259261
flags[offset] |= OpcodeFlags.Mark;
260262
ILOpcode opcode = reader.ReadILOpcode();
261263

264+
typeEqualityAnalyzer.Advance(opcode, reader, method);
265+
262266
// Mark any applicable EH blocks
263267
foreach (ILExceptionRegion ehRegion in ehRegions)
264268
{
@@ -297,7 +301,8 @@ public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method)
297301
|| opcode == ILOpcode.brtrue || opcode == ILOpcode.brtrue_s)
298302
{
299303
int destination = reader.ReadBranchDestination(opcode);
300-
if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int constant))
304+
if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int constant)
305+
&& !TryExpandTypeEquality(typeEqualityAnalyzer, method, out constant))
301306
{
302307
// Can't get the constant - both branches are live.
303308
offsetsToVisit.Push(destination);
@@ -681,13 +686,6 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[
681686
constant = (int)substitution.Value;
682687
return true;
683688
}
684-
else if (method.IsIntrinsic && method.Name is "op_Inequality" or "op_Equality"
685-
&& method.OwningType is MetadataType mdType
686-
&& mdType.Name == "Type" && mdType.Namespace == "System" && mdType.Module == mdType.Context.SystemModule
687-
&& TryExpandTypeEquality(methodIL, body, flags, currentOffset, method.Name, out constant))
688-
{
689-
return true;
690-
}
691689
else if (method.IsIntrinsic && method.Name is "get_IsValueType" or "get_IsEnum"
692690
&& method.OwningType is MetadataType mdt
693691
&& mdt.Name == "Type" && mdt.Namespace == "System" && mdt.Module == mdt.Context.SystemModule
@@ -873,175 +871,63 @@ private static bool TryExpandTypeIs(MethodIL methodIL, byte[] body, OpcodeFlags[
873871
return true;
874872
}
875873

876-
private bool TryExpandTypeEquality(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, string op, out int constant)
877-
{
878-
if (TryExpandTypeEquality_TokenToken(methodIL, body, flags, offset, out constant)
879-
|| TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 1, expectGetType: false, out constant)
880-
|| TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 2, expectGetType: false, out constant)
881-
|| TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 3, expectGetType: false, out constant)
882-
|| TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 1, expectGetType: true, out constant)
883-
|| TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 2, expectGetType: true, out constant)
884-
|| TryExpandTypeEquality_TokenOther(methodIL, body, flags, offset, 3, expectGetType: true, out constant))
885-
{
886-
if (op == "op_Inequality")
887-
constant ^= 1;
888-
889-
return true;
890-
}
891-
892-
return false;
893-
}
894-
895-
private static bool TryExpandTypeEquality_TokenToken(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, out int constant)
874+
private bool TryExpandTypeEquality(in TypeEqualityPatternAnalyzer analyzer, MethodIL methodIL, out int constant)
896875
{
897-
// We expect to see a sequence:
898-
// ldtoken Foo
899-
// call GetTypeFromHandle
900-
// ldtoken Bar
901-
// call GetTypeFromHandle
902-
// -> offset points here
903876
constant = 0;
904-
const int SequenceLength = 20;
905-
if (offset < SequenceLength)
906-
return false;
907-
908-
if ((flags[offset - SequenceLength] & OpcodeFlags.InstructionStart) == 0)
877+
if (!analyzer.IsTypeEqualityBranch)
909878
return false;
910879

911-
ILReader reader = new ILReader(body, offset - SequenceLength);
912-
913-
TypeDesc type1 = ReadLdToken(ref reader, methodIL, flags);
914-
if (type1 == null)
915-
return false;
916-
917-
if (!ReadGetTypeFromHandle(ref reader, methodIL, flags))
918-
return false;
919-
920-
TypeDesc type2 = ReadLdToken(ref reader, methodIL, flags);
921-
if (type2 == null)
922-
return false;
923-
924-
if (!ReadGetTypeFromHandle(ref reader, methodIL, flags))
925-
return false;
926-
927-
// No value in making this work for definitions
928-
if (type1.IsGenericDefinition || type2.IsGenericDefinition)
929-
return false;
930-
931-
// Dataflow runs on top of uninstantiated IL and we can't answer some questions there.
932-
// Unfortunately this means dataflow will still see code that the rest of the system
933-
// might have optimized away. It should not be a problem in practice.
934-
if (type1.ContainsSignatureVariables() || type2.ContainsSignatureVariables())
935-
return false;
936-
937-
bool? equality = TypeExtensions.CompareTypesForEquality(type1, type2);
938-
if (!equality.HasValue)
939-
return false;
940-
941-
constant = equality.Value ? 1 : 0;
942-
943-
return true;
944-
}
945-
946-
private bool TryExpandTypeEquality_TokenOther(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, int ldInstructionSize, bool expectGetType, out int constant)
947-
{
948-
// We expect to see a sequence:
949-
// ldtoken Foo
950-
// call GetTypeFromHandle
951-
// ldloc.X/ldloc_s X/ldarg.X/ldarg_s X
952-
// [optional] call Object.GetType
953-
// -> offset points here
954-
//
955-
// The ldtoken part can potentially be in the second argument position
956-
957-
constant = 0;
958-
int sequenceLength = 5 + 5 + ldInstructionSize + (expectGetType ? 5 : 0);
959-
if (offset < sequenceLength)
960-
return false;
961-
962-
if ((flags[offset - sequenceLength] & OpcodeFlags.InstructionStart) == 0)
963-
return false;
964-
965-
ILReader reader = new ILReader(body, offset - sequenceLength);
880+
if (analyzer.IsTwoTokens)
881+
{
882+
var type1 = (TypeDesc)methodIL.GetObject(analyzer.Token1);
883+
var type2 = (TypeDesc)methodIL.GetObject(analyzer.Token2);
966884

967-
TypeDesc knownType = null;
885+
// No value in making this work for definitions
886+
if (type1.IsGenericDefinition || type2.IsGenericDefinition)
887+
return false;
968888

969-
// Is the ldtoken in the first position?
970-
if (reader.PeekILOpcode() == ILOpcode.ldtoken)
971-
{
972-
knownType = ReadLdToken(ref reader, methodIL, flags);
973-
if (knownType == null)
889+
// Dataflow runs on top of uninstantiated IL and we can't answer some questions there.
890+
// Unfortunately this means dataflow will still see code that the rest of the system
891+
// might have optimized away. It should not be a problem in practice.
892+
if (type1.ContainsSignatureVariables() || type2.ContainsSignatureVariables())
974893
return false;
975894

976-
if (!ReadGetTypeFromHandle(ref reader, methodIL, flags))
895+
bool? equality = TypeExtensions.CompareTypesForEquality(type1, type2);
896+
if (!equality.HasValue)
977897
return false;
978-
}
979898

980-
ILOpcode opcode = reader.ReadILOpcode();
981-
if (ldInstructionSize == 1 && opcode is (>= ILOpcode.ldloc_0 and <= ILOpcode.ldloc_3) or (>= ILOpcode.ldarg_0 and <= ILOpcode.ldarg_3))
982-
{
983-
// Nothing to read
984-
}
985-
else if (ldInstructionSize == 2 && opcode is ILOpcode.ldloc_s or ILOpcode.ldarg_s)
986-
{
987-
reader.ReadILByte();
988-
}
989-
else if (ldInstructionSize == 3 && opcode is ILOpcode.ldloc or ILOpcode.ldarg)
990-
{
991-
reader.ReadILUInt16();
899+
constant = equality.Value ? 1 : 0;
992900
}
993901
else
994902
{
995-
return false;
996-
}
997-
998-
if ((flags[reader.Offset] & OpcodeFlags.BasicBlockStart) != 0)
999-
return false;
903+
var knownType = (TypeDesc)methodIL.GetObject(analyzer.Token1);
1000904

1001-
if (expectGetType)
1002-
{
1003-
if (reader.ReadILOpcode() is not ILOpcode.callvirt and not ILOpcode.call)
905+
// No value in making this work for definitions
906+
if (knownType.IsGenericDefinition)
1004907
return false;
1005908

1006-
// We don't actually mind if this is not Object.GetType
1007-
reader.ReadILToken();
1008-
1009-
if ((flags[reader.Offset] & OpcodeFlags.BasicBlockStart) != 0)
909+
// Dataflow runs on top of uninstantiated IL and we can't answer some questions there.
910+
// Unfortunately this means dataflow will still see code that the rest of the system
911+
// might have optimized away. It should not be a problem in practice.
912+
if (knownType.ContainsSignatureVariables())
1010913
return false;
1011-
}
1012914

1013-
// If the ldtoken wasn't in the first position, it must be in the other
1014-
if (knownType == null)
1015-
{
1016-
knownType = ReadLdToken(ref reader, methodIL, flags);
1017-
if (knownType == null)
915+
if (knownType.IsCanonicalDefinitionType(CanonicalFormKind.Any))
1018916
return false;
1019917

1020-
if (!ReadGetTypeFromHandle(ref reader, methodIL, flags))
918+
// We don't track types without a constructed MethodTable very well.
919+
if (!ConstructedEETypeNode.CreationAllowed(knownType))
1021920
return false;
1022-
}
1023921

1024-
// No value in making this work for definitions
1025-
if (knownType.IsGenericDefinition)
1026-
return false;
1027-
1028-
// Dataflow runs on top of uninstantiated IL and we can't answer some questions there.
1029-
// Unfortunately this means dataflow will still see code that the rest of the system
1030-
// might have optimized away. It should not be a problem in practice.
1031-
if (knownType.ContainsSignatureVariables())
1032-
return false;
1033-
1034-
if (knownType.IsCanonicalDefinitionType(CanonicalFormKind.Any))
1035-
return false;
922+
if (_devirtualizationManager.CanReferenceConstructedTypeOrCanonicalFormOfType(knownType.NormalizeInstantiation()))
923+
return false;
1036924

1037-
// We don't track types without a constructed MethodTable very well.
1038-
if (!ConstructedEETypeNode.CreationAllowed(knownType))
1039-
return false;
925+
constant = 0;
926+
}
1040927

1041-
if (_devirtualizationManager.CanReferenceConstructedTypeOrCanonicalFormOfType(knownType.NormalizeInstantiation()))
1042-
return false;
928+
if (analyzer.IsInequality)
929+
constant ^= 1;
1043930

1044-
constant = 0;
1045931
return true;
1046932
}
1047933

0 commit comments

Comments
 (0)