summaryrefslogtreecommitdiff
path: root/src/jit/morph.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit/morph.cpp')
-rwxr-xr-xsrc/jit/morph.cpp217
1 files changed, 159 insertions, 58 deletions
diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp
index f5c8d083ab..c8835ead82 100755
--- a/src/jit/morph.cpp
+++ b/src/jit/morph.cpp
@@ -89,7 +89,7 @@ GenTreePtr Compiler::fgMorphIntoHelperCall(GenTreePtr tree,
GenTreeCall* callNode = tree->AsCall();
ReturnTypeDesc* retTypeDesc = callNode->GetReturnTypeDesc();
retTypeDesc->Reset();
- retTypeDesc->InitializeReturnType(this, callNode->gtRetClsHnd);
+ retTypeDesc->InitializeLongReturnType(this);
callNode->ClearOtherRegs();
NYI("Helper with TYP_LONG return type");
@@ -1508,11 +1508,11 @@ void fgArgInfo::ArgsComplete()
// so we skip this for ARM32 until it is ported to use RyuJIT backend
//
#if FEATURE_MULTIREG_ARGS
- if ((argx->TypeGet() == TYP_STRUCT) &&
- (curArgTabEntry->numRegs > 1) &&
- (curArgTabEntry->needTmp == false))
+ bool isMultiRegArg = (curArgTabEntry->numRegs > 1);
+
+ if ((argx->TypeGet() == TYP_STRUCT) && (curArgTabEntry->needTmp == false))
{
- if ((argx->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) != 0)
+ if (isMultiRegArg && ((argx->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) != 0))
{
// Spill multireg struct arguments that have Assignments or Calls embedded in them
curArgTabEntry->needTmp = true;
@@ -1522,7 +1522,7 @@ void fgArgInfo::ArgsComplete()
// We call gtPrepareCost to measure the cost of evaluating this tree
compiler->gtPrepareCost(argx);
- if (argx->gtCostEx > (6 * IND_COST_EX))
+ if (isMultiRegArg && (argx->gtCostEx > (6 * IND_COST_EX)))
{
// Spill multireg struct arguments that are expensive to evaluate twice
curArgTabEntry->needTmp = true;
@@ -1534,6 +1534,21 @@ void fgArgInfo::ArgsComplete()
unsigned structSize = compiler->info.compCompHnd->getClassSize(objClass);
switch (structSize)
{
+ case 3:
+ case 5:
+ case 6:
+ case 7:
+ // If we have a stack based LclVar we can perform a wider read of 4 or 8 bytes
+ //
+ if (argObj->gtObj.gtOp1->IsVarAddr() == false) // Is the source not a LclVar?
+ {
+ // If we don't have a LclVar we need to read exactly 3,5,6 or 7 bytes
+ // For now we use a a GT_CPBLK to copy the exact size into a GT_LCL_VAR temp.
+ //
+ curArgTabEntry->needTmp = true;
+ }
+ break;
+
case 11:
case 13:
case 14:
@@ -2032,7 +2047,7 @@ GenTreePtr Compiler::fgMakeTmpArgNode(unsigned tmpVarNum
#if FEATURE_MULTIREG_ARGS
#ifdef _TARGET_ARM64_
assert(varTypeIsStruct(type));
- if (varDsc->lvIsMultiregStruct())
+ if (lvaIsMultiregStruct(varDsc))
{
// ToDo-ARM64: Consider using: arg->ChangeOper(GT_LCL_FLD);
// as that is how FEATURE_UNIX_AMD64_STRUCT_PASSING works.
@@ -2191,22 +2206,45 @@ void fgArgInfo::EvalArgsToTemps()
{
setupArg = compiler->gtNewTempAssign(tmpVarNum, argx);
+ LclVarDsc* varDsc = compiler->lvaTable + tmpVarNum;
+
#ifndef LEGACY_BACKEND
if (compiler->fgOrder == Compiler::FGOrderLinear)
{
// We'll reference this temporary variable just once
// when we perform the function call after
// setting up this argument.
- LclVarDsc* varDsc = compiler->lvaTable + tmpVarNum;
varDsc->lvRefCnt = 1;
}
#endif // !LEGACY_BACKEND
+ var_types lclVarType = genActualType(argx->gtType);
+ var_types scalarType = TYP_UNKNOWN;
+
if (setupArg->OperIsCopyBlkOp())
+ {
setupArg = compiler->fgMorphCopyBlock(setupArg);
+#ifdef _TARGET_ARM64_
+ // This scalar LclVar widening step is only performed for ARM64
+ //
+ CORINFO_CLASS_HANDLE clsHnd = compiler->lvaGetStruct(tmpVarNum);
+ unsigned structSize = varDsc->lvExactSize;
- /* Create a copy of the temp to go to the late argument list */
- defArg = compiler->gtNewLclvNode(tmpVarNum, genActualType(argx->gtType));
+ scalarType = compiler->getPrimitiveTypeForStruct(structSize, clsHnd);
+#endif // _TARGET_ARM64_
+ }
+
+ // scalarType can be set to a wider type for ARM64: (3 => 4) or (5,6,7 => 8)
+ if ((scalarType != TYP_UNKNOWN) && (scalarType != lclVarType))
+ {
+ // Create a GT_LCL_FLD using the wider type to go to the late argument list
+ defArg = compiler->gtNewLclFldNode(tmpVarNum, scalarType, 0);
+ }
+ else
+ {
+ // Create a copy of the temp to go to the late argument list
+ defArg = compiler->gtNewLclvNode(tmpVarNum, lclVarType);
+ }
curArgTabEntry->isTmp = true;
curArgTabEntry->tmpNum = tmpVarNum;
@@ -2303,7 +2341,7 @@ void fgArgInfo::EvalArgsToTemps()
{
BADCODE("Unhandled struct argument tree in fgMorphArgs");
}
- }
+ }
#endif // !(defined(_TARGET_AMD64_) && !defined(FEATURE_UNIX_AMD64_STRUCT_PASSING))
@@ -2564,6 +2602,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode)
regMaskTP argSkippedRegMask = RBM_NONE;
#if defined(_TARGET_ARM_) || defined(_TARGET_AMD64_)
+ // Note this in ONLY used by _TARGET_ARM_
regMaskTP fltArgSkippedRegMask = RBM_NONE;
#endif
@@ -2861,7 +2900,10 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode)
call->fgArgInfo = new (this, CMK_Unknown) fgArgInfo(this, call, numArgs);
}
- fgFixupStructReturn(call);
+ if (varTypeIsStruct(call))
+ {
+ fgFixupStructReturn(call);
+ }
/* First we morph the argument subtrees ('this' pointer, arguments, etc.).
* During the first call to fgMorphArgs we also record the
@@ -3323,7 +3365,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode)
}
else // We must have a GT_OBJ with a struct type, but the GT_OBJ may be be a child of a GT_COMMA
{
- GenTreePtr argObj = argx;
+ GenTreePtr argObj = argx;
GenTreePtr* parentOfArgObj = parentArgx;
assert(args->IsList());
@@ -3333,7 +3375,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode)
while (argObj->gtOper == GT_COMMA)
{
parentOfArgObj = &argObj->gtOp.gtOp2;
- argObj = argObj->gtOp.gtOp2;
+ argObj = argObj->gtOp.gtOp2;
}
if (argObj->gtOper != GT_OBJ)
@@ -3346,10 +3388,26 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode)
unsigned originalSize = info.compCompHnd->getClassSize(objClass);
originalSize = (originalSize == 0 ? TARGET_POINTER_SIZE : originalSize);
- unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE);
+ unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE);
structSize = originalSize;
+ structPassingKind howToPassStruct;
+ structBaseType = getArgTypeForStruct(objClass, &howToPassStruct, originalSize);
+
+#ifdef _TARGET_ARM64_
+ if ((howToPassStruct == SPK_PrimitiveType) && // Passed in a single register
+ !isPow2(originalSize)) // size is 3,5,6 or 7 bytes
+ {
+ if (argObj->gtObj.gtOp1->IsVarAddr()) // Is the source a LclVar?
+ {
+ // For ARM64 we pass structs that are 3,5,6,7 bytes in size
+ // we can read 4 or 8 bytes from the LclVar to pass this arg
+ originalSize = genTypeSize(structBaseType);
+ }
+ }
+#endif // _TARGET_ARM64_
+
#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
// On System V OS-es a struct is never passed by reference.
// It is either passed by value on the stack or in registers.
@@ -3371,7 +3429,12 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode)
#ifndef FEATURE_UNIX_AMD64_STRUCT_PASSING
// Check for struct argument with size 1, 2, 4 or 8 bytes
// As we can optimize these by turning them into a GT_IND of the correct type
- if ((originalSize > TARGET_POINTER_SIZE) || ((originalSize & (originalSize - 1)) != 0) || (isHfaArg && (hfaSlots != 1)))
+ //
+ // Check for cases that we cannot optimize:
+ //
+ if ((originalSize > TARGET_POINTER_SIZE) || // it is struct that is larger than a pointer
+ !isPow2(originalSize) || // it is not a power of two (1, 2, 4 or 8)
+ (isHfaArg && (hfaSlots != 1))) // it is a one element HFA struct
#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
{
// Normalize 'size' to the number of pointer sized items
@@ -3479,8 +3542,6 @@ 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.
- 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
@@ -3615,19 +3676,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode)
// There are a few special cases where we can omit using a CopyBlk
// where we normally would need to use one.
- GenTreePtr objAddr = argObj->gtObj.gtOp1;
- if (objAddr->gtOper == GT_ADDR)
+ if (argObj->gtObj.gtOp1->IsVarAddr()) // Is the source a LclVar?
{
- // exception : no need to use CopyBlk if the valuetype is on the stack
- if (objAddr->gtFlags & GTF_ADDR_ONSTACK)
- {
- copyBlkClass = NO_CLASS_HANDLE;
- }
- // exception : no need to use CopyBlk if the valuetype is already a struct local
- else if (objAddr->gtOp.gtOp1->gtOper == GT_LCL_VAR)
- {
- copyBlkClass = NO_CLASS_HANDLE;
- }
+ copyBlkClass = NO_CLASS_HANDLE;
}
}
@@ -3701,12 +3752,30 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode)
//
unsigned roundupSize = (unsigned)roundUp(structSize, TARGET_POINTER_SIZE);
size = roundupSize / TARGET_POINTER_SIZE;
+
+ // We also must update fltArgRegNum so that we no longer try to
+ // allocate any new floating point registers for args
+ // This prevents us from backfilling a subsequent arg into d7
+ //
+ fltArgRegNum = MAX_FLOAT_REG_ARG;
}
}
else
{
// Check if the last register needed is still in the int argument register range.
isRegArg = (intArgRegNum + (size - 1)) < maxRegArgs;
+
+ // Did we run out of registers when we had a 16-byte struct (size===2) ?
+ // (i.e we only have one register remaining but we needed two registers to pass this arg)
+ // This prevents us from backfilling a subsequent arg into x7
+ //
+ if (!isRegArg && (size > 1))
+ {
+ // We also must update intArgRegNum so that we no longer try to
+ // allocate any new general purpose registers for args
+ //
+ intArgRegNum = maxRegArgs;
+ }
}
#else // not _TARGET_ARM_ or _TARGET_ARM64_
@@ -5043,38 +5112,68 @@ void Compiler::fgAddSkippedRegsInPromotedStructArg(LclVarDsc* var
#endif // _TARGET_ARM_
-/*****************************************************************************
- *
- * The companion to impFixupCallStructReturn. Now that the importer is done
- * and we no longer care as much about the declared return type, change to
- * precomputed native return type (at least for architectures that don't
- * always use return buffers for structs).
- *
- */
+//****************************************************************************
+// fgFixupStructReturn:
+// The companion to impFixupCallStructReturn. Now that the importer is done
+// change the gtType to the precomputed native return type
+// requires that callNode currently has a struct type
+//
void Compiler::fgFixupStructReturn(GenTreePtr callNode)
{
+ assert(varTypeIsStruct(callNode));
+
GenTreeCall* call = callNode->AsCall();
bool callHasRetBuffArg = call->HasRetBufArg();
+ bool isHelperCall = call->IsHelperCall();
+
+ // Decide on the proper return type for this call that currently returns a struct
+ //
+ CORINFO_CLASS_HANDLE retClsHnd = call->gtRetClsHnd;
+ Compiler::structPassingKind howToReturnStruct;
+ var_types returnType;
- if (!callHasRetBuffArg && varTypeIsStruct(call))
+ // There are a couple of Helper Calls that say they return a TYP_STRUCT but they
+ // expect this method to re-type this to a TYP_REF (what is in call->gtReturnType)
+ //
+ // CORINFO_HELP_METHODDESC_TO_STUBRUNTIMEMETHOD
+ // CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD
+ // CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE_MAYBENULL
+ //
+ if (isHelperCall)
{
-#if FEATURE_MULTIREG_RET
- if (call->gtCall.IsVarargs() || !IsHfa(call))
-#endif // FEATURE_MULTIREG_RET
+ assert(!callHasRetBuffArg);
+ assert(retClsHnd == NO_CLASS_HANDLE);
+
+ // Now that we are past the importer, re-type this node
+ howToReturnStruct = SPK_PrimitiveType;
+ returnType = (var_types)call->gtReturnType;
+ }
+ else
+ {
+ returnType = getReturnTypeForStruct(retClsHnd, &howToReturnStruct);
+ }
+
+ if (howToReturnStruct == SPK_ByReference)
+ {
+ assert(returnType == TYP_UNKNOWN);
+ assert(callHasRetBuffArg);
+ }
+ else
+ {
+ assert(returnType != TYP_UNKNOWN);
+
+ if (returnType != TYP_STRUCT)
{
- // Now that we are past the importer, re-type this node so the register predictor does
- // the right thing
- call->gtType = genActualType((var_types)call->gtCall.gtReturnType);
+ // Widen the primitive type if necessary
+ returnType = genActualType(returnType);
}
+ call->gtType = returnType;
}
-#ifdef FEATURE_HFA
- // Either we don't have a struct now or if struct, then it is HFA returned in regs.
- assert(!varTypeIsStruct(call) || (IsHfa(call) && !callHasRetBuffArg));
-#elif defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+#if FEATURE_MULTIREG_RET
// Either we don't have a struct now or if struct, then it is a struct returned in regs or in return buffer.
assert(!varTypeIsStruct(call) || call->HasMultiRegRetVal() || callHasRetBuffArg);
-#else
+#else // !FEATURE_MULTIREG_RET
// No more struct returns
assert(call->TypeGet() != TYP_STRUCT);
#endif
@@ -7309,7 +7408,10 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call)
}
#endif // FEATURE_TAILCALL_OPT
- fgFixupStructReturn(call);
+ if (varTypeIsStruct(call))
+ {
+ fgFixupStructReturn(call);
+ }
var_types callType = call->TypeGet();
@@ -16002,11 +16104,11 @@ void Compiler::fgPromoteStructs()
#if !FEATURE_MULTIREG_STRUCT_PROMOTE
#if defined(_TARGET_ARM64_)
//
- // For now we currently don't promote structs that could be passed in registers
+ // For now we currently don't promote structs that are passed in registers
//
- else if (varDsc->lvIsMultiregStruct())
+ else if (lvaIsMultiregStruct(varDsc))
{
- JITDUMP("Not promoting promotable struct local V%02u (size==%d): ",
+ JITDUMP("Not promoting promotable multireg struct local V%02u (size==%d): ",
lclNum, lvaLclExactSize(lclNum));
shouldPromote = false;
}
@@ -16015,8 +16117,8 @@ void Compiler::fgPromoteStructs()
else if (varDsc->lvIsParam)
{
#if FEATURE_MULTIREG_STRUCT_PROMOTE
- if (varDsc->lvIsMultiregStruct() && // Is this a variable holding a value that is passed in multiple registers?
- (structPromotionInfo.fieldCnt != 2)) // Does it have exactly two fields
+ if (lvaIsMultiregStruct(varDsc) && // Is this a variable holding a value that is passed in multiple registers?
+ (structPromotionInfo.fieldCnt != 2)) // Does it have exactly two fields
{
JITDUMP("Not promoting multireg struct local V%02u, because lvIsParam is true and #fields != 2\n",
lclNum);
@@ -16316,8 +16418,7 @@ void Compiler::fgMarkImplicitByRefArgs()
#if defined(_TARGET_AMD64_)
if (size > REGSIZE_BYTES || (size & (size - 1)) != 0)
#elif defined(_TARGET_ARM64_)
- if ((size > TARGET_POINTER_SIZE) && !varDsc->lvIsMultiregStruct())
-
+ if ((size > TARGET_POINTER_SIZE) && !lvaIsMultiregStruct(varDsc))
#endif
{
// Previously nobody was ever setting lvIsParam and lvIsTemp on the same local