ARM64: Enable Fast Tail Call
authorKyungwoo Lee <kyulee@microsoft.com>
Wed, 4 May 2016 20:26:33 +0000 (13:26 -0700)
committerKyungwoo Lee <kyulee@microsoft.com>
Fri, 6 May 2016 20:43:39 +0000 (13:43 -0700)
Fixes https://github.com/dotnet/coreclr/issues/4420
Like x64, enable fast tail call but tail call opt is still off.
This means explicit tail call is converted to epilog + jmp IP0.
Tail call recursive/tail call via helper is still not enabled yet.

src/jit/codegenarm64.cpp
src/jit/codegencommon.cpp
src/jit/codegenlinear.h
src/jit/codegenxarch.cpp
src/jit/compiler.hpp
src/jit/importer.cpp
src/jit/lowerarm64.cpp
src/jit/morph.cpp
src/jit/target.h
tests/arm64/Tests.lst

index 06d47ab..c3d2260 100644 (file)
@@ -5172,12 +5172,10 @@ void CodeGen::genCallInstruction(GenTreePtr node)
 #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);
 
@@ -5185,14 +5183,13 @@ void CodeGen::genCallInstruction(GenTreePtr node)
         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.
@@ -6467,10 +6464,7 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode)
     // 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());
@@ -6478,11 +6472,9 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode)
         // 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
     {
index 186ec9b..3b64b40 100644 (file)
@@ -9497,11 +9497,23 @@ void                CodeGen::genFnEpilog(BasicBlock* block)
         // 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.
@@ -9530,6 +9542,16 @@ void                CodeGen::genFnEpilog(BasicBlock* block)
                 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
     {
@@ -11532,6 +11554,72 @@ instruction CodeGen::genMapShiftInsToShiftByConstantIns(instruction ins, int shi
 
 #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
 
index b72fbc3..3c96fbe 100644 (file)
@@ -41,9 +41,9 @@
     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);
 
index edd485b..cb709ac 100644 (file)
@@ -7728,62 +7728,6 @@ CodeGen::genIntrinsic(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.
 //
index cfe2064..a9e3693 100644 (file)
@@ -632,7 +632,7 @@ bool                isRegParamType(var_types type)
 #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.
@@ -680,7 +680,7 @@ bool   Compiler::VarTypeIsMultiByteAndCanEnreg(var_types type,
 
     return result;
 }
-#endif //_TARGET_AMD64_
+#endif //_TARGET_AMD64_ || _TARGET_ARM64_
 
 
 /*****************************************************************************/
index b783874..490d0bf 100644 (file)
@@ -5380,9 +5380,9 @@ bool                Compiler::impTailCallRetTypeCompatible(var_types callerRetTy
     if (callerRetType == calleeRetType)
     {
         return true;
-    }    
+    }
 
-#ifdef _TARGET_AMD64_
+#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
     // Jit64 compat:
     if (callerRetType == TYP_VOID)
     {
@@ -5410,7 +5410,7 @@ bool                Compiler::impTailCallRetTypeCompatible(var_types callerRetTy
     {
         return (varTypeIsIntegral(calleeRetType) || isCalleeRetTypMBEnreg) && (callerRetTypeSize == calleeRetTypeSize);
     }
-#endif // _TARGET_AMD64_
+#endif // _TARGET_AMD64_ || _TARGET_ARM64_
 
     return false;
 }
@@ -5621,13 +5621,6 @@ var_types           Compiler::impImportCall (OPCODE         opcode,
         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
index 82a6e3c..f4411e6 100644 (file)
@@ -526,10 +526,7 @@ void Lowering::TreeNodeInfoInit(GenTree* stmt)
                     {
                         // 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));
                     }
                 }
 
index d32f6f8..a1f5710 100644 (file)
@@ -6217,30 +6217,31 @@ bool                Compiler::fgCanFastTailCall(GenTreeCall* callee)
             // 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
@@ -6962,6 +6963,14 @@ GenTreePtr          Compiler::fgMorphCall(GenTreeCall* call)
                     // 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
             }
         }
index 5c3936d..d50eddb 100644 (file)
@@ -1451,7 +1451,7 @@ typedef unsigned short          regPairNoSmall; // arm: need 12 bits
   #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  
@@ -1685,8 +1685,26 @@ typedef unsigned short          regPairNoSmall; // arm: need 12 bits
   #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 };
index 8a43f8b..d189310 100644 (file)
@@ -5051,7 +5051,7 @@ RelativePath=JIT\Directed\tailcall\tailcall\tailcall.cmd
 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
@@ -38245,7 +38245,7 @@ RelativePath=JIT\Regression\JitBlue\devdiv_902271\DevDiv_902271\DevDiv_902271.cm
 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