/// <summary> /// Subset of <see cref="Scan(ref DependencyList, NodeFactory, MethodIL)"/> that only deals with Marshal.SizeOf. /// </summary> public static void ScanMarshalOnly(ref DependencyList list, NodeFactory factory, MethodIL methodIL) { ILReader reader = new ILReader(methodIL.GetILBytes()); Tracker tracker = new Tracker(methodIL); while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldtoken: tracker.TrackLdTokenToken(reader.ReadILToken()); break; case ILOpcode.call: var method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc; if (method != null && method.Name == "SizeOf" && IsMarshalSizeOf(method)) { TypeDesc type = tracker.GetLastType(); if (IsTypeEligibleForMarshalSizeOfTracking(type)) { list = list ?? new DependencyList(); list.Add(factory.StructMarshallingData((DefType)type), "Marshal.SizeOf"); } } break; default: reader.Skip(opcode); break; } } }
public bool TryReadLdtoken(out int token) { if (_reader.PeekILOpcode() != ILOpcode.ldtoken) { token = 0; return(false); } _reader.ReadILOpcode(); token = _reader.ReadILToken(); return(true); }
public static void Scan(ref DependencyList list, NodeFactory factory, MethodIL methodIL) { ILReader reader = new ILReader(methodIL.GetILBytes()); Tracker tracker = new Tracker(methodIL); // The algorithm here is really primitive: we scan the IL forward in a single pass, remembering // the last type/string/token we saw. // // We then intrinsically recognize a couple methods that consume this information. // // This has obvious problems since we don't have exact knowledge of the parameters passed // (something being in front of a call doesn't mean it's a parameter to the call). But since // this is a heuristic, it's okay. We want this to be as fast as possible. // // The main purposes of this scanner is to make following patterns work: // // * Enum.GetValues(typeof(Foo)) - this is very common and we need to make sure Foo[] is compiled. // * Type.GetType("Foo, Bar").GetMethod("Blah") - framework uses this to work around layering problems. // * typeof(Foo<>).MakeGenericType(arg).GetMethod("Blah") - used in e.g. LINQ expressions implementation // * Marshal.SizeOf(typeof(Foo)) - very common and we need to make sure interop data is generated while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldstr: tracker.TrackStringToken(reader.ReadILToken()); break; case ILOpcode.ldtoken: tracker.TrackLdTokenToken(reader.ReadILToken()); break; case ILOpcode.call: case ILOpcode.callvirt: var method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc; if (method != null) { HandleCall(ref list, factory, methodIL, method, ref tracker); } break; default: reader.Skip(opcode); break; } } }
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); }
/// <summary> /// Skips over a "foo == typeof(Bar)" or "typeof(Foo) == typeof(Bar)" sequence. /// </summary> private static bool IsTypeEqualityTest(MethodIL methodIL, ILReader reader, out ILReader afterTest) { afterTest = default; if (reader.ReadILOpcode() != ILOpcode.call) { return(false); } MethodDesc method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc; if (method == null || method.Name != "GetTypeFromHandle" && !method.OwningType.IsSystemType()) { return(false); } ILOpcode opcode = reader.ReadILOpcode(); if (opcode == ILOpcode.ldtoken) { reader.ReadILToken(); opcode = reader.ReadILOpcode(); if (opcode != ILOpcode.call) { return(false); } method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc; if (method == null || method.Name != "GetTypeFromHandle" && !method.OwningType.IsSystemType()) { return(false); } opcode = reader.ReadILOpcode(); } if (opcode != ILOpcode.call) { return(false); } method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc; if (method == null || method.Name != "op_Equality" && !method.OwningType.IsSystemType()) { return(false); } afterTest = reader; return(true); }
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)); }
private static bool ScanMethodBodyForFieldAccess(MethodIL body, bool write, out FieldDesc?found) { // Tries to find the backing field for a property getter/setter. // Returns true if this is a method body that we can unambiguously analyze. // The found field could still be null if there's no backing store. found = null; ILReader ilReader = new ILReader(body.GetILBytes()); while (ilReader.HasNext) { ILOpcode opcode = ilReader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldsfld when !write: case ILOpcode.ldfld when !write: case ILOpcode.stsfld when write: case ILOpcode.stfld when write: { // This writes/reads multiple fields - can't guess which one is the backing store. // Return failure. if (found != null) { found = null; return(false); } found = (FieldDesc)body.GetObject(ilReader.ReadILToken()); } break; default: ilReader.Skip(opcode); break; } } if (found == null) { // Doesn't access any fields. Could be e.g. "Type Foo => typeof(Bar);" // Return success. return(true); } if (found.OwningType != body.OwningMethod.OwningType || found.IsStatic != body.OwningMethod.Signature.IsStatic || !found.HasCustomAttribute("System.Runtime.CompilerServices", "CompilerGeneratedAttribute")) { // A couple heuristics to make sure we got the right field. // Return failure. found = null; return(false); } return(true); }
private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, int argIndex, out int constant) { if ((flags[offset] & OpcodeFlags.BasicBlockStart) != 0) { constant = 0; return(false); } for (int currentOffset = offset - 1; currentOffset >= 0; currentOffset--) { if ((flags[currentOffset] & OpcodeFlags.InstructionStart) == 0) { continue; } ILReader reader = new ILReader(body, currentOffset); ILOpcode opcode = reader.ReadILOpcode(); if (opcode == ILOpcode.call || opcode == ILOpcode.callvirt) { MethodDesc method = (MethodDesc)methodIL.GetObject(reader.ReadILToken()); if (argIndex == 0) { BodySubstitution substitution = GetSubstitution(method); if (substitution != null && substitution.Value is int && (opcode != ILOpcode.callvirt || !method.IsVirtual)) { constant = (int)substitution.Value; return(true); } else { constant = 0; return(false); } } argIndex--; if (method.Signature.Length > 0 || !method.Signature.IsStatic) { // We don't know how to skip over the parameters break; } } else if (opcode == ILOpcode.ldsfld) { FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken()); if (argIndex == 0) { object substitution = GetSubstitution(field); if (substitution is int) { constant = (int)substitution; return(true); } else { constant = 0; return(false); } } argIndex--; } else if (opcode >= ILOpcode.ldc_i4_0 && opcode <= ILOpcode.ldc_i4_8) { if (argIndex == 0) { constant = opcode - ILOpcode.ldc_i4_0; return(true); } argIndex--; } else if (opcode == ILOpcode.ldc_i4) { if (argIndex == 0) { constant = (int)reader.ReadILUInt32(); return(true); } argIndex--; } else if (opcode == ILOpcode.ldc_i4_s) { if (argIndex == 0) { constant = (int)(sbyte)reader.ReadILByte(); return(true); } argIndex--; } else if ((opcode == ILOpcode.ldloc || opcode == ILOpcode.ldloc_s || (opcode >= ILOpcode.ldloc_0 && opcode <= ILOpcode.ldloc_3)) && ((flags[currentOffset] & OpcodeFlags.BasicBlockStart) == 0)) { // Paired stloc/ldloc that the C# compiler generates in debug code? int locIndex = opcode switch { ILOpcode.ldloc => reader.ReadILUInt16(), ILOpcode.ldloc_s => reader.ReadILByte(), _ => opcode - ILOpcode.ldloc_0, }; for (int potentialStlocOffset = currentOffset - 1; potentialStlocOffset >= 0; potentialStlocOffset--) { if ((flags[potentialStlocOffset] & OpcodeFlags.InstructionStart) == 0) { continue; } 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)) && otherOpcode switch { ILOpcode.stloc => nestedReader.ReadILUInt16(), ILOpcode.stloc_s => nestedReader.ReadILByte(), _ => otherOpcode - ILOpcode.stloc_0, } == locIndex)
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; } } }
internal TypeCache(MetadataType type, Logger?logger, ILProvider ilProvider) { Debug.Assert(type == type.GetTypeDefinition()); Debug.Assert(!CompilerGeneratedNames.IsGeneratedMemberName(type.Name)); Type = type; var callGraph = new CompilerGeneratedCallGraph(); var userDefinedMethods = new HashSet <MethodDesc>(); void ProcessMethod(MethodDesc method) { Debug.Assert(method == method.GetTypicalMethodDefinition()); bool isStateMachineMember = CompilerGeneratedNames.IsStateMachineType(((MetadataType)method.OwningType).Name); if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) { if (!isStateMachineMember) { // If it's not a nested function, track as an entry point to the call graph. var added = userDefinedMethods.Add(method); Debug.Assert(added); } } else { // We don't expect lambdas or local functions to be emitted directly into // state machine types. Debug.Assert(!isStateMachineMember); } // Discover calls or references to lambdas or local functions. This includes // calls to local functions, and lambda assignments (which use ldftn). var methodBody = ilProvider.GetMethodIL(method); if (methodBody != null) { ILReader reader = new ILReader(methodBody.GetILBytes()); while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldftn: case ILOpcode.ldtoken: case ILOpcode.call: case ILOpcode.callvirt: case ILOpcode.newobj: { MethodDesc?referencedMethod = methodBody.GetObject(reader.ReadILToken(), NotFoundBehavior.ReturnNull) as MethodDesc; if (referencedMethod == null) { continue; } referencedMethod = referencedMethod.GetTypicalMethodDefinition(); if (referencedMethod.IsConstructor && referencedMethod.OwningType is MetadataType generatedType && // Don't consider calls in the same type, like inside a static constructor method.OwningType != generatedType && CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) { Debug.Assert(generatedType.IsTypeDefinition); // fill in null for now, attribute providers will be filled in later _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>(); if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, new TypeArgumentInfo(method, null))) { var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod; logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); } continue; } if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(referencedMethod.Name)) { continue; } if (isStateMachineMember) { callGraph.TrackCall((MetadataType)method.OwningType, referencedMethod); } else { callGraph.TrackCall(method, referencedMethod); } } break; case ILOpcode.stsfld: { // Same as above, but stsfld instead of a call to the constructor FieldDesc?field = methodBody.GetObject(reader.ReadILToken()) as FieldDesc; if (field == null) { continue; } field = field.GetTypicalFieldDefinition(); if (field.OwningType is MetadataType generatedType && // Don't consider field accesses in the same type, like inside a static constructor method.OwningType != generatedType && CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) { Debug.Assert(generatedType.IsTypeDefinition); _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>(); if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, new TypeArgumentInfo(method, null))) { // It's expected that there may be multiple methods associated with the same static closure environment. // All of these methods will substitute the same type arguments into the closure environment // (if it is generic). Don't warn. } continue; } } break; default: reader.Skip(opcode); break; } } } if (TryGetStateMachineType(method, out MetadataType? stateMachineType)) { Debug.Assert(stateMachineType.ContainingType == type || (CompilerGeneratedNames.IsGeneratedMemberName(stateMachineType.ContainingType.Name) && stateMachineType.ContainingType.ContainingType == type)); Debug.Assert(stateMachineType == stateMachineType.GetTypeDefinition()); callGraph.TrackCall(method, stateMachineType); _compilerGeneratedTypeToUserCodeMethod ??= new Dictionary <MetadataType, MethodDesc>(); if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) { var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName()); } // Already warned above if multiple methods map to the same type // Fill in null for argument providers now, the real providers will be filled in later _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>(); _generatedTypeToTypeArgumentInfo[stateMachineType] = new TypeArgumentInfo(method, null); } } // Look for state machine methods, and methods which call local functions. foreach (MethodDesc method in type.GetMethods()) { ProcessMethod(method); } // Also scan compiler-generated state machine methods (in case they have calls to nested functions), // and nested functions inside compiler-generated closures (in case they call other nested functions). // State machines can be emitted into lambda display classes, so we need to go down at least two // levels to find calls from iterator nested functions to other nested functions. We just recurse into // all compiler-generated nested types to avoid depending on implementation details. foreach (var nestedType in GetCompilerGeneratedNestedTypes(type)) { foreach (var method in nestedType.GetMethods()) { ProcessMethod(method); } } // Now we've discovered the call graphs for calls to nested functions. // Use this to map back from nested functions to the declaring user methods. // Note: This maps all nested functions back to the user code, not to the immediately // declaring local function. The IL doesn't contain enough information in general for // us to determine the nesting of local functions and lambdas. // Note: this only discovers nested functions which are referenced from the user // code or its referenced nested functions. There is no reliable way to determine from // IL which user code an unused nested function belongs to. foreach (var userDefinedMethod in userDefinedMethods) { var callees = callGraph.GetReachableMembers(userDefinedMethod); if (!callees.Any()) { continue; } _compilerGeneratedMembers ??= new Dictionary <MethodDesc, List <TypeSystemEntity> >(); _compilerGeneratedMembers.Add(userDefinedMethod, new List <TypeSystemEntity>(callees)); foreach (var compilerGeneratedMember in callees) { switch (compilerGeneratedMember) { case MethodDesc nestedFunction: Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(nestedFunction.Name)); // Nested functions get suppressions from the user method only. _compilerGeneratedMethodToUserCodeMethod ??= new Dictionary <MethodDesc, MethodDesc>(); if (!_compilerGeneratedMethodToUserCodeMethod.TryAdd(nestedFunction, userDefinedMethod)) { var alreadyAssociatedMethod = _compilerGeneratedMethodToUserCodeMethod[nestedFunction]; logger?.LogWarning(new MessageOrigin(userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), nestedFunction.GetDisplayName()); } break; case MetadataType stateMachineType: // Types in the call graph are always state machine types // For those all their methods are not tracked explicitly in the call graph; instead, they // are represented by the state machine type itself. // We are already tracking the association of the state machine type to the user code method // above, so no need to track it here. Debug.Assert(CompilerGeneratedNames.IsStateMachineType(stateMachineType.Name)); break; default: throw new InvalidOperationException(); } } } // Now that we have instantiating methods fully filled out, walk the generated types and fill in the attribute // providers if (_generatedTypeToTypeArgumentInfo != null) { foreach (var generatedType in _generatedTypeToTypeArgumentInfo.Keys) { Debug.Assert(generatedType == generatedType.GetTypeDefinition()); if (HasGenericParameters(generatedType)) { MapGeneratedTypeTypeParameters(generatedType); } } }
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 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); ILReader ilReader = new ILReader(ilBody.GetILBytes()); 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 (ilReader.HasNext) { ILOpcode opcode = ilReader.ReadILOpcode(); processIntValue = false; switch (opcode) { case ILOpcode.ldtoken: { int token = ilReader.ReadILToken(); if (state == MibcGroupParseState.ProcessingInstrumentationData) { instrumentationDataLongs.Add(token); } else { metadataObject = null; try { metadataObject = ilBody.GetObject(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 = ilReader.ReadILFloat(); switch (state) { case MibcGroupParseState.ProcessingExclusiveWeight: exclusiveWeight = fltValue; state = MibcGroupParseState.LookingForOptionalData; break; default: state = MibcGroupParseState.LookingForOptionalData; break; } break; } case ILOpcode.ldc_r8: { double dblValue = ilReader.ReadILDouble(); 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)ilReader.ReadILByte(); processIntValue = true; break; case ILOpcode.ldc_i4: intValue = (int)ilReader.ReadILUInt32(); processIntValue = true; break; case ILOpcode.ldc_i8: if (state == MibcGroupParseState.ProcessingInstrumentationData) { instrumentationDataLongs.Add((long)ilReader.ReadILUInt64()); } break; case ILOpcode.ldstr: { int userStringToken = ilReader.ReadILToken(); string optionalDataName = (string)ilBody.GetObject(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; ilReader.Skip(opcode); 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; } } } }
/// <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)); }
private bool IsNonVersionableWithILTokensThatDoNotNeedTranslationUncached(EcmaMethod method) { bool result = false; try { // Validate that there are no tokens in the IL other than tokens associated with the following // instructions with the // 1. ldfld, ldflda, and stfld to instance fields of NonVersionable structs and NonVersionable classes // 2. cpobj, initobj, ldobj, stobj, ldelem, ldelema or sizeof, to NonVersionable structures, signature variables, pointers, function pointers, byrefs, classes, or arrays // 3. stelem, to NonVersionable structures // In addition, the method must not have any EH. // The method may only have locals which are NonVersionable structures, or classes MethodIL methodIL = new ReadyToRunILProvider().GetMethodIL(method); if (methodIL.GetExceptionRegions().Length > 0) { return(false); } foreach (var local in methodIL.GetLocals()) { if (local.Type.IsPrimitive) { continue; } if (local.Type.IsArray) { continue; } if (local.Type.IsSignatureVariable) { continue; } MetadataType metadataType = local.Type as MetadataType; if (metadataType == null) { return(false); } if (metadataType.IsValueType) { if (metadataType.IsNonVersionable()) { continue; } else { return(false); } } } ILReader ilReader = new ILReader(methodIL.GetILBytes()); while (ilReader.HasNext) { ILOpcode opcode = ilReader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldfld: case ILOpcode.ldflda: case ILOpcode.stfld: { int token = ilReader.ReadILToken(); FieldDesc field = methodIL.GetObject(token) as FieldDesc; if (field == null) { return(false); } if (field.IsStatic) { return(false); } MetadataType owningMetadataType = (MetadataType)field.OwningType; if (!owningMetadataType.IsNonVersionable()) { return(false); } break; } case ILOpcode.ldelem: case ILOpcode.ldelema: case ILOpcode.stobj: case ILOpcode.ldobj: case ILOpcode.initobj: case ILOpcode.cpobj: case ILOpcode.sizeof_: { int token = ilReader.ReadILToken(); TypeDesc type = methodIL.GetObject(token) as TypeDesc; if (type == null) { return(false); } MetadataType metadataType = type as MetadataType; if (metadataType == null) { continue; // Types which are not metadata types are all well defined in size } if (!metadataType.IsValueType) { continue; // Reference types are all well defined in size for the sizeof instruction } if (metadataType.IsNonVersionable()) { continue; } return(false); } case ILOpcode.stelem: { int token = ilReader.ReadILToken(); MetadataType type = methodIL.GetObject(token) as MetadataType; if (type == null) { return(false); } if (!type.IsValueType) { return(false); } if (!type.IsNonVersionable()) { return(false); } break; } // IL instructions which refer to tokens which are not safe for NonVersionable methods case ILOpcode.box: case ILOpcode.call: case ILOpcode.calli: case ILOpcode.callvirt: case ILOpcode.castclass: case ILOpcode.jmp: case ILOpcode.isinst: case ILOpcode.ldstr: case ILOpcode.ldsfld: case ILOpcode.ldsflda: case ILOpcode.ldtoken: case ILOpcode.ldvirtftn: case ILOpcode.ldftn: case ILOpcode.mkrefany: case ILOpcode.newarr: case ILOpcode.newobj: case ILOpcode.refanyval: case ILOpcode.stsfld: case ILOpcode.unbox: case ILOpcode.unbox_any: case ILOpcode.constrained: return(false); default: // Unless its a opcode known to be permitted with a ilReader.Skip(opcode); break; } } result = true; } catch (TypeSystemException) { return(false); } return(result); }
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)); }