diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index c184a263bf1bf5..ba3c4515402fb2 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -2707,6 +2707,7 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re { uint32_t token = getU4LittleEndian(m_ip + 1); bool isVirtual = (*m_ip == CEE_CALLVIRT); + bool isDelegateInvoke = false; CORINFO_RESOLVED_TOKEN resolvedCallToken; CORINFO_CALL_INFO callInfo; @@ -2758,6 +2759,11 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re } } + if (callInfo.methodFlags & CORINFO_FLG_DELEGATE_INVOKE) + { + isDelegateInvoke = true; + } + if (callInfo.thisTransform != CORINFO_NO_THIS_TRANSFORM) { assert(pConstrainedToken != NULL); @@ -3031,7 +3037,15 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re // before the call. // TODO: Add null checking behavior somewhere here! } - AddIns((isPInvoke && !isMarshaledPInvoke) ? INTOP_CALL_PINVOKE : INTOP_CALL); + if (isDelegateInvoke) + { + assert(!isPInvoke && !isMarshaledPInvoke); + AddIns(INTOP_CALLDELEGATE); + } + else + { + AddIns((isPInvoke && !isMarshaledPInvoke) ? INTOP_CALL_PINVOKE : INTOP_CALL); + } m_pLastNewIns->data[0] = GetMethodDataItemIndex(callInfo.hMethod); if (isPInvoke && !isMarshaledPInvoke) { diff --git a/src/coreclr/interpreter/intops.def b/src/coreclr/interpreter/intops.def index 0861d0f21fbfa7..b88b71129a28c2 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -356,6 +356,7 @@ OPDEF(INTOP_LDFLDA, "ldflda", 4, 1, 1, InterpOpInt) // Calls OPDEF(INTOP_CALL, "call", 4, 1, 1, InterpOpMethodHandle) +OPDEF(INTOP_CALLDELEGATE, "call.delegate", 4, 1, 1, InterpOpMethodHandle) OPDEF(INTOP_CALLI, "calli", 5, 1, 2, InterpOpLdPtr) OPDEF(INTOP_CALLVIRT, "callvirt", 4, 1, 1, InterpOpMethodHandle) OPDEF(INTOP_CALL_PINVOKE, "call.pinvoke", 6, 1, 1, InterpOpMethodHandle) // inlined (no marshaling wrapper) pinvokes only diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 881b58c4c3a469..e5cc637e17fa06 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -14,9 +14,41 @@ #ifdef TARGET_WASM void InvokeCalliStub(PCODE ftn, CallStubHeader *stubHeaderTemplate, int8_t *pArgs, int8_t *pRet); void InvokeCompiledMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE target); +void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, int8_t *pRet, PCODE target); #else #include "callstubgenerator.h" +CallStubHeader *UpdateCallStubForMethod(MethodDesc *pMD) +{ + CONTRACTL + { + THROWS; + MODE_ANY; + PRECONDITION(CheckPointer(pMD)); + } + CONTRACTL_END + + GCX_PREEMP(); + + CallStubGenerator callStubGenerator; + + AllocMemTracker amTracker; + CallStubHeader *header = callStubGenerator.GenerateCallStub(pMD, &amTracker, true /* interpreterToNative */); + + if (pMD->SetCallStub(header)) + { + amTracker.SuppressRelease(); + } + else + { + // We have lost the race for generating the header, use the one that was generated by another thread + // and let the amTracker release the memory of the one we generated. + header = pMD->GetCallStub(); + } + + return header; +} + void InvokeCompiledMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE target) { CONTRACTL @@ -32,22 +64,7 @@ void InvokeCompiledMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE ta CallStubHeader *pHeader = pMD->GetCallStub(); if (pHeader == NULL) { - CallStubGenerator callStubGenerator; - GCX_PREEMP(); - - AllocMemTracker amTracker; - pHeader = callStubGenerator.GenerateCallStub(pMD, &amTracker, true /* interpreterToNative */); - - if (pMD->SetCallStub(pHeader)) - { - amTracker.SuppressRelease(); - } - else - { - // We have lost the race for generating the header, use the one that was generated by another thread - // and let the amTracker release the memory of the one we generated. - pHeader = pMD->GetCallStub(); - } + pHeader = UpdateCallStubForMethod(pMD); } // Interpreter-FIXME: Potential race condition if a single CallStubHeader is reused for multiple targets. @@ -56,6 +73,34 @@ void InvokeCompiledMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE ta pHeader->Invoke(pHeader->Routines, pArgs, pRet, pHeader->TotalStackSize); } +void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, int8_t *pRet, PCODE target) +{ + CONTRACTL + { + THROWS; + MODE_ANY; + PRECONDITION(CheckPointer(pMDDelegateInvoke)); + PRECONDITION(CheckPointer(pArgs)); + PRECONDITION(CheckPointer(pRet)); + } + CONTRACTL_END + + CallStubHeader *stubHeaderTemplate = pMDDelegateInvoke->GetCallStub(); + if (stubHeaderTemplate == NULL) + { + stubHeaderTemplate = UpdateCallStubForMethod(pMDDelegateInvoke); + } + + // CallStubHeaders encode their destination addresses in the Routines array, so they need to be + // copied to a local buffer before we can actually set their target address. + size_t templateSize = stubHeaderTemplate->GetSize(); + uint8_t* actualCallStub = (uint8_t*)alloca(templateSize); + memcpy(actualCallStub, stubHeaderTemplate, templateSize); + CallStubHeader *pHeader = (CallStubHeader*)actualCallStub; + pHeader->SetTarget(target); // The method to call + pHeader->Invoke(pHeader->Routines, pArgs, pRet, pHeader->TotalStackSize); +} + void InvokeCalliStub(PCODE ftn, CallStubHeader *stubHeaderTemplate, int8_t *pArgs, int8_t *pRet) { CONTRACTL @@ -84,7 +129,6 @@ CallStubHeader *CreateNativeToInterpreterCallStub(InterpMethod* pInterpMethod) CallStubHeader *pHeader = VolatileLoadWithoutBarrier(&pInterpMethod->pCallStub); GCX_PREEMP(); - AllocMemTracker amTracker; if (pHeader == NULL) { // Ensure that there is an interpreter thread context instance and thus an interpreter stack @@ -1881,6 +1925,30 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr break; } + case INTOP_CALLDELEGATE: + { + returnOffset = ip[1]; + callArgsOffset = ip[2]; + methodSlot = ip[3]; + + // targetMethod holds a pointer to the Invoke method of the delegate, not the final actual target. + targetMethod = (MethodDesc*)pMethod->pDataItems[methodSlot]; + + ip += 4; + + DELEGATEREF delegateObj = LOCAL_VAR(callArgsOffset, DELEGATEREF); + NULL_CHECK(delegateObj); + PCODE targetAddress = delegateObj->GetMethodPtr(); + OBJECTREF targetMethodObj = delegateObj->GetTarget(); + LOCAL_VAR(callArgsOffset, OBJECTREF) = targetMethodObj; + + // TODO! Once we are investigating performance here, we may want to optimize this so that + // delegate calls to interpeted methods don't have to go through the native invoke here, but for + // now this should work well. + InvokeDelegateInvokeMethod(targetMethod, stack + callArgsOffset, stack + returnOffset, targetAddress); + break; + } + case INTOP_CALL: { returnOffset = ip[1]; diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 7f1fde32c918fc..ac2298f34837a1 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -487,3 +487,8 @@ void InvokeCompiledMethod(MethodDesc *pMD, int8_t *pArgs, int8_t *pRet, PCODE ta { PORTABILITY_ASSERT("Attempted to execute non-interpreter code from interpreter on wasm, this is not yet implemented"); } + +void InvokeDelegateInvokeMethod(MethodDesc *pMDDelegateInvoke, int8_t *pArgs, int8_t *pRet, PCODE target) +{ + PORTABILITY_ASSERT("Attempted to execute non-interpreter code from interpreter on wasm, this is not yet implemented"); +} \ No newline at end of file