public void RootVirtualMethodForReflection(MethodDesc method, string reason) { Debug.Assert(method.IsVirtual); if (method.HasInstantiation) { _rootAdder(_factory.GVMDependencies(method), reason); } else { // Virtual method use is tracked on the slot defining method only. MethodDesc slotDefiningMethod = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method); if (!_factory.VTable(slotDefiningMethod.OwningType).HasFixedSlots) { _rootAdder(_factory.VirtualMethodUse(slotDefiningMethod), reason); } } if (method.IsAbstract) { _rootAdder(_factory.ReflectableMethod(method), reason); } }
private void ResolveInterfaceDispatch_ForMultiGenericTest(MetadataType type, out MethodDesc md1, out MethodDesc md2) { var algo = new MetadataVirtualMethodAlgorithm(); var stringType = _context.GetWellKnownType(WellKnownType.String); var bang0Type = _context.GetSignatureVariable(0, false); var bang1Type = _context.GetSignatureVariable(1, false); MethodSignature sigBang0Bang1 = new MethodSignature(0, 0, stringType, new TypeDesc[] { bang0Type, bang1Type }); MethodSignature sigBang1Bang0 = new MethodSignature(0, 0, stringType, new TypeDesc[] { bang1Type, bang0Type }); var iMultiGeneric = type.ExplicitlyImplementedInterfaces.First(); var method0_1 = iMultiGeneric.GetMethod("Func", sigBang0Bang1); Assert.NotNull(method0_1); var method1_0 = iMultiGeneric.GetMethod("Func", sigBang1Bang0); Assert.NotNull(method1_0); md1 = algo.ResolveInterfaceMethodToVirtualMethodOnType(method0_1, type); md2 = algo.ResolveInterfaceMethodToVirtualMethodOnType(method1_0, type); Assert.NotNull(md1); Assert.NotNull(md2); }
public static void GetVirtualInvokeMapDependencies(ref DependencyList dependencies, NodeFactory factory, MethodDesc method) { if (NeedsVirtualInvokeInfo(method)) { dependencies = dependencies ?? new DependencyList(); dependencies.Add( factory.NecessaryTypeSymbol(method.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific)), "Reflection virtual invoke owning type"); NativeLayoutMethodNameAndSignatureVertexNode nameAndSig = factory.NativeLayout.MethodNameAndSignatureVertex(method.GetTypicalMethodDefinition()); NativeLayoutPlacedSignatureVertexNode placedNameAndSig = factory.NativeLayout.PlacedSignatureVertex(nameAndSig); dependencies.Add(placedNameAndSig, "Reflection virtual invoke method signature"); if (!method.HasInstantiation) { MethodDesc slotDefiningMethod = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method); if (!factory.VTable(slotDefiningMethod.OwningType).HasFixedSlots) { dependencies.Add(factory.VirtualMethodUse(slotDefiningMethod), "Reflection virtual invoke method"); } } } }
protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType implType) { // Quick check: if decl matches impl, we're done. if (declMethod.OwningType == implType) { return(declMethod); } MethodDesc impl; if (declMethod.OwningType.IsInterface) { impl = implType.ResolveInterfaceMethodTarget(declMethod); if (impl != null) { impl = implType.FindVirtualFunctionTargetMethodOnObjectType(impl); } } else { impl = implType.FindVirtualFunctionTargetMethodOnObjectType(declMethod); if (impl != null && (impl != declMethod)) { MethodDesc slotDefiningMethodImpl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(impl); MethodDesc slotDefiningMethodDecl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(declMethod); if (slotDefiningMethodImpl != slotDefiningMethodDecl) { // We cannot resolve virtual method in case the impl is a different slot from the declMethod impl = null; } } } return(impl); }
public override IEnumerable <CombinedDependencyListEntry> SearchDynamicDependencies(List <DependencyNodeCore <NodeFactory> > markedNodes, int firstNode, NodeFactory factory) { Debug.Assert(_method.IsVirtual && _method.HasInstantiation); List <CombinedDependencyListEntry> dynamicDependencies = new List <CombinedDependencyListEntry>(); for (int i = firstNode; i < markedNodes.Count; i++) { DependencyNodeCore <NodeFactory> entry = markedNodes[i]; EETypeNode entryAsEETypeNode = entry as EETypeNode; if (entryAsEETypeNode == null) { continue; } TypeDesc potentialOverrideType = entryAsEETypeNode.Type; if (!(potentialOverrideType is DefType)) { continue; } Debug.Assert(!potentialOverrideType.IsRuntimeDeterminedSubtype); if (_method.OwningType.HasSameTypeDefinition(potentialOverrideType) && potentialOverrideType.IsInterface && (potentialOverrideType != _method.OwningType)) { if (_method.OwningType.CanCastTo(potentialOverrideType)) { // Variance expansion MethodDesc matchingMethodOnRelatedVariantMethod = potentialOverrideType.GetMethod(_method.Name, _method.GetTypicalMethodDefinition().Signature); matchingMethodOnRelatedVariantMethod = _method.Context.GetInstantiatedMethod(matchingMethodOnRelatedVariantMethod, _method.Instantiation); dynamicDependencies.Add(new CombinedDependencyListEntry(factory.GVMDependencies(matchingMethodOnRelatedVariantMethod), null, "GVM Variant Interface dependency")); } } // If this is an interface gvm, look for types that implement the interface // and other instantantiations that have the same canonical form. // This ensure the various slot numbers remain equivalent across all types where there is an equivalence // relationship in the vtable. if (_method.OwningType.IsInterface) { if (potentialOverrideType.IsInterface) { continue; } foreach (DefType interfaceImpl in potentialOverrideType.RuntimeInterfaces) { if (interfaceImpl.ConvertToCanonForm(CanonicalFormKind.Specific) == _method.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific)) { // Find if the type implements this method. (Note, do this comparision against the generic definition of the method, not the // specific method instantiation that is "method" MethodDesc genericDefinition = interfaceImpl.GetMethod(_method.Name, _method.GetTypicalMethodDefinition().Signature); MethodDesc slotDecl = potentialOverrideType.ResolveInterfaceMethodTarget(genericDefinition); if (slotDecl != null) { CreateDependencyForMethodSlotAndInstantiation(slotDecl, dynamicDependencies, factory); } } } } else { // TODO: Ensure GVM Canon Target TypeDesc overrideTypeCanonCur = potentialOverrideType; TypeDesc methodCanonContainingType = _method.OwningType; while (overrideTypeCanonCur != null) { if (overrideTypeCanonCur.ConvertToCanonForm(CanonicalFormKind.Specific) == methodCanonContainingType.ConvertToCanonForm(CanonicalFormKind.Specific)) { MethodDesc methodDefInDerivedType = potentialOverrideType.GetMethod(_method.Name, _method.GetTypicalMethodDefinition().Signature); if (methodDefInDerivedType != null) { CreateDependencyForMethodSlotAndInstantiation(methodDefInDerivedType, dynamicDependencies, factory); } MethodDesc slotDecl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(_method); if (slotDecl != null) { CreateDependencyForMethodSlotAndInstantiation(slotDecl.GetMethodDefinition(), dynamicDependencies, factory); } } overrideTypeCanonCur = overrideTypeCanonCur.BaseType; } } } return(dynamicDependencies); }
protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType implType, out CORINFO_DEVIRTUALIZATION_DETAIL devirtualizationDetail) { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN; MethodDesc impl; if (declMethod.OwningType.IsInterface) { if (declMethod.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any) || implType.IsCanonicalSubtype(CanonicalFormKind.Any)) { DefType[] implTypeRuntimeInterfaces = implType.RuntimeInterfaces; int canonicallyMatchingInterfacesFound = 0; DefType canonicalInterfaceType = (DefType)declMethod.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific); for (int i = 0; i < implTypeRuntimeInterfaces.Length; i++) { DefType runtimeInterface = implTypeRuntimeInterfaces[i]; if (canonicalInterfaceType.HasSameTypeDefinition(runtimeInterface) && runtimeInterface.ConvertToCanonForm(CanonicalFormKind.Specific) == canonicalInterfaceType) { canonicallyMatchingInterfacesFound++; if (canonicallyMatchingInterfacesFound > 1) { // We cannot resolve the interface as we don't know with exact enough detail which interface // of multiple possible interfaces is being called. devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_MULTIPLE_IMPL; return(null); } } } } if (!implType.CanCastTo(declMethod.OwningType)) { devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CAST; return(null); } impl = implType.ResolveInterfaceMethodTargetWithVariance(declMethod); if (impl != null) { impl = implType.FindVirtualFunctionTargetMethodOnObjectType(impl); } else { MethodDesc dimMethod = null; // This isn't the correct lookup algorithm for variant default interface methods // but as we will drop any results we find in any case, it doesn't matter much. // Non-variant dispatch can simply use ResolveInterfaceMethodToDefaultImplementationOnType // but that implementation currently cannot handle variance. MethodDesc defaultInterfaceDispatchDeclMethod = null; foreach (TypeDesc iface in implType.RuntimeInterfaces) { if (iface == declMethod.OwningType) { defaultInterfaceDispatchDeclMethod = declMethod; break; } if (iface.HasSameTypeDefinition(declMethod.OwningType) && iface.CanCastTo(declMethod.OwningType)) { defaultInterfaceDispatchDeclMethod = iface.FindMethodOnTypeWithMatchingTypicalMethod(declMethod); // Prefer to find the exact match, so don't break immediately } } if (defaultInterfaceDispatchDeclMethod != null) { switch (implType.ResolveInterfaceMethodToDefaultImplementationOnType(defaultInterfaceDispatchDeclMethod, out dimMethod)) { case DefaultInterfaceMethodResolution.Diamond: case DefaultInterfaceMethodResolution.Reabstraction: devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_DIM; return(null); case DefaultInterfaceMethodResolution.DefaultImplementation: if (dimMethod.OwningType.HasInstantiation || (declMethod != defaultInterfaceDispatchDeclMethod)) { // If we devirtualized into a default interface method on a generic type, we should actually return an // instantiating stub but this is not happening. // Making this work is tracked by https://github.com/dotnet/runtime/issues/9588 // In addition, we fail here for variant default interface dispatch devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_DIM; return(null); } else { impl = dimMethod; } break; } } } } else { // The derived class should be a subclass of the base class. // this check is perfomed via typedef checking instead of casting, as we accept canon methods calling exact types TypeDesc checkType; for (checkType = implType; checkType != null && !checkType.HasSameTypeDefinition(declMethod.OwningType); checkType = checkType.BaseType) { } if ((checkType == null) || (checkType.ConvertToCanonForm(CanonicalFormKind.Specific) != declMethod.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific))) { // The derived class should be a subclass of the base class. devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_SUBCLASS; return(null); } else { // At this point, the decl method may be only canonically compatible, but not an exact match to a method in the type hierarchy // Convert it to an exact match. (Or if it is an exact match, the FindMethodOnTypeWithMatchingTypicalMethod will be a no-op) declMethod = checkType.FindMethodOnTypeWithMatchingTypicalMethod(declMethod); } impl = implType.FindVirtualFunctionTargetMethodOnObjectType(declMethod); if (impl != null && (impl != declMethod)) { MethodDesc slotDefiningMethodImpl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(impl); MethodDesc slotDefiningMethodDecl = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(declMethod); if (slotDefiningMethodImpl != slotDefiningMethodDecl) { // If the derived method's slot does not match the vtable slot, // bail on devirtualization, as the method was installed into // the vtable slot via an explicit override and even if the // method is final, the slot may not be. // // Note the jit could still safely devirtualize if it had an exact // class, but such cases are likely rare. devirtualizationDetail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_SLOT; impl = null; } } } return(impl); }
/// <summary> /// Retrieves method whose runtime handle is suitable for use with GVMLookupForSlot. /// </summary> public MethodDesc GetTargetOfGenericVirtualMethodCall(MethodDesc calledMethod) { // Should be a generic virtual method Debug.Assert(calledMethod.HasInstantiation && calledMethod.IsVirtual); // Needs to be either a concrete method, or a runtime determined form. Debug.Assert(!calledMethod.IsCanonicalMethod(CanonicalFormKind.Specific)); MethodDesc targetMethod = calledMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); MethodDesc targetMethodDefinition = targetMethod.GetMethodDefinition(); MethodDesc slotNormalizedMethodDefinition = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(targetMethodDefinition); // If the method defines the slot, we can use that. if (slotNormalizedMethodDefinition == targetMethodDefinition) { return(calledMethod); } // Normalize to the slot defining method MethodDesc slotNormalizedMethod = TypeSystemContext.GetInstantiatedMethod( slotNormalizedMethodDefinition, targetMethod.Instantiation); // Since the slot normalization logic modified what method we're looking at, we need to compute the new target of lookup. // // If we could use virtual method resolution logic with runtime determined methods, we wouldn't need what we're going // to do below. MethodDesc runtimeDeterminedSlotNormalizedMethod; if (!slotNormalizedMethod.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any)) { // If the owning type is not generic, we can use it as-is, potentially only replacing the runtime-determined // method instantiation part. runtimeDeterminedSlotNormalizedMethod = slotNormalizedMethod.GetMethodDefinition(); } else { // If we need a runtime lookup but a normalization to the slot defining method happened above, we need to compute // the runtime lookup in terms of the base type that introduced the slot. // // To do that, we walk the base hierarchy of the runtime determined thing, looking for a type definition that matches // the slot-normalized virtual method. We then find the method on that type. TypeDesc runtimeDeterminedOwningType = calledMethod.OwningType; Debug.Assert(!runtimeDeterminedOwningType.IsInterface); while (!slotNormalizedMethod.OwningType.HasSameTypeDefinition(runtimeDeterminedOwningType)) { TypeDesc runtimeDeterminedBaseTypeDefinition = runtimeDeterminedOwningType.GetTypeDefinition().BaseType; if (runtimeDeterminedBaseTypeDefinition.HasInstantiation) { runtimeDeterminedOwningType = runtimeDeterminedBaseTypeDefinition.InstantiateSignature(runtimeDeterminedOwningType.Instantiation, default); } else { runtimeDeterminedOwningType = runtimeDeterminedBaseTypeDefinition; } } // Now get the method on the newly found type Debug.Assert(runtimeDeterminedOwningType.HasInstantiation); runtimeDeterminedSlotNormalizedMethod = TypeSystemContext.GetMethodForInstantiatedType( slotNormalizedMethod.GetTypicalMethodDefinition(), (InstantiatedType)runtimeDeterminedOwningType); } return(TypeSystemContext.GetInstantiatedMethod(runtimeDeterminedSlotNormalizedMethod, calledMethod.Instantiation)); }
/// <summary> /// Get the virtual slot index of a given virtual method. (This is only valid for non-interface virtuals) /// </summary> /// <param name="virtualMethod">virtual method to get slot index of</param> /// <returns>slot index, or -1</returns> public static int VirtualMethodToSlotIndex(MethodDesc virtualMethod) { Debug.Assert(virtualMethod.IsVirtual); Debug.Assert(!virtualMethod.OwningType.IsInterface); MethodDesc definingMethod = virtualMethod; // If not newslot, make sure we've got the defining method here if (!definingMethod.IsNewSlot) { definingMethod = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(definingMethod); } TypeDesc definingType = definingMethod.OwningType; // Two possible cases for determining slot index that will work // 1. The definingType is a R2R type with full metadata. Compute the MethodDesc, by scanning the list of virtuals present in metadata. Its possible to not get a slot index. In that case return -1 // 2. The definingType is pregenerated, but we can go from metadata to slot index via the runtime mapping tables. if (!IsPregeneratedOrTemplateTypeLoaded(definingType)) { // Case 1 MetadataType definingMetadataType = (MetadataType)definingType; int baseTypeSlotCount = 0; if (definingMetadataType.BaseType != null) { unsafe { if (definingMetadataType.BaseType.RetrieveRuntimeTypeHandleIfPossible()) { baseTypeSlotCount = definingMetadataType.BaseType.GetRuntimeTypeHandle().ToEETypePtr()->NumVtableSlots; } else { baseTypeSlotCount = definingMetadataType.BaseType.GetOrCreateTypeBuilderState().NumVTableSlots; } } } int currentSlot = baseTypeSlotCount; if (definingMetadataType.ConvertToCanonForm(CanonicalFormKind.Specific).IsCanonicalSubtype(CanonicalFormKind.Specific)) { // Deal with the space reserved for the canonical dictionary currentSlot++; } foreach (MethodDesc method in definingMetadataType.GetMethods()) { if (!MethodDefinesVTableSlot(method)) { continue; } if (method == definingMethod) { return(currentSlot); } else { currentSlot++; } } // No slot index defined. return(-1); } else { // Case 2, pregenerated type TypeSystem.NativeFormat.NativeFormatMethod definingMethodOpenType = (TypeSystem.NativeFormat.NativeFormatMethod)definingMethod.GetTypicalMethodDefinition(); MethodSignatureComparer methodSignatureComparer = new MethodSignatureComparer( definingMethodOpenType.MetadataReader, definingMethodOpenType.Handle); if (!definingType.RetrieveRuntimeTypeHandleIfPossible()) { new TypeBuilder().BuildType(definingType); } TypeSystem.NativeFormat.NativeFormatType definingNativeFormatType = (TypeSystem.NativeFormat.NativeFormatType)definingType.GetTypeDefinition(); NativeFormatModuleInfo moduleToLookIn = definingNativeFormatType.MetadataUnit.RuntimeModuleInfo; TypeLoaderEnvironment.VirtualResolveDataResult virtualSlotInfo; if (!TypeLoaderEnvironment.TryGetVirtualResolveData(moduleToLookIn, definingType.RuntimeTypeHandle, Array.Empty <RuntimeTypeHandle>(), ref methodSignatureComparer, out virtualSlotInfo)) { return(-1); } Debug.Assert(!virtualSlotInfo.IsGVM); return(virtualSlotInfo.SlotIndex); } }
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; } } }
public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { // This node does not trigger generation of other nodes. if (relocsOnly) { return(new ObjectData(Array.Empty <byte>(), Array.Empty <Relocation>(), 1, new ISymbolNode[] { this })); } // Ensure the native layout blob has been saved factory.MetadataManager.NativeLayoutInfo.SaveNativeLayoutInfoWriter(factory); var writer = new NativeWriter(); var typeMapHashTable = new VertexHashtable(); Section hashTableSection = writer.NewSection(); hashTableSection.Place(typeMapHashTable); Dictionary <int, HashSet <TypeDesc> > methodsEmitted = new Dictionary <int, HashSet <TypeDesc> >(); // Get a list of all methods that have a method body and metadata from the metadata manager. foreach (var mappingEntry in factory.MetadataManager.GetMethodMapping(factory)) { MethodDesc method = mappingEntry.Entity; // The current format requires us to have an EEType for the owning type. We might want to lift this. if (!factory.MetadataManager.TypeGeneratesEEType(method.OwningType)) { continue; } // We have a method body, we have a metadata token, but we can't get an invoke stub. Bail. if (!factory.MetadataManager.IsReflectionInvokable(method)) { continue; } // Only virtual methods are interesting if (!NeedsVirtualInvokeInfo(method)) { continue; } // // When working with the ProjectN ABI, the entries in the map are based on the definition types. All // instantiations of these definition types will have the same vtable method entries. // On CoreRT, the vtable entries for each instantiated type might not necessarily exist. // Example 1: // If there's a call to Foo<string>.Method1 and a call to Foo<int>.Method2, Foo<string> will // not have Method2 in its vtable and Foo<int> will not have Method1. // Example 2: // If there's a call to Foo<string>.Method1 and a call to Foo<object>.Method2, given that both // of these instantiations share the same canonical form, Foo<__Canon> will have both method // entries, and therefore Foo<string> and Foo<object> will have both entries too. // For this reason, the entries that we write to the map in CoreRT will be based on the canonical form // of the method's containing type instead of the open type definition. // // Similarly, given that we use the open type definition for ProjectN, the slot numbers ignore dictionary // entries in the vtable, and computing the correct slot number in the presence of dictionary entries is // done at runtime. When working with the CoreRT ABI, the correct slot numbers will be written to the map, // and no adjustments will be performed at runtime. // TypeDesc containingTypeKey; if (factory.Target.Abi == TargetAbi.ProjectN) { containingTypeKey = method.OwningType.GetTypeDefinition(); } else { containingTypeKey = method.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific); } HashSet <TypeDesc> cache; if (!methodsEmitted.TryGetValue(mappingEntry.MetadataHandle, out cache)) { methodsEmitted[mappingEntry.MetadataHandle] = cache = new HashSet <TypeDesc>(); } // Only one record is needed for any instantiation. if (!cache.Add(containingTypeKey)) { continue; } // Grammar of an entry in the hash table: // Virtual Method uses a normal slot // TypeKey + NameAndSig metadata offset into the native layout metadata + (NumberOfStepsUpParentHierarchyToType << 1) + slot // OR // Generic Virtual Method // TypeKey + NameAndSig metadata offset into the native layout metadata + (NumberOfStepsUpParentHierarchyToType << 1 + 1) MethodDesc declaringMethodForSlot = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method.GetTypicalMethodDefinition()); uint parentHierarchyDistance = 0; TypeDesc typeOfDeclaringMethodForSlot = declaringMethodForSlot.OwningType.GetTypeDefinition(); TypeDesc currentType = method.OwningType.GetTypeDefinition(); while (typeOfDeclaringMethodForSlot != currentType) { parentHierarchyDistance++; currentType = currentType.BaseType.GetTypeDefinition(); } Vertex vertex = null; ISymbolNode containingTypeKeyNode = factory.NecessaryTypeSymbol(containingTypeKey); NativeLayoutMethodNameAndSignatureVertexNode nameAndSig = factory.NativeLayout.MethodNameAndSignatureVertex(method.GetTypicalMethodDefinition()); NativeLayoutPlacedSignatureVertexNode placedNameAndSig = factory.NativeLayout.PlacedSignatureVertex(nameAndSig); if (method.HasInstantiation) { vertex = writer.GetTuple( writer.GetUnsignedConstant(_externalReferences.GetIndex(containingTypeKeyNode)), writer.GetUnsignedConstant((uint)placedNameAndSig.SavedVertex.VertexOffset), writer.GetUnsignedConstant((parentHierarchyDistance << 1) + VirtualInvokeTableEntry.GenericVirtualMethod)); } else { // Get the declaring method for slot on the instantiated declaring type if (method.OwningType.HasInstantiation) { TypeDesc containingTypeOfDeclaringMethodForSlot = method.OwningType; for (int i = 0; i < parentHierarchyDistance; i++) { containingTypeOfDeclaringMethodForSlot = containingTypeOfDeclaringMethodForSlot.BaseType; } declaringMethodForSlot = containingTypeOfDeclaringMethodForSlot.GetMethod(declaringMethodForSlot.Name, declaringMethodForSlot.Signature); } int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, declaringMethodForSlot, factory.Target.Abi != TargetAbi.ProjectN); if (slot == -1) { // This method doesn't have a slot. (At this time, this is only done for the Object.Finalize method) Debug.Assert(declaringMethodForSlot.Name == "Finalize"); continue; } vertex = writer.GetTuple( writer.GetUnsignedConstant(_externalReferences.GetIndex(containingTypeKeyNode)), writer.GetUnsignedConstant((uint)placedNameAndSig.SavedVertex.VertexOffset)); vertex = writer.GetTuple(vertex, writer.GetUnsignedConstant(parentHierarchyDistance << 1), writer.GetUnsignedConstant((uint)slot)); } int hashCode = containingTypeKey.GetHashCode(); typeMapHashTable.Append((uint)hashCode, hashTableSection.Place(vertex)); } byte[] hashTableBytes = writer.Save(); _endSymbol.SetSymbolOffset(hashTableBytes.Length); return(new ObjectData(hashTableBytes, Array.Empty <Relocation>(), 1, new ISymbolNode[] { this, _endSymbol })); }
public override IEnumerable <DependencyListEntry> GetStaticDependencies(NodeFactory factory) { Debug.Assert(!factory.MetadataManager.IsReflectionBlocked(_method.GetTypicalMethodDefinition())); // Depends on static virtual method support. Turning off reflection for now. if (_method.IsVirtual && _method.Signature.IsStatic) { return(null); } DependencyList dependencies = new DependencyList(); factory.MetadataManager.GetDependenciesDueToReflectability(ref dependencies, factory, _method); // No runtime artifacts needed if this is a generic definition if (_method.IsGenericMethodDefinition || _method.OwningType.IsGenericDefinition) { return(dependencies); } // Ensure we consistently apply reflectability to all methods sharing the same definition. // Different instantiations of the method have a conditional dependency on the definition node that // brings a ReflectableMethod of the instantiated method if it's necessary for it to be reflectable. MethodDesc typicalMethod = _method.GetTypicalMethodDefinition(); if (typicalMethod != _method) { dependencies.Add(factory.ReflectableMethod(typicalMethod), "Definition of the reflectable method"); } MethodDesc canonMethod = _method.GetCanonMethodTarget(CanonicalFormKind.Specific); if (canonMethod != _method) { dependencies.Add(factory.ReflectableMethod(canonMethod), "Canonical version of the reflectable method"); } // Make sure we generate the method body and other artifacts. if (MetadataManager.IsMethodSupportedInReflectionInvoke(_method)) { if (_method.IsVirtual) { if (_method.HasInstantiation) { dependencies.Add(factory.GVMDependencies(_method.GetCanonMethodTarget(CanonicalFormKind.Specific)), "GVM callable reflectable method"); } else { // Virtual method use is tracked on the slot defining method only. MethodDesc slotDefiningMethod = MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(_method); if (!factory.VTable(slotDefiningMethod.OwningType).HasFixedSlots) { dependencies.Add(factory.VirtualMethodUse(slotDefiningMethod), "Virtually callable reflectable method"); } } } if (!_method.IsAbstract) { dependencies.Add(factory.MethodEntrypoint(canonMethod), "Body of a reflectable method"); if (_method.HasInstantiation && _method != canonMethod) { dependencies.Add(factory.MethodGenericDictionary(_method), "Dictionary of a reflectable method"); } } } return(dependencies); }
private void ImportCall(ILOpcode opcode, int token) { // Strip runtime determined characteristics off of the method (because that's how RyuJIT operates) var runtimeDeterminedMethod = (MethodDesc)_methodIL.GetObject(token); MethodDesc method = runtimeDeterminedMethod; if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) { method = runtimeDeterminedMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); } if (method.IsRawPInvoke()) { // Raw P/invokes don't have any dependencies. return; } string reason = null; switch (opcode) { case ILOpcode.newobj: reason = "newobj"; break; case ILOpcode.call: reason = "call"; break; case ILOpcode.callvirt: reason = "callvirt"; break; case ILOpcode.ldftn: reason = "ldftn"; break; case ILOpcode.ldvirtftn: reason = "ldvirtftn"; break; default: Debug.Assert(false); break; } if (opcode == ILOpcode.newobj) { TypeDesc owningType = runtimeDeterminedMethod.OwningType; if (owningType.IsString) { // String .ctor handled specially below } else if (owningType.IsGCPointer) { if (owningType.IsRuntimeDeterminedSubtype) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, owningType), reason); } else { _dependencies.Add(_factory.ConstructedTypeSymbol(owningType), reason); } if (owningType.IsMdArray) { _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArr_NonVarArg), reason); return; } else { _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewObject), reason); } } if (owningType.IsDelegate) { // If this is a verifiable delegate construction sequence, the previous instruction is a ldftn/ldvirtftn if (_previousInstructionOffset >= 0 && _ilBytes[_previousInstructionOffset] == (byte)ILOpcode.prefix1) { // TODO: for ldvirtftn we need to also check for the `dup` instruction, otherwise this is a normal newobj. ILOpcode previousOpcode = (ILOpcode)(0x100 + _ilBytes[_previousInstructionOffset + 1]); if (previousOpcode == ILOpcode.ldvirtftn || previousOpcode == ILOpcode.ldftn) { int delTargetToken = ReadILTokenAt(_previousInstructionOffset + 2); var delTargetMethod = (MethodDesc)_methodIL.GetObject(delTargetToken); TypeDesc canonDelegateType = method.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific); DelegateCreationInfo info = _compilation.GetDelegateCtor(canonDelegateType, delTargetMethod, previousOpcode == ILOpcode.ldvirtftn); if (info.NeedsRuntimeLookup) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.DelegateCtor, info), reason); } else { _dependencies.Add(_factory.ReadyToRunHelper(ReadyToRunHelperId.DelegateCtor, info), reason); } return; } } } } if (method.OwningType.IsDelegate && method.Name == "Invoke") { // TODO: might not want to do this if scanning for reflection. // This is expanded as an intrinsic, not a function call. return; } if (method.IsIntrinsic) { if (IsRuntimeHelpersInitializeArray(method)) { if (_previousInstructionOffset >= 0 && _ilBytes[_previousInstructionOffset] == (byte)ILOpcode.ldtoken) { return; } } if (IsRuntimeTypeHandleGetValueInternal(method)) { if (_previousInstructionOffset >= 0 && _ilBytes[_previousInstructionOffset] == (byte)ILOpcode.ldtoken) { return; } } if (IsActivatorDefaultConstructorOf(method)) { if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.DefaultConstructor, runtimeDeterminedMethod.Instantiation[0]), reason); } else { MethodDesc ctor = method.Instantiation[0].GetDefaultConstructor(); if (ctor == null) { MetadataType activatorType = _compilation.TypeSystemContext.SystemModule.GetKnownType("System", "Activator"); MetadataType classWithMissingCtor = activatorType.GetKnownNestedType("ClassWithMissingConstructor"); ctor = classWithMissingCtor.GetParameterlessConstructor(); } _dependencies.Add(_factory.CanonicalEntrypoint(ctor), reason); } return; } if (method.OwningType.IsByReferenceOfT && (method.IsConstructor || method.Name == "get_Value")) { return; } if (IsEETypePtrOf(method)) { if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.Instantiation[0]), reason); } else { _dependencies.Add(_factory.ConstructedTypeSymbol(method.Instantiation[0]), reason); } return; } } TypeDesc exactType = method.OwningType; if (method.IsNativeCallable && (opcode != ILOpcode.ldftn && opcode != ILOpcode.ldvirtftn)) { ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramNativeCallable, method); } bool resolvedConstraint = false; bool forceUseRuntimeLookup = false; MethodDesc methodAfterConstraintResolution = method; if (_constrained != null) { // We have a "constrained." call. Try a partial resolve of the constraint call. Note that this // will not necessarily resolve the call exactly, since we might be compiling // shared generic code - it may just resolve it to a candidate suitable for // JIT compilation, and require a runtime lookup for the actual code pointer // to call. TypeDesc constrained = _constrained; if (constrained.IsRuntimeDeterminedSubtype) { constrained = constrained.ConvertToCanonForm(CanonicalFormKind.Specific); } MethodDesc directMethod = constrained.GetClosestDefType().TryResolveConstraintMethodApprox(method.OwningType, method, out forceUseRuntimeLookup); if (directMethod == null && constrained.IsEnum) { // Constrained calls to methods on enum methods resolve to System.Enum's methods. System.Enum is a reference // type though, so we would fail to resolve and box. We have a special path for those to avoid boxing. directMethod = _compilation.TypeSystemContext.TryResolveConstrainedEnumMethod(constrained, method); } if (directMethod != null) { // Either // 1. no constraint resolution at compile time (!directMethod) // OR 2. no code sharing lookup in call // OR 3. we have have resolved to an instantiating stub methodAfterConstraintResolution = directMethod; Debug.Assert(!methodAfterConstraintResolution.OwningType.IsInterface); resolvedConstraint = true; exactType = constrained; } else if (constrained.IsValueType) { // We'll need to box `this`. Note we use _constrained here, because the other one is canonical. AddBoxingDependencies(_constrained, reason); } } MethodDesc targetMethod = methodAfterConstraintResolution; bool exactContextNeedsRuntimeLookup; if (targetMethod.HasInstantiation) { exactContextNeedsRuntimeLookup = targetMethod.IsSharedByGenericInstantiations; } else { exactContextNeedsRuntimeLookup = exactType.IsCanonicalSubtype(CanonicalFormKind.Any); } // // Determine whether to perform direct call // bool directCall = false; if (targetMethod.Signature.IsStatic) { // Static methods are always direct calls directCall = true; } else if (targetMethod.OwningType.IsInterface) { // Force all interface calls to be interpreted as if they are virtual. directCall = false; } else if ((opcode != ILOpcode.callvirt && opcode != ILOpcode.ldvirtftn) || resolvedConstraint) { directCall = true; } else { if (!targetMethod.IsVirtual || targetMethod.IsFinal || targetMethod.OwningType.IsSealed()) { directCall = true; } } bool allowInstParam = opcode != ILOpcode.ldvirtftn && opcode != ILOpcode.ldftn; if (directCall && !allowInstParam && targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstArg()) { // Needs a single address to call this method but the method needs a hidden argument. // We need a fat function pointer for this that captures both things. if (exactContextNeedsRuntimeLookup) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.MethodEntry, runtimeDeterminedMethod), reason); } else { _dependencies.Add(_factory.FatFunctionPointer(runtimeDeterminedMethod), reason); } } else if (directCall) { bool referencingArrayAddressMethod = false; if (targetMethod.IsIntrinsic) { // If this is an intrinsic method with a callsite-specific expansion, this will replace // the method with a method the intrinsic expands into. If it's not the special intrinsic, // method stays unchanged. targetMethod = _compilation.ExpandIntrinsicForCallsite(targetMethod, _canonMethod); // Array address method requires special dependency tracking. referencingArrayAddressMethod = targetMethod.IsArrayAddressMethod(); } MethodDesc concreteMethod = targetMethod; targetMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); if (targetMethod.IsConstructor && targetMethod.OwningType.IsString) { _dependencies.Add(_factory.StringAllocator(targetMethod), reason); } else if (exactContextNeedsRuntimeLookup) { if (targetMethod.IsSharedByGenericInstantiations && !resolvedConstraint && !referencingArrayAddressMethod) { ISymbolNode instParam = null; if (targetMethod.RequiresInstMethodDescArg()) { instParam = GetGenericLookupHelper(ReadyToRunHelperId.MethodDictionary, runtimeDeterminedMethod); } else if (targetMethod.RequiresInstMethodTableArg()) { bool hasHiddenParameter = true; if (targetMethod.IsIntrinsic) { if (_factory.TypeSystemContext.IsSpecialUnboxingThunkTargetMethod(targetMethod)) { hasHiddenParameter = false; } } if (hasHiddenParameter) { instParam = GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.OwningType); } } if (instParam != null) { _dependencies.Add(instParam, reason); } _dependencies.Add(_factory.CanonicalEntrypoint(targetMethod), reason); } else { Debug.Assert(!forceUseRuntimeLookup); _dependencies.Add(_factory.MethodEntrypoint(targetMethod), reason); if (targetMethod.RequiresInstMethodTableArg() && resolvedConstraint) { if (_constrained.IsRuntimeDeterminedSubtype) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, _constrained), reason); } else { _dependencies.Add(_factory.ConstructedTypeSymbol(_constrained), reason); } } if (referencingArrayAddressMethod && !_isReadOnly) { // Address method is special - it expects an instantiation argument, unless a readonly prefix was applied. _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, runtimeDeterminedMethod.OwningType), reason); } } } else { ISymbolNode instParam = null; if (targetMethod.RequiresInstMethodDescArg()) { instParam = _compilation.NodeFactory.MethodGenericDictionary(concreteMethod); } else if (targetMethod.RequiresInstMethodTableArg() || (referencingArrayAddressMethod && !_isReadOnly)) { // Ask for a constructed type symbol because we need the vtable to get to the dictionary instParam = _compilation.NodeFactory.ConstructedTypeSymbol(concreteMethod.OwningType); } if (instParam != null) { _dependencies.Add(instParam, reason); if (!referencingArrayAddressMethod) { _dependencies.Add(_compilation.NodeFactory.ShadowConcreteMethod(concreteMethod), reason); } else { // We don't want array Address method to be modeled in the generic dependency analysis. // The method doesn't actually have runtime determined dependencies (won't do // any generic lookups). _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(targetMethod), reason); } } else if (targetMethod.AcquiresInstMethodTableFromThis()) { _dependencies.Add(_compilation.NodeFactory.ShadowConcreteMethod(concreteMethod), reason); } else { _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(targetMethod), reason); } } } else if (method.HasInstantiation) { // Generic virtual method call if (exactContextNeedsRuntimeLookup) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.MethodHandle, runtimeDeterminedMethod), reason); } else { _dependencies.Add(_factory.RuntimeMethodHandle(runtimeDeterminedMethod), reason); } _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GVMLookupForSlot), reason); } else if (method.OwningType.IsInterface) { if (exactContextNeedsRuntimeLookup) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.VirtualDispatchCell, runtimeDeterminedMethod), reason); } else { _dependencies.Add(_factory.InterfaceDispatchCell(method), reason); } } else if (_compilation.HasFixedSlotVTable(method.OwningType)) { // No dependencies: virtual call through the vtable } else { MethodDesc slotDefiningMethod = targetMethod.IsNewSlot ? targetMethod : MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(targetMethod).Normalize(); _dependencies.Add(_factory.VirtualMethodUse(slotDefiningMethod), reason); } }
public void TestFindBaseUnificationGroup() { var algo = new MetadataVirtualMethodAlgorithm(); var ilModule = _context.GetModuleForSimpleName("ILTestAssembly"); MetadataType myDerived2Type = ilModule.GetType("VirtualFunctionOverride", "MyDerived2"); Assert.NotNull(myDerived2Type); MethodDesc method = myDerived2Type.GetMethod("get_foo", null); Assert.NotNull(method); MethodDesc virtualMethod = algo.FindVirtualFunctionTargetMethodOnObjectType(method, myDerived2Type); Assert.NotNull(virtualMethod); Assert.Equal(method, virtualMethod); }
private void ImportCall(ILOpcode opcode, int token) { // Strip runtime determined characteristics off of the method (because that's how RyuJIT operates) var runtimeDeterminedMethod = (MethodDesc)_methodIL.GetObject(token); MethodDesc method = runtimeDeterminedMethod; if (runtimeDeterminedMethod.IsRuntimeDeterminedExactMethod) { method = runtimeDeterminedMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); } if (method.IsRawPInvoke()) { // Raw P/invokes don't have any dependencies. return; } string reason = null; switch (opcode) { case ILOpcode.newobj: reason = "newobj"; break; case ILOpcode.call: reason = "call"; break; case ILOpcode.callvirt: reason = "callvirt"; break; case ILOpcode.ldftn: reason = "ldftn"; break; case ILOpcode.ldvirtftn: reason = "ldvirtftn"; break; default: Debug.Assert(false); break; } // If we're scanning the fallback body because scanning the real body failed, don't trigger cctor. // Accessing the cctor could have been a reason why we failed. if (!_isFallbackBodyCompilation) { // Do we need to run the cctor? TypeDesc owningType = runtimeDeterminedMethod.OwningType; if (_factory.TypeSystemContext.HasLazyStaticConstructor(owningType)) { // For beforefieldinit, we can wait for field access. if (!((MetadataType)owningType).IsBeforeFieldInit) { // Accessing the static base will trigger the cctor. if (owningType.IsRuntimeDeterminedSubtype) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.GetNonGCStaticBase, owningType), reason); } else { _dependencies.Add(_factory.ReadyToRunHelper(ReadyToRunHelperId.GetNonGCStaticBase, owningType), reason); } } } } if (opcode == ILOpcode.newobj) { TypeDesc owningType = runtimeDeterminedMethod.OwningType; if (owningType.IsString) { // String .ctor handled specially below } else { // Nullable needs to be unwrapped. if (owningType.IsNullable) { owningType = owningType.Instantiation[0]; } if (owningType.IsRuntimeDeterminedSubtype) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, owningType), reason); } else { _dependencies.Add(_factory.ConstructedTypeSymbol(owningType), reason); } if (owningType.IsMdArray) { _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewMultiDimArr_NonVarArg), reason); return; } else { _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.NewObject), reason); } } if (owningType.IsDelegate) { // If this is a verifiable delegate construction sequence, the previous instruction is a ldftn/ldvirtftn if (_previousInstructionOffset >= 0 && _ilBytes[_previousInstructionOffset] == (byte)ILOpcode.prefix1) { // TODO: for ldvirtftn we need to also check for the `dup` instruction, otherwise this is a normal newobj. ILOpcode previousOpcode = (ILOpcode)(0x100 + _ilBytes[_previousInstructionOffset + 1]); if (previousOpcode == ILOpcode.ldvirtftn || previousOpcode == ILOpcode.ldftn) { int delTargetToken = ReadILTokenAt(_previousInstructionOffset + 2); var delTargetMethod = (MethodDesc)_methodIL.GetObject(delTargetToken); TypeDesc canonDelegateType = method.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific); DelegateCreationInfo info = _compilation.GetDelegateCtor(canonDelegateType, delTargetMethod, previousOpcode == ILOpcode.ldvirtftn); if (info.NeedsRuntimeLookup) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.DelegateCtor, info), reason); } else { _dependencies.Add(_factory.ReadyToRunHelper(ReadyToRunHelperId.DelegateCtor, info), reason); } return; } } } } if (method.OwningType.IsDelegate && method.Name == "Invoke") { // TODO: might not want to do this if scanning for reflection. // This is expanded as an intrinsic, not a function call. return; } if (method.IsIntrinsic) { if (IsRuntimeHelpersInitializeArray(method)) { if (_previousInstructionOffset >= 0 && _ilBytes[_previousInstructionOffset] == (byte)ILOpcode.ldtoken) { return; } } if (IsRuntimeTypeHandleGetValueInternal(method)) { if (_previousInstructionOffset >= 0 && _ilBytes[_previousInstructionOffset] == (byte)ILOpcode.ldtoken) { return; } } } TypeDesc exactType = method.OwningType; bool resolvedConstraint = false; bool forceUseRuntimeLookup = false; MethodDesc methodAfterConstraintResolution = method; if (_constrained != null) { // We have a "constrained." call. Try a partial resolve of the constraint call. Note that this // will not necessarily resolve the call exactly, since we might be compiling // shared generic code - it may just resolve it to a candidate suitable for // JIT compilation, and require a runtime lookup for the actual code pointer // to call. MethodDesc directMethod = _constrained.GetClosestDefType().TryResolveConstraintMethodApprox(method.OwningType, method, out forceUseRuntimeLookup); if (directMethod == null && _constrained.IsEnum) { // Constrained calls to methods on enum methods resolve to System.Enum's methods. System.Enum is a reference // type though, so we would fail to resolve and box. We have a special path for those to avoid boxing. directMethod = _compilation.TypeSystemContext.TryResolveConstrainedEnumMethod(_constrained, method); } if (directMethod != null) { // Either // 1. no constraint resolution at compile time (!directMethod) // OR 2. no code sharing lookup in call // OR 3. we have have resolved to an instantiating stub methodAfterConstraintResolution = directMethod; Debug.Assert(!methodAfterConstraintResolution.OwningType.IsInterface); resolvedConstraint = true; exactType = _constrained; } else if (_constrained.IsValueType) { // We'll need to box `this`. AddBoxingDependencies(_constrained, reason); } _constrained = null; } MethodDesc targetMethod = methodAfterConstraintResolution; bool exactContextNeedsRuntimeLookup; if (targetMethod.HasInstantiation) { exactContextNeedsRuntimeLookup = targetMethod.IsSharedByGenericInstantiations; } else { exactContextNeedsRuntimeLookup = exactType.IsCanonicalSubtype(CanonicalFormKind.Any); } // // Determine whether to perform direct call // bool directCall = false; if (targetMethod.Signature.IsStatic) { // Static methods are always direct calls directCall = true; } else if (targetMethod.OwningType.IsInterface) { // Force all interface calls to be interpreted as if they are virtual. directCall = false; } else if ((opcode != ILOpcode.callvirt && opcode != ILOpcode.ldvirtftn) || resolvedConstraint) { directCall = true; } else { if (!targetMethod.IsVirtual || targetMethod.IsFinal || targetMethod.OwningType.IsSealed()) { directCall = true; } } bool allowInstParam = opcode != ILOpcode.ldvirtftn && opcode != ILOpcode.ldftn; if (directCall && !allowInstParam && targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific).RequiresInstArg()) { // Needs a single address to call this method but the method needs a hidden argument. // We need a fat function pointer for this that captures both things. if (exactContextNeedsRuntimeLookup) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.MethodEntry, runtimeDeterminedMethod), reason); } else { _dependencies.Add(_factory.FatFunctionPointer(runtimeDeterminedMethod), reason); } } else if (directCall) { bool referencingArrayAddressMethod = false; if (targetMethod.IsIntrinsic) { // If this is an intrinsic method with a callsite-specific expansion, this will replace // the method with a method the intrinsic expands into. If it's not the special intrinsic, // method stays unchanged. targetMethod = _compilation.ExpandIntrinsicForCallsite(targetMethod, _canonMethod); // Array address method requires special dependency tracking. referencingArrayAddressMethod = targetMethod.IsArrayAddressMethod(); } MethodDesc concreteMethod = targetMethod; targetMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific); if (targetMethod.IsConstructor && targetMethod.OwningType.IsString) { _dependencies.Add(_factory.StringAllocator(targetMethod), reason); } else if (exactContextNeedsRuntimeLookup) { if (targetMethod.IsSharedByGenericInstantiations && !resolvedConstraint && !referencingArrayAddressMethod) { _dependencies.Add(_factory.RuntimeDeterminedMethod(runtimeDeterminedMethod), reason); } else { Debug.Assert(!forceUseRuntimeLookup); _dependencies.Add(_factory.MethodEntrypoint(targetMethod), reason); } } else { ISymbolNode instParam = null; if (targetMethod.RequiresInstMethodDescArg()) { instParam = _compilation.NodeFactory.MethodGenericDictionary(concreteMethod); } else if (targetMethod.RequiresInstMethodTableArg() || referencingArrayAddressMethod) { // Ask for a constructed type symbol because we need the vtable to get to the dictionary instParam = _compilation.NodeFactory.ConstructedTypeSymbol(concreteMethod.OwningType); } if (instParam != null) { _dependencies.Add(instParam, reason); if (!referencingArrayAddressMethod) { _dependencies.Add(_compilation.NodeFactory.ShadowConcreteMethod(concreteMethod), reason); } else { // We don't want array Address method to be modeled in the generic dependency analysis. // The method doesn't actually have runtime determined dependencies (won't do // any generic lookups). _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(targetMethod), reason); } } else if (targetMethod.AcquiresInstMethodTableFromThis()) { _dependencies.Add(_compilation.NodeFactory.ShadowConcreteMethod(concreteMethod), reason); } else { _dependencies.Add(_compilation.NodeFactory.MethodEntrypoint(targetMethod), reason); } } } else if (method.HasInstantiation) { // Generic virtual method call if (exactContextNeedsRuntimeLookup) { _dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.MethodHandle, runtimeDeterminedMethod), reason); } else { _dependencies.Add(_factory.RuntimeMethodHandle(runtimeDeterminedMethod), reason); } _dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GVMLookupForSlot), reason); } else { ReadyToRunHelperId helper; if (opcode == ILOpcode.ldvirtftn) { helper = ReadyToRunHelperId.ResolveVirtualFunction; } else { Debug.Assert(opcode == ILOpcode.callvirt); helper = ReadyToRunHelperId.VirtualCall; } if (exactContextNeedsRuntimeLookup && targetMethod.OwningType.IsInterface) { _dependencies.Add(GetGenericLookupHelper(helper, runtimeDeterminedMethod), reason); } else { // Get the slot defining method to make sure our virtual method use tracking gets this right. // For normal C# code the targetMethod will always be newslot. MethodDesc slotDefiningMethod = targetMethod.IsNewSlot ? targetMethod : MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(targetMethod); _dependencies.Add(_factory.ReadyToRunHelper(helper, slotDefiningMethod), reason); } } }