Allow inlining when inlinee has `starg`
authorJoseph Tremoulet <jotrem@microsoft.com>
Fri, 29 Jul 2016 17:54:37 +0000 (13:54 -0400)
committerJoseph Tremoulet <jotrem@microsoft.com>
Fri, 5 Aug 2016 00:29:03 +0000 (20:29 -0400)
The inliner's restriction that methods that make use of the `starg` opcode
cannot be inlined is somewhat artificial.  The comments indicate that the
reason for this excluion is because the inliner forward-substitutes
argument expressions in place of parameter references, but the codepaths
required to force evaluating an argument expression at the callsite and
reference that result in place of parameter references is already in place
to handle `ldarga` and complex argument expressions.

This change
 - adds an `argHasStargOp` flag to the `inlArgInfo` type, similar
   to the existing `argHasLdargaOp` flag
 - removes the `CALLEE_STORES_TO_ARGUMENT` inline observation and instead
   sets the new flag for `starg` cases
 - updates `impInlineFetchArg` to force evaluation to a temp for arguments
   that are marked `argHasStargOp`
 - updates `starg` processing in the importer to store to the
   corresponding temp when importing for an inlinee

Resolves dotnet/coreclr#6014.

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

src/coreclr/src/jit/flowgraph.cpp
src/coreclr/src/jit/importer.cpp
src/coreclr/src/jit/inline.def
src/coreclr/src/jit/inline.h
src/coreclr/src/jit/inlinepolicy.cpp
src/coreclr/tests/issues.targets
src/coreclr/tests/src/JIT/Methodical/AsgOp/r4/r4.cs

index baf3575..d4508e0 100644 (file)
@@ -4580,49 +4580,31 @@ DECODE_OPCODE:
         case CEE_STARG:
         case CEE_STARG_S:
             {
-                if (makeInlineObservations)
-                {
-                    // The inliner keeps the args as trees and clones
-                    // them.  Storing the arguments breaks that
-                    // simplification.  To allow this, flag the argument
-                    // as written to and spill it before inlining.  That
-                    // way the STARG in the inlinee is trivial.
-                    //
-                    // Arguably this should be NoteFatal, but the legacy behavior is
-                    // to ignore this for the prejit root.
-                    compInlineResult->Note(InlineObservation::CALLEE_STORES_TO_ARGUMENT);
-
-                    // Fail fast, if inlining
-                    if (isInlining)
-                    {
-                        assert(compInlineResult->IsFailure());
-                        JITDUMP("INLINER: Inline expansion aborted; opcode at offset [%02u]"
-                                " writes to an argument\n", codeAddr-codeBegp-1);
-                        return;
-                    }
-                }
+                noway_assert(sz == sizeof(BYTE) || sz == sizeof(WORD));
 
-                // In non-inline cases, note written-to locals.
-                if (!isInlining)
+                if (codeAddr > codeEndp - sz)
                 {
-                    noway_assert(sz == sizeof(BYTE) || sz == sizeof(WORD));
-
-                    if (codeAddr > codeEndp - sz)
-                    {
-                        goto TOO_FAR;
-                    }
-
-                    varNum = (sz == sizeof(BYTE)) ? getU1LittleEndian(codeAddr)
-                                                  : getU2LittleEndian(codeAddr);
-                    varNum = compMapILargNum(varNum); // account for possible hidden param
-
-                    // This check is only intended to prevent an AV.  Bad varNum values will later
-                    // be handled properly by the verifier.
-                    if (varNum < lvaTableCnt)
-                    {
-                        lvaTable[varNum].lvArgWrite = 1;
-                    }
+                    goto TOO_FAR;
                 }
+
+                varNum = (sz == sizeof(BYTE)) ? getU1LittleEndian(codeAddr)
+                                                : getU2LittleEndian(codeAddr);
+                varNum = compMapILargNum(varNum); // account for possible hidden param
+
+                               // This check is only intended to prevent an AV.  Bad varNum values will later
+                               // be handled properly by the verifier.
+                               if (varNum < lvaTableCnt)
+                               {
+                                       if (isInlining)
+                                       {
+                                               impInlineInfo->inlArgInfo[varNum].argHasStargOp = true;
+                                       }
+                                       else
+                                       {
+                                               // In non-inline cases, note written-to locals.
+                                               lvaTable[varNum].lvArgWrite = 1;
+                                       }
+                               }
             }
             break;
 
@@ -22401,7 +22383,8 @@ GenTreePtr      Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo)
 
                 if (argSingleUseNode &&
                     !(argSingleUseNode->gtFlags & GTF_VAR_CLONED) &&
-                    !inlArgInfo[argNum].argHasLdargaOp)
+                    !inlArgInfo[argNum].argHasLdargaOp &&
+                    !inlArgInfo[argNum].argHasStargOp)
                 {
                     /* Change the temp in-place to the actual argument */
 
index d2a92ee..680fa55 100644 (file)
@@ -9405,6 +9405,15 @@ PUSH_I4CON:
                 Verify(lclNum < info.compILargsCount, "bad arg num");
             }
 
+            if (compIsForInlining())
+            {
+                op1 = impInlineFetchArg(lclNum, impInlineInfo->inlArgInfo, impInlineInfo->lclVarInfo);
+                noway_assert(op1->gtOper == GT_LCL_VAR);
+                lclNum = op1->AsLclVar()->gtLclNum;
+
+                goto VAR_ST_VALID;
+            }
+
             lclNum = compMapILargNum(lclNum);     // account for possible hidden param
             assertImp(lclNum < numArgs);
 
@@ -9471,6 +9480,8 @@ PUSH_I4CON:
                 BADCODE("Bad IL");
             }
 
+               VAR_ST_VALID:
+
             /* if it is a struct assignment, make certain we don't overflow the buffer */ 
             assert(lclTyp != TYP_STRUCT || lvaLclSize(lclNum) >= info.compCompHnd->getClassSize(clsHnd));
 
@@ -16341,6 +16352,8 @@ void Compiler::impInlineRecordArgInfo(InlineInfo *  pInlineInfo,
             printf(" has side effects"); 
         if  (inlCurArgInfo->argHasLdargaOp)
             printf(" has ldarga effect");
+        if (inlCurArgInfo->argHasStargOp)
+            printf(" has starg effect");
         if  (inlCurArgInfo->argIsByRefToStructLocal)
             printf(" is byref to a struct local");
 
@@ -16748,7 +16761,7 @@ GenTreePtr  Compiler::impInlineFetchArg(unsigned lclNum, InlArgInfo *inlArgInfo,
     GenTreePtr op1 = NULL;
 
             // constant or address of local
-    if (inlArgInfo[lclNum].argIsInvariant && !inlArgInfo[lclNum].argHasLdargaOp)
+    if (inlArgInfo[lclNum].argIsInvariant && !inlArgInfo[lclNum].argHasLdargaOp && !inlArgInfo[lclNum].argHasStargOp)
     {
         /* Clone the constant. Note that we cannot directly use argNode
         in the trees even if inlArgInfo[lclNum].argIsUsed==false as this
@@ -16760,7 +16773,7 @@ GenTreePtr  Compiler::impInlineFetchArg(unsigned lclNum, InlArgInfo *inlArgInfo,
         PREFIX_ASSUME(op1 != NULL);
         inlArgInfo[lclNum].argTmpNum = (unsigned)-1;      // illegal temp
     }
-    else if (inlArgInfo[lclNum].argIsLclVar && !inlArgInfo[lclNum].argHasLdargaOp)
+    else if (inlArgInfo[lclNum].argIsLclVar && !inlArgInfo[lclNum].argHasLdargaOp && !inlArgInfo[lclNum].argHasStargOp)
     {
         /* Argument is a local variable (of the caller)
          * Can we re-use the passed argument node? */
@@ -16780,7 +16793,7 @@ GenTreePtr  Compiler::impInlineFetchArg(unsigned lclNum, InlArgInfo *inlArgInfo,
             op1 = gtNewLclvNode(op1->gtLclVarCommon.gtLclNum, lclTyp, op1->gtLclVar.gtLclILoffs);
         }
     }
-    else if (inlArgInfo[lclNum].argIsByRefToStructLocal)
+    else if (inlArgInfo[lclNum].argIsByRefToStructLocal && !inlArgInfo[lclNum].argHasStargOp)
     {
         /* Argument is a by-ref address to a struct, a normed struct, or its field. 
            In these cases, don't spill the byref to a local, simply clone the tree and use it.
index f2d90ea..69b617d 100644 (file)
@@ -93,7 +93,6 @@ INLINE_OBSERVATION(NUMBER_OF_ARGUMENTS,       int,    "number of arguments",
 INLINE_OBSERVATION(NUMBER_OF_BASIC_BLOCKS,    int,    "number of basic blocks",        INFORMATION, CALLEE)
 INLINE_OBSERVATION(NUMBER_OF_LOCALS,          int,    "number of locals",              INFORMATION, CALLEE)
 INLINE_OBSERVATION(RANDOM_ACCEPT,             bool,   "random accept",                 INFORMATION, CALLEE)
-INLINE_OBSERVATION(STORES_TO_ARGUMENT,        bool,   "stores to argument",            INFORMATION, CALLEE)
 INLINE_OBSERVATION(UNSUPPORTED_OPCODE,        bool,   "unsupported opcode",            INFORMATION, CALLEE)
 
 // ------ Caller Corectness ------- 
index aa0573c..8f91247 100644 (file)
@@ -521,6 +521,7 @@ struct InlArgInfo
     unsigned    argHasTmp     :1;   // the argument will be evaluated to a temp
     unsigned    argIsByRefToStructLocal:1;  // Is this arg an address of a struct local or a normed struct local or a field in them?
     unsigned    argHasLdargaOp:1;   // Is there LDARGA(s) operation on this argument?
+    unsigned    argHasStargOp :1;   // Is there STARG(s) operation on this argument?
 
     unsigned    argTmpNum;          // the argument tmp number
     GenTreePtr  argNode;
index 8a8166b..28a9d21 100644 (file)
@@ -298,7 +298,6 @@ void LegacyPolicy::NoteBool(InlineObservation obs, bool value)
 
         case InlineObservation::CALLEE_HAS_SWITCH:
         case InlineObservation::CALLEE_UNSUPPORTED_OPCODE:
-        case InlineObservation::CALLEE_STORES_TO_ARGUMENT:
             // LegacyPolicy ignores these for prejit roots.
             if (!m_IsPrejitRoot)
             {
@@ -927,7 +926,6 @@ void RandomPolicy::NoteBool(InlineObservation obs, bool value)
 
         case InlineObservation::CALLEE_HAS_SWITCH:
         case InlineObservation::CALLEE_UNSUPPORTED_OPCODE:
-        case InlineObservation::CALLEE_STORES_TO_ARGUMENT:
             // Pass these on, they should cause inlining to fail.
             propagate = true;
             break;
index ebb4e59..1f4ae04 100644 (file)
         <ExcludeList Include="$(XunitTestBinBase)\JIT\jit64\hfa\main\testG\hfa_sf2G_r\hfa_sf2G_r.cmd">
             <Issue>6180</Issue>
         </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)\JIT\Regression\CLR-x86-JIT\V1-M11-Beta1\b44410\b44410\b44410.cmd">
+             <Issue>6588</Issue>
+        </ExcludeList>
     </ItemGroup>
     
     <!-- Tests that need to be triaged for vararg usage as that is not supported -->
index 6e72caf..36672c8 100644 (file)
 // See the LICENSE file in the project root for more information.
 
 using System;
+using System.Runtime.CompilerServices;
 
 internal class test
 {
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f00(float x, float y)
     {
         x = x + y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f01(float x, float y)
     {
         x = x - y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f02(float x, float y)
     {
         x = x * y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f03(float x, float y)
     {
         x = x / y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f04(float x, float y)
     {
         x = x % y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f10(float x, float y)
     {
         x += x + y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f11(float x, float y)
     {
         x += x - y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f12(float x, float y)
     {
         x += x * y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f13(float x, float y)
     {
         x += x / y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f14(float x, float y)
     {
         x += x % y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f20(float x, float y)
     {
         x -= x + y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f21(float x, float y)
     {
         x -= x - y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f22(float x, float y)
     {
         x -= x * y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f23(float x, float y)
     {
         x -= x / y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f24(float x, float y)
     {
         x -= x % y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f30(float x, float y)
     {
         x *= x + y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f31(float x, float y)
     {
         x *= x - y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f32(float x, float y)
     {
         x *= x * y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f33(float x, float y)
     {
         x *= x / y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f34(float x, float y)
     {
         x *= x % y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f40(float x, float y)
     {
         x /= x + y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f41(float x, float y)
     {
         x /= x - y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f42(float x, float y)
     {
         x /= x * y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f43(float x, float y)
     {
         x /= x / y;
         return x;
     }
 
+    [MethodImpl(MethodImplOptions.NoInlining)]
     private static float f44(float x, float y)
     {
         x /= x % y;