diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index dcf1768c60a7dd..bf870d25d6d3de 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -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 diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 529d1fe948799c..959e06e8b9ffb8 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -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); diff --git a/src/coreclr/jit/lowerxarch.cpp b/src/coreclr/jit/lowerxarch.cpp index 43c0df62042365..92dcae26cfbe50 100644 --- a/src/coreclr/jit/lowerxarch.cpp +++ b/src/coreclr/jit/lowerxarch.cpp @@ -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. diff --git a/src/coreclr/jit/lsraxarch.cpp b/src/coreclr/jit/lsraxarch.cpp index 854e4521ec9001..9d02e398ad7698 100644 --- a/src/coreclr/jit/lsraxarch.cpp +++ b/src/coreclr/jit/lsraxarch.cpp @@ -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; @@ -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 diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index ff268b787e440d..02549319752d1d 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -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 diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 6a325a5844c816..f3701699cca617 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -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)