Skip to content

Commit

Permalink
Support fast tailcalls in R2R
Browse files Browse the repository at this point in the history
Partially addresses dotnet#5857
  • Loading branch information
jakobbotsch committed Jul 31, 2021
1 parent a70bd64 commit 7137494
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 33 deletions.
37 changes: 33 additions & 4 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8483,10 +8483,39 @@ void CodeGen::genFnEpilog(BasicBlock* block)
}
else
{
// Target requires indirection to obtain. genCallInstruction will have materialized
// it into RAX already, so just jump to it. The stack walker requires that a register
// indirect tail call be rex.w prefixed.
GetEmitter()->emitIns_R(INS_rex_jmp, emitTypeSize(TYP_I_IMPL), REG_RAX);
GenTree* target = callType == CT_INDIRECT ? call->gtCallAddr : call->gtControlExpr;
if (target->isContained())
{
// We can only tailcall with indirect target if the target
// is an offset as otherwise we may need registers that
// could have been wiped out by the epilog.
noway_assert(target->isIndir());
assert(target->AsIndir()->HasBase() && target->AsIndir()->Base()->isContainedIntOrIImmed());
assert(!target->AsIndir()->HasIndex());
assert(target->AsIndir()->Base()->AsIntConCommon()->FitsInAddrBase(compiler));

GetEmitter()->emitIns_Call(
emitter::EC_FUNC_TOKEN_INDIR,
call->gtCallMethHnd,
INDEBUG_LDISASM_COMMA(nullptr)
(void*)target->AsIndir()->Base()->AsIntConCommon()->IconValue(),
0,
EA_UNKNOWN
MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(EA_UNKNOWN),
gcInfo.gcVarPtrSetCur,
gcInfo.gcRegGCrefSetCur,
gcInfo.gcRegByrefSetCur,
BAD_IL_OFFSET, REG_NA, REG_NA, 0, 0,
true /* isJump */
);
}
else
{
// Target requires indirection to obtain. genCallInstruction will have materialized
// it into RAX already, so just jump to it. The stack walker requires that a register
// indirect tail call be rex.w prefixed.
GetEmitter()->emitIns_R(INS_rex_jmp, emitTypeSize(TYP_I_IMPL), REG_RAX);
}
}

#else
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/codegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5191,7 +5191,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
// If this is indirect then we go through RAX with epilog sequence
// generating "jmp rax". Otherwise epilog will try to generate a
// rip-relative jump.
if (target != nullptr)
if (target != nullptr && !target->isContained())
{
genConsumeReg(target);
genCopyRegIfNeeded(target, REG_RAX);
Expand Down
48 changes: 31 additions & 17 deletions src/coreclr/jit/lowerxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4466,26 +4466,40 @@ void Lowering::ContainCheckCallOperands(GenTreeCall* call)
// we should never see a gtControlExpr whose type is void.
assert(ctrlExpr->TypeGet() != TYP_VOID);

// In case of fast tail implemented as jmp, make sure that gtControlExpr is
// computed into a register.
if (!call->IsFastTailCall())
{
// In case of fast tail implemented as jmp it can be problematic to mark the control expression as contained
// since it may rely on registers that have been cleaned up. The exception is indirections off of constants
// that don't need any registers.
#ifdef TARGET_X86
// On x86, we need to generate a very specific pattern for indirect VSD calls:
//
// 3-byte nop
// call dword ptr [eax]
//
// Where EAX is also used as an argument to the stub dispatch helper. Make
// sure that the call target address is computed into EAX in this case.
if (call->IsVirtualStub() && (call->gtCallType == CT_INDIRECT))
// On x86, we need to generate a very specific pattern for indirect VSD calls:
//
// 3-byte nop
// call dword ptr [eax]
//
// Where EAX is also used as an argument to the stub dispatch helper. Make
// sure that the call target address is computed into EAX in this case.
if (call->IsVirtualStub() && (call->gtCallType == CT_INDIRECT))
{
assert(ctrlExpr->isIndir());
MakeSrcContained(call, ctrlExpr);
}
else
#endif // TARGET_X86


if (ctrlExpr->isIndir())
{
bool canContainIndir = true;
if (call->IsFastTailCall())
{
assert(ctrlExpr->isIndir());
MakeSrcContained(call, ctrlExpr);
// Currently we only allow fast tailcalls with indirections when no registers are required in the indirection.
// This is to ensure we won't need a register for the addressing mode since registers will have been cleaned up
// by the epilog at this point.
canContainIndir =
ctrlExpr->AsIndir()->HasBase() && ctrlExpr->AsIndir()->Base()->isContainedIntOrIImmed() &&
!ctrlExpr->AsIndir()->HasIndex();
}
else
#endif // TARGET_X86
if (ctrlExpr->isIndir())

if (canContainIndir)
{
// We may have cases where we have set a register target on the ctrlExpr, but if it
// contained we must clear it.
Expand Down
5 changes: 2 additions & 3 deletions src/coreclr/jit/lsraxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1214,9 +1214,8 @@ int LinearScan::BuildCall(GenTreeCall* call)

// In case of fast tail implemented as jmp, make sure that gtControlExpr is
// computed into a register.
if (call->IsFastTailCall())
if (call->IsFastTailCall() && !ctrlExpr->isContained())
{
assert(!ctrlExpr->isContained());
// Fast tail call - make sure that call target is always computed in RAX
// so that epilog sequence can generate "jmp rax" to achieve fast tail call.
ctrlExprCandidates = RBM_RAX;
Expand All @@ -1239,7 +1238,7 @@ int LinearScan::BuildCall(GenTreeCall* call)
#if FEATURE_VARARG
// If it is a fast tail call, it is already preferenced to use RAX.
// Therefore, no need set src candidates on call tgt again.
if (call->IsVarargs() && callHasFloatRegArgs && !call->IsFastTailCall())
if (call->IsVarargs() && callHasFloatRegArgs && (ctrlExprCandidates == RBM_NONE))
{
// Don't assign the call target to any of the argument registers because
// we will use them to also pass floating point arguments as required
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3205,7 +3205,7 @@ private bool getTailCallHelpers(ref CORINFO_RESOLVED_TOKEN callToken, CORINFO_SI
// Slow tailcalls are not supported yet
// https://github.com/dotnet/runtime/issues/35423
#if READYTORUN
throw new NotImplementedException(nameof(getTailCallHelpers));
throw new RequiresRuntimeJitException(nameof(getTailCallHelpers));
#else
return false;
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -915,13 +915,7 @@ private void getFunctionEntryPoint(CORINFO_METHOD_STRUCT_* ftn, ref CORINFO_CONS

private bool canTailCall(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHOD_STRUCT_* declaredCalleeHnd, CORINFO_METHOD_STRUCT_* exactCalleeHnd, bool fIsTailPrefix)
{
if (fIsTailPrefix)
{
// FUTURE: Delay load fixups for tailcalls
throw new RequiresRuntimeJitException(nameof(fIsTailPrefix));
}

return false;
return true;
}

private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RESOLVED_TOKEN pResolvedToken, TypeDesc constrainedType, bool unboxing)
Expand Down

0 comments on commit 7137494

Please sign in to comment.