Refactor and cleanup work for passing and returning struct types
authorBrian Sullivan <briansul@microsoft.com>
Thu, 7 Jul 2016 20:23:20 +0000 (13:23 -0700)
committerBrian Sullivan <briansul@microsoft.com>
Tue, 12 Jul 2016 01:38:19 +0000 (18:38 -0700)
Replaces argOrReturnTypeForStruct with two new methods:
  getArgTypeForStruct - Provides information on how to pass a struct type
  getReturnTypeForStruct - Provides information on how to return a struct type

A struct can be passed or return in the following different ways:
1. A struct type may be passed/returned as a primitive type when its size is small
2. A struct type may be passed/returned by reference to a copy
3. A struct type may be passed/returned by value using multiple registers
4. A struct type may be passed by value using a copy on the stack

Incorporated code review feedback with expanded comments.

src/jit/compiler.cpp
src/jit/compiler.h
src/jit/compiler.hpp
src/jit/importer.cpp
src/jit/lclvars.cpp
src/jit/morph.cpp
src/jit/target.h

index ea14d35..b7b3325 100644 (file)
@@ -447,62 +447,41 @@ void Compiler::getStructGcPtrsFromOp(GenTreePtr op, BYTE *gcPtrsOut)
 }
 #endif // FEATURE_MULTIREG_ARGS
 
-//------------------------------------------------------------------------
-// argOrReturnTypeForStruct: Get the "primitive" type, if any, that is used to pass or return
-//                           values of the given struct type.
-//
-// Arguments:
-//    clsHnd    - the handle for the struct type
-//    forReturn - true if we asking for this in a GT_RETURN context
-//                false if we are asking for this in a parameter passing context
-//
-// Return Value:
-//    The primitive type used to pass or return the struct, if applicable, or
-//    TYP_UNKNOWN otherwise.
-//
-// Assumptions:
-//    The given class handle must be for a value type (struct).
-//
-// Notes:
-//    Most of the work is done by the method of the same name that takes the
-//    size of the struct.
-
-
-var_types    Compiler::argOrReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, bool forReturn)
-{
-    unsigned size = info.compCompHnd->getClassSize(clsHnd);
-    return argOrReturnTypeForStruct(size, clsHnd, forReturn);
-}
 
-//------------------------------------------------------------------------
-// argOrReturnTypeForStruct: Get the "primitive" type, if any, that is used to pass or return
-//                           values of the given struct type.
+//-----------------------------------------------------------------------------
+// getPrimitiveTypeForStruct: 
+//     Get the "primitive" type that is is used for a struct 
+//     of size 'structSize'. 
+//     We examine 'clsHnd' to check the GC layout of the struct and 
+//     return TYP_REF for structs that simply wrap an object.
+//     If the struct is a one element HFA, we will return the 
+//     proper floating point type.
 //
 // Arguments:
-//    size      - the size of the struct type
-//    clsHnd    - the handle for the struct type
-//    forReturn - true if we asking for this in a GT_RETURN context
-//                false if we are asking for this in a parameter passing context
+//    structSize - the size of the struct type, cannot be zero
+//    clsHnd     - the handle for the struct type, used when may have 
+//                 an HFA or if we need the GC layout for an object ref.
 //
 // Return Value:
-//    The primitive type used to pass or return the struct, if applicable, or
-//    TYP_UNKNOWN otherwise.
-//
-// Assumptions:
-//    The size must be the size of the given type.
-//    The given class handle must be for a value type (struct).
-//
+//    The primitive type (i.e. byte, short, int, long, ref, float, double)
+//    used to pass or return structs of this size.
+//    If we shouldn't use a "primitive" type then TYP_UNKNOWN is returned.
 // Notes:
-//    Some callers call into this method directly, instead of the above method,
-//    when they have already determined the size.
-//    This is to avoid a redundant call across the JIT/EE interface.
-
-var_types    Compiler::argOrReturnTypeForStruct(unsigned size, CORINFO_CLASS_HANDLE clsHnd, bool forReturn)
+//    For 32-bit targets (X86/ARM32) the 64-bit TYP_LONG type is not 
+//    considered a primitive type by this method.
+//    So a struct that wraps a 'long' is passed and returned in the
+//    same way as any other 8-byte struct
+//    For ARM32 if we have an HFA struct that wraps a 64-bit double
+//    we will return TYP_DOUBLE.
+//
+var_types    Compiler::getPrimitiveTypeForStruct( unsigned  structSize,
+                                                  CORINFO_CLASS_HANDLE clsHnd)
 {
-    BYTE gcPtr = 0;
-    var_types useType = TYP_UNKNOWN;
+    assert(structSize != 0);
+
+    var_types useType;
 
-    switch (size)
+    switch (structSize)
     {
     case 1:
         useType = TYP_BYTE;
@@ -516,63 +495,508 @@ var_types    Compiler::argOrReturnTypeForStruct(unsigned size, CORINFO_CLASS_HAN
     case 3:
         useType = TYP_INT;
         break;
+
 #endif // _TARGET_XARCH_
 
 #ifdef _TARGET_64BIT_
     case 4:
-        useType = TYP_INT;
+        if (IsHfa(clsHnd))
+        {
+            // A structSize of 4 with IsHfa, it must be an HFA of one float
+            useType = TYP_FLOAT;
+        }
+        else
+        {
+            useType = TYP_INT;
+        }
         break;
-#endif // _TARGET_64BIT_
 
-    // Pointer size
-#ifdef _TARGET_64BIT_
-#ifndef _TARGET_AMD64_
+#ifndef _TARGET_XARCH_   
     case 5:
     case 6:
     case 7:
-#endif // _TARGET_AMD64_
+        useType = TYP_I_IMPL;
+        break;
+
+#endif // _TARGET_XARCH_
+#endif // _TARGET_64BIT_
+
+    case TARGET_POINTER_SIZE:
+        if (IsHfa(clsHnd))
+        {
+#ifdef _TARGET_64BIT_
+            var_types hfaType = GetHfaType(clsHnd);
+
+            // A structSize of 8 with IsHfa, we have two possiblities:
+            // An HFA of one double or an HFA of two floats
+            //
+            // Check and exclude the case of an HFA of two floats
+            if (hfaType == TYP_DOUBLE)
+            {
+                // We have an HFA of one double
+                useType = TYP_DOUBLE;
+            }  
+            else 
+            {
+                assert(hfaType == TYP_FLOAT);
+
+                // We have an HFA of two floats
+                // This should be passed or returned in two FP registers
+                useType = TYP_UNKNOWN;
+            }
+#else  // a 32BIT target
+            // A structSize of 4 with IsHfa, it must be an HFA of one float
+            useType = TYP_FLOAT;
+#endif
+        }
+        else
+        {
+            BYTE gcPtr = 0;
+            // Check if this pointer-sized struct is wrapping a GC object
+            info.compCompHnd->getClassGClayout(clsHnd, &gcPtr);
+            useType = getJitGCType(gcPtr);
+        }
+        break;
+
+#ifdef _TARGET_ARM_
     case 8:
-#else // !_TARGET_64BIT_
-    case 4:
-#endif // !_TARGET_64BIT_
-        info.compCompHnd->getClassGClayout(clsHnd, &gcPtr);
-        useType = getJitGCType(gcPtr);
+        if (IsHfa(clsHnd))
+        {
+            var_types hfaType = GetHfaType(clsHnd);
+
+            // A structSize of 8 with IsHfa, we have two possiblities:
+            // An HFA of one double or an HFA of two floats
+            //
+            // Check and exclude the case of an HFA of two floats
+            if (hfaType == TYP_DOUBLE)
+            {
+                // We have an HFA of one double
+                useType = TYP_DOUBLE;
+            }  
+            else 
+            {
+                assert(hfaType == TYP_FLOAT);
+
+                // We have an HFA of two floats
+                // This should be passed or returned in two FP registers
+                useType = TYP_UNKNOWN;
+            }
+        }
+        else
+        {
+            // We don't have an HFA
+            useType = TYP_UNKNOWN;
+        }
         break;
+#endif // _TARGET_ARM_
 
     default:
-#if FEATURE_MULTIREG_RET
-        if (forReturn)
+        useType = TYP_UNKNOWN;
+        break;
+    }
+
+    return useType;
+}
+
+//-----------------------------------------------------------------------------
+// getArgTypeForStruct: 
+//     Get the type that is used to pass values of the given struct type.
+//     If you have already retrieved the struct size then it should be 
+//     passed as the optional third argument, as this allows us to avoid
+//     an extra call to getClassSize(clsHnd)
+//
+// Arguments:
+//    clsHnd       - the handle for the struct type
+//    wbPassStruct - An "out" argument with information about how 
+//                   the struct is to be passed
+//    structSize   - the size of the struct type, 
+//                   or zero if we should call getClassSize(clsHnd)
+//
+// Return Value:
+//    For wbPassStruct you can pass a 'nullptr' and nothing will be written
+//     or returned for that out parameter.
+//    When *wbPassStruct is SPK_PrimitiveType this method's return value
+//       is the primitive type used to pass the struct.
+//    When *wbPassStruct is SPK_ByReference this method's return value
+//       is always TYP_UNKNOWN and the struct type is passed by reference to a copy
+//    When *wbPassStruct is SPK_ByValue or SPK_ByValueAsHfa this method's return value 
+//       can be TYP_STRUCT and the struct type is passed using multiple registers.
+//       or can be TYP_UNKNOWN and the struct type is passed by value (for x86 and ARM32)
+//
+// Assumptions:
+//    The size must be the size of the given type.
+//    The given class handle must be for a value type (struct).
+//
+// Notes:
+//    About HFA types:
+//        When the clsHnd is a one element HFA type we return the appropriate
+//         floating point primitive type and *wbPassStruct is SPK_PrimitiveType
+//        If there are two or more elements in the HFA type then the this method's 
+//         return value is TYP_STRUCT and *wbPassStruct is SPK_ByValueAsHfa
+//    About returning TYP_STRUCT:
+//        Whenever this method's return value is TYP_STRUCT it usually means that multiple 
+//        registers will be used to pass this struct.
+//        The only exception occurs if all of the parameters registers are used up 
+//        then we must use stack slots instead.  In such a case the amount of stack needed
+//        is always equal to the structSize (rounded up to the next pointer size)
+//
+var_types  Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
+                                         structPassingKind*   wbPassStruct,
+                                         unsigned             structSize /* = 0 */)
+{
+    var_types          useType         = TYP_UNKNOWN;
+    structPassingKind  howToPassStruct = SPK_Unknown;   // We must change this before we return
+
+    if (structSize == 0)
+    {
+        structSize = info.compCompHnd->getClassSize(clsHnd);
+    }
+    assert(structSize > 0);
+
+#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+
+    // An 8-byte struct may need to be passed in a floating point register
+    // So we always consult the struct "Classifier" routine
+    //
+    SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc;
+    eeGetSystemVAmd64PassStructInRegisterDescriptor(clsHnd, &structDesc);
+
+    // If we have one eightByteCount then we can set 'useType' based on that
+    if (structDesc.eightByteCount == 1)
+    {
+        // Set 'useType' to the type of the first eightbyte item
+        useType = GetEightByteType(structDesc, 0);
+    }
+
+#else // not UNIX_AMD64
+
+    // We set the "primitive" useType based upon the structSize
+    // and also examine the clsHnd to see if it is an HFA of count one
+    useType = getPrimitiveTypeForStruct(structSize, clsHnd);
+
+#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+
+    // Did we change this struct type into a simple "primitive" type?
+    //
+    if (useType != TYP_UNKNOWN)
+    {
+        // Yes, we should use the "primitive" type in 'useType'
+        howToPassStruct = SPK_PrimitiveType;
+    }
+    else  // We can't replace the struct with a "primitive" type
+    {
+        // See if we can pass this struct by value, possibly in multiple registers
+        // or if we should pass it by reference to a copy
+        //
+        if (structSize <= MAX_PASS_MULTIREG_BYTES)
         {
-            if (size <= MAX_RET_MULTIREG_BYTES)
+            // Structs that are HFA's are passed by value in multiple registers
+            if (IsHfa(clsHnd))
             {
-#ifdef _TARGET_ARM64_
-                // TODO-ARM64-HFA - Implement x0,x1 returns   
-                // TODO-ARM64     - Implement HFA returns   
-#endif // _TARGET_XXX_
+                // HFA's of count one should have been handled by getPrimitiveTypeForStruct
+                assert(GetHfaCount(clsHnd) >= 2);
+
+                // setup wbPassType and useType indicate that this is passed by value as an HFA
+                //  using multiple registers
+                //  (when all of the parameters registers are used, then the stack will be used)
+                howToPassStruct = SPK_ByValueAsHfa;
+                useType = TYP_STRUCT;
+            }
+            else  // Not an HFA struct type
+            {
+
+#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+
+                // The case of (structDesc.eightByteCount == 1) should have already been handled
+                if (structDesc.eightByteCount > 1)
+                {
+                    // setup wbPassType and useType indicate that this is passed by value in multiple registers
+                    //  (when all of the parameters registers are used, then the stack will be used)
+                    howToPassStruct = SPK_ByValue;
+                    useType = TYP_STRUCT;
+                }
+                else
+                {
+                    assert(structDesc.eightByteCount == 0);
+                    // Otherwise we pass this struct by reference to a copy
+                    // setup wbPassType and useType indicate that this is passed using one register
+                    //  (by reference to a copy)
+                    howToPassStruct = SPK_ByReference;
+                    useType = TYP_UNKNOWN;
+                }
+
+#elif  defined(_TARGET_ARM64_)
+
+                // Structs that are pointer sized or smaller should have been handled by getPrimitiveTypeForStruct
+                assert(structSize > TARGET_POINTER_SIZE);
+
+                // On ARM64 structs that are 9-16 bytes are passed by value in multiple registers
+                //
+                if (structSize <= (TARGET_POINTER_SIZE * 2))
+                {
+                    // setup wbPassType and useType indicate that this is passed by value in multiple registers
+                    //  (when all of the parameters registers are used, then the stack will be used)
+                    howToPassStruct = SPK_ByValue;
+                    useType = TYP_STRUCT;
+                }
+                else // a structSize that is 17-32 bytes in size
+                {
+                    // Otherwise we pass this struct by reference to a copy
+                    // setup wbPassType and useType indicate that this is passed using one register
+                    //  (by reference to a copy)
+                    howToPassStruct = SPK_ByReference;
+                    useType = TYP_UNKNOWN;
+                }
+
+#elif  defined(_TARGET_X86_) || defined(_TARGET_ARM_)
+
+                // Otherwise we pass this struct by value 
+                // setup wbPassType and useType indicate that this is passed by value according to the X86/ARM32 ABI
+                howToPassStruct = SPK_ByValue;
+                useType = TYP_UNKNOWN;
+
+#else  //  _TARGET_XXX_
+
+                noway_assert(!"Unhandled TARGET in getArgTypeForStruct (with FEATURE_MULTIREG_ARGS=1)");
+
+#endif  //  _TARGET_XXX_
+
             }
         }
-#endif // FEATURE_MULTIREG_RET
+        else  // (structSize > MAX_PASS_MULTIREG_BYTES)
+        {
+            // We have a (large) struct that can't be replaced with a "primitive" type
+            // and can't be passed in multiple registers
 
-#if FEATURE_MULTIREG_ARGS
-        if (!forReturn)
+#if defined(_TARGET_X86_) || defined(_TARGET_ARM_)
+
+            // Otherwise we pass this struct by value on the stack 
+            // setup wbPassType and useType indicate that this is passed by value according to the X86/ARM32 ABI
+            howToPassStruct = SPK_ByValue;
+            useType = TYP_UNKNOWN;
+
+#elif defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_)
+
+            // Otherwise we pass this struct by reference to a copy
+            // setup wbPassType and useType indicate that this is passed using one register (by reference to a copy)
+            howToPassStruct = SPK_ByReference;
+            useType = TYP_UNKNOWN;
+
+#else  //  _TARGET_XXX_
+
+            noway_assert(!"Unhandled TARGET in getArgTypeForStruct");
+
+#endif  //  _TARGET_XXX_
+
+        }
+    }
+
+    // 'howToPassStruct' must be set to one of the valid values before we return
+    assert(howToPassStruct != SPK_Unknown);
+    if (wbPassStruct != nullptr)
+    {
+        *wbPassStruct = howToPassStruct;
+    }
+    return useType;
+}
+
+//-----------------------------------------------------------------------------
+// getReturnTypeForStruct: 
+//     Get the type that is used to return values of the given struct type.
+//     If you have already retrieved the struct size then it should be 
+//     passed as the optional third argument, as this allows us to avoid
+//     an extra call to getClassSize(clsHnd)
+//
+// Arguments:
+//    clsHnd         - the handle for the struct type
+//    wbReturnStruct - An "out" argument with information about how
+//                     the struct is to be returned
+//    structSize     - the size of the struct type, 
+//                     or zero if we should call getClassSize(clsHnd)
+//
+// Return Value:
+//    For wbReturnStruct you can pass a 'nullptr' and nothing will be written
+//     or returned for that out parameter.
+//    When *wbReturnStruct is SPK_PrimitiveType this method's return value
+//       is the primitive type used to return the struct.
+//    When *wbReturnStruct is SPK_ByReference this method's return value
+//       is always TYP_UNKNOWN and the struct type is returned using a return buffer
+//    When *wbReturnStruct is SPK_ByValue or SPK_ByValueAsHfa this method's return value
+//       is always TYP_STRUCT and the struct type is returned using multiple registers.
+//
+// Assumptions:
+//    The size must be the size of the given type.
+//    The given class handle must be for a value type (struct).
+//
+// Notes:
+//    About HFA types:
+//        When the clsHnd is a one element HFA type then this method's return
+//          value is the appropriate floating point primitive type and 
+//          *wbReturnStruct is SPK_PrimitiveType.
+//        If there are two or more elements in the HFA type and the target supports
+//          multireg return types then the return value is TYP_STRUCT and
+//          *wbReturnStruct is SPK_ByValueAsHfa.
+//        Additionally if there are two or more elements in the HFA type and
+//          the target doesn't support multreg return types then it is treated
+//          as if it wasn't an HFA type.
+//    About returning TYP_STRUCT:
+//        Whenever this method's return value is TYP_STRUCT it always means 
+//         that multiple registers are used to return this struct.
+//
+var_types  Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, 
+                                            structPassingKind*   wbReturnStruct,
+                                            unsigned             structSize /* = 0 */)
+{
+    var_types          useType           = TYP_UNKNOWN;
+    structPassingKind  howToReturnStruct = SPK_Unknown;   // We must change this before we return
+
+    if (structSize == 0)
+    {
+        structSize = info.compCompHnd->getClassSize(clsHnd);
+    }
+    assert(structSize > 0);
+
+#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+
+    // An 8-byte struct may need to be returned in a floating point register
+    // So we always consult the struct "Classifier" routine
+    //
+    SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc;
+    eeGetSystemVAmd64PassStructInRegisterDescriptor(clsHnd, &structDesc);
+
+    // If we have one eightByteCount then we can set 'useType' based on that
+    if (structDesc.eightByteCount == 1)
+    {
+        // Set 'useType' to the type of the first eightbyte item
+        useType = GetEightByteType(structDesc, 0);
+    }
+
+#else // not UNIX_AMD64
+
+    // We set the "primitive" useType based upon the structSize
+    // and also examine the clsHnd to see if it is an HFA of count one
+    useType = getPrimitiveTypeForStruct(structSize, clsHnd);
+
+#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+    
+    // Note this handles an odd case when FEATURE_MULTIREG_RET is disabled and HFAs are enabled
+    //
+    // getPrimitiveTypeForStruct will return TYP_UNKNOWN for a struct that is an HFA of two floats
+    // because when HFA are enabled, normally we would use two FP registers to pass or return it
+    //
+    // But if we don't have support for multiple register return types, we have to change this.
+    // Since we what we have an 8-byte struct (float + float)  we change useType to TYP_I_IMPL 
+    // so that the struct is returned instead using an 8-byte integer register.
+    //
+    if ((MAX_RET_MULTIREG_BYTES == 0) && (useType == TYP_UNKNOWN) &&
+        (structSize == 8) && IsHfa(clsHnd))
+    {
+        useType = TYP_I_IMPL;
+    }
+
+    // Did we change this struct type into a simple "primitive" type?
+    //
+    if (useType != TYP_UNKNOWN)
+    {
+        // Yes, we should use the "primitive" type in 'useType'
+        howToReturnStruct = SPK_PrimitiveType;
+    }
+    else  // We can't replace the struct with a "primitive" type
+    {
+        // See if we can return this struct by value, possibly in multiple registers
+        // or if we should return it using a return buffer register
+        //
+        if (structSize <= MAX_RET_MULTIREG_BYTES)
         {
-            if (size <= MAX_PASS_MULTIREG_BYTES)
+            // Structs that are HFA's are returned in multiple registers
+            if (IsHfa(clsHnd))
             {
-#ifdef _TARGET_ARM64_
-                assert(size > TARGET_POINTER_SIZE);
+                // HFA's of count one should have been handled by getPrimitiveTypeForStruct
+                assert(GetHfaCount(clsHnd) >= 2);
+
+                // setup wbPassType and useType indicate that this is returned by value as an HFA
+                //  using multiple registers
+                howToReturnStruct = SPK_ByValueAsHfa;
+                useType = TYP_STRUCT;
+            }
+            else  // Not an HFA struct type
+            {
+
+#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
 
-                // On ARM64 structs that are 9-16 bytes are passed by value
-                // or if the struct is an HFA it is passed by value
-                if ((size <= (TARGET_POINTER_SIZE * 2)) || IsHfa(clsHnd))
+                // The case of (structDesc.eightByteCount == 1) should have already been handled
+                if (structDesc.eightByteCount > 1)
                 {
-                    // set useType to TYP_STRUCT to indicate that this is passed by value in registers
+                    // setup wbPassType and useType indicate that this is returned by value in multiple registers
+                    howToReturnStruct = SPK_ByValue;
                     useType = TYP_STRUCT;
                 }
-#endif // _TARGET_ARM64_
+                else
+                {
+                    assert(structDesc.eightByteCount == 0);
+                    // Otherwise we return this struct using a return buffer
+                    // setup wbPassType and useType indicate that this is return using a return buffer register
+                    //  (reference to a return buffer)
+                    howToReturnStruct = SPK_ByReference;
+                    useType = TYP_UNKNOWN;
+                }
+
+#elif  defined(_TARGET_ARM64_)
+
+                // Structs that are pointer sized or smaller should have been handled by getPrimitiveTypeForStruct
+                assert(structSize > TARGET_POINTER_SIZE);
+
+                // On ARM64 structs that are 9-16 bytes are returned by value in multiple registers
+                //
+                if (structSize <= (TARGET_POINTER_SIZE * 2))
+                {
+                    // setup wbPassType and useType indicate that this is return by value in multiple registers
+                    howToReturnStruct = SPK_ByValue;
+                    useType = TYP_STRUCT;
+                }
+                else // a structSize that is 17-32 bytes in size
+                {
+                    // Otherwise we return this struct using a return buffer
+                    // setup wbPassType and useType indicate that this is returned using a return buffer register
+                    //  (reference to a return buffer)
+                    howToReturnStruct = SPK_ByReference;
+                    useType = TYP_UNKNOWN;
+                }
+
+#elif  defined(_TARGET_ARM_) || defined(_TARGET_X86_)
+
+                // Otherwise we return this struct using a return buffer
+                // setup wbPassType and useType indicate that this is returned using a return buffer register
+                //  (reference to a return buffer)
+                howToReturnStruct = SPK_ByReference;
+                useType = TYP_UNKNOWN;
+
+#else  //  _TARGET_XXX_
+
+                noway_assert(!"Unhandled TARGET in getReturnTypeForStruct (with FEATURE_MULTIREG_ARGS=1)");
+
+#endif  //  _TARGET_XXX_
+
             }
         }
-#endif // FEATURE_MULTIREG_ARGS
-        break;
+        else  // (structSize > MAX_RET_MULTIREG_BYTES)
+        {
+            // We have a (large) struct that can't be replaced with a "primitive" type
+            // and can't be returned in multiple registers
+
+            // We return this struct using a return buffer register
+            // setup wbPassType and useType indicate that this is returned using a return buffer register
+            //  (reference to a return buffer)
+            howToReturnStruct = SPK_ByReference;
+            useType = TYP_UNKNOWN;
+        }
+    }
+
+    // 'howToReturnStruct' must be set to one of the valid values before we return
+    assert(howToReturnStruct != SPK_Unknown);
+    if (wbReturnStruct != nullptr)
+    {
+        *wbReturnStruct = howToReturnStruct;
     }
     return useType;
 }
index 41b902a..251b03e 100644 (file)
@@ -3865,13 +3865,37 @@ public :
     // Convert a BYTE which represents the VM's CorInfoGCtype to the JIT's var_types
     var_types   getJitGCType(BYTE gcType);
 
-    // Get the "primitive" type, if any, that is used to pass or return
-    // values of the given struct type.
-    var_types    argOrReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, bool forReturn);
+    enum structPassingKind { SPK_Unknown,        // Invalid value, never returned
+                             SPK_PrimitiveType,  // The struct is passed/returned using a primitive type.
+                             SPK_ByValue,        // The struct is passed/returned by value (using the ABI rules) 
+                                                 //  for ARM64 and UNIX_X64 in multiple registers. (when all of the 
+                                                 //   parameters registers are used, then the stack will be used)
+                                                 //  for X86 passed on the stack, for ARM32 passed in registers
+                                                 //   or the stack or split between registers and the stack.
+                             SPK_ByValueAsHfa,   // The struct is passed/returned as an HFA in multiple registers.
+                             SPK_ByReference };  // The struct is passed/returned by reference to a copy/buffer.
+
+    // Get the "primitive" type that is is used when we are given a struct of size 'structSize'.
+    // For pointer sized structs the 'clsHnd' is used to determine if the struct contains GC ref.
+    // A "primitive" type is one of the scalar types: byte, short, int, long, ref, float, double
+    // If we can't or shouldn't use a "primitive" type then TYP_UNKNOWN is returned.
+    //
+    var_types    getPrimitiveTypeForStruct(unsigned structSize, CORINFO_CLASS_HANDLE clsHnd);
+
+    // Get the type that is used to pass values of the given struct type.
+    // If you have already retrieved the struct size then pass it as the optional third argument
+    //
+    var_types    getArgTypeForStruct(CORINFO_CLASS_HANDLE  clsHnd, 
+                                     structPassingKind*    wbPassStruct, 
+                                     unsigned              structSize = 0);
+
+    // Get the type that is used to return values of the given struct type.
+    // If you have already retrieved the struct size then pass it as the optional third argument
+    //
+    var_types    getReturnTypeForStruct(CORINFO_CLASS_HANDLE  clsHnd, 
+                                        structPassingKind*    wbPassStruct, 
+                                        unsigned              structSize = 0);
 
-    // Slightly optimized version of the above where we've already computed the size,
-    // so as to avoid a repeated JIT/EE interface call.
-    var_types    argOrReturnTypeForStruct(unsigned size, CORINFO_CLASS_HANDLE clsHnd, bool forReturn);
 
 #ifdef DEBUG
     // Print a representation of "vnp" or "vn" on standard output.
index 9b10fd8..6bf7c48 100644 (file)
@@ -661,7 +661,16 @@ bool   Compiler::VarTypeIsMultiByteAndCanEnreg(var_types type,
         // Account for the classification of the struct.
         result = IsRegisterPassable(typeClass);
 #else // !FEATURE_UNIX_AMD64_STRUCT_PASSING
-        type = argOrReturnTypeForStruct(size, typeClass, forReturn);
+        if (forReturn)
+        {
+            structPassingKind howToReturnStruct;
+            type = getReturnTypeForStruct(typeClass, &howToReturnStruct, size);
+        }
+        else
+        {
+            structPassingKind howToPassStruct;
+            type = getArgTypeForStruct(typeClass, &howToPassStruct, size);
+        }
         if (type != TYP_UNKNOWN)
         {
             result = true;
index f4f29d7..a420591 100644 (file)
@@ -7459,16 +7459,28 @@ GenTreePtr                Compiler::impFixupCallStructReturn(GenTreePtr     call
     return call;
 #endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
 
-    // Check for TYP_STRUCT argument that can fit into a single register
-    // change the type on those trees.
-    var_types regType = argOrReturnTypeForStruct(retClsHnd, true);
-    if (regType != TYP_UNKNOWN)
+    // Check for TYP_STRUCT type that wraps a primitive type
+    // Such structs are returned using a single register 
+    // and we change the return type on those calls here.
+    //
+    structPassingKind howToReturnStruct;
+    var_types returnType = getReturnTypeForStruct(retClsHnd, &howToReturnStruct);
+
+    if (howToReturnStruct == SPK_ByReference)
     {
-        call->gtCall.gtReturnType = regType;
+        assert(returnType == TYP_UNKNOWN);
+        call->gtCall.gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG;
     }
     else
     {
-        call->gtCall.gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG; 
+        assert(returnType != TYP_UNKNOWN);
+        call->gtCall.gtReturnType = returnType;
+
+        // ToDo: Refactor this common code sequence into its own method as it is used 4+ times
+        if ((returnType == TYP_LONG) && (compLongUsed == false))
+            compLongUsed = true;
+        else if (((returnType == TYP_FLOAT) || (returnType == TYP_DOUBLE)) && (compFloatingPointUsed == false))
+            compFloatingPointUsed = true;
     }
 
     return call;
index cf6e2b0..c8eefb4 100644 (file)
@@ -156,12 +156,20 @@ void                Compiler::lvaInitTypeRef()
             }
 #else // !FEATURE_UNIX_AMD64_STRUCT_PASSING
             // Check for TYP_STRUCT argument that can fit into a single register
-            var_types argRetType = argOrReturnTypeForStruct(info.compMethodInfo->args.retTypeClass, true /* forReturn */);
-            info.compRetNativeType = argRetType;
-            if (argRetType == TYP_UNKNOWN)
+            structPassingKind howToReturnStruct;
+            var_types returnType = getReturnTypeForStruct(info.compMethodInfo->args.retTypeClass, &howToReturnStruct);
+            assert(howToReturnStruct != SPK_ByReference);  // hasRetBuffArg is false, so we can't have this answer here
+            info.compRetNativeType = returnType;
+            if (returnType == TYP_UNKNOWN)
             {
                 assert(!"Unexpected size when returning struct by value");
             }
+
+            // ToDo: Refactor this common code sequence into its own method as it is used 4+ times
+            if ((returnType == TYP_LONG) && (compLongUsed == false))
+                compLongUsed = true;
+            else if (((returnType == TYP_FLOAT) || (returnType == TYP_DOUBLE)) && (compFloatingPointUsed == false))
+                compFloatingPointUsed = true;
 #endif // !FEATURE_UNIX_AMD64_STRUCT_PASSING
         }
     }
index 3379142..12ee779 100644 (file)
@@ -3354,13 +3354,19 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode)
                     {
                         // change our GT_OBJ into a GT_IND of the correct type.
                         // We've already ensured above that size is a power of 2, and less than or equal to pointer size.
-                        structBaseType = argOrReturnTypeForStruct(originalSize, objClass, false /* forReturn */);
+
+                        structPassingKind howToPassStruct;
+                        structBaseType = getArgTypeForStruct(objClass, &howToPassStruct, originalSize);
+                        assert(howToPassStruct == SPK_PrimitiveType);
+
+                        // ToDo: remove this block as getArgTypeForStruct properly handles turning one element HFAs into primitives
                         if (isHfaArg)
                         {
                             // If we reach here with an HFA arg it has to be a one element HFA
                             assert(hfaSlots == 1);
                             structBaseType = hfaType;   // change the indirection type to a floating point type
                         }
+
                         noway_assert(structBaseType != TYP_UNKNOWN);
 
                         argObj->ChangeOper(GT_IND);
index 9a3621c..aaddcba 100644 (file)
@@ -384,11 +384,14 @@ typedef unsigned short          regPairNoSmall; // arm: need 12 bits
 #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  
+  #define FEATURE_MULTIREG_RET          0  // Support for returning a single value in more than one register
+  #define MAX_PASS_MULTIREG_BYTES       0  // No multireg arguments 
+  #define MAX_RET_MULTIREG_BYTES        0  // No multireg return values 
 #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 FEATURE_MULTIREG_RET          1  // Support for returning a single value in more than one register
+  #define MAX_PASS_MULTIREG_BYTES       0  // No multireg arguments (note this seems wrong as MAX_ARG_REG_COUNT is 2)
   #define MAX_RET_MULTIREG_BYTES        8  // Maximum size of a struct that could be returned in more than one register
 #endif
 
@@ -728,7 +731,9 @@ typedef unsigned short          regPairNoSmall; // arm: need 12 bits
 #else // !UNIX_AMD64_ABI
   #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  
+  #define FEATURE_MULTIREG_RET          0  // Support for returning a single value in more than one register
+  #define MAX_PASS_MULTIREG_BYTES       0  // No multireg arguments 
+  #define MAX_RET_MULTIREG_BYTES        0  // No multireg return values 
   #define MAX_ARG_REG_COUNT             1  // Maximum registers used to pass a single argument (no arguments are passed using multiple registers)
   #define MAX_RET_REG_COUNT             1  // Maximum registers used to return a value.
 #endif // !UNIX_AMD64_ABI