Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Consider that retbuf arg can point to GC heap in fgCreateCallDispatcherAndGetResult() #39815

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 30, 2020

Conversation

echesakov
Copy link
Contributor

@echesakov echesakov commented Jul 23, 2020

The issue manifests themselves when

  1. methods Callee() and Caller() have the following simple structure
S Callee()
{
  // produce and return instance of S
}

S Caller()
{
    return Callee();
}
  1. S is a value type with non-GC fields and has such size that the result values of Callee and Caller must passed via return buffer;
  2. call to Callee is transformed to tail call with helpers;
  3. Caller is invoked via Reflection.

JIT will transform Caller according to https://github.com/dotnet/runtime/blob/master/docs/design/features/tailcalls-with-helpers.md to

S result;
DispatchTailCalls(&IL_STUB_CallTailCallTarget, (IntPtr)&result, _AddressOfReturnAddress());
return result;

DispatchTailCalls expects result to be a pointer on the stack.

However, during reflection call the return buffer for S value type can be placed on the GC heap

if (fHasRetBuffArg)
{
// We stack-allocate this ret buff, to preserve the invariant that ret-buffs are always in the
// caller's stack frame. We'll copy into gc.retVal later.
TypeHandle retTH = gc.pSig->GetReturnTypeHandle();
MethodTable* pMT = retTH.GetMethodTable();
if (pMT->IsStructRequiringStackAllocRetBuf())
{
SIZE_T sz = pMT->GetNumInstanceFieldBytes();
pRetBufStackCopy = _alloca(sz);
memset(pRetBufStackCopy, 0, sz);
pValueClasses = new (_alloca(sizeof(ValueClassInfo))) ValueClassInfo(pRetBufStackCopy, pMT, pValueClasses);
*((LPVOID*) (pTransitionBlock + argit.GetRetBuffArgOffset())) = pRetBufStackCopy;
}
else
{
PVOID pRetBuff = gc.retVal->GetData();
*((LPVOID*) (pTransitionBlock + argit.GetRetBuffArgOffset())) = pRetBuff;
}
}

JIT makes a wrong assumption that if Caller and Callee both have return buffer arguments it can be simple passed from the former to the latter in

// Use existing retbuf if there is one.
if (origCall->HasRetBufArg())
{
JITDUMP("Transferring retbuf\n");
GenTree* retBufArg = origCall->gtCallArgs->GetNode();
assert((info.compRetBuffArg != BAD_VAR_NUM) && retBufArg->OperIsLocal() &&
(retBufArg->AsLclVarCommon()->GetLclNum() == info.compRetBuffArg));
retValArg = retBufArg;
if (origCall->gtType != TYP_VOID)
{
retVal = gtClone(retValArg);
}
}

As a consequence, when GC is called at DispatchTailCalls the object corresponding to the return value buffer can be moved but pointers passed to DispatchTailCalls will not be update that leads to Callee using the old non-updated location when writing the return value.

Solution: JIT needs to account for IsStructRequiringStackAllocRetBuf in fgCreateCallDispatcherAndGetResult in the similar way as it is done in fgMorphCall in

if (dest->gtType == TYP_BYREF && !(dest->OperGet() == GT_ADDR && dest->AsOp()->gtOp1->OperGet() == GT_LCL_VAR))
{
// We'll exempt helper calls from this, assuming that the helper implementation
// follows the old convention, and does whatever barrier is required.
if (call->gtCallType != CT_HELPER)
{
structHnd = call->gtRetClsHnd;
if (info.compCompHnd->isStructRequiringStackAllocRetBuf(structHnd) &&
!(dest->OperGet() == GT_LCL_VAR && dest->AsLclVar()->GetLclNum() == info.compRetBuffArg))
{
// Force re-evaluating the argInfo as the return argument has changed.
call->fgArgInfo = nullptr;

Thanks @erozenfeld for help with identifying the place with wrong logic in fgCreateCallDispatcherAndGetResult!

@echesakov echesakov added the NO-REVIEW Experimental/testing PR, do NOT review it label Jul 23, 2020
@danmoseley danmoseley added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 23, 2020
@echesakov echesakov removed the NO-REVIEW Experimental/testing PR, do NOT review it label Jul 28, 2020
@echesakov
Copy link
Contributor Author

@erozenfeld PTAL
cc @dotnet/jit-contrib

@echesakov echesakov changed the title Add regression test for #39581 Consider that retbuf arg can point to GC heap in fgCreateCallDispatcherAndGetResult() Jul 28, 2020
Copy link
Member

@erozenfeld erozenfeld left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a few minor suggestions.

@jakobbotsch
Copy link
Member

DispatchTailCalls expects result to be a pointer on the stack.

I don't think this needs to be a hard requirement, if the pointer can be a by-ref to GC heap then the mechanism could pass it through as such.

@echesakov
Copy link
Contributor Author

I don't think this needs to be a hard requirement, if the pointer can be a by-ref to GC heap then the mechanism could pass it through as such.

@jakobbotsch retBufArg points to the stack in most of the cases. Even during Reflection call we might end up with the return buffer allocated on the stack (e.g., when the corresponding return value type has a GC pointer field) rather than GC heap.

I am not sure if making DispatchTailCalls and TailCallCallTargetStub aware that retVal can point to GC heap (i.e. passing this parameter as byref instead of IntPtr) will be more beneficial. GC will need to check that these byref pointers point to GC heap anyway and in most of the cases they will be pointing to the stack.

if (origCall->gtType != TYP_VOID)
{
retVal = gtClone(retValArg);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jakobbotsch Do you remember why this handling was needed here? I understand that on System-V the method that uses return buffer must return an address to the return buffer in rax at the end. But do we use the value in the caller somehow?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if JIT uses the returned pointer, but even if not it is presumably necessary for reverse pinvoke.
This is already what the JIT does for functions with ret buffers so I just did the same.

@echesakov
Copy link
Contributor Author

All GCStress legs except win-arm64 are green with the change. The ones that failed in win-arm64 are due to #38518

@jakobbotsch
Copy link
Member

@jakobbotsch retBufArg points to the stack in most of the cases. Even during Reflection call we might end up with the return buffer allocated on the stack (e.g., when the corresponding return value type has a GC pointer field) rather than GC heap.

I am not sure if making DispatchTailCalls and TailCallCallTargetStub aware that retVal can point to GC heap (i.e. passing this parameter as byref instead of IntPtr) will be more beneficial. GC will need to check that these byref pointers point to GC heap anyway and in most of the cases they will be pointing to the stack.

I don't think one more interior pointer should move the needle much and it should simplify the JIT's handling of this.
But the current approach is also fine to me.

@echesakov echesakov merged commit 1e23a06 into dotnet:master Jul 30, 2020
@echesakov echesakov deleted the Runtime_39581 branch July 30, 2020 17:06
Jacksondr5 pushed a commit to Jacksondr5/runtime that referenced this pull request Aug 10, 2020
…erAndGetResult() (dotnet#39815)

Caller return buffer argument can point to GC heap while DispatchTailCalls expects the return value argument to point to the stack. We should use a temporary stack allocated return buffer to hold the value during the dispatcher call and copy the value back to the caller return buffer after that.
@karelz karelz added this to the 5.0.0 milestone Aug 18, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 8, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
6 participants