Skip to content

MethodWithToken doesn't resolve the correct OwningType for methods on generic types. #123190

@jtschuster

Description

@jtschuster

Task-returning thunk methods on generic types need support for resolving MethodDesc's to MethodSpec or at least MemberRef tokens. MethodWithToken expects a valid ModuleToken for a given method, which exists for methods on generic types and generic methods if the method is in an assembly. In generated IL, we don't have these tokens available. We emit TypeRefs and MemberRefs in the mutable module that point to TypeDefs and MethodDefs in referenced assemblies. We expect to be able to emit any types and methods we need to reference as a signature, only needing tokens that point to required TypeDefs. However, this internal use of a single token that represents a generic method that we may not already have a valid ModuleToken for. If we have a scheme to go from GenericMethod to MethodSpec / MemberRef in the MutableModule, we should be able to work around this issue. However, the ModuleToken resolver and mutable module only resolve or create ModuleTokens that resolve to Definitions. At the very least, we need a MemberRef to the method on the instantiated type.

The relevant code is in HandleToModuleToken:

MethodILScope methodILDef = methodIL.GetMethodILScopeDefinition();
bool isFauxMethodIL = !(methodILDef is IEcmaMethodIL);
if (isFauxMethodIL)
{

// It's okay to strip the instantiation away because we don't need a MethodSpec
// token - SignatureBuilder will generate the generic method signature
// using instantiation parameters from the MethodDesc entity.
resultMethod = resultMethod.GetTypicalMethodDefinition();

Repro program (requires #122651 and reenabling generic async methods):

public class Program
{
    public static async Task Main()
    {
        await GenericType<int>.AsyncMethod()
    }

    public class GenericType<T>
    {
        public static async Task AsyncMethod()
        {
            Task.Delay(100);
        }
    }
}

IIRC the call stack with issues is:

// READYTORUN: FUTURE: Direct calls if possible
pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol(
_compilation.NodeFactory.MethodEntrypoint(
ComputeMethodWithToken(nonUnboxingMethod, ref pResolvedToken, constrainedType, unboxing: isUnboxingStub),
isInstantiatingStub: useInstantiatingStub,
isPrecodeImportRequired: (flags & CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN) != 0,
isJumpableImportRequired: false));

private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RESOLVED_TOKEN pResolvedToken, TypeDesc constrainedType, bool unboxing)
{
ModuleToken token = HandleToModuleToken(ref pResolvedToken, method, out object context, ref constrainedType);
TypeDesc devirtualizedMethodOwner = null;
if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod)
{
devirtualizedMethodOwner = HandleToObject(pResolvedToken.hClass);
}
return new MethodWithToken(method, token, constrainedType: constrainedType, unboxing: unboxing, context: context, devirtualizedMethodOwner: devirtualizedMethodOwner);
}

private ModuleToken HandleToModuleToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken)

In HandleToModuleToken, the instantiation is stripped. Then it comes back to new MethodWithToken()
return new MethodWithToken(method, token, constrainedType: constrainedType, unboxing: unboxing, context: context, devirtualizedMethodOwner: devirtualizedMethodOwner);

Which calculates the OwningType from the ModuleToken for the method
public MethodWithToken(MethodDesc method, ModuleToken token, TypeDesc constrainedType, bool unboxing, object context, TypeDesc devirtualizedMethodOwner = null)
{
Debug.Assert(!method.IsUnboxingThunk());
Debug.Assert(!method.IsAsyncVariant());
Method = method;
Token = token;
ConstrainedType = constrainedType;
Unboxing = unboxing;
OwningType = GetMethodTokenOwningType(this, constrainedType, context, devirtualizedMethodOwner, out OwningTypeNotDerivedFromToken);
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions