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
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;
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 */
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);
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));
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");
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
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? */
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.
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 -------
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;
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)
{
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;
<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 -->
// 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;