#endif // DEBUG
// If fast tail call, then we are done. In this case we setup the args (both reg args
- // and stack args in incoming arg area) and call target in rax. Epilog sequence would
- // generate "br x0".
+ // and stack args in incoming arg area) and call target in IP0. Epilog sequence would
+ // generate "br IP0".
if (call->IsFastTailCall())
{
- NYI_ARM64("CodeGen - IsFastTailCall");
-
// Don't support fast tail calling JIT helpers
assert(callType != CT_HELPER);
assert(target != nullptr);
genConsumeReg(target);
-#if 0
- if (target->gtRegNum != REG_RAX)
+
+ if (target->gtRegNum != REG_IP0)
{
- inst_RV_RV(INS_mov, REG_RAX, target->gtRegNum);
+ inst_RV_RV(INS_mov, REG_IP0, target->gtRegNum);
}
-#endif
return;
- }
+ }
// For a pinvoke to unmanged code we emit a label to clear
// the GC pointer state before the callsite.
// All other calls - stk arg is setup in out-going arg area.
if (putInIncomingArgArea)
{
- // The first varNum is guaranteed to be the first incoming arg of the method being compiled.
- // See lvaInitTypeRef() for the order in which lvaTable entries are initialized.
- varNum = 0;
-#ifdef DEBUG
+ varNum = getFirstArgWithStackSlot();
#if FEATURE_FASTTAILCALL
// This must be a fast tail call.
assert(treeNode->AsPutArgStk()->gtCall->AsCall()->IsFastTailCall());
// Since it is a fast tail call, the existence of first incoming arg is guaranteed
// because fast tail call requires that in-coming arg area of caller is >= out-going
// arg area required for tail call.
- LclVarDsc* varDsc = compiler->lvaTable;mit
+ LclVarDsc* varDsc = &(compiler->lvaTable[varNum]);
assert(varDsc != nullptr);
- assert(varDsc->lvIsRegArg && ((varDsc->lvArgReg == REG_ARG_0) || (varDsc->lvArgReg == REG_FLTARG_0)));
#endif // FEATURE_FASTTAILCALL
-#endif
}
else
{
// figure out what jump we have
GenTreePtr jmpStmt = block->lastTopLevelStmt();
noway_assert(jmpStmt && (jmpStmt->gtOper == GT_STMT));
-
+#if !FEATURE_FASTTAILCALL
noway_assert(jmpStmt->gtNext == nullptr);
GenTreePtr jmpNode = jmpStmt->gtStmt.gtStmtExpr;
noway_assert(jmpNode->gtOper == GT_JMP);
+#else
+ // arm64
+ // If jmpNode is GT_JMP then gtNext must be null.
+ // If jmpNode is a fast tail call, gtNext need not be null since it could have embedded stmts.
+ GenTreePtr jmpNode = jmpStmt->gtStmt.gtStmtExpr;
+ noway_assert((jmpNode->gtOper != GT_JMP) || (jmpStmt->gtNext == nullptr));
+ // Could either be a "jmp method" or "fast tail call" implemented as epilog+jmp
+ noway_assert((jmpNode->gtOper == GT_JMP) || ((jmpNode->gtOper == GT_CALL) && jmpNode->AsCall()->IsFastTailCall()));
+
+ // The next block is associated with this "if" stmt
+ if (jmpNode->gtOper == GT_JMP)
+#endif
{
// Simply emit a jump to the methodHnd. This is similar to a call so we can use
// the same descriptor with some minor adjustments.
BAD_IL_OFFSET, REG_NA, REG_NA, 0, 0, /* iloffset, ireg, xreg, xmul, disp */
true); /* isJump */
}
+#if FEATURE_FASTTAILCALL
+ else
+ {
+ // Fast tail call.
+ // Call target = REG_IP0.
+ // https://github.com/dotnet/coreclr/issues/4827
+ // Do we need a special encoding for stack walker like rex.w prefix for x64?
+ getEmitter()->emitIns_R(INS_br, emitTypeSize(TYP_I_IMPL), REG_IP0);
+ }
+#endif //FEATURE_FASTTAILCALL
}
else
{
#endif // _TARGET_XARCH_
+#if !defined(LEGACY_BACKEND) && (defined(_TARGET_XARCH_) || defined(_TARGET_ARM64_))
+
+//------------------------------------------------------------------------------------------------ //
+// getFirstArgWithStackSlot - returns the first argument with stack slot on the caller's frame.
+//
+// Return value:
+// The number of the first argument with stack slot on the caller's frame.
+//
+// Note:
+// On x64 Windows the caller always creates slots (homing space) in its frame for the
+// first 4 arguments of a callee (register passed args). So, the the variable number
+// (lclNum) for the first argument with a stack slot is always 0.
+// For System V systems or arm64, there is no such calling convention requirement, and the code needs to find
+// the first stack passed argument from the caller. This is done by iterating over
+// all the lvParam variables and finding the first with lvArgReg equals to REG_STK.
+//
+unsigned
+CodeGen::getFirstArgWithStackSlot()
+{
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) || defined(_TARGET_ARM64_)
+ unsigned baseVarNum = 0;
+#if defined(FEATURE_UNIX_AMR64_STRUCT_PASSING)
+ baseVarNum = compiler->lvaFirstStackIncomingArgNum;
+
+ if (compiler->lvaFirstStackIncomingArgNum != BAD_VAR_NUM)
+ {
+ baseVarNum = compiler->lvaFirstStackIncomingArgNum;
+ }
+ else
+#endif // FEATURE_UNIX_ARM64_STRUCT_PASSING
+ {
+ // Iterate over all the local variables in the Lcl var table.
+ // They contain all the implicit arguments - thisPtr, retBuf,
+ // generic context, PInvoke cookie, var arg cookie,no-standard args, etc.
+ LclVarDsc* varDsc = nullptr;
+ for (unsigned i = 0; i < compiler->info.compArgsCount; i++)
+ {
+ varDsc = &(compiler->lvaTable[i]);
+
+ // We are iterating over the arguments only.
+ assert(varDsc->lvIsParam);
+
+ if (varDsc->lvArgReg == REG_STK)
+ {
+ baseVarNum = i;
+#if defined(FEATURE_UNIX_AMR64_STRUCT_PASSING)
+ compiler->lvaFirstStackIncomingArgNum = baseVarNum;
+#endif // FEATURE_UNIX_ARM64_STRUCT_PASSING
+ break;
+ }
+ }
+ assert(varDsc != nullptr);
+ }
+
+ return baseVarNum;
+#elif defined(_TARGET_AMD64_)
+ return 0;
+#else
+ // Not implemented for x86.
+ NYI_X86("getFirstArgWithStackSlot not yet implemented for x86.");
+ return BAD_VAR_NUM;
+#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING || _TARGET_ARM64_
+}
+
+#endif // !LEGACY_BACKEND && (_TARGET_XARCH_ || _TARGET_ARM64_)
+
/*****************************************************************************/
#ifdef DEBUGGING_SUPPORT
void genPutArgStk(GenTreePtr treeNode);
unsigned getBaseVarForPutArgStk(GenTreePtr treeNode);
-#ifdef _TARGET_XARCH_
+#if defined(_TARGET_XARCH_) || defined(_TARGET_ARM64_)
unsigned getFirstArgWithStackSlot();
-#endif // !_TARGET_XARCH_
+#endif // _TARGET_XARCH_ || _TARGET_ARM64_
void genCompareFloat(GenTreePtr treeNode);
genProduceReg(treeNode);
}
-//------------------------------------------------------------------------------------------------ //
-// getFirstArgWithStackSlot - returns the first argument with stack slot on the caller's frame.
-//
-// Return value:
-// The number of the first argument with stack slot on the caller's frame.
-//
-// Note:
-// On Windows the caller always creates slots (homing space) in its frame for the
-// first 4 arguments of a calee (register passed args). So, the the variable number
-// (lclNum) for the first argument with a stack slot is always 0.
-// For System V systems there is no such calling convention requirement, and the code needs to find
-// the first stack passed argument from the caller. This is done by iterating over
-// all the lvParam variables and finding the first with lvArgReg equals to REG_STK.
-//
-unsigned
-CodeGen::getFirstArgWithStackSlot()
-{
-#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
- unsigned baseVarNum = compiler->lvaFirstStackIncomingArgNum;
-
- if (compiler->lvaFirstStackIncomingArgNum != BAD_VAR_NUM)
- {
- baseVarNum = compiler->lvaFirstStackIncomingArgNum;
- }
- else
- {
- // Iterate over all the local variables in the lclvartable.
- // They contain all the implicit argumets - thisPtr, retBuf,
- // generic context, PInvoke cookie, var arg cookie,no-standard args, etc.
- LclVarDsc* varDsc = nullptr;
- for (unsigned i = 0; i < compiler->lvaCount; i++)
- {
- varDsc = &(compiler->lvaTable[i]);
-
- // We are iterating over the arguments only.
- assert(varDsc->lvIsParam);
-
- if (varDsc->lvArgReg == REG_STK)
- {
- baseVarNum = compiler->lvaFirstStackIncomingArgNum = i;
- break;
- }
- }
- assert(varDsc != nullptr);
- }
-
- return baseVarNum;
-#elif defined(_TARGET_AMD64_)
- return 0;
-#else
- // Not implemented for x86.
- NYI_X86("getFirstArgWithStackSlot not yet implemented for x86.");
- return BAD_VAR_NUM;
-#endif // !FEATURE_UNIX_AMD64_STRUCT_PASSING
-}
-
//-------------------------------------------------------------------------- //
// getBaseVarForPutArgStk - returns the baseVarNum for passing a stack arg.
//
#endif // !_TARGET_X86_
}
-#ifdef _TARGET_AMD64_
+#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
/*****************************************************************************/
// Returns true if 'type' is a struct that can be enregistered for call args
// or can be returned by value in multiple registers.
return result;
}
-#endif //_TARGET_AMD64_
+#endif //_TARGET_AMD64_ || _TARGET_ARM64_
/*****************************************************************************/
if (callerRetType == calleeRetType)
{
return true;
- }
+ }
-#ifdef _TARGET_AMD64_
+#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
// Jit64 compat:
if (callerRetType == TYP_VOID)
{
{
return (varTypeIsIntegral(calleeRetType) || isCalleeRetTypMBEnreg) && (callerRetTypeSize == calleeRetTypeSize);
}
-#endif // _TARGET_AMD64_
+#endif // _TARGET_AMD64_ || _TARGET_ARM64_
return false;
}
szCanTailCallFailReason = "Caller requires a security check.";
}
-#ifdef _TARGET_ARM64_
- // Don't do opportunistic tail calls in ARM64 for now.
- // TODO-ARM64-NYI: Get this to work.
- canTailCall = false;
- szCanTailCallFailReason = "Arm64.";
-#endif // _TARGET_ARM64_
-
// We only need to cast the return value of pinvoke inlined calls that return small types
// TODO-AMD64-Cleanup: Remove this when we stop interoperating with JIT64, or if we decide to stop
{
// Fast tail call - make sure that call target is always computed in IP0
// so that epilog sequence can generate "br xip0" to achieve fast tail call.
-
- NYI_ARM64("Lower - Fast tail call");
-
- ctrlExpr->gtLsraInfo.setSrcCandidates(l, genRegMask(REG_IP0)); // ip0?
+ ctrlExpr->gtLsraInfo.setSrcCandidates(l, genRegMask(REG_IP0));
}
}
// Get the size of the struct and see if it is register passable.
if (argx->OperGet() == GT_OBJ)
{
-#ifdef _TARGET_AMD64_
+#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
unsigned typeSize = 0;
hasMultiByteArgs = !VarTypeIsMultiByteAndCanEnreg(argx->TypeGet(), argx->gtObj.gtClass, &typeSize, false);
-#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
- // On System V the args could be a 2 eightbyte struct that is passed in two registers.
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) || defined(_TARGET_ARM64_)
+ // On System V/arm64 the args could be a 2 eightbyte struct that is passed in two registers.
// Account for the second eightbyte in the nCalleeArgs.
- // TODO-CQ-Amd64-Unix: Structs of size between 9 to 16 bytes are conservatively estimated
- // as two args, since they need two registers. Whereas nCallerArgs is
- // counting such an arg as one.This would mean we will not be optimizing
- // certain calls though technically possible.
+ // https://github.com/dotnet/coreclr/issues/2666
+ // TODO-CQ-Amd64-Unix/arm64: Structs of size between 9 to 16 bytes are conservatively estimated
+ // as two args, since they need two registers whereas nCallerArgs is
+ // counting such an arg as one. This would mean we will not be optimizing
+ // certain calls though technically possible.
if (typeSize > TARGET_POINTER_SIZE)
{
unsigned extraArgRegsToAdd = (typeSize / TARGET_POINTER_SIZE);
nCalleeArgs += extraArgRegsToAdd;
}
-#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING || _TARGET_ARM64_
#else
assert(!"Target platform ABI rules regarding passing struct type args in registers");
unreached();
-#endif //_TARGET_AMD64_
+#endif //_TARGET_AMD64_ || _TARGET_ARM64_
}
else
// in Stublinkerx86.cpp.
szFailReason = "Method with non-standard args passed in callee trash register cannot be tail called via helper";
}
+#ifdef _TARGET_ARM64_
+ else
+ {
+ // NYI - TAILCALL_RECURSIVE/TAILCALL_HELPER.
+ // So, bail out if we can't make fast tail call.
+ szFailReason = "Non-qualified fast tail call";
+ }
+#endif
#endif //LEGACY_BACKEND
}
}
#define FEATURE_FIXED_OUT_ARGS 1 // Preallocate the outgoing arg area in the prolog
#define FEATURE_STRUCTPROMOTE 1 // JIT Optimization to promote fields of structs into registers
#define FEATURE_MULTIREG_STRUCT_PROMOTE 1 // True when we want to promote fields of a multireg struct into registers
- #define FEATURE_FASTTAILCALL 0 // Tail calls made as epilog+jmp
+ #define FEATURE_FASTTAILCALL 1 // Tail calls made as epilog+jmp
#define FEATURE_TAILCALL_OPT 0 // opportunistic Tail calls (i.e. without ".tail" prefix) made as fast tail calls.
#define FEATURE_SET_FLAGS 1 // Set to true to force the JIT to mark the trees with GTF_SET_FLAGS when the flags need to be set
#define FEATURE_MULTIREG_ARGS_OR_RET 1 // Support for passing and/or returning single values in more than one register
#define RBM_ARG_6 RBM_R6
#define RBM_ARG_7 RBM_R7
+ #define REG_FLTARG_0 REG_V0
+ #define REG_FLTARG_1 REG_V1
+ #define REG_FLTARG_2 REG_V2
+ #define REG_FLTARG_3 REG_V3
+ #define REG_FLTARG_4 REG_V4
+ #define REG_FLTARG_5 REG_V5
+ #define REG_FLTARG_6 REG_V6
+ #define REG_FLTARG_7 REG_V7
+
+ #define RBM_FLTARG_0 RBM_V0
+ #define RBM_FLTARG_1 RBM_V1
+ #define RBM_FLTARG_2 RBM_V2
+ #define RBM_FLTARG_3 RBM_V3
+ #define RBM_FLTARG_4 RBM_V4
+ #define RBM_FLTARG_5 RBM_V5
+ #define RBM_FLTARG_6 RBM_V6
+ #define RBM_FLTARG_7 RBM_V7
+
#define RBM_ARG_REGS (RBM_ARG_0|RBM_ARG_1|RBM_ARG_2|RBM_ARG_3|RBM_ARG_4|RBM_ARG_5|RBM_ARG_6|RBM_ARG_7)
- #define RBM_FLTARG_REGS (RBM_V0|RBM_V1|RBM_V2|RBM_V3|RBM_V4|RBM_V5|RBM_V6|RBM_V7)
+ #define RBM_FLTARG_REGS (RBM_FLTARG_0|RBM_FLTARG_1|RBM_FLTARG_2|RBM_FLTARG_3|RBM_FLTARG_4|RBM_FLTARG_5|RBM_FLTARG_6|RBM_FLTARG_7)
SELECTANY const regNumber fltArgRegs [] = {REG_V0, REG_V1, REG_V2, REG_V3, REG_V4, REG_V5, REG_V6, REG_V7 };
SELECTANY const regMaskTP fltArgMasks[] = {RBM_V0, RBM_V1, RBM_V2, RBM_V3, RBM_V4, RBM_V5, RBM_V6, RBM_V7 };
WorkingDir=JIT\Directed\tailcall\tailcall
Expected=0
MaxAllowedDurationSeconds=600
-Categories=Pri0;EXPECTED_FAIL;ISSUE_4420
+Categories=Pri0;EXPECTED_PASS
HostStyle=0
[fault.cmd_745]
RelativePath=JIT\Directed\throwbox\fault\fault.cmd
WorkingDir=JIT\Regression\JitBlue\devdiv_902271\DevDiv_902271
Expected=0
MaxAllowedDurationSeconds=600
-Categories=Pri0;EXPECTED_FAIL;ISSUE_4420
+Categories=Pri0;EXPECTED_PASS
HostStyle=0
[DevDiv_911875_d.cmd_5569]
RelativePath=JIT\Regression\JitBlue\devdiv_911875\DevDiv_911875_d\DevDiv_911875_d.cmd