summaryrefslogtreecommitdiff
path: root/src/jit/morph.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit/morph.cpp')
-rw-r--r--src/jit/morph.cpp232
1 files changed, 188 insertions, 44 deletions
diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp
index 92d5e0967e..f63496b686 100644
--- a/src/jit/morph.cpp
+++ b/src/jit/morph.cpp
@@ -60,7 +60,8 @@ GenTreePtr Compiler::fgMorphCastIntoHelper(GenTreePtr tree, int helper, GenTreeP
GenTreePtr Compiler::fgMorphIntoHelperCall(GenTreePtr tree, int helper, GenTreeArgList* args)
{
- tree->ChangeOper(GT_CALL);
+ // The helper call ought to be semantically equivalent to the original node, so preserve its VN.
+ tree->ChangeOper(GT_CALL, GenTree::PRESERVE_VN);
tree->gtFlags |= GTF_CALL;
if (args)
@@ -3384,10 +3385,19 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
size = (unsigned)(roundUp(info.compCompHnd->getClassSize(argx->gtArgPlace.gtArgPlaceClsHnd),
TARGET_POINTER_SIZE)) /
TARGET_POINTER_SIZE;
+ if (isHfaArg)
+ {
+ hasMultiregStructArgs = true;
+ }
+ else if (size > 1 && size <= 4)
+ {
+ hasMultiregStructArgs = true;
+ }
}
else
{
// The typical case
+ // long/double type argument(s) will be changed to GT_FIELD_LIST in lower phase
size = genTypeStSz(argx->gtType);
}
#elif defined(_TARGET_X86_)
@@ -3399,7 +3409,8 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
#ifdef _TARGET_ARM_
else if (isHfaArg)
{
- size = GetHfaCount(argx);
+ size = GetHfaCount(argx);
+ hasMultiregStructArgs = true;
}
#endif // _TARGET_ARM_
else // struct type
@@ -3759,14 +3770,25 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}
-#ifndef _TARGET_X86_
- // TODO-Arm: Does this apply for _TARGET_ARM_, where structs passed by value can be split between
- // registers and stack?
+#ifdef _TARGET_64BIT_
if (size > 1)
{
hasMultiregStructArgs = true;
}
-#endif // !_TARGET_X86_
+#elif defined(_TARGET_ARM_)
+ // TODO-Arm: Need to handle the case
+ // where structs passed by value can be split between registers and stack.
+ if (size > 1 && size <= 4)
+ {
+ hasMultiregStructArgs = true;
+ }
+#ifndef LEGACY_BACKEND
+ else if (size > 4 && passUsingIntRegs)
+ {
+ NYI_ARM("Struct can be split between registers and stack");
+ }
+#endif // !LEGACY_BACKEND
+#endif // _TARGET_ARM_
}
// The 'size' value has now must have been set. (the original value of zero is an invalid value)
@@ -4058,6 +4080,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
#ifdef _TARGET_ARM_
if (fltArgRegNum > MAX_FLOAT_REG_ARG)
{
+#ifndef LEGACY_BACKEND
+ NYI_ARM("Struct split between float registers and stack");
+#endif // !LEGACY_BACKEND
// This indicates a partial enregistration of a struct type
assert(varTypeIsStruct(argx));
unsigned numRegsPartial = size - (fltArgRegNum - MAX_FLOAT_REG_ARG);
@@ -4087,6 +4112,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
#ifdef _TARGET_ARM_
if (intArgRegNum > MAX_REG_ARG)
{
+#ifndef LEGACY_BACKEND
+ NYI_ARM("Struct split between integer registers and stack");
+#endif // !LEGACY_BACKEND
// This indicates a partial enregistration of a struct type
assert((isStructArg) || argx->OperIsCopyBlkOp() ||
(argx->gtOper == GT_COMMA && (args->gtFlags & GTF_ASG)));
@@ -4145,7 +4173,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// 'Lower' the MKREFANY tree and insert it.
noway_assert(!reMorphing);
-#ifdef _TARGET_X86_
+#ifndef _TARGET_64BIT_
// Build the mkrefany as a GT_FIELD_LIST
GenTreeFieldList* fieldList = new (this, GT_FIELD_LIST)
@@ -4156,7 +4184,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
fp->node = fieldList;
args->gtOp.gtOp1 = fieldList;
-#else // !_TARGET_X86_
+#else // _TARGET_64BIT_
// Get a new temp
// Here we don't need unsafe value cls check since the addr of temp is used only in mkrefany
@@ -4182,7 +4210,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// EvalArgsToTemps will cause tmp to actually get loaded as the argument
call->fgArgInfo->EvalToTmp(argIndex, tmp, asg);
lvaSetVarAddrExposed(tmp);
-#endif // !_TARGET_X86_
+#endif // _TARGET_64BIT_
}
#endif // !LEGACY_BACKEND
@@ -4221,7 +4249,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}
}
-#endif // defined (_TARGET_X86_) && !defined(LEGACY_BACKEND)
+#endif // _TARGET_X86_ && !LEGACY_BACKEND
#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
if (isStructArg && !isRegArg)
@@ -4601,14 +4629,10 @@ void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call)
unsigned flagsSummary = 0;
fgArgInfoPtr allArgInfo = call->fgArgInfo;
- // Currently only ARM64 is using this method to morph the MultiReg struct args
- // in the future AMD64_UNIX and for HFAs ARM32, will also use this method
- //
+ // Currently ARM64/ARM is using this method to morph the MultiReg struct args
+ // in the future AMD64_UNIX will also use this method
CLANG_FORMAT_COMMENT_ANCHOR;
-#ifdef _TARGET_ARM_
- NYI_ARM("fgMorphMultiregStructArgs");
-#endif
#ifdef _TARGET_X86_
assert(!"Logic error: no MultiregStructArgs for X86");
#endif
@@ -4704,13 +4728,13 @@ void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call)
// this also forces the struct to be stack allocated into the local frame.
// For the GT_OBJ case will clone the address expression and generate two (or more)
// indirections.
-// Currently the implementation only handles ARM64 and will NYI for other architectures.
+// Currently the implementation handles ARM64/ARM and will NYI for other architectures.
//
GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr fgEntryPtr)
{
assert(arg->TypeGet() == TYP_STRUCT);
-#ifndef _TARGET_ARM64_
+#ifndef _TARGET_ARMARCH_
NYI("fgMorphMultiregStructArg requires implementation for this target");
#endif
@@ -4766,21 +4790,36 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f
}
else
{
+#ifdef _TARGET_ARM64_
assert(structSize <= 2 * TARGET_POINTER_SIZE);
+#elif defined(_TARGET_ARM_)
+ assert(structSize <= 4 * TARGET_POINTER_SIZE);
+#endif
+
+#ifdef _TARGET_ARM64_
BYTE gcPtrs[2] = {TYPE_GC_NONE, TYPE_GC_NONE};
info.compCompHnd->getClassGClayout(objClass, &gcPtrs[0]);
elemCount = 2;
type[0] = getJitGCType(gcPtrs[0]);
type[1] = getJitGCType(gcPtrs[1]);
+#elif defined(_TARGET_ARM_)
+ BYTE gcPtrs[4] = {TYPE_GC_NONE, TYPE_GC_NONE, TYPE_GC_NONE, TYPE_GC_NONE};
+ elemCount = (unsigned)roundUp(structSize, TARGET_POINTER_SIZE) / TARGET_POINTER_SIZE;
+ info.compCompHnd->getClassGClayout(objClass, &gcPtrs[0]);
+ for (unsigned inx = 0; inx < elemCount; inx++)
+ {
+ type[inx] = getJitGCType(gcPtrs[inx]);
+ }
+#endif // _TARGET_ARM_
if ((argValue->OperGet() == GT_LCL_FLD) || (argValue->OperGet() == GT_LCL_VAR))
{
- // We can safely widen this to 16 bytes since we are loading from
+ elemSize = TARGET_POINTER_SIZE;
+ // We can safely widen this to aligned bytes since we are loading from
// a GT_LCL_VAR or a GT_LCL_FLD which is properly padded and
// lives in the stack frame or will be a promoted field.
//
- elemSize = TARGET_POINTER_SIZE;
- structSize = 2 * TARGET_POINTER_SIZE;
+ structSize = elemCount * TARGET_POINTER_SIZE;
}
else // we must have a GT_OBJ
{
@@ -4788,21 +4827,25 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f
// We need to load the struct from an arbitrary address
// and we can't read past the end of the structSize
- // We adjust the second load type here
+ // We adjust the last load type here
//
- if (structSize < 2 * TARGET_POINTER_SIZE)
+ unsigned remainingBytes = structSize % TARGET_POINTER_SIZE;
+ unsigned lastElem = elemCount - 1;
+ if (remainingBytes != 0)
{
- switch (structSize - TARGET_POINTER_SIZE)
+ switch (remainingBytes)
{
case 1:
- type[1] = TYP_BYTE;
+ type[lastElem] = TYP_BYTE;
break;
case 2:
- type[1] = TYP_SHORT;
+ type[lastElem] = TYP_SHORT;
break;
+#ifdef _TARGET_ARM64_
case 4:
- type[1] = TYP_INT;
+ type[lastElem] = TYP_INT;
break;
+#endif // _TARGET_ARM64_
default:
noway_assert(!"NYI: odd sized struct in fgMorphMultiregStructArg");
break;
@@ -4824,10 +4867,10 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f
assert(varNum < lvaCount);
LclVarDsc* varDsc = &lvaTable[varNum];
- // At this point any TYP_STRUCT LclVar must be a 16-byte struct
+ // At this point any TYP_STRUCT LclVar must be an aligned struct
// or an HFA struct, both which are passed by value.
//
- assert((varDsc->lvSize() == 2 * TARGET_POINTER_SIZE) || varDsc->lvIsHfa());
+ assert((varDsc->lvSize() == elemCount * TARGET_POINTER_SIZE) || varDsc->lvIsHfa());
varDsc->lvIsMultiRegArg = true;
@@ -4855,8 +4898,12 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f
}
else
{
+#ifdef _TARGET_ARM64_
// We must have a 16-byte struct (non-HFA)
noway_assert(elemCount == 2);
+#elif defined(_TARGET_ARM_)
+ noway_assert(elemCount <= 4);
+#endif
for (unsigned inx = 0; inx < elemCount; inx++)
{
@@ -4878,6 +4925,7 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f
}
}
+#ifdef _TARGET_ARM64_
// Is this LclVar a promoted struct with exactly 2 fields?
// TODO-ARM64-CQ: Support struct promoted HFA types here
if (varDsc->lvPromoted && (varDsc->lvFieldCnt == 2) && !varDsc->lvIsHfa())
@@ -4929,6 +4977,78 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f
//
lvaSetVarDoNotEnregister(varNum DEBUG_ARG(DNER_LocalField));
}
+#elif defined(_TARGET_ARM_)
+ // Is this LclVar a promoted struct with exactly same size?
+ if (varDsc->lvPromoted && (varDsc->lvFieldCnt == elemCount) && !varDsc->lvIsHfa())
+ {
+ // See if we have promoted fields?
+ unsigned varNums[4];
+ bool hasBadVarNum = false;
+ for (unsigned inx = 0; inx < elemCount; inx++)
+ {
+ varNums[inx] = lvaGetFieldLocal(varDsc, TARGET_POINTER_SIZE * inx);
+ if (varNums[inx] == BAD_VAR_NUM)
+ {
+ hasBadVarNum = true;
+ break;
+ }
+ }
+
+ // Did we find the promoted fields at the necessary offsets?
+ if (!hasBadVarNum)
+ {
+ LclVarDsc* varDscs[4];
+ var_types varType[4];
+ bool varIsFloat = false;
+
+ for (unsigned inx = 0; inx < elemCount; inx++)
+ {
+ varDscs[inx] = &lvaTable[varNums[inx]];
+ varType[inx] = varDscs[inx]->lvType;
+ if (varTypeIsFloating(varType[inx]))
+ {
+ // TODO-LSRA - It currently doesn't support the passing of floating point LCL_VARS in the
+ // integer
+ // registers. So for now we will use GT_LCLFLD's to pass this struct (it won't be enregistered)
+ //
+ JITDUMP("Multireg struct V%02u will be passed using GT_LCLFLD because it has float fields.\n",
+ varNum);
+ //
+ // we call lvaSetVarDoNotEnregister and do the proper transformation below.
+ //
+ varIsFloat = true;
+ break;
+ }
+ }
+
+ if (!varIsFloat)
+ {
+ unsigned offset = 0;
+ GenTreeFieldList* listEntry = nullptr;
+ // We can use the struct promoted field as arguments
+ for (unsigned inx = 0; inx < elemCount; inx++)
+ {
+ GenTreePtr lclVar = gtNewLclvNode(varNums[inx], varType[inx], varNums[inx]);
+ // Create a new tree for 'arg'
+ // replace the existing LDOBJ(ADDR(LCLVAR))
+ listEntry = new (this, GT_FIELD_LIST) GenTreeFieldList(lclVar, offset, varType[inx], listEntry);
+ if (newArg == nullptr)
+ {
+ newArg = listEntry;
+ }
+ offset += TARGET_POINTER_SIZE;
+ }
+ }
+ }
+ }
+ else
+ {
+ //
+ // We will create a list of GT_LCL_FLDs nodes to pass this struct
+ //
+ lvaSetVarDoNotEnregister(varNum DEBUG_ARG(DNER_LocalField));
+ }
+#endif // _TARGET_ARM_
}
// If we didn't set newarg to a new List Node tree
@@ -7862,7 +7982,7 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call)
// the call.
GenTreeStmt* nextMorphStmt = fgMorphStmt->gtNextStmt;
-#ifdef _TARGET_AMD64_
+#if !defined(FEATURE_CORECLR) && defined(_TARGET_AMD64_)
// Legacy Jit64 Compat:
// There could be any number of GT_NOPs between tail call and GT_RETURN.
// That is tail call pattern could be one of the following:
@@ -7929,7 +8049,7 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call)
fgRemoveStmt(compCurBB, morphStmtToRemove);
}
}
-#endif // _TARGET_AMD64_
+#endif // !FEATURE_CORECLR && _TARGET_AMD64_
// Delete GT_RETURN if any
if (nextMorphStmt != nullptr)
@@ -11416,6 +11536,20 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac)
}
}
+ // If gtOp1 is a GT_FIELD, we need to pass down the mac if
+ // its parent is GT_ADDR, since the address of the field
+ // is part of an ongoing address computation. Otherwise
+ // op1 represents the value of the field and so any address
+ // calculations it does are in a new context.
+ if ((op1->gtOper == GT_FIELD) && (tree->gtOper != GT_ADDR))
+ {
+ subMac1 = nullptr;
+
+ // The impact of this field's value to any ongoing
+ // address computation is handled below when looking
+ // at op2.
+ }
+
tree->gtOp.gtOp1 = op1 = fgMorphTree(op1, subMac1);
#if LOCAL_ASSERTION_PROP
@@ -11496,7 +11630,6 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac)
// (These are used to convey parent context about how addresses being calculated
// will be used; see the specification comment for MorphAddrContext for full details.)
// Assume it's an Ind context to start.
- MorphAddrContext subIndMac2(MACK_Ind);
switch (tree->gtOper)
{
case GT_ADD:
@@ -11517,6 +11650,17 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac)
default:
break;
}
+
+ // If gtOp2 is a GT_FIELD, we must be taking its value,
+ // so it should evaluate its address in a new context.
+ if (op2->gtOper == GT_FIELD)
+ {
+ // The impact of this field's value to any ongoing
+ // address computation is handled above when looking
+ // at op1.
+ mac = nullptr;
+ }
+
tree->gtOp.gtOp2 = op2 = fgMorphTree(op2, mac);
/* Propagate the side effect flags from op2 */
@@ -13676,20 +13820,20 @@ GenTree* Compiler::fgMorphSmpOpOptional(GenTreeOp* tree)
/* Make sure we have the operator range right */
- noway_assert(GT_SUB == GT_ADD + 1);
- noway_assert(GT_MUL == GT_ADD + 2);
- noway_assert(GT_DIV == GT_ADD + 3);
- noway_assert(GT_MOD == GT_ADD + 4);
- noway_assert(GT_UDIV == GT_ADD + 5);
- noway_assert(GT_UMOD == GT_ADD + 6);
+ static_assert(GT_SUB == GT_ADD + 1, "bad oper value");
+ static_assert(GT_MUL == GT_ADD + 2, "bad oper value");
+ static_assert(GT_DIV == GT_ADD + 3, "bad oper value");
+ static_assert(GT_MOD == GT_ADD + 4, "bad oper value");
+ static_assert(GT_UDIV == GT_ADD + 5, "bad oper value");
+ static_assert(GT_UMOD == GT_ADD + 6, "bad oper value");
- noway_assert(GT_OR == GT_ADD + 7);
- noway_assert(GT_XOR == GT_ADD + 8);
- noway_assert(GT_AND == GT_ADD + 9);
+ static_assert(GT_OR == GT_ADD + 7, "bad oper value");
+ static_assert(GT_XOR == GT_ADD + 8, "bad oper value");
+ static_assert(GT_AND == GT_ADD + 9, "bad oper value");
- noway_assert(GT_LSH == GT_ADD + 10);
- noway_assert(GT_RSH == GT_ADD + 11);
- noway_assert(GT_RSZ == GT_ADD + 12);
+ static_assert(GT_LSH == GT_ADD + 10, "bad oper value");
+ static_assert(GT_RSH == GT_ADD + 11, "bad oper value");
+ static_assert(GT_RSZ == GT_ADD + 12, "bad oper value");
/* Check for a suitable operator on the RHS */