public void TestMDArrayFunctionReading() { MetadataType mdArrayFunctionResolutionType = _testModule.GetType("", "MDArrayFunctionResolution"); MethodDesc methodWithMDArrayUsage = mdArrayFunctionResolutionType.GetMethods().Single(m => string.Equals(m.Name, "MethodWithUseOfMDArrayFunctions")); MethodIL methodIL = EcmaMethodIL.Create((EcmaMethod)methodWithMDArrayUsage); ILReader ilReader = new ILReader(methodIL.GetILBytes()); int failures = 0; int successes = 0; while (ilReader.HasNext) { ILOpcode opcode = ilReader.ReadILOpcode(); switch (opcode) { case ILOpcode.call: case ILOpcode.newobj: int token = ilReader.ReadILToken(); object tokenReferenceResult = methodIL.GetObject(token, NotFoundBehavior.ReturnNull); if (tokenReferenceResult == null) { failures++; tokenReferenceResult = "null"; } else { successes++; } _output.WriteLine($"call {tokenReferenceResult.ToString()}"); break; } } Assert.Equal(0, failures); Assert.Equal(4, successes); }
static IEnumerable <MIbcData> ReadMIbcGroup(TypeSystemContext tsc, EcmaMethod method) { EcmaMethodIL ilBody = EcmaMethodIL.Create((EcmaMethod)method); byte[] ilBytes = ilBody.GetILBytes(); int currentOffset = 0; object metadataObject = null; while (currentOffset < ilBytes.Length) { ILOpcode opcode = (ILOpcode)ilBytes[currentOffset]; if (opcode == ILOpcode.prefix1) { opcode = 0x100 + (ILOpcode)ilBytes[currentOffset + 1]; } switch (opcode) { case ILOpcode.ldtoken: UInt32 token = (UInt32)(ilBytes[currentOffset + 1] + (ilBytes[currentOffset + 2] << 8) + (ilBytes[currentOffset + 3] << 16) + (ilBytes[currentOffset + 4] << 24)); metadataObject = ilBody.GetObject((int)token); break; case ILOpcode.pop: MIbcData mibcData = new MIbcData(); mibcData.MetadataObject = metadataObject; yield return(mibcData); break; } // This isn't correct if there is a switch opcode, but since we won't do that, its ok currentOffset += opcode.GetSize(); } }
private void VerifyModule(EcmaModule module) { foreach (var methodHandle in module.MetadataReader.MethodDefinitions) { var method = (EcmaMethod)module.GetMethod(methodHandle); var methodIL = EcmaMethodIL.Create(method); if (methodIL == null) { continue; } var methodName = method.ToString(); if (_includePatterns.Count > 0 && !_includePatterns.Any(p => p.IsMatch(methodName))) { continue; } if (_excludePatterns.Any(p => p.IsMatch(methodName))) { continue; } VerifyMethod(method, methodIL); } }
private ILImporter ConstructILImporter(TestCase testCase) { var module = TestDataLoader.GetModuleForTestAssembly(testCase.ModuleName); var method = (EcmaMethod)module.GetMethod(MetadataTokens.EntityHandle(testCase.MetadataToken)); var methodIL = EcmaMethodIL.Create(method); return(new ILImporter(method, methodIL)); }
public static MibcConfig ParseMibcConfig(TypeSystemContext tsc, PEReader pEReader) { EcmaModule mibcModule = EcmaModule.Create(tsc, pEReader, null); EcmaMethod mibcConfigMth = (EcmaMethod)mibcModule.GetGlobalModuleType().GetMethod(nameof(MibcConfig), null); if (mibcConfigMth == null) { return(null); } var ilBody = EcmaMethodIL.Create(mibcConfigMth); var ilReader = new ILReader(ilBody.GetILBytes()); // Parse: // // ldstr "key1" // ldstr "value1" // pop // pop // ldstr "key2" // ldstr "value2" // pop // pop // ... // ret string fieldName = null; Dictionary <string, string> keyValue = new(); while (ilReader.HasNext) { ILOpcode opcode = ilReader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldstr: var ldStrValue = (string)ilBody.GetObject(ilReader.ReadILToken()); if (fieldName != null) { keyValue[fieldName] = ldStrValue; } else { fieldName = ldStrValue; } break; case ILOpcode.ret: case ILOpcode.pop: fieldName = null; break; default: throw new InvalidOperationException($"Unexpected opcode: {opcode}"); } } return(MibcConfig.FromKeyValueMap(keyValue)); }
/// <summary> /// Parse MIbcGroup method and return enumerable of MethodProfileData /// /// Like the AssemblyDictionary method, data is encoded via IL instructions. The format is /// /// ldtoken methodInProfileData /// Any series of instructions that does not include pop /// pop /// {Repeat N times for N methods described} /// /// This format is designed to be extensible to hold more data as we add new per method profile data without breaking existing parsers. /// </summary> static IEnumerable <MethodProfileData> ReadMIbcGroup(TypeSystemContext tsc, EcmaMethod method) { EcmaMethodIL ilBody = EcmaMethodIL.Create(method); byte[] ilBytes = ilBody.GetILBytes(); int currentOffset = 0; object metadataObject = null; object methodNotResolvable = new object(); while (currentOffset < ilBytes.Length) { ILOpcode opcode = (ILOpcode)ilBytes[currentOffset]; if (opcode == ILOpcode.prefix1) { opcode = 0x100 + (ILOpcode)ilBytes[currentOffset + 1]; } switch (opcode) { case ILOpcode.ldtoken: if (metadataObject == null) { uint token = (uint)(ilBytes[currentOffset + 1] + (ilBytes[currentOffset + 2] << 8) + (ilBytes[currentOffset + 3] << 16) + (ilBytes[currentOffset + 4] << 24)); try { metadataObject = ilBody.GetObject((int)token); } catch (TypeSystemException) { // The method being referred to may be missing. In that situation, // use the methodNotResolvable sentinel to indicate that this record should be ignored metadataObject = methodNotResolvable; } } break; case ILOpcode.pop: if (metadataObject != methodNotResolvable) { MethodProfileData mibcData = new MethodProfileData((MethodDesc)metadataObject, MethodProfilingDataFlags.ReadMethodCode, 0xFFFFFFFF); yield return(mibcData); } metadataObject = null; break; } // This isn't correct if there is a switch opcode, but since we won't do that, its ok currentOffset += opcode.GetSize(); } }
private IEnumerable <VerificationResult> VerifyMethods(EcmaModule module, IEnumerable <MethodDefinitionHandle> methodHandles) { foreach (var methodHandle in methodHandles) { var method = (EcmaMethod)module.GetMethod(methodHandle); var methodIL = EcmaMethodIL.Create(method); if (methodIL != null) { var results = VerifyMethod(module, methodIL, methodHandle); foreach (var result in results) { yield return(result); } } } }
public void TestGenericNameFormatting() { MetadataType testClass = _testModule.GetType("ILDisassembler", "TestGenericClass`1"); EcmaMethod testMethod = (EcmaMethod)testClass.GetMethod("TestMethod", null); EcmaMethodIL methodIL = EcmaMethodIL.Create(testMethod); Dictionary <int, string> interestingLines = new Dictionary <int, string> { { 4, "IL_0003: ldstr \"Hello \\\"World\\\"!\\n\"" }, { 9, "IL_000D: call instance void class ILDisassembler.TestGenericClass`1<!TClassParam>::VoidGenericMethod<string, valuetype ILDisassembler.TestStruct>(!!0, int32, native int, class ILDisassembler.TestClass&)" }, { 14, "IL_0017: initobj !TClassParam" }, { 16, "IL_001E: call !!0 class ILDisassembler.TestGenericClass`1<!TClassParam>::MethodParamGenericMethod<class ILDisassembler.TestClass>(class ILDisassembler.TestGenericClass`1<!!0>, class ILDisassembler.TestGenericClass`1/Nested<!0>, valuetype ILDisassembler.TestStruct*[], !0)" }, { 24, "IL_0030: call !!0 class ILDisassembler.TestGenericClass`1<!TClassParam>::MethodParamGenericMethod<!0>(class ILDisassembler.TestGenericClass`1<!!0>, class ILDisassembler.TestGenericClass`1/Nested<!0>, valuetype ILDisassembler.TestStruct*[], !0)" }, { 26, "IL_0036: ldtoken !TClassParam" }, { 28, "IL_003C: ldtoken valuetype [CoreTestAssembly]System.Nullable`1<int32>" }, { 31, "IL_0043: ldc.r8 3.14" }, { 32, "IL_004C: ldc.r4 1.68" }, { 34, "IL_0053: call instance valuetype ILDisassembler.TestStruct class ILDisassembler.TestGenericClass`1<!TClassParam>::NonGenericMethod(float64, float32, int16)" }, { 37, "IL_005A: ldflda !0 class ILDisassembler.TestGenericClass`1<!TClassParam>::somefield" }, { 41, "IL_0067: stfld class ILDisassembler.TestClass class ILDisassembler.TestGenericClass`1<!TClassParam>::otherfield" }, { 44, "IL_006E: stfld class ILDisassembler.TestGenericClass`1<class ILDisassembler.TestGenericClass`1<class ILDisassembler.TestClass>> class ILDisassembler.TestGenericClass`1<!TClassParam>::genericfield" }, { 47, "IL_0075: stfld !0[] class ILDisassembler.TestGenericClass`1<!TClassParam>::arrayfield" }, { 48, "IL_007A: call void ILDisassembler.TestClass::NonGenericMethod()" }, { 49, "IL_007F: ldsflda valuetype ILDisassembler.TestStruct ILDisassembler.TestClass::somefield" }, { 50, "IL_0084: initobj ILDisassembler.TestStruct" } }; ILDisassembler disasm = new ILDisassembler(methodIL); int numLines = 1; while (disasm.HasNextInstruction) { string line = disasm.GetNextInstruction(); string expectedLine; if (interestingLines.TryGetValue(numLines, out expectedLine)) { Assert.Equal(expectedLine, line); } numLines++; } Assert.Equal(52, numLines); }
static IEnumerable <MIbcData> ReadMIbcData(TraceTypeSystemContext tsc, FileInfo outputFileName, byte[] moduleBytes) { var peReader = new System.Reflection.PortableExecutable.PEReader(System.Collections.Immutable.ImmutableArray.Create <byte>(moduleBytes)); var module = EcmaModule.Create(tsc, peReader, null, null, new CustomCanonResolver(tsc)); var loadedMethod = (EcmaMethod)module.GetGlobalModuleType().GetMethod("AssemblyDictionary", null); EcmaMethodIL ilBody = EcmaMethodIL.Create(loadedMethod); byte[] ilBytes = ilBody.GetILBytes(); int currentOffset = 0; while (currentOffset < ilBytes.Length) { ILOpcode opcode = (ILOpcode)ilBytes[currentOffset]; if (opcode == ILOpcode.prefix1) { opcode = 0x100 + (ILOpcode)ilBytes[currentOffset + 1]; } switch (opcode) { case ILOpcode.ldtoken: UInt32 token = (UInt32)(ilBytes[currentOffset + 1] + (ilBytes[currentOffset + 2] << 8) + (ilBytes[currentOffset + 3] << 16) + (ilBytes[currentOffset + 4] << 24)); foreach (var data in ReadMIbcGroup(tsc, (EcmaMethod)ilBody.GetObject((int)token))) { yield return(data); } break; case ILOpcode.pop: break; } // This isn't correct if there is a switch opcode, but since we won't do that, its ok currentOffset += opcode.GetSize(); } GC.KeepAlive(peReader); }
private PerMethodInfo GetOrCreateInfo(MethodDesc md) { if (!_methodInf.TryGetValue(md, out PerMethodInfo pmi)) { MethodIL il = md switch { EcmaMethod em => EcmaMethodIL.Create(em), _ => new InstantiatedMethodIL(md, EcmaMethodIL.Create((EcmaMethod)md.GetTypicalMethodDefinition())), }; if (il == null) { return(null); } _methodInf.Add(md, pmi = new PerMethodInfo()); pmi.IL = il; pmi.FlowGraph = FlowGraph.Create(il); pmi.Profile = new SampleProfile(pmi.IL, pmi.FlowGraph); } return(pmi); }
/// <summary> /// Parse an MIBC file for the methods that are interesting. /// The version bubble must be specified and will describe the restrict the set of methods parsed to those relevant to the compilation /// The onlyDefinedInAssembly parameter is used to restrict the set of types parsed to include only those which are defined in a specific module. Specify null to allow definitions from all modules. /// This limited parsing is not necessarily an exact set of prevention, so detailed algorithms that work at the individual method level are still necessary, but this allows avoiding excessive parsing. /// /// The format of the Mibc file is that of a .NET dll, with a global method named "AssemblyDictionary". Inside of that file are a series of references that are broken up by which assemblies define the individual methods. /// These references are encoded as IL code that represents the details. /// The format of these IL instruction is as follows. /// /// ldstr mibcGroupName /// ldtoken mibcGroupMethod /// pop /// {Repeat the above pattern N times, once per Mibc group} /// /// See comment above ReadMIbcGroup for details of the group format /// /// The mibcGroupName is in the following format "Assembly_{definingAssemblyName};{OtherAssemblyName};{OtherAssemblyName};...; (OtherAssemblyName is ; delimited) /// /// </summary> /// <returns></returns> public static ProfileData ParseMIbcFile(CompilerTypeSystemContext tsc, string filename, HashSet <string> assemblyNamesInVersionBubble, string onlyDefinedInAssembly) { byte[] peData; using (var zipFile = ZipFile.OpenRead(filename)) { var mibcDataEntry = zipFile.GetEntry(Path.GetFileName(filename) + ".dll"); using (var mibcDataStream = mibcDataEntry.Open()) { peData = new byte[mibcDataEntry.Length]; using (BinaryReader br = new BinaryReader(mibcDataStream)) { peData = br.ReadBytes(checked ((int)mibcDataEntry.Length)); } } } using (var peReader = new System.Reflection.PortableExecutable.PEReader(System.Collections.Immutable.ImmutableArray.Create <byte>(peData))) { var mibcModule = EcmaModule.Create(tsc, peReader, null, null, new CustomCanonResolver(tsc)); var assemblyDictionary = (EcmaMethod)mibcModule.GetGlobalModuleType().GetMethod("AssemblyDictionary", null); IEnumerable <MethodProfileData> loadedMethodProfileData = Enumerable.Empty <MethodProfileData>(); EcmaMethodIL ilBody = EcmaMethodIL.Create(assemblyDictionary); byte[] ilBytes = ilBody.GetILBytes(); int currentOffset = 0; string mibcGroupName = ""; while (currentOffset < ilBytes.Length) { ILOpcode opcode = (ILOpcode)ilBytes[currentOffset]; if (opcode == ILOpcode.prefix1) { opcode = 0x100 + (ILOpcode)ilBytes[currentOffset + 1]; } switch (opcode) { case ILOpcode.ldstr: if (mibcGroupName == "") { UInt32 userStringToken = (UInt32)(ilBytes[currentOffset + 1] + (ilBytes[currentOffset + 2] << 8) + (ilBytes[currentOffset + 3] << 16) + (ilBytes[currentOffset + 4] << 24)); mibcGroupName = (string)ilBody.GetObject((int)userStringToken); } break; case ILOpcode.ldtoken: if (String.IsNullOrEmpty(mibcGroupName)) { break; } string[] assembliesByName = mibcGroupName.Split(';'); bool hasMatchingDefinition = (onlyDefinedInAssembly == null) || assembliesByName[0].Equals(onlyDefinedInAssembly); if (!hasMatchingDefinition) { break; } bool areAllEntriesInVersionBubble = true; foreach (string s in assembliesByName) { if (string.IsNullOrEmpty(s)) { continue; } if (!assemblyNamesInVersionBubble.Contains(s)) { areAllEntriesInVersionBubble = false; break; } } if (!areAllEntriesInVersionBubble) { break; } uint token = (uint)(ilBytes[currentOffset + 1] + (ilBytes[currentOffset + 2] << 8) + (ilBytes[currentOffset + 3] << 16) + (ilBytes[currentOffset + 4] << 24)); loadedMethodProfileData = loadedMethodProfileData.Concat(ReadMIbcGroup(tsc, (EcmaMethod)ilBody.GetObject((int)token))); break; case ILOpcode.pop: mibcGroupName = ""; break; } // This isn't correct if there is a switch opcode, but since we won't do that, its ok currentOffset += opcode.GetSize(); } return(new IBCProfileData(false, loadedMethodProfileData)); } }
private void WalkMethod(EcmaMethod method) { Instantiation typeContext = method.OwningType.Instantiation; Instantiation methodContext = method.Instantiation; MethodSignature methodSig = method.Signature; ProcessTypeReference(methodSig.ReturnType, typeContext, methodContext); foreach (TypeDesc parameterType in methodSig) { ProcessTypeReference(parameterType, typeContext, methodContext); } if (method.IsAbstract) { return; } var methodIL = EcmaMethodIL.Create(method); if (methodIL == null) { return; } // If this is a generic virtual method, add an edge from each of the generic parameters // of the implementation to the generic parameters of the declaration - any call to the // declaration will be modeled as if the declaration was calling into the implementation. if (method.IsVirtual && method.HasInstantiation) { var decl = (EcmaMethod)MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method).GetTypicalMethodDefinition(); if (decl != method) { Instantiation declInstantiation = decl.Instantiation; Instantiation implInstantiation = method.Instantiation; for (int i = 0; i < declInstantiation.Length; i++) { RecordBinding( (EcmaGenericParameter)implInstantiation[i], (EcmaGenericParameter)declInstantiation[i], isProperEmbedding: false); } } } // Walk the method body looking at referenced things that have some genericness. // Nongeneric things cannot be forming cycles. // In particular, we don't care about MemberRefs to non-generic things, TypeDefs/MethodDefs/FieldDefs. // Avoid the work to even materialize type system entities for those. ILReader reader = new ILReader(methodIL.GetILBytes()); while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.sizeof_: case ILOpcode.newarr: case ILOpcode.initobj: case ILOpcode.stelem: case ILOpcode.ldelem: case ILOpcode.ldelema: case ILOpcode.box: case ILOpcode.unbox: case ILOpcode.unbox_any: case ILOpcode.cpobj: case ILOpcode.ldobj: case ILOpcode.castclass: case ILOpcode.isinst: case ILOpcode.stobj: case ILOpcode.refanyval: case ILOpcode.mkrefany: case ILOpcode.constrained: EntityHandle accessedType = MetadataTokens.EntityHandle(reader.ReadILToken()); typeCase: if (accessedType.Kind == HandleKind.TypeSpecification) { var t = methodIL.GetObject(MetadataTokens.GetToken(accessedType), NotFoundBehavior.ReturnNull) as TypeDesc; if (t != null) { ProcessTypeReference(t, typeContext, methodContext); } } break; case ILOpcode.stsfld: case ILOpcode.ldsfld: case ILOpcode.ldsflda: case ILOpcode.stfld: case ILOpcode.ldfld: case ILOpcode.ldflda: EntityHandle accessedField = MetadataTokens.EntityHandle(reader.ReadILToken()); fieldCase: if (accessedField.Kind == HandleKind.MemberReference) { accessedType = _metadataReader.GetMemberReference((MemberReferenceHandle)accessedField).Parent; goto typeCase; } break; case ILOpcode.call: case ILOpcode.callvirt: case ILOpcode.newobj: case ILOpcode.ldftn: case ILOpcode.ldvirtftn: case ILOpcode.jmp: EntityHandle accessedMethod = MetadataTokens.EntityHandle(reader.ReadILToken()); methodCase: if (accessedMethod.Kind == HandleKind.MethodSpecification || (accessedMethod.Kind == HandleKind.MemberReference && _metadataReader.GetMemberReference((MemberReferenceHandle)accessedMethod).Parent.Kind == HandleKind.TypeSpecification)) { var m = methodIL.GetObject(MetadataTokens.GetToken(accessedMethod), NotFoundBehavior.ReturnNull) as MethodDesc; ProcessTypeReference(m.OwningType, typeContext, methodContext); ProcessMethodCall(m, typeContext, methodContext); } break; case ILOpcode.ldtoken: EntityHandle accessedEntity = MetadataTokens.EntityHandle(reader.ReadILToken()); if (accessedEntity.Kind == HandleKind.MethodSpecification || (accessedEntity.Kind == HandleKind.MemberReference && _metadataReader.GetMemberReference((MemberReferenceHandle)accessedEntity).GetKind() == MemberReferenceKind.Method)) { accessedMethod = accessedEntity; goto methodCase; } else if (accessedEntity.Kind == HandleKind.MemberReference) { accessedField = accessedEntity; goto fieldCase; } else if (accessedEntity.Kind == HandleKind.TypeSpecification) { accessedType = accessedEntity; goto typeCase; } break; default: reader.Skip(opcode); break; } } }
/// <summary> /// Parse an MIBC file for the methods that are interesting. /// The version bubble must be specified and will describe the restrict the set of methods parsed to those relevant to the compilation /// The onlyDefinedInAssembly parameter is used to restrict the set of types parsed to include only those which are defined in a specific module. Specify null to allow definitions from all modules. /// This limited parsing is not necessarily an exact set of prevention, so detailed algorithms that work at the individual method level are still necessary, but this allows avoiding excessive parsing. /// /// The format of the Mibc file is that of a .NET dll, with a global method named "AssemblyDictionary". Inside of that file are a series of references that are broken up by which assemblies define the individual methods. /// These references are encoded as IL code that represents the details. /// The format of these IL instruction is as follows. /// /// ldstr mibcGroupName /// ldtoken mibcGroupMethod /// pop /// {Repeat the above pattern N times, once per Mibc group} /// /// See comment above ReadMIbcGroup for details of the group format /// /// The mibcGroupName is in the following format "Assembly_{definingAssemblyName};{OtherAssemblyName};{OtherAssemblyName};...; (OtherAssemblyName is ; delimited) /// /// </summary> /// <returns></returns> public static ProfileData ParseMIbcFile(CompilerTypeSystemContext tsc, string filename, HashSet <string> assemblyNamesInVersionBubble, string onlyDefinedInAssembly) { byte[] peData = null; PEReader unprotectedPeReader = null; { FileStream fsMibcFile = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 0x1000, useAsync: false); bool disposeOnException = true; try { byte firstByte = (byte)fsMibcFile.ReadByte(); byte secondByte = (byte)fsMibcFile.ReadByte(); fsMibcFile.Seek(0, SeekOrigin.Begin); if (firstByte == 0x4d && secondByte == 0x5a) { // Uncompressed Mibc format, starts with 'MZ' prefix like all other PE files unprotectedPeReader = new PEReader(fsMibcFile, PEStreamOptions.Default); disposeOnException = false; } else { using (var zipFile = new ZipArchive(fsMibcFile, ZipArchiveMode.Read, leaveOpen: false, entryNameEncoding: null)) { disposeOnException = false; var mibcDataEntry = zipFile.GetEntry(Path.GetFileName(filename) + ".dll"); using (var mibcDataStream = mibcDataEntry.Open()) { peData = new byte[mibcDataEntry.Length]; using (BinaryReader br = new BinaryReader(mibcDataStream)) { peData = br.ReadBytes(checked ((int)mibcDataEntry.Length)); } } } } } finally { if (disposeOnException) { fsMibcFile.Dispose(); } } } if (peData != null) { unprotectedPeReader = new PEReader(System.Collections.Immutable.ImmutableArray.Create <byte>(peData)); } using (var peReader = unprotectedPeReader) { var mibcModule = EcmaModule.Create(tsc, peReader, null, null, new CustomCanonResolver(tsc)); var assemblyDictionary = (EcmaMethod)mibcModule.GetGlobalModuleType().GetMethod("AssemblyDictionary", null); IEnumerable <MethodProfileData> loadedMethodProfileData = Enumerable.Empty <MethodProfileData>(); EcmaMethodIL ilBody = EcmaMethodIL.Create(assemblyDictionary); byte[] ilBytes = ilBody.GetILBytes(); int currentOffset = 0; string mibcGroupName = ""; while (currentOffset < ilBytes.Length) { ILOpcode opcode = (ILOpcode)ilBytes[currentOffset]; if (opcode == ILOpcode.prefix1) { opcode = 0x100 + (ILOpcode)ilBytes[currentOffset + 1]; } switch (opcode) { case ILOpcode.ldstr: if (mibcGroupName == "") { UInt32 userStringToken = BinaryPrimitives.ReadUInt32LittleEndian(ilBytes.AsSpan(currentOffset + 1)); mibcGroupName = (string)ilBody.GetObject((int)userStringToken); } break; case ILOpcode.ldtoken: if (String.IsNullOrEmpty(mibcGroupName)) { break; } string[] assembliesByName = mibcGroupName.Split(';'); bool hasMatchingDefinition = (onlyDefinedInAssembly == null) || assembliesByName[0].Equals(onlyDefinedInAssembly); if (!hasMatchingDefinition) { break; } bool areAllEntriesInVersionBubble = true; foreach (string s in assembliesByName) { if (string.IsNullOrEmpty(s)) { continue; } if (!assemblyNamesInVersionBubble.Contains(s)) { areAllEntriesInVersionBubble = false; break; } } if (!areAllEntriesInVersionBubble) { break; } uint token = BinaryPrimitives.ReadUInt32LittleEndian(ilBytes.AsSpan(currentOffset + 1)); loadedMethodProfileData = loadedMethodProfileData.Concat(ReadMIbcGroup(tsc, (EcmaMethod)ilBody.GetObject((int)token))); break; case ILOpcode.pop: mibcGroupName = ""; break; } // This isn't correct if there is a switch opcode, but since we won't do that, its ok currentOffset += opcode.GetSize(); } return(new IBCProfileData(false, loadedMethodProfileData)); } }
public InterpreterCallInterceptor(TypeSystemContext context, MethodDesc method) : base(false) { _context = context; _method = method; _methodIL = EcmaMethodIL.Create(method as EcmaMethod); }
/// <summary> /// Parse an MIBC file for the methods that are interesting. /// The version bubble must be specified and will describe the restrict the set of methods parsed to those relevant to the compilation /// The onlyDefinedInAssembly parameter is used to restrict the set of types parsed to include only those which are defined in a specific module. Specify null to allow definitions from all modules. /// This limited parsing is not necessarily an exact set of prevention, so detailed algorithms that work at the individual method level are still necessary, but this allows avoiding excessive parsing. /// /// The format of the Mibc file is that of a .NET dll, with a global method named "AssemblyDictionary". Inside of that file are a series of references that are broken up by which assemblies define the individual methods. /// These references are encoded as IL code that represents the details. /// The format of these IL instruction is as follows. /// /// ldstr mibcGroupName /// ldtoken mibcGroupMethod /// pop /// {Repeat the above pattern N times, once per Mibc group} /// /// See comment above ReadMIbcGroup for details of the group format /// /// The mibcGroupName is in the following format "Assembly_{definingAssemblyName};{OtherAssemblyName};{OtherAssemblyName};...; (OtherAssemblyName is ; delimited) /// /// </summary> /// <returns></returns> public static ProfileData ParseMIbcFile(TypeSystemContext tsc, PEReader peReader, HashSet <string> assemblyNamesInVersionBubble, string onlyDefinedInAssembly) { var mibcModule = EcmaModule.Create(tsc, peReader, null, null, new CustomCanonResolver(tsc)); var assemblyDictionary = (EcmaMethod)mibcModule.GetGlobalModuleType().GetMethod("AssemblyDictionary", null); IEnumerable <MethodProfileData> loadedMethodProfileData = Enumerable.Empty <MethodProfileData>(); EcmaMethodIL ilBody = EcmaMethodIL.Create(assemblyDictionary); byte[] ilBytes = ilBody.GetILBytes(); int currentOffset = 0; string mibcGroupName = ""; while (currentOffset < ilBytes.Length) { ILOpcode opcode = (ILOpcode)ilBytes[currentOffset]; if (opcode == ILOpcode.prefix1) { opcode = 0x100 + (ILOpcode)ilBytes[currentOffset + 1]; } switch (opcode) { case ILOpcode.ldstr: Debug.Assert(mibcGroupName == ""); if (mibcGroupName == "") { UInt32 userStringToken = BinaryPrimitives.ReadUInt32LittleEndian(ilBytes.AsSpan(currentOffset + 1)); mibcGroupName = (string)ilBody.GetObject((int)userStringToken); } break; case ILOpcode.ldtoken: if (String.IsNullOrEmpty(mibcGroupName)) { break; } string[] assembliesByName = mibcGroupName.Split(';'); bool hasMatchingDefinition = (onlyDefinedInAssembly == null) || assembliesByName[0].Equals(onlyDefinedInAssembly); if (!hasMatchingDefinition) { break; } if (assemblyNamesInVersionBubble != null) { bool areAllEntriesInVersionBubble = true; foreach (string s in assembliesByName) { if (string.IsNullOrEmpty(s)) { continue; } if (!assemblyNamesInVersionBubble.Contains(s)) { areAllEntriesInVersionBubble = false; break; } } if (!areAllEntriesInVersionBubble) { break; } } uint token = BinaryPrimitives.ReadUInt32LittleEndian(ilBytes.AsSpan(currentOffset + 1)); loadedMethodProfileData = loadedMethodProfileData.Concat(ReadMIbcGroup(tsc, (EcmaMethod)ilBody.GetObject((int)token))); break; case ILOpcode.pop: mibcGroupName = ""; break; } // This isn't correct if there is a switch opcode, but since we won't do that, its ok currentOffset += opcode.GetSize(); } return(new IBCProfileData(false, loadedMethodProfileData)); }
private void WalkMethod(EcmaMethod method) { Instantiation typeContext = method.OwningType.Instantiation; Instantiation methodContext = method.Instantiation; MethodSignature methodSig = method.Signature; ProcessTypeReference(methodSig.ReturnType, typeContext, methodContext); foreach (TypeDesc parameterType in methodSig) { ProcessTypeReference(parameterType, typeContext, methodContext); } if (method.IsAbstract) { return; } var methodIL = EcmaMethodIL.Create(method); if (methodIL == null) { return; } // Walk the method body looking at referenced things that have some genericness. // Nongeneric things cannot be forming cycles. // In particular, we don't care about MemberRefs to non-generic things, TypeDefs/MethodDefs/FieldDefs. // Avoid the work to even materialize type system entities for those. ILReader reader = new ILReader(methodIL.GetILBytes()); while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.sizeof_: case ILOpcode.newarr: case ILOpcode.initobj: case ILOpcode.stelem: case ILOpcode.ldelem: case ILOpcode.ldelema: case ILOpcode.box: case ILOpcode.unbox: case ILOpcode.unbox_any: case ILOpcode.cpobj: case ILOpcode.ldobj: case ILOpcode.castclass: case ILOpcode.isinst: case ILOpcode.stobj: case ILOpcode.refanyval: case ILOpcode.mkrefany: case ILOpcode.constrained: EntityHandle accessedType = MetadataTokens.EntityHandle(reader.ReadILToken()); typeCase: if (accessedType.Kind == HandleKind.TypeSpecification) { var t = methodIL.GetObject(MetadataTokens.GetToken(accessedType), NotFoundBehavior.ReturnNull) as TypeDesc; if (t != null) { ProcessTypeReference(t, typeContext, methodContext); } } break; case ILOpcode.stsfld: case ILOpcode.ldsfld: case ILOpcode.ldsflda: case ILOpcode.stfld: case ILOpcode.ldfld: case ILOpcode.ldflda: EntityHandle accessedField = MetadataTokens.EntityHandle(reader.ReadILToken()); fieldCase: if (accessedField.Kind == HandleKind.MemberReference) { accessedType = _metadataReader.GetMemberReference((MemberReferenceHandle)accessedField).Parent; goto typeCase; } break; case ILOpcode.call: case ILOpcode.callvirt: case ILOpcode.newobj: case ILOpcode.ldftn: case ILOpcode.ldvirtftn: case ILOpcode.jmp: EntityHandle accessedMethod = MetadataTokens.EntityHandle(reader.ReadILToken()); methodCase: if (accessedMethod.Kind == HandleKind.MethodSpecification || (accessedMethod.Kind == HandleKind.MemberReference && _metadataReader.GetMemberReference((MemberReferenceHandle)accessedMethod).Parent.Kind == HandleKind.TypeSpecification)) { var m = methodIL.GetObject(MetadataTokens.GetToken(accessedMethod), NotFoundBehavior.ReturnNull) as MethodDesc; if (m != null) { ProcessTypeReference(m.OwningType, typeContext, methodContext); ProcessMethodCall(m, typeContext, methodContext); } } break; case ILOpcode.ldtoken: EntityHandle accessedEntity = MetadataTokens.EntityHandle(reader.ReadILToken()); if (accessedEntity.Kind == HandleKind.MethodSpecification || (accessedEntity.Kind == HandleKind.MemberReference && _metadataReader.GetMemberReference((MemberReferenceHandle)accessedEntity).GetKind() == MemberReferenceKind.Method)) { accessedMethod = accessedEntity; goto methodCase; } else if (accessedEntity.Kind == HandleKind.MemberReference) { accessedField = accessedEntity; goto fieldCase; } else if (accessedEntity.Kind == HandleKind.TypeSpecification) { accessedType = accessedEntity; goto typeCase; } break; default: reader.Skip(opcode); break; } } }
/// <summary> /// Parse an MIBC file for the methods that are interesting. /// The version bubble must be specified and will describe the restrict the set of methods parsed to those relevant to the compilation /// The onlyDefinedInAssembly parameter is used to restrict the set of types parsed to include only those which are defined in a specific module. Specify null to allow definitions from all modules. /// This limited parsing is not necessarily an exact set of prevention, so detailed algorithms that work at the individual method level are still necessary, but this allows avoiding excessive parsing. /// /// The format of the Mibc file is that of a .NET dll, with a global method named "AssemblyDictionary". Inside of that file are a series of references that are broken up by which assemblies define the individual methods. /// These references are encoded as IL code that represents the details. /// The format of these IL instruction is as follows. /// /// ldstr mibcGroupName /// ldtoken mibcGroupMethod /// pop /// {Repeat the above pattern N times, once per Mibc group} /// /// See comment above ReadMIbcGroup for details of the group format /// /// The mibcGroupName is in the following format "Assembly_{definingAssemblyName};{OtherAssemblyName};{OtherAssemblyName};...; (OtherAssemblyName is ; delimited) /// /// </summary> /// <returns></returns> public static ProfileData ParseMIbcFile(TypeSystemContext tsc, PEReader peReader, HashSet <string> assemblyNamesInVersionBubble, string onlyDefinedInAssembly) { var mibcModule = EcmaModule.Create(tsc, peReader, null, null, new CustomCanonResolver(tsc)); var assemblyDictionary = (EcmaMethod)mibcModule.GetGlobalModuleType().GetMethod("AssemblyDictionary", null); IEnumerable <MethodProfileData> loadedMethodProfileData = Enumerable.Empty <MethodProfileData>(); EcmaMethodIL ilBody = EcmaMethodIL.Create(assemblyDictionary); ILReader ilReader = new ILReader(ilBody.GetILBytes()); string mibcGroupName = ""; while (ilReader.HasNext) { ILOpcode opcode = ilReader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldstr: int userStringToken = ilReader.ReadILToken(); Debug.Assert(mibcGroupName == ""); if (mibcGroupName == "") { mibcGroupName = (string)ilBody.GetObject(userStringToken); } break; case ILOpcode.ldtoken: int token = ilReader.ReadILToken(); if (String.IsNullOrEmpty(mibcGroupName)) { break; } string[] assembliesByName = mibcGroupName.Split(';'); bool hasMatchingDefinition = (onlyDefinedInAssembly == null) || assembliesByName[0].Equals(onlyDefinedInAssembly); if (!hasMatchingDefinition) { break; } if (assemblyNamesInVersionBubble != null) { bool areAllEntriesInVersionBubble = true; foreach (string s in assembliesByName) { if (string.IsNullOrEmpty(s)) { continue; } if (!assemblyNamesInVersionBubble.Contains(s)) { areAllEntriesInVersionBubble = false; break; } } if (!areAllEntriesInVersionBubble) { break; } } loadedMethodProfileData = loadedMethodProfileData.Concat(ReadMIbcGroup(tsc, (EcmaMethod)ilBody.GetObject(token))); break; case ILOpcode.pop: mibcGroupName = ""; break; default: ilReader.Skip(opcode); break; } } return(new IBCProfileData(false, loadedMethodProfileData)); }
/// <summary> /// Parse MIbcGroup method and return enumerable of MethodProfileData /// /// Like the AssemblyDictionary method, data is encoded via IL instructions. The format is /// /// ldtoken methodInProfileData /// Any series of instructions that does not include pop. Expansion data is encoded via ldstr "id" /// followed by a expansion specific sequence of il opcodes. /// pop /// {Repeat N times for N methods described} /// /// Extensions supported with current parser: /// /// ldstr "ExclusiveWeight" /// Any ldc.i4 or ldc.r4 or ldc.r8 instruction to indicate the exclusive weight /// /// ldstr "WeightedCallData" /// ldc.i4 <Count of methods called> /// Repeat <Count of methods called times> /// ldtoken <Method called from this method> /// ldc.i4 <Weight associated with calling the <Method called from this method>> /// /// This format is designed to be extensible to hold more data as we add new per method profile data without breaking existing parsers. /// </summary> static IEnumerable <MethodProfileData> ReadMIbcGroup(TypeSystemContext tsc, EcmaMethod method) { EcmaMethodIL ilBody = EcmaMethodIL.Create(method); MetadataLoaderForPgoData metadataLoader = new MetadataLoaderForPgoData(ilBody); byte[] ilBytes = ilBody.GetILBytes(); int currentOffset = 0; object methodInProgress = null; object metadataNotResolvable = new object(); object metadataObject = null; MibcGroupParseState state = MibcGroupParseState.LookingForNextMethod; int intValue = 0; int weightedCallGraphSize = 0; int profileEntryFound = 0; double exclusiveWeight = 0; Dictionary <MethodDesc, int> weights = null; bool processIntValue = false; List <long> instrumentationDataLongs = null; PgoSchemaElem[] pgoSchemaData = null; while (currentOffset < ilBytes.Length) { ILOpcode opcode = (ILOpcode)ilBytes[currentOffset]; if (opcode == ILOpcode.prefix1) { opcode = 0x100 + (ILOpcode)ilBytes[currentOffset + 1]; } processIntValue = false; switch (opcode) { case ILOpcode.ldtoken: { uint token = BitConverter.ToUInt32(ilBytes.AsSpan(currentOffset + 1, 4)); if (state == MibcGroupParseState.ProcessingInstrumentationData) { instrumentationDataLongs.Add(token); } else { metadataObject = null; try { metadataObject = ilBody.GetObject((int)token); } catch (TypeSystemException) { // The method being referred to may be missing. In that situation, // use the metadataNotResolvable sentinel to indicate that this record should be ignored metadataObject = metadataNotResolvable; } switch (state) { case MibcGroupParseState.ProcessingCallgraphToken: state = MibcGroupParseState.ProcessingCallgraphWeight; break; case MibcGroupParseState.LookingForNextMethod: methodInProgress = metadataObject; state = MibcGroupParseState.LookingForOptionalData; break; default: state = MibcGroupParseState.LookingForOptionalData; break; } } } break; case ILOpcode.ldc_r4: { float fltValue = BitConverter.ToSingle(ilBytes.AsSpan(currentOffset + 1, 4)); switch (state) { case MibcGroupParseState.ProcessingExclusiveWeight: exclusiveWeight = fltValue; state = MibcGroupParseState.LookingForOptionalData; break; default: state = MibcGroupParseState.LookingForOptionalData; break; } break; } case ILOpcode.ldc_r8: { double dblValue = BitConverter.ToDouble(ilBytes.AsSpan(currentOffset + 1, 8)); switch (state) { case MibcGroupParseState.ProcessingExclusiveWeight: exclusiveWeight = dblValue; state = MibcGroupParseState.LookingForOptionalData; break; default: state = MibcGroupParseState.LookingForOptionalData; break; } break; } case ILOpcode.ldc_i4_0: intValue = 0; processIntValue = true; break; case ILOpcode.ldc_i4_1: intValue = 1; processIntValue = true; break; case ILOpcode.ldc_i4_2: intValue = 2; processIntValue = true; break; case ILOpcode.ldc_i4_3: intValue = 3; processIntValue = true; break; case ILOpcode.ldc_i4_4: intValue = 4; processIntValue = true; break; case ILOpcode.ldc_i4_5: intValue = 5; processIntValue = true; break; case ILOpcode.ldc_i4_6: intValue = 6; processIntValue = true; break; case ILOpcode.ldc_i4_7: intValue = 7; processIntValue = true; break; case ILOpcode.ldc_i4_8: intValue = 8; processIntValue = true; break; case ILOpcode.ldc_i4_m1: intValue = -1; processIntValue = true; break; case ILOpcode.ldc_i4_s: intValue = (sbyte)ilBytes[currentOffset + 1]; processIntValue = true; break; case ILOpcode.ldc_i4: intValue = BitConverter.ToInt32(ilBytes.AsSpan(currentOffset + 1, 4)); processIntValue = true; break; case ILOpcode.ldc_i8: if (state == MibcGroupParseState.ProcessingInstrumentationData) { instrumentationDataLongs.Add(BitConverter.ToInt64(ilBytes.AsSpan(currentOffset + 1, 8))); } break; case ILOpcode.ldstr: { UInt32 userStringToken = BitConverter.ToUInt32(ilBytes.AsSpan(currentOffset + 1, 4)); string optionalDataName = (string)ilBody.GetObject((int)userStringToken); switch (optionalDataName) { case "ExclusiveWeight": state = MibcGroupParseState.ProcessingExclusiveWeight; break; case "WeightedCallData": state = MibcGroupParseState.ProcessingCallgraphCount; break; case "InstrumentationDataStart": state = MibcGroupParseState.ProcessingInstrumentationData; instrumentationDataLongs = new List <long>(); break; case "InstrumentationDataEnd": if (instrumentationDataLongs != null) { instrumentationDataLongs.Add(2); // MarshalMask 2 (Type) instrumentationDataLongs.Add(0); // PgoInstrumentationKind.Done (0) pgoSchemaData = PgoProcessor.ParsePgoData <TypeSystemEntityOrUnknown>(metadataLoader, instrumentationDataLongs, false).ToArray(); } state = MibcGroupParseState.LookingForOptionalData; break; default: state = MibcGroupParseState.LookingForOptionalData; break; } } break; case ILOpcode.pop: if (methodInProgress != metadataNotResolvable) { profileEntryFound++; if (exclusiveWeight == 0) { // If no exclusive weight is found assign a non zero value that assumes the order in the pgo file is significant. exclusiveWeight = Math.Min(1000000.0 - profileEntryFound, 0.0) / 1000000.0; } MethodProfileData mibcData = new MethodProfileData((MethodDesc)methodInProgress, MethodProfilingDataFlags.ReadMethodCode, exclusiveWeight, weights, 0xFFFFFFFF, pgoSchemaData); state = MibcGroupParseState.LookingForNextMethod; exclusiveWeight = 0; weights = null; instrumentationDataLongs = null; pgoSchemaData = null; yield return(mibcData); } methodInProgress = null; break; default: state = MibcGroupParseState.LookingForOptionalData; break; } if (processIntValue) { switch (state) { case MibcGroupParseState.ProcessingExclusiveWeight: exclusiveWeight = intValue; state = MibcGroupParseState.LookingForOptionalData; break; case MibcGroupParseState.ProcessingCallgraphCount: weightedCallGraphSize = intValue; weights = new Dictionary <MethodDesc, int>(); if (weightedCallGraphSize > 0) { state = MibcGroupParseState.ProcessingCallgraphToken; } else { state = MibcGroupParseState.LookingForOptionalData; } break; case MibcGroupParseState.ProcessingCallgraphWeight: if (metadataObject != metadataNotResolvable) { weights.Add((MethodDesc)metadataObject, intValue); } weightedCallGraphSize--; if (weightedCallGraphSize > 0) { state = MibcGroupParseState.ProcessingCallgraphToken; } else { state = MibcGroupParseState.LookingForOptionalData; } break; case MibcGroupParseState.ProcessingInstrumentationData: instrumentationDataLongs.Add(intValue); break; default: state = MibcGroupParseState.LookingForOptionalData; instrumentationDataLongs = null; break; } } // This isn't correct if there is a switch opcode, but since we won't do that, its ok currentOffset += opcode.GetSize(); } }
public static ProfileData ParseMIbcFile(TypeSystemContext tsc, PEReader peReader, HashSet <string> assemblyNamesInVersionBubble, string onlyDefinedInAssembly, MibcGroupParseRules parseRule = MibcGroupParseRules.VersionBubble, HashSet <string> crossModuleInlineModules = null) { if (parseRule == MibcGroupParseRules.VersionBubble) { crossModuleInlineModules = s_EmptyHash; } if (parseRule == MibcGroupParseRules.AllGroups) { assemblyNamesInVersionBubble = null; } if (crossModuleInlineModules == null) { crossModuleInlineModules = s_EmptyHash; } var mibcModule = EcmaModule.Create(tsc, peReader, null, null, new CustomCanonResolver(tsc)); var assemblyDictionary = (EcmaMethod)mibcModule.GetGlobalModuleType().GetMethod("AssemblyDictionary", null); IEnumerable <MethodProfileData> loadedMethodProfileData = Enumerable.Empty <MethodProfileData>(); EcmaMethodIL ilBody = EcmaMethodIL.Create(assemblyDictionary); ILReader ilReader = new ILReader(ilBody.GetILBytes()); string mibcGroupName = ""; while (ilReader.HasNext) { ILOpcode opcode = ilReader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldstr: int userStringToken = ilReader.ReadILToken(); Debug.Assert(mibcGroupName == ""); if (mibcGroupName == "") { mibcGroupName = (string)ilBody.GetObject(userStringToken); } break; case ILOpcode.ldtoken: int token = ilReader.ReadILToken(); if (String.IsNullOrEmpty(mibcGroupName)) { break; } string[] assembliesByName = mibcGroupName.Split(';'); bool hasMatchingDefinition = (onlyDefinedInAssembly == null) || assembliesByName[0].Equals(onlyDefinedInAssembly); if (!hasMatchingDefinition) { break; } if (assemblyNamesInVersionBubble != null) { bool mibcGroupUseable = true; bool someEntryInVersionBubble = false; foreach (string s in assembliesByName) { if (string.IsNullOrEmpty(s)) { continue; } bool entryInVersionBubble = assemblyNamesInVersionBubble.Contains(s); someEntryInVersionBubble = someEntryInVersionBubble || entryInVersionBubble; if (!entryInVersionBubble && !crossModuleInlineModules.Contains(s)) { // If the group references a module that isn't in the version bubble and isn't cross module inlineable, its not useful. mibcGroupUseable = false; break; } } if (!someEntryInVersionBubble && (parseRule == MibcGroupParseRules.VersionBubbleWithCrossModule1)) { mibcGroupUseable = false; } if (!mibcGroupUseable) { break; } } loadedMethodProfileData = loadedMethodProfileData.Concat(ReadMIbcGroup(tsc, (EcmaMethod)ilBody.GetObject(token))); break; case ILOpcode.pop: mibcGroupName = ""; break; default: ilReader.Skip(opcode); break; } } return(new IBCProfileData(ParseMibcConfig(tsc, peReader), false, loadedMethodProfileData)); }