Enable FEATURE_MULTIREG_RET for x86 RyuJIT
authorMichelle McDaniel <adiaaida@gmail.com>
Thu, 19 May 2016 16:55:09 +0000 (09:55 -0700)
committerMichelle McDaniel <adiaaida@gmail.com>
Wed, 25 May 2016 15:04:42 +0000 (08:04 -0700)
This change enables the following:

1) Enable FEATURE_MULTIREG_RET for x86 RyuJIT, which is used for calls
with long return types.

2) Enable ReturnTypeDesc for x86 RyuJIT to describe the return types for
longs. Included in this is setting up the correct number of return
registers for longs on x86, and setting up the correct registers for long
returns. This is required for enabling impFixupCallLongReturn. Add Reset
function for ReturnTypeDesc for future work to reset ReturnTypeDesc to the
defaults.

3) Enabling HasMultiRegRetVal for longs on x86.

4) Enabling impFixupCallLongReturn and impAssignStructClassToVar for x86
longs. Sets up the call's ReturnTypeDesc and sets lvIsMultiRegArgOrRet for
long's tmp. Rename impAssignStructClassToVar to
impAssignMultiRegTypeToVar.

Commit migrated from https://github.com/dotnet/coreclr/commit/6fb5ce9128b1a2d1b4d5b05cff4c011b98f2f5f7

src/coreclr/src/jit/compiler.h
src/coreclr/src/jit/gentree.cpp
src/coreclr/src/jit/gentree.h
src/coreclr/src/jit/importer.cpp
src/coreclr/src/jit/target.h

index edcc951..20d2dd7 100644 (file)
@@ -1330,7 +1330,7 @@ public:
 #endif
 
 #if FEATURE_MULTIREG_RET
-    GenTreePtr               impAssignStructClassToVar(GenTreePtr op, CORINFO_CLASS_HANDLE hClass);
+    GenTreePtr               impAssignMultiRegTypeToVar(GenTreePtr op, CORINFO_CLASS_HANDLE hClass);
 #endif // FEATURE_MULTIREG_RET
 
 #ifdef _TARGET_ARM_
@@ -2644,6 +2644,9 @@ protected :
     GenTreePtr          impFixupCallStructReturn(GenTreePtr           call,
                                                  CORINFO_CLASS_HANDLE retClsHnd);
 
+    GenTreePtr          impFixupCallLongReturn(GenTreePtr           call,
+                                               CORINFO_CLASS_HANDLE retClsHnd);
+
     GenTreePtr          impFixupStructReturnType(GenTreePtr       op,
                                                  CORINFO_CLASS_HANDLE retClsHnd);
 
index f9328a5..4a5b4ab 100644 (file)
@@ -1314,7 +1314,7 @@ GenTreeCall::GetOtherRegMask() const
 {
     regMaskTP resultMask = RBM_NONE;
 
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
     for (unsigned i = 0; i < MAX_RET_REG_COUNT - 1; ++i)
     {
         if (gtOtherRegs[i] != REG_NA)
@@ -13738,9 +13738,10 @@ bool GenTree::isCommutativeSIMDIntrinsic()
 void ReturnTypeDesc::Initialize(Compiler* comp, CORINFO_CLASS_HANDLE retClsHnd)
 {
     assert(!m_inited);
-    assert(retClsHnd != NO_CLASS_HANDLE);
 
 #ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+    assert(retClsHnd != NO_CLASS_HANDLE);
+
     SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc;
     comp->eeGetSystemVAmd64PassStructInRegisterDescriptor(retClsHnd, &structDesc);
 
@@ -13758,11 +13759,16 @@ void ReturnTypeDesc::Initialize(Compiler* comp, CORINFO_CLASS_HANDLE retClsHnd)
         }
     }
 
+#elif defined(_TARGET_X86_)
+    // TODO-X86: Assumes we are only using ReturnTypeDesc for longs on x86. Will
+    // need to be updated in the future to handle other return types
+    m_regType0 = TYP_INT;
+    m_regType1 = TYP_INT;
+#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+
 #ifdef DEBUG
     m_inited = true;
 #endif
-
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
 }
 
 //-------------------------------------------------------------------
@@ -13781,7 +13787,6 @@ void ReturnTypeDesc::Initialize(Compiler* comp, CORINFO_CLASS_HANDLE retClsHnd)
 //     targets (Arm64/Arm32/x86).
 //
 // TODO-ARM: Implement this routine to support HFA returns.
-// TODO-X86: Implement this routine to support long returns.
 regNumber ReturnTypeDesc::GetABIReturnReg(unsigned idx)
 {
     unsigned count = GetReturnRegCount();
@@ -13832,6 +13837,16 @@ regNumber ReturnTypeDesc::GetABIReturnReg(unsigned idx)
             }
         }
     }
+
+#elif defined(_TARGET_X86_)
+    if (idx == 0)
+    {
+        resultReg = REG_LNGRET_LO;
+    }
+    else if (idx == 1)
+    {
+        resultReg = REG_LNGRET_HI;
+    }
 #endif //FEATURE_UNIX_AMD64_STRUCT_PASSING
 
     assert(resultReg != REG_NA);
@@ -13855,7 +13870,6 @@ regNumber ReturnTypeDesc::GetABIReturnReg(unsigned idx)
 //    of return registers and wants to know the set of return registers.
 //
 // TODO-ARM: Implement this routine to support HFA returns.
-// TODO-X86: Implement this routine to support long returns.
 //
 //static
 regMaskTP ReturnTypeDesc::GetABIReturnRegs()
index e72d728..3969597 100644 (file)
@@ -2379,7 +2379,6 @@ enum class InlineObservation;
 //    - count of return registers in which the value is returned
 //
 // TODO-ARM: Update this to meet the needs of Arm64 and Arm32
-// TODO-X86: Update this to meet the needs of x86
 //
 // TODO-AllArch: Right now it is used for describing multi-reg returned types.
 // Eventually we would want to use it for describing even single-reg
@@ -2399,6 +2398,15 @@ private:
 public:
     ReturnTypeDesc()
     {
+        Reset();
+    }
+
+    // Initialize type descriptor given its type handle
+    void Initialize(Compiler* comp, CORINFO_CLASS_HANDLE retClsHnd);
+
+    // Reset type descriptor to defaults
+    void Reset()
+    {
         m_regType0 = TYP_UNKNOWN;
         m_regType1 = TYP_UNKNOWN;
 
@@ -2407,9 +2415,6 @@ public:
 #endif
     }
 
-    // Initialize type descriptor given its type handle
-    void Initialize(Compiler* comp, CORINFO_CLASS_HANDLE retClsHnd);
-
     //--------------------------------------------------------------------------------------------
     // GetReturnRegCount:  Get the count of return registers in which the return value is returned.
     //
@@ -2438,7 +2443,6 @@ public:
             // If regType0 is TYP_UNKNOWN then regType1 must also be TYP_UNKNOWN.
             assert(m_regType1 == TYP_UNKNOWN);
         }
-
         return regCount;
     }
 
@@ -2513,10 +2517,8 @@ struct GenTreeCall final : public GenTree
     // State required to support multi-reg returning call nodes.
     // For now it is enabled only for x64 unix.
     //
-    // TODO-ARM: enable this for HFA returns on Arm64 and Arm32
-    // TODO-X86: enable this for long returns on x86
     // TODO-AllArch: enable for all call nodes to unify single-reg and multi-reg returns.
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
     ReturnTypeDesc    gtReturnTypeDesc;
 
     // gtRegNum would always be the first return reg.
@@ -2542,12 +2544,10 @@ struct GenTreeCall final : public GenTree
     //    Right now implemented only for x64 unix and yet to be 
     //    implemented for other multi-reg target arch (Arm64/Arm32/x86).
     //
-    // TODO-ARM: Implement this routine for Arm64 and Arm32
-    // TODO-X86: Implement this routine for x86
     // TODO-AllArch: enable for all call nodes to unify single-reg and multi-reg returns.
     ReturnTypeDesc*   GetReturnTypeDesc()
     {
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
         return &gtReturnTypeDesc;
 #else
         return nullptr;
@@ -2564,8 +2564,6 @@ struct GenTreeCall final : public GenTree
     //     Return regNumber of ith return register of call node.
     //     Returns REG_NA if there is no valid return register for the given index.
     //
-    // TODO-ARM: Implement this routine for Arm64 and Arm32
-    // TODO-X86: Implement this routine for x86
     regNumber  GetRegNumByIdx(unsigned idx) const
     {
         assert(idx < MAX_RET_REG_COUNT);
@@ -2575,7 +2573,7 @@ struct GenTreeCall final : public GenTree
             return gtRegNum;
         }
 
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
         return gtOtherRegs[idx-1];
 #else
         return REG_NA;
@@ -2592,8 +2590,6 @@ struct GenTreeCall final : public GenTree
     // Return Value:
     //    None
     //
-    // TODO-ARM: Implement this routine for Arm64 and Arm32
-    // TODO-X86: Implement this routine for x86
     void  SetRegNumByIdx(regNumber reg, unsigned idx)
     {
         assert(idx < MAX_RET_REG_COUNT);
@@ -2602,7 +2598,7 @@ struct GenTreeCall final : public GenTree
         {
             gtRegNum = reg;
         }
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
         else
         {
             gtOtherRegs[idx - 1] = reg;
@@ -2624,7 +2620,7 @@ struct GenTreeCall final : public GenTree
     //
     void  ClearOtherRegs()
     {
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
         for (unsigned i = 0; i < MAX_RET_REG_COUNT - 1; ++i)
         {
             gtOtherRegs[i] = REG_NA;
@@ -2643,7 +2639,7 @@ struct GenTreeCall final : public GenTree
     //
     void CopyOtherRegs(GenTreeCall* fromCall)
     {
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
         for (unsigned i = 0; i < MAX_RET_REG_COUNT - 1; ++i)
         {
             this->gtOtherRegs[i] = fromCall->gtOtherRegs[i];
@@ -2667,7 +2663,7 @@ struct GenTreeCall final : public GenTree
     {
         assert(idx < MAX_RET_REG_COUNT);
 
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
         return gtSpillFlags[idx];
 #else
         assert(!"unreached");
@@ -2689,7 +2685,7 @@ struct GenTreeCall final : public GenTree
     {
         assert(idx < MAX_RET_REG_COUNT);
 
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
         gtSpillFlags[idx] = flags;
 #else
         unreached();
@@ -2706,7 +2702,7 @@ struct GenTreeCall final : public GenTree
     //     None
     void ClearOtherRegFlags()
     {
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
         for (unsigned i = 0; i < MAX_RET_REG_COUNT; ++i)
         {
             gtSpillFlags[i] = 0;
@@ -2726,7 +2722,7 @@ struct GenTreeCall final : public GenTree
     //
     void CopyOtherRegFlags(GenTreeCall* fromCall)
     {
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
         for (unsigned i = 0; i < MAX_RET_REG_COUNT; ++i)
         {
             this->gtSpillFlags[i] = fromCall->gtSpillFlags[i];
@@ -2821,11 +2817,13 @@ struct GenTreeCall final : public GenTree
     //     other multi-reg return target arch (arm64/arm32/x86).
     //
     // TODO-ARM: Implement this routine for Arm64 and Arm32
-    // TODO-X86: Implement this routine for x86
     bool HasMultiRegRetVal() const 
     { 
 #ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
         return varTypeIsStruct(gtType) && !HasRetBufArg(); 
+#elif defined(_TARGET_X86_) && !defined(LEGACY_BACKEND)
+        // LEGACY_BACKEND does not use multi reg returns for calls with long return types
+        return varTypeIsLong(gtType);
 #else
         return false;
 #endif
@@ -3960,11 +3958,8 @@ struct GenTreeCopyOrReload : public GenTreeUnOp
 {
     // State required to support copy/reload of a multi-reg call node.
     // First register is is always given by gtRegNum.
-    // Currently enabled for x64 unix.
     //
-    // TODO-ARM: Enable this when multi-reg call node support is added.
-    // TODO-X86: Enable this when multi-reg call node support is added.
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
     regNumber gtOtherRegs[MAX_RET_REG_COUNT - 1];
 #endif
 
@@ -3977,11 +3972,9 @@ struct GenTreeCopyOrReload : public GenTreeUnOp
     // Return Value:
     //    None
     //
-    // TODO-ARM: Implement this routine for Arm64 and Arm32
-    // TODO-X86: Implement this routine for x86
     void ClearOtherRegs()
     {
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
         for (unsigned i = 0; i < MAX_RET_REG_COUNT - 1; ++i)
         {
             gtOtherRegs[i] = REG_NA;
@@ -3998,8 +3991,6 @@ struct GenTreeCopyOrReload : public GenTreeUnOp
     // Return Value:
     //    Returns regNumber assigned to ith position.
     //
-    // TODO-ARM: Implement this routine for Arm64 and Arm32
-    // TODO-X86: Implement this routine for x86
     regNumber GetRegNumByIdx(unsigned idx) const
     {
         assert(idx < MAX_RET_REG_COUNT);
@@ -4009,7 +4000,7 @@ struct GenTreeCopyOrReload : public GenTreeUnOp
             return gtRegNum;
         }
 
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_MULTIREG_RET
         return gtOtherRegs[idx - 1];
 #else
         return REG_NA;
index 1e7d6c4..ff2ffdc 100755 (executable)
@@ -6826,21 +6826,28 @@ DONE_CALL:
             }
         }
 
-        // Sometimes "call" is not a GT_CALL (if we imported an intrinsic that didn't turn into a call)
-        if  (varTypeIsStruct(callRetTyp) && (call->gtOper == GT_CALL))
+        if (call->gtOper == GT_CALL)
         {
-            call = impFixupCallStructReturn(call, sig->retTypeClass);
-        }
+            // Sometimes "call" is not a GT_CALL (if we imported an intrinsic that didn't turn into a call)
+            if (varTypeIsStruct(callRetTyp))
+            {
+                call = impFixupCallStructReturn(call, sig->retTypeClass);
+            }
+            else if (varTypeIsLong(callRetTyp))
+            {
+                call = impFixupCallLongReturn(call, sig->retTypeClass);
+            }
 
-        if ((call->gtOper == GT_CALL) && ((call->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0))
-        {
-            assert(opts.OptEnabled(CLFLG_INLINING));
+            if ((call->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0)
+            {
+                assert(opts.OptEnabled(CLFLG_INLINING));
 
-            // Make the call its own tree (spill the stack if needed).            
-            impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs);
+                // Make the call its own tree (spill the stack if needed).            
+                impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs);
 
-            // TODO: Still using the widened type.
-            call = gtNewInlineCandidateReturnExpr(call, genActualType(callRetTyp));
+                // TODO: Still using the widened type.
+                call = gtNewInlineCandidateReturnExpr(call, genActualType(callRetTyp));
+            }
         }
 
         if (!bIntrinsicImported)
@@ -7045,7 +7052,7 @@ GenTreePtr                Compiler::impFixupCallStructReturn(GenTreePtr     call
             return call;
         }
 
-        return impAssignStructClassToVar(call, retClsHnd);
+        return impAssignMultiRegTypeToVar(call, retClsHnd);
     }
 #elif defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
 
@@ -7083,7 +7090,7 @@ GenTreePtr                Compiler::impFixupCallStructReturn(GenTreePtr     call
                 // No need to assign a multi-reg struct to a local var if:
                 //  - It is a tail call or 
                 //  - The call is marked for in-lining later
-                return impAssignStructClassToVar(call, retClsHnd);
+                return impAssignMultiRegTypeToVar(call, retClsHnd);
             }
         }
     }
@@ -7111,6 +7118,60 @@ GenTreePtr                Compiler::impFixupCallStructReturn(GenTreePtr     call
     return call;
 }
 
+
+//-----------------------------------------------------------------------------------
+//  impFixupCallLongReturn: For a call node that returns a long type, force the call
+//  to always be in the IR form tmp = call
+//
+//  Arguments:
+//    call       -  GT_CALL GenTree node
+//    retClsHnd  -  Class handle of return type of the call
+//
+//  Return Value:
+//    Returns new GenTree node after fixing long return of call node
+//
+GenTreePtr                Compiler::impFixupCallLongReturn(GenTreePtr     call,
+                                                           CORINFO_CLASS_HANDLE retClsHnd)
+{
+#if defined(_TARGET_X86_) && !defined(LEGACY_BACKEND)
+    // LEGACY_BACKEND does not use multi reg returns for calls with long return types
+    assert(call->gtOper == GT_CALL);
+
+    if (!varTypeIsLong(call))
+    {
+        return call;
+    }
+
+    call->gtCall.gtRetClsHnd = retClsHnd;
+
+    GenTreeCall* callNode = call->AsCall();
+
+    // The return type will remain as the incoming long type
+    callNode->gtReturnType = call->gtType;
+
+    // Initialize Return type descriptor of call node
+    ReturnTypeDesc* retTypeDesc = callNode->GetReturnTypeDesc();
+    retTypeDesc->Initialize(this, retClsHnd);
+
+    unsigned retRegCount = retTypeDesc->GetReturnRegCount();
+    // must be a long returned in two registers
+    assert(retRegCount == 2);
+
+    if ((!callNode->CanTailCall()) && (!callNode->IsInlineCandidate()))
+    {
+        // Force a call returning multi-reg long to be always of the IR form
+        //   tmp = call
+        //
+        // No need to assign a multi-reg long to a local var if:
+        //  - It is a tail call or 
+        //  - The call is marked for in-lining later
+        return impAssignMultiRegTypeToVar(call, retClsHnd);
+    }
+#endif // _TARGET_X86_ && !LEGACY_BACKEND
+
+    return call;
+}
+
 /*****************************************************************************
    For struct return values, re-type the operand in the case where the ABI
    does not use a struct return buffer
@@ -7149,7 +7210,7 @@ GenTreePtr          Compiler::impFixupStructReturnType(GenTreePtr op, CORINFO_CL
             return op;
         }
 
-        return impAssignStructClassToVar(op, retClsHnd);
+        return impAssignMultiRegTypeToVar(op, retClsHnd);
     }
 #else // !FEATURE_UNIX_AMD64_STRUCT_PASSING
     assert(info.compRetNativeType != TYP_STRUCT);
@@ -7181,7 +7242,7 @@ GenTreePtr          Compiler::impFixupStructReturnType(GenTreePtr op, CORINFO_CL
                 return op;
             }
         }
-        return impAssignStructClassToVar(op, retClsHnd);
+        return impAssignMultiRegTypeToVar(op, retClsHnd);
     }
 #endif //_TARGET_ARM_
 
@@ -13632,7 +13693,7 @@ void Compiler::impMarkLclDstNotPromotable(unsigned tmpNum, GenTreePtr src, CORIN
 #endif
 
 #if FEATURE_MULTIREG_RET
-GenTreePtr Compiler::impAssignStructClassToVar(GenTreePtr op, CORINFO_CLASS_HANDLE hClass)
+GenTreePtr Compiler::impAssignMultiRegTypeToVar(GenTreePtr op, CORINFO_CLASS_HANDLE hClass)
 {
     unsigned tmpNum = lvaGrabTemp(true DEBUGARG("Return value temp for multireg return."));
     impAssignTempGen(tmpNum, op, hClass, (unsigned) CHECK_SPILL_NONE);
@@ -13645,6 +13706,8 @@ GenTreePtr Compiler::impAssignStructClassToVar(GenTreePtr op, CORINFO_CLASS_HAND
 
     // Mark the var so that fields are not promoted and stay together.
     lvaTable[tmpNum].lvIsMultiRegArgOrRet = true;
+#elif defined(_TARGET_X86_) && !defined(LEGACY_BACKEND)
+    lvaTable[tmpNum].lvIsMultiRegArgOrRet = true;
 #endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
 
     return ret;
index 0b2bff3..ce23bb4 100644 (file)
@@ -385,9 +385,17 @@ typedef unsigned short          regPairNoSmall; // arm: need 12 bits
   #define FEATURE_FASTTAILCALL     0       // Tail calls made as epilog+jmp
   #define FEATURE_TAILCALL_OPT     0       // opportunistic Tail calls (without ".tail" prefix) made as fast tail calls.
   #define FEATURE_SET_FLAGS        0       // Set to true to force the JIT to mark the trees with GTF_SET_FLAGS when the flags need to be set
+#ifdef LEGACY_BACKEND
   #define FEATURE_MULTIREG_ARGS_OR_RET  0  // Support for passing and/or returning single values in more than one register
   #define FEATURE_MULTIREG_ARGS         0  // Support for passing a single argument in more than one register  
   #define FEATURE_MULTIREG_RET          0  // Support for returning a single value in more than one register  
+#else
+  #define FEATURE_MULTIREG_ARGS_OR_RET  1  // Support for passing and/or returning single values in more than one register
+  #define FEATURE_MULTIREG_ARGS         0  // Support for passing a single argument in more than one register  
+  #define FEATURE_MULTIREG_RET          1  // Support for returning a single value in more than one register  
+  #define MAX_RET_MULTIREG_BYTES        8  // Maximum size of a struct that could be returned in more than one register
+#endif
+
   #define MAX_ARG_REG_COUNT             2  // Maximum registers used to pass an argument.
   #define MAX_RET_REG_COUNT             2  // Maximum registers used to return a value.