break;
case GT_LIST:
+ case GT_FIELD_LIST:
case GT_ARGPLACE:
// Nothing to do
break;
break;
case GT_LIST:
+ case GT_FIELD_LIST:
case GT_ARGPLACE:
// Nothing to do
break;
// Consume all the arg regs
for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext())
{
- assert(list->IsList());
+ assert(list->OperIsList());
GenTreePtr argNode = list->Current();
{
assert(source->isContained()); // We expect that this node was marked as contained in LowerArm64
- if (source->OperGet() == GT_LIST)
+ if (source->OperGet() == GT_FIELD_LIST)
{
// Deal with the multi register passed struct args.
- GenTreeArgList* argListPtr = source->AsArgList();
+ GenTreeFieldList* fieldListPtr = source->AsFieldList();
- // Evaluate each of the GT_LIST items into their register
+ // Evaluate each of the GT_FIELD_LIST items into their register
// and store their register into the outgoing argument area
- for (; argListPtr != nullptr; argListPtr = argListPtr->Rest())
+ for (; fieldListPtr != nullptr; fieldListPtr = fieldListPtr->Rest())
{
- GenTreePtr nextArgNode = argListPtr->gtOp.gtOp1;
+ GenTreePtr nextArgNode = fieldListPtr->gtOp.gtOp1;
genConsumeReg(nextArgNode);
regNumber reg = nextArgNode->gtRegNum;
var_types type = nextArgNode->TypeGet();
emitAttr attr = emitTypeSize(type);
- // Emit store instructions to store the registers produced by the GT_LIST into the outgoing argument
+ // Emit store instructions to store the registers produced by the GT_FIELD_LIST into the outgoing argument
// area
emit->emitIns_S_R(ins_Store(type), attr, reg, varNumOut, argOffsetOut);
argOffsetOut += EA_SIZE_IN_BYTES(attr);
u8ToDblBitmask = nullptr;
#endif // defined(_TARGET_XARCH_) && !FEATURE_STACK_FP_X87
+#if defined(FEATURE_PUT_STRUCT_ARG_STK) && !defined(_TARGET_X86_)
+ m_stkArgVarNum = BAD_VAR_NUM;
+#endif
+
regTracker.rsTrackInit(compiler, ®Set);
gcInfo.regSet = ®Set;
m_cgEmitter = new (compiler->getAllocator()) emitter();
GenTree* genConsumeBlockSrc(GenTreeBlk* blkNode);
void genConsumeBlockOp(GenTreeBlk* blkNode, regNumber dstReg, regNumber srcReg, regNumber sizeReg);
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
-void genConsumePutStructArgStk(
- GenTreePutArgStk* putArgStkNode, regNumber dstReg, regNumber srcReg, regNumber sizeReg, unsigned baseVarNum);
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
+void genConsumePutStructArgStk(GenTreePutArgStk* putArgStkNode, regNumber dstReg, regNumber srcReg, regNumber sizeReg);
+#endif // FEATURE_PUT_STRUCT_ARG_STK
void genConsumeRegs(GenTree* tree);
void genCodeForCpBlkUnroll(GenTreeBlk* cpBlkNode);
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
-void genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum);
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
+void genPutStructArgStk(GenTreePutArgStk* treeNode);
-void genStructPutArgRepMovs(GenTreePutArgStk* putArgStkNode, unsigned baseVarNum);
-void genStructPutArgUnroll(GenTreePutArgStk* putArgStkNode, unsigned baseVarNum);
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+int genMove8IfNeeded(unsigned size, regNumber tmpReg, GenTree* srcAddr, unsigned offset);
+int genMove4IfNeeded(unsigned size, regNumber tmpReg, GenTree* srcAddr, unsigned offset);
+int genMove2IfNeeded(unsigned size, regNumber tmpReg, GenTree* srcAddr, unsigned offset);
+int genMove1IfNeeded(unsigned size, regNumber tmpReg, GenTree* srcAddr, unsigned offset);
+void genStructPutArgRepMovs(GenTreePutArgStk* putArgStkNode);
+void genStructPutArgUnroll(GenTreePutArgStk* putArgStkNode);
+void genStoreRegToStackArg(var_types type, regNumber reg, unsigned offset);
+#endif // FEATURE_PUT_STRUCT_ARG_STK
void genCodeForLoadOffset(instruction ins, emitAttr size, regNumber dst, GenTree* base, unsigned offset);
return (varDsc->lvIsRegCandidate());
}
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
+#ifdef _TARGET_X86_
+bool m_pushStkArg;
+#else // !_TARGET_X86_
+unsigned m_stkArgVarNum;
+#endif // !_TARGET_X86_
+unsigned m_stkArgOffset;
+#endif // !FEATURE_PUT_STRUCT_ARG_STK
+
#ifdef DEBUG
GenTree* lastConsumedNode;
void genCheckConsumeNode(GenTree* treeNode);
#endif // !_TARGET_X86_
}
- regMaskTP byrefPushedRegs = RBM_NONE;
- regMaskTP norefPushedRegs = RBM_NONE;
- regMaskTP pushedRegs = RBM_NONE;
+ regMaskTP byrefPushedRegs = RBM_NONE;
+ regMaskTP norefPushedRegs = RBM_NONE;
+ regMaskTP pushedRegs = RBM_NONE;
if (compiler->gsGlobalSecurityCookieAddr == nullptr)
{
emitter* emit = getEmitter();
#ifdef _TARGET_X86_
- bool dividendIsLong = varTypeIsLong(dividend->TypeGet());
- GenTree* dividendLo = nullptr;
- GenTree* dividendHi = nullptr;
+ bool dividendIsLong = varTypeIsLong(dividend->TypeGet());
+ GenTree* dividendLo = nullptr;
+ GenTree* dividendHi = nullptr;
if (dividendIsLong)
{
}
else
#endif
- if (dividend->gtRegNum != REG_RAX)
+ if (dividend->gtRegNum != REG_RAX)
{
// dividend must be in RAX
inst_RV_RV(INS_mov, REG_RAX, dividend->gtRegNum, targetType);
}
- // zero or sign extend rax to rdx
#ifdef _TARGET_X86_
if (!dividendIsLong)
#endif
{
+ // zero or sign extend rax to rdx
if (oper == GT_UMOD || oper == GT_UDIV)
{
instGen_Set_Reg_To_Zero(EA_PTRSIZE, REG_EDX);
assert(compiler->compCurBB->bbJumpKind == BBJ_COND);
- CompareKind compareKind = ((jcc->gtFlags & GTF_UNSIGNED) != 0) ? CK_UNSIGNED : CK_SIGNED;
- emitJumpKind jumpKind = genJumpKindForOper(jcc->gtCondition, compareKind);
+ CompareKind compareKind = ((jcc->gtFlags & GTF_UNSIGNED) != 0) ? CK_UNSIGNED : CK_SIGNED;
+ emitJumpKind jumpKind = genJumpKindForOper(jcc->gtCondition, compareKind);
inst_JMP(jumpKind, compiler->compCurBB->bbJumpDest);
}
break;
case GT_LIST:
+ case GT_FIELD_LIST:
case GT_ARGPLACE:
// Nothing to do
break;
instGen(INS_r_movsb);
}
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
+//------------------------------------------------------------------------
+// CodeGen::genMove8IfNeeded: Conditionally move 8 bytes of a struct to the argument area
+//
+// Arguments:
+// size - The size of bytes remaining to be moved
+// longTmpReg - The tmp register to be used for the long value
+// srcAddr - The address of the source struct
+// offset - The current offset being copied
+//
+// Return Value:
+// Returns the number of bytes moved (8 or 0).
+//
+// Notes:
+// This is used in the PutArgStkKindUnroll case, to move any bytes that are
+// not an even multiple of 16.
+// On x86, longTmpReg must be an xmm reg; on x64 it must be an integer register.
+// This is checked by genStoreRegToStackArg.
+//
+int CodeGen::genMove8IfNeeded(unsigned size, regNumber longTmpReg, GenTree* srcAddr, unsigned offset)
+{
+#ifdef _TARGET_X86_
+ instruction longMovIns = INS_movq;
+#else // !_TARGET_X86_
+ instruction longMovIns = INS_mov;
+#endif // !_TARGET_X86_
+ if ((size & 8) != 0)
+ {
+ genCodeForLoadOffset(longMovIns, EA_8BYTE, longTmpReg, srcAddr, offset);
+ genStoreRegToStackArg(TYP_LONG, longTmpReg, offset);
+ return 8;
+ }
+ return 0;
+}
+
+//------------------------------------------------------------------------
+// CodeGen::genMove4IfNeeded: Conditionally move 4 bytes of a struct to the argument area
+//
+// Arguments:
+// size - The size of bytes remaining to be moved
+// intTmpReg - The tmp register to be used for the long value
+// srcAddr - The address of the source struct
+// offset - The current offset being copied
+//
+// Return Value:
+// Returns the number of bytes moved (4 or 0).
+//
+// Notes:
+// This is used in the PutArgStkKindUnroll case, to move any bytes that are
+// not an even multiple of 16.
+// intTmpReg must be an integer register.
+// This is checked by genStoreRegToStackArg.
+//
+int CodeGen::genMove4IfNeeded(unsigned size, regNumber intTmpReg, GenTree* srcAddr, unsigned offset)
+{
+ if ((size & 4) != 0)
+ {
+ genCodeForLoadOffset(INS_mov, EA_4BYTE, intTmpReg, srcAddr, offset);
+ genStoreRegToStackArg(TYP_INT, intTmpReg, offset);
+ return 4;
+ }
+ return 0;
+}
+
+//------------------------------------------------------------------------
+// CodeGen::genMove2IfNeeded: Conditionally move 2 bytes of a struct to the argument area
+//
+// Arguments:
+// size - The size of bytes remaining to be moved
+// intTmpReg - The tmp register to be used for the long value
+// srcAddr - The address of the source struct
+// offset - The current offset being copied
+//
+// Return Value:
+// Returns the number of bytes moved (2 or 0).
+//
+// Notes:
+// This is used in the PutArgStkKindUnroll case, to move any bytes that are
+// not an even multiple of 16.
+// intTmpReg must be an integer register.
+// This is checked by genStoreRegToStackArg.
+//
+int CodeGen::genMove2IfNeeded(unsigned size, regNumber intTmpReg, GenTree* srcAddr, unsigned offset)
+{
+ if ((size & 2) != 0)
+ {
+ genCodeForLoadOffset(INS_mov, EA_2BYTE, intTmpReg, srcAddr, offset);
+ genStoreRegToStackArg(TYP_SHORT, intTmpReg, offset);
+ return 2;
+ }
+ return 0;
+}
+
+//------------------------------------------------------------------------
+// CodeGen::genMove1IfNeeded: Conditionally move 1 byte of a struct to the argument area
+//
+// Arguments:
+// size - The size of bytes remaining to be moved
+// intTmpReg - The tmp register to be used for the long value
+// srcAddr - The address of the source struct
+// offset - The current offset being copied
+//
+// Return Value:
+// Returns the number of bytes moved (1 or 0).
+//
+// Notes:
+// This is used in the PutArgStkKindUnroll case, to move any bytes that are
+// not an even multiple of 16.
+// intTmpReg must be an integer register.
+// This is checked by genStoreRegToStackArg.
+//
+int CodeGen::genMove1IfNeeded(unsigned size, regNumber intTmpReg, GenTree* srcAddr, unsigned offset)
+{
+
+ if ((size & 1) != 0)
+ {
+ genCodeForLoadOffset(INS_mov, EA_1BYTE, intTmpReg, srcAddr, offset);
+ genStoreRegToStackArg(TYP_BYTE, intTmpReg, offset);
+ return 1;
+ }
+ return 0;
+}
//---------------------------------------------------------------------------------------------------------------//
// genStructPutArgUnroll: Generates code for passing a struct arg on stack by value using loop unrolling.
//
// Arguments:
// putArgNode - the PutArgStk tree.
-// baseVarNum - the base var number, relative to which the by-val struct will be copied on the stack.
+//
+// Notes:
+// m_stkArgVarNum must be set to the base var number, relative to which the by-val struct will be copied to the
+// stack.
//
// TODO-Amd64-Unix: Try to share code with copyblk.
// Need refactoring of copyblk before it could be used for putarg_stk.
// The difference for now is that a putarg_stk contains its children, while cpyblk does not.
// This creates differences in code. After some significant refactoring it could be reused.
//
-void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode, unsigned baseVarNum)
+void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode)
{
// We will never call this method for SIMD types, which are stored directly
// in genPutStructArgStk().
unsigned offset = 0;
+ regNumber xmmTmpReg = REG_NA;
+ regNumber intTmpReg = REG_NA;
+ regNumber longTmpReg = REG_NA;
+#ifdef _TARGET_X86_
+ if (size >= 8)
+ {
+ xmmTmpReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLFLOAT);
+ longTmpReg = xmmTmpReg;
+ }
+ if ((size & 0x7) != 0)
+ {
+ intTmpReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLINT);
+ }
+#else // !_TARGET_X86_
+ if (size >= XMM_REGSIZE_BYTES)
+ {
+ xmmTmpReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLFLOAT);
+ }
+ if ((size & 0xf) != 0)
+ {
+ intTmpReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLINT);
+ longTmpReg = intTmpReg;
+ }
+#endif // !_TARGET_X86_
+
// If the size of this struct is larger than 16 bytes
// let's use SSE2 to be able to do 16 byte at a time
// loads and stores.
if (size >= XMM_REGSIZE_BYTES)
{
+#ifdef _TARGET_X86_
+ assert(!m_pushStkArg);
+#endif // _TARGET_X86_
assert(putArgNode->gtRsvdRegs != RBM_NONE);
- regNumber xmmReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLFLOAT);
- assert(genIsValidFloatReg(xmmReg));
size_t slots = size / XMM_REGSIZE_BYTES;
assert(putArgNode->gtGetOp1()->isContained());
while (slots-- > 0)
{
// Load
- genCodeForLoadOffset(INS_movdqu, EA_8BYTE, xmmReg, src->gtGetOp1(),
- offset); // Load the address of the child of the Obj node.
+ genCodeForLoadOffset(INS_movdqu, EA_8BYTE, xmmTmpReg, src->gtGetOp1(), offset);
// Store
- emit->emitIns_S_R(INS_movdqu, EA_8BYTE, xmmReg, baseVarNum, putArgOffset + offset);
+ genStoreRegToStackArg(TYP_STRUCT, xmmTmpReg, offset);
offset += XMM_REGSIZE_BYTES;
}
// Fill the remainder (15 bytes or less) if there's one.
if ((size & 0xf) != 0)
{
- // Grab the integer temp register to emit the remaining loads and stores.
- regNumber tmpReg = genRegNumFromMask(putArgNode->gtRsvdRegs & RBM_ALLINT);
- assert(genIsValidIntReg(tmpReg));
-
- if ((size & 8) != 0)
- {
- genCodeForLoadOffset(INS_mov, EA_8BYTE, tmpReg, src->gtOp.gtOp1, offset);
-
- emit->emitIns_S_R(INS_mov, EA_8BYTE, tmpReg, baseVarNum, putArgOffset + offset);
-
- offset += 8;
- }
-
- if ((size & 4) != 0)
- {
- genCodeForLoadOffset(INS_mov, EA_4BYTE, tmpReg, src->gtOp.gtOp1, offset);
-
- emit->emitIns_S_R(INS_mov, EA_4BYTE, tmpReg, baseVarNum, putArgOffset + offset);
-
- offset += 4;
- }
-
- if ((size & 2) != 0)
- {
- genCodeForLoadOffset(INS_mov, EA_2BYTE, tmpReg, src->gtOp.gtOp1, offset);
-
- emit->emitIns_S_R(INS_mov, EA_2BYTE, tmpReg, baseVarNum, putArgOffset + offset);
-
- offset += 2;
+#ifdef _TARGET_X86_
+ if (m_pushStkArg)
+ {
+ // This case is currently supported only for the case where the total size is
+ // less than XMM_REGSIZE_BYTES. We need to push the remaining chunks in reverse
+ // order. However, morph has ensured that we have a struct that is an even
+ // multiple of TARGET_POINTER_SIZE, so we don't need to worry about alignment.
+ assert(((size & 0xc) == size) && (offset == 0));
+ // If we have a 4 byte chunk, load it from either offset 0 or 8, depending on
+ // whether we've got an 8 byte chunk, and then push it on the stack.
+ unsigned pushedBytes = genMove4IfNeeded(size, intTmpReg, src->gtOp.gtOp1, size & 0x8);
+ // Now if we have an 8 byte chunk, load it from offset 0 (it's the first chunk)
+ // and push it on the stack.
+ pushedBytes += genMove8IfNeeded(size, longTmpReg, src->gtOp.gtOp1, 0);
}
-
- if ((size & 1) != 0)
+ else
+#endif // _TARGET_X86_
{
- genCodeForLoadOffset(INS_mov, EA_1BYTE, tmpReg, src->gtOp.gtOp1, offset);
- emit->emitIns_S_R(INS_mov, EA_1BYTE, tmpReg, baseVarNum, putArgOffset + offset);
+ offset += genMove8IfNeeded(size, longTmpReg, src->gtOp.gtOp1, offset);
+ offset += genMove4IfNeeded(size, intTmpReg, src->gtOp.gtOp1, offset);
+ offset += genMove2IfNeeded(size, intTmpReg, src->gtOp.gtOp1, offset);
+ offset += genMove1IfNeeded(size, intTmpReg, src->gtOp.gtOp1, offset);
+ assert(offset == size);
}
}
}
//
// Arguments:
// putArgNode - the PutArgStk tree.
-// baseVarNum - the base var number, relative to which the by-val struct bits will go.
//
// Preconditions:
// The size argument of the PutArgStk (for structs) is a constant and is between
// CPBLK_UNROLL_LIMIT and CPBLK_MOVS_LIMIT bytes.
+// m_stkArgVarNum must be set to the base var number, relative to which the by-val struct bits will go.
//
-void CodeGen::genStructPutArgRepMovs(GenTreePutArgStk* putArgNode, unsigned baseVarNum)
+void CodeGen::genStructPutArgRepMovs(GenTreePutArgStk* putArgNode)
{
assert(putArgNode->TypeGet() == TYP_STRUCT);
assert(putArgNode->getArgSize() > CPBLK_UNROLL_LIMIT);
- assert(baseVarNum != BAD_VAR_NUM);
// Make sure we got the arguments of the cpblk operation in the right registers
GenTreePtr dstAddr = putArgNode;
assert(putArgNode->gtRsvdRegs == (RBM_RDI | RBM_RCX | RBM_RSI));
assert(srcAddr->isContained());
- genConsumePutStructArgStk(putArgNode, REG_RDI, REG_RSI, REG_RCX, baseVarNum);
+ genConsumePutStructArgStk(putArgNode, REG_RDI, REG_RSI, REG_RCX);
instGen(INS_r_movsb);
}
}
}
#endif // FEATURE_SIMD
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
// Generate code for CpObj nodes wich copy structs that have interleaved
// GC pointers.
-// This will generate a sequence of movs{d,q} instructions for the cases of non-gc members
+// This will generate a sequence of movsp instructions for the cases of non-gc members.
+// Note that movsp is an alias for movsd on x86 and movsq on x64.
// and calls to the BY_REF_ASSIGN helper otherwise.
void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode)
{
bool dstOnStack = dstAddr->OperIsLocalAddr();
#ifdef DEBUG
- bool isRepMovsPtrUsed = false;
+ bool isRepMovspUsed = false;
assert(!dstAddr->isContained());
// with CpObj, so this requires special logic.
assert(cpObjNode->gtGcPtrCount > 0);
- // MovSq instruction is used for copying non-gcref fields and it needs
- // src = RSI and dst = RDI.
+ // MovSp (alias for movsq on x64 and movsd on x86) instruction is used for copying non-gcref fields
+ // and it needs src = RSI and dst = RDI.
// Either these registers must not contain lclVars, or they must be dying or marked for spill.
// This is because these registers are incremented as we go through the struct.
GenTree* actualSrcAddr = srcAddr->gtSkipReloadOrCopy();
if (slots >= CPOBJ_NONGC_SLOTS_LIMIT)
{
#ifdef DEBUG
- // If the destination of the CpObj is on the stack
- // make sure we allocated RCX to emit rep movs{d,q}.
+ // If the destination of the CpObj is on the stack, make sure we allocated
+ // RCX to emit the movsp (alias for movsd or movsq for 32 and 64 bits respectively).
regNumber tmpReg = genRegNumFromMask(cpObjNode->gtRsvdRegs & RBM_ALLINT);
assert(tmpReg == REG_RCX);
- isRepMovsPtrUsed = true;
+ isRepMovspUsed = true;
#endif // DEBUG
getEmitter()->emitIns_R_I(INS_mov, EA_4BYTE, REG_RCX, slots);
- instGen(INS_r_movs_ptr);
+ instGen(INS_r_movsp);
}
else
{
- // For small structs, it's better to emit a sequence of movsq than to
- // emit a rep movsq instruction.
+ // For small structs, it's better to emit a sequence of movsp than to
+ // emit a rep movsp instruction.
while (slots > 0)
{
- instGen(INS_movs_ptr);
+ instGen(INS_movsp);
slots--;
}
}
switch (gcPtrs[i])
{
case TYPE_GC_NONE:
- // Let's see if we can use rep movs{d,q} instead of a sequence of movs{d,q} instructions
+ // Let's see if we can use rep movsp instead of a sequence of movsp instructions
// to save cycles and code size.
{
unsigned nonGcSlotCount = 0;
} while (i < slots && gcPtrs[i] == TYPE_GC_NONE);
// If we have a very small contiguous non-gc region, it's better just to
- // emit a sequence of movs{d,q} instructions
+ // emit a sequence of movsp instructions
if (nonGcSlotCount < CPOBJ_NONGC_SLOTS_LIMIT)
{
while (nonGcSlotCount > 0)
{
- instGen(INS_movs_ptr);
+ instGen(INS_movsp);
nonGcSlotCount--;
}
}
{
#ifdef DEBUG
// Otherwise, we can save code-size and improve CQ by emitting
- // rep movs{d,q}
+ // rep movsp
regNumber tmpReg = genRegNumFromMask(cpObjNode->gtRsvdRegs & RBM_ALLINT);
assert(tmpReg == REG_RCX);
- isRepMovsPtrUsed = true;
+ isRepMovspUsed = true;
#endif // DEBUG
getEmitter()->emitIns_R_I(INS_mov, EA_4BYTE, REG_RCX, nonGcSlotCount);
- instGen(INS_r_movs_ptr);
+ instGen(INS_r_movsp);
}
}
break;
regNumber operandReg = operand->gtRegNum;
GenTreePtr shiftBy = tree->gtGetOp2();
-
+
if (shiftBy->isContainedIntOrIImmed())
{
// First, move the operand to the destination register and
}
}
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#if FEATURE_PUT_STRUCT_ARG_STK
//------------------------------------------------------------------------
// genConsumePutStructArgStk: Do liveness update for the operands of a PutArgStk node.
// Also loads in the right register the addresses of the
// dstReg - the dstReg for the rep move operation.
// srcReg - the srcReg for the rep move operation.
// sizeReg - the sizeReg for the rep move operation.
-// baseVarNum - the varnum for the local used for placing the "by-value" args on the stack.
//
// Return Value:
// None.
//
-// Note: sizeReg can be REG_NA when this function is used to consume the dstReg and srcReg
-// for copying on the stack a struct with references.
-// The source address/offset is determined from the address on the GT_OBJ node, while
-// the destination address is the address contained in 'baseVarNum' plus the offset
-// provided in the 'putArgNode'.
-
-void CodeGen::genConsumePutStructArgStk(
- GenTreePutArgStk* putArgNode, regNumber dstReg, regNumber srcReg, regNumber sizeReg, unsigned baseVarNum)
+// Notes:
+// sizeReg can be REG_NA when this function is used to consume the dstReg and srcReg
+// for copying on the stack a struct with references.
+// The source address/offset is determined from the address on the GT_OBJ node, while
+// the destination address is the address contained in 'm_stkArgVarNum' plus the offset
+// provided in the 'putArgNode'.
+// m_stkArgVarNum must be set to the varnum for the local used for placing the "by-value" args on the stack.
+
+void CodeGen::genConsumePutStructArgStk(GenTreePutArgStk* putArgNode,
+ regNumber dstReg,
+ regNumber srcReg,
+ regNumber sizeReg)
{
assert(varTypeIsStruct(putArgNode));
- assert(baseVarNum != BAD_VAR_NUM);
// The putArgNode children are always contained. We should not consume any registers.
assert(putArgNode->gtGetOp1()->isContained());
// If the op1 is already in the dstReg - nothing to do.
// Otherwise load the op1 (GT_ADDR) into the dstReg to copy the struct on the stack by value.
+ CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef _TARGET_X86_
+ assert(dstReg != REG_SPBASE);
+ inst_RV_RV(INS_mov, dstReg, REG_SPBASE);
+#else // !_TARGET_X86_
if (dstAddr->gtRegNum != dstReg)
{
// Generate LEA instruction to load the stack of the outgoing var + SlotNum offset (or the incoming arg area
// for tail calls) in RDI.
// Destination is always local (on the stack) - use EA_PTRSIZE.
- getEmitter()->emitIns_R_S(INS_lea, EA_PTRSIZE, dstReg, baseVarNum, putArgNode->getArgOffset());
+ assert(m_stkArgVarNum != BAD_VAR_NUM);
+ getEmitter()->emitIns_R_S(INS_lea, EA_PTRSIZE, dstReg, m_stkArgVarNum, putArgNode->getArgOffset());
}
+#endif // !_TARGET_X86_
if (srcAddr->gtRegNum != srcReg)
{
if (sizeReg != REG_NA)
{
- inst_RV_IV(INS_mov, sizeReg, size, EA_8BYTE);
+ inst_RV_IV(INS_mov, sizeReg, size, EA_PTRSIZE);
}
}
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
//------------------------------------------------------------------------
// genConsumeBlockSize: Ensure that the block size is in the given register
// Consume all the arg regs
for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext())
{
- assert(list->IsList());
+ assert(list->OperIsList());
GenTreePtr argNode = list->Current();
#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
// Deal with multi register passed struct args.
- if (argNode->OperGet() == GT_LIST)
+ if (argNode->OperGet() == GT_FIELD_LIST)
{
- GenTreeArgList* argListPtr = argNode->AsArgList();
- unsigned iterationNum = 0;
- for (; argListPtr != nullptr; argListPtr = argListPtr->Rest(), iterationNum++)
+ GenTreeFieldList* fieldListPtr = argNode->AsFieldList();
+ unsigned iterationNum = 0;
+ for (; fieldListPtr != nullptr; fieldListPtr = fieldListPtr->Rest(), iterationNum++)
{
- GenTreePtr putArgRegNode = argListPtr->gtOp.gtOp1;
+ GenTreePtr putArgRegNode = fieldListPtr->gtOp.gtOp1;
assert(putArgRegNode->gtOper == GT_PUTARG_REG);
regNumber argReg = REG_NA;
{
assert((arg->gtGetOp1()->OperGet() == GT_PUTARG_STK) && (arg->gtGetOp2()->OperGet() == GT_PUTARG_STK));
}
+ if ((arg->OperGet() == GT_PUTARG_STK) && (arg->gtGetOp1()->OperGet() == GT_FIELD_LIST))
+ {
+ fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(call, arg);
+ assert(curArgTabEntry);
+ stackArgBytes += curArgTabEntry->numSlots * TARGET_POINTER_SIZE;
+ }
+ else
#endif // defined(_TARGET_X86_)
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
- if (genActualType(arg->TypeGet()) == TYP_STRUCT)
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
+ if (genActualType(arg->TypeGet()) == TYP_STRUCT)
{
assert(arg->OperGet() == GT_PUTARG_STK);
- GenTreeObj* obj = arg->gtGetOp1()->AsObj();
- stackArgBytes = compiler->info.compCompHnd->getClassSize(obj->gtClass);
+ GenTreeObj* obj = arg->gtGetOp1()->AsObj();
+ unsigned argBytes = (unsigned)roundUp(obj->gtBlkSize, TARGET_POINTER_SIZE);
+#ifdef DEBUG
+ fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(call, arg);
+ assert((curArgTabEntry->numSlots * TARGET_POINTER_SIZE) == argBytes);
+#endif // DEBUG
+ stackArgBytes += argBytes;
}
else
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
stackArgBytes += genTypeSize(genActualType(arg->TypeGet()));
}
getEmitter()->emitIns_Nop(3);
getEmitter()->emitIns_Call(emitter::EmitCallType(emitter::EC_INDIR_ARD), methHnd,
- INDEBUG_LDISASM_COMMA(sigInfo) nullptr, argSizeForEmitter, retSize
- MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize),
+ INDEBUG_LDISASM_COMMA(sigInfo) nullptr, argSizeForEmitter,
+ retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize),
gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur,
ilOffset, REG_VIRTUAL_STUB_TARGET, REG_NA, 1, 0);
}
else
#endif
- if (target->isContainedIndir())
+ if (target->isContainedIndir())
{
if (target->AsIndir()->HasBase() && target->AsIndir()->Base()->isContainedIntOrIImmed())
{
emitAttr cmpAttr;
regNumber targetReg = treeNode->gtRegNum;
- assert(!op1->isContainedIntOrIImmed()); // We no longer support swapping op1 and op2 to generate cmp reg, imm
+ // TODO-CQ: We should be able to support swapping op1 and op2 to generate cmp reg, imm.
+ // https://github.com/dotnet/coreclr/issues/7270
+ assert(!op1->isContainedIntOrIImmed()); // We no longer support
assert(!varTypeIsFloating(op2Type));
#ifdef _TARGET_X86_
// Generate an overflow check for [u]long to [u]int casts:
//
// long -> int - check if the upper 33 bits are all 0 or all 1
- //
+ //
// ulong -> int - check if the upper 33 bits are all 0
//
// long -> uint - check if the upper 32 bits are all 0
if ((srcType == TYP_LONG) && (dstType == TYP_INT))
{
- BasicBlock* allOne = genCreateTempLabel();
+ BasicBlock* allOne = genCreateTempLabel();
BasicBlock* success = genCreateTempLabel();
inst_RV_RV(INS_test, loSrcReg, loSrcReg, TYP_INT, EA_4BYTE);
genJumpToThrowHlpBlk(EJ_jne, SCK_OVERFLOW);
}
}
-
+
if (dstReg != loSrcReg)
{
inst_RV_RV(INS_mov, dstReg, loSrcReg, TYP_INT, EA_4BYTE);
}
#endif // !defined(_TARGET_64BIT_)
- regNumber targetReg = treeNode->gtRegNum;
- regNumber sourceReg = castOp->gtRegNum;
- var_types dstType = treeNode->CastToType();
- bool isUnsignedDst = varTypeIsUnsigned(dstType);
- bool isUnsignedSrc = varTypeIsUnsigned(srcType);
+ regNumber targetReg = treeNode->gtRegNum;
+ regNumber sourceReg = castOp->gtRegNum;
+ var_types dstType = treeNode->CastToType();
+ bool isUnsignedDst = varTypeIsUnsigned(dstType);
+ bool isUnsignedSrc = varTypeIsUnsigned(srcType);
// if necessary, force the srcType to unsigned when the GT_UNSIGNED flag is set
if (!isUnsignedSrc && (treeNode->gtFlags & GTF_UNSIGNED) != 0)
#if FEATURE_FIXED_OUT_ARGS
baseVarNum = compiler->lvaOutgoingArgSpaceVar;
#else // !FEATURE_FIXED_OUT_ARGS
- NYI_X86("Stack args for x86/RyuJIT");
+ assert(!"No BaseVarForPutArgStk on x86");
baseVarNum = BAD_VAR_NUM;
#endif // !FEATURE_FIXED_OUT_ARGS
}
}
//--------------------------------------------------------------------- //
-// genPutStructArgStk - generate code for passing an arg on the stack.
+// genPutArgStk - generate code for passing an arg on the stack.
//
// Arguments
// treeNode - the GT_PUTARG_STK node
{
var_types targetType = treeNode->TypeGet();
#ifdef _TARGET_X86_
- noway_assert(targetType != TYP_STRUCT);
+ if (varTypeIsStruct(targetType))
+ {
+ // If this is less than 16 bytes, and has no gc refs, then we will push the args.
+ // Otherwise, we will decrement sp and do regular stores.
+ if ((treeNode->AsPutArgStk()->getArgSize() < 16) && (treeNode->AsPutArgStk()->gtNumberReferenceSlots == 0))
+ {
+ m_pushStkArg = true;
+ }
+ else
+ {
+ inst_RV_IV(INS_sub, REG_SPBASE, treeNode->AsPutArgStk()->getArgSize(), EA_PTRSIZE);
+ m_pushStkArg = false;
+ genStackLevel += treeNode->AsPutArgStk()->getArgSize();
+ }
+ m_stkArgOffset = 0;
+ genPutStructArgStk(treeNode->AsPutArgStk());
+ return;
+ }
// The following logic is applicable for x86 arch.
assert(!varTypeIsFloating(targetType) || (targetType == treeNode->gtGetOp1()->TypeGet()));
inst_IV(INS_push, data->gtIntCon.gtIconVal);
}
}
+ else if (data->OperGet() == GT_FIELD_LIST)
+ {
+ GenTreeFieldList* fieldList = data->AsFieldList();
+ // TODO-X86-CQ: If there are no gaps or padding in the layout, we can use a series of pushes.
+ // For now, always make space for the struct, and store each field at its offset.
+
+ unsigned structSize = treeNode->AsPutArgStk()->getArgSize();
+ m_pushStkArg = false;
+ inst_RV_IV(INS_sub, REG_SPBASE, structSize, EA_PTRSIZE);
+ genStackLevel += structSize;
+ for (; fieldList != nullptr; fieldList = fieldList->Rest())
+ {
+ GenTree* fieldNode = fieldList->Current();
+ // TODO-X86-CQ: If this is not a register candidate, or is not in a register,
+ // make it contained.
+ genConsumeReg(fieldNode);
+ // Note that the argReg may not be the lcl->gtRegNum, if it has been copied
+ // or reloaded to a different register.
+ regNumber argReg = fieldNode->gtRegNum;
+ unsigned offset = fieldList->gtFieldOffset;
+ var_types fieldType = fieldList->gtFieldType;
+ if (varTypeIsLong(fieldType))
+ {
+ NYI_X86("Long field of promoted struct arg");
+ }
+ genStoreRegToStackArg(fieldType, argReg, offset);
+ }
+ }
else if (data->isContained())
{
NYI_X86("Contained putarg_stk of non-constant");
if (varTypeIsStruct(targetType))
{
- genPutStructArgStk(treeNode, baseVarNum);
+ m_stkArgVarNum = baseVarNum;
+ m_stkArgOffset = treeNode->AsPutArgStk()->getArgOffset();
+ genPutStructArgStk(treeNode->AsPutArgStk());
+ m_stkArgVarNum = BAD_VAR_NUM;
return;
}
#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
#endif // !_TARGET_X86_
}
-#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+#if defined(FEATURE_PUT_STRUCT_ARG_STK)
+// genStoreRegToStackArg: Store a register value into the stack argument area
+//
+// Arguments:
+// type - the type of value to be stored
+// reg - the register containing the value
+// offset - the offset from the base (see Assumptions below)
+//
+// Notes:
+// A type of TYP_STRUCT instructs this method to store a 16-byte chunk
+// at the given offset (i.e. not the full struct).
+//
+// Assumptions:
+// The caller must set the context appropriately before calling this method:
+// - On x64, m_stkArgVarNum must be set according to whether this is a regular or tail call.
+// - On x86, the caller must set m_pushStkArg if this method should push the argument.
+// Otherwise, the argument is stored at the given offset from sp.
+//
+// TODO: In the below code the load and store instructions are for 16 bytes, but the
+// type is EA_8BYTE. The movdqa/u are 16 byte instructions, so it works, but
+// this probably needs to be changed.
+//
+void CodeGen::genStoreRegToStackArg(var_types type, regNumber srcReg, unsigned offset)
+{
+ assert(srcReg != REG_NA);
+ instruction ins;
+ emitAttr attr;
+ unsigned size;
+ if (type == TYP_STRUCT)
+ {
+ ins = INS_movdqu;
+ // This should be changed!
+ attr = EA_8BYTE;
+ size = 16;
+ }
+ else
+ {
+#ifdef FEATURE_SIMD
+ if (varTypeIsSIMD(type))
+ {
+ assert(genIsValidFloatReg(srcReg));
+ ins = ins_Store(type);
+ }
+ else
+#endif // FEATURE_SIMD
+#ifdef _TARGET_X86_
+ if (type == TYP_LONG)
+ {
+ assert(genIsValidFloatReg(srcReg));
+ ins = INS_movq;
+ }
+ else
+#endif // _TARGET_X86_
+ {
+ assert((varTypeIsFloating(type) && genIsValidFloatReg(srcReg)) ||
+ (varTypeIsIntegralOrI(type) && genIsValidIntReg(srcReg)));
+ ins = ins_Store(type);
+ }
+ attr = emitTypeSize(type);
+ size = genTypeSize(type);
+ }
+#ifdef _TARGET_X86_
+ if (m_pushStkArg)
+ {
+ if (varTypeIsIntegralOrI(type) && type != TYP_LONG)
+ {
+ inst_RV(INS_push, srcReg, type);
+ }
+ else
+ {
+ inst_RV_IV(INS_sub, REG_SPBASE, size, EA_PTRSIZE);
+ getEmitter()->emitIns_AR_R(ins, attr, srcReg, REG_SPBASE, 0);
+ }
+ genStackLevel += size;
+ }
+ else
+ {
+ getEmitter()->emitIns_AR_R(ins, attr, srcReg, REG_SPBASE, offset);
+ }
+#else // !_TARGET_X86_
+ assert(m_stkArgVarNum != BAD_VAR_NUM);
+ getEmitter()->emitIns_S_R(ins, attr, srcReg, m_stkArgVarNum, m_stkArgOffset + offset);
+#endif // !_TARGET_X86_
+}
//---------------------------------------------------------------------
// genPutStructArgStk - generate code for copying a struct arg on the stack by value.
//
// Arguments
// treeNode - the GT_PUTARG_STK node
-// baseVarNum - the variable number relative to which to put the argument on the stack.
-// For tail calls this is the baseVarNum = 0.
-// For non tail calls this is the outgoingArgSpace.
//
-// Return value:
-// None
+// Notes:
+// In the case of fixed out args, the caller must have set m_stkArgVarNum to the variable number
+// corresponding to the argument area (where we will put the argument on the stack).
+// For tail calls this is the baseVarNum = 0.
+// For non tail calls this is the outgoingArgSpace.
+
//
-void CodeGen::genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum)
+void CodeGen::genPutStructArgStk(GenTreePutArgStk* putArgStk)
{
- assert(treeNode->OperGet() == GT_PUTARG_STK);
- assert(baseVarNum != BAD_VAR_NUM);
-
- var_types targetType = treeNode->TypeGet();
+ var_types targetType = putArgStk->TypeGet();
if (varTypeIsSIMD(targetType))
{
- regNumber srcReg = genConsumeReg(treeNode->gtGetOp1());
+ regNumber srcReg = genConsumeReg(putArgStk->gtGetOp1());
assert((srcReg != REG_NA) && (genIsValidFloatReg(srcReg)));
- getEmitter()->emitIns_S_R(ins_Store(targetType), emitTypeSize(targetType), srcReg, baseVarNum,
- treeNode->AsPutArgStk()->getArgOffset());
+ genStoreRegToStackArg(targetType, srcReg, 0);
return;
}
assert(targetType == TYP_STRUCT);
- GenTreePutArgStk* putArgStk = treeNode->AsPutArgStk();
if (putArgStk->gtNumberReferenceSlots == 0)
{
switch (putArgStk->gtPutArgStkKind)
{
case GenTreePutArgStk::PutArgStkKindRepInstr:
- genStructPutArgRepMovs(putArgStk, baseVarNum);
+ genStructPutArgRepMovs(putArgStk);
break;
case GenTreePutArgStk::PutArgStkKindUnroll:
- genStructPutArgUnroll(putArgStk, baseVarNum);
+ genStructPutArgUnroll(putArgStk);
break;
default:
unreached();
// Consume these registers.
// They may now contain gc pointers (depending on their type; gcMarkRegPtrVal will "do the right thing").
- genConsumePutStructArgStk(putArgStk, REG_RDI, REG_RSI, REG_NA, baseVarNum);
+ genConsumePutStructArgStk(putArgStk, REG_RDI, REG_RSI, REG_NA);
GenTreePtr dstAddr = putArgStk;
GenTreePtr src = putArgStk->gtOp.gtOp1;
assert(src->OperGet() == GT_OBJ);
switch (gcPtrs[i])
{
case TYPE_GC_NONE:
- // Let's see if we can use rep movs{d,q} instead of a sequence of movs{d,q} instructions
- // to save cycles and code size.
+ // Let's see if we can use rep movsp (alias for movsd or movsq for 32 and 64 bits respectively)
+ // instead of a sequence of movsp instructions to save cycles and code size.
{
unsigned nonGcSlotCount = 0;
} while (i < slots && gcPtrs[i] == TYPE_GC_NONE);
// If we have a very small contiguous non-gc region, it's better just to
- // emit a sequence of movs{d,q} instructions
+ // emit a sequence of movsp instructions
if (nonGcSlotCount < CPOBJ_NONGC_SLOTS_LIMIT)
{
copiedSlots += nonGcSlotCount;
while (nonGcSlotCount > 0)
{
- instGen(INS_movs_ptr);
+ instGen(INS_movsp);
nonGcSlotCount--;
}
}
{
getEmitter()->emitIns_R_I(INS_mov, EA_4BYTE, REG_RCX, nonGcSlotCount);
copiedSlots += nonGcSlotCount;
- instGen(INS_r_movs_ptr);
+ instGen(INS_r_movsp);
}
}
break;
case TYPE_GC_BYREF: // Is an interior pointer - promote it but don't scan it
{
// We have a GC (byref or ref) pointer
- // TODO-Amd64-Unix: Here a better solution (for code size and CQ) would be to use movs{d,q} instruction,
+ // TODO-Amd64-Unix: Here a better solution (for code size and CQ) would be to use movsp instruction,
// but the logic for emitting a GC info record is not available (it is internal for the emitter
// only.) See emitGCVarLiveUpd function. If we could call it separately, we could do
- // instGen(INS_movs{d,q}); and emission of gc info.
+ // instGen(INS_movsp); and emission of gc info.
var_types memType;
if (gcPtrs[i] == TYPE_GC_REF)
}
getEmitter()->emitIns_R_AR(ins_Load(memType), emitTypeSize(memType), REG_RCX, REG_RSI, 0);
- getEmitter()->emitIns_S_R(ins_Store(memType), emitTypeSize(memType), REG_RCX, baseVarNum,
- ((copiedSlots + putArgStk->gtSlotNum) * TARGET_POINTER_SIZE));
+ genStoreRegToStackArg(memType, REG_RCX, copiedSlots * TARGET_POINTER_SIZE);
// Source for the copy operation.
// If a LocalAddr, use EA_PTRSIZE - copy from stack.
assert(gcPtrCount == 0);
}
}
-#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+#endif // defined(FEATURE_PUT_STRUCT_ARG_STK)
/*****************************************************************************
*
GenTreePtr loVal = op1->gtGetOp1();
GenTreePtr hiVal = op1->gtGetOp2();
// NYI: Contained immediates.
- NYI_IF((loVal->gtRegNum == REG_NA) || (hiVal->gtRegNum == REG_NA), "Store of long lclVar with contained immediate");
+ NYI_IF((loVal->gtRegNum == REG_NA) || (hiVal->gtRegNum == REG_NA),
+ "Store of long lclVar with contained immediate");
emit->emitIns_R_S(ins_Store(TYP_INT), EA_4BYTE, loVal->gtRegNum, lclNum, 0);
emit->emitIns_R_S(ins_Store(TYP_INT), EA_4BYTE, hiVal->gtRegNum, lclNum, genTypeSize(TYP_INT));
}
// Stack store
getEmitter()->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), REG_LNGRET_LO, lclNum, 0);
- getEmitter()->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), REG_LNGRET_HI, lclNum, genTypeSize(TYP_INT));
+ getEmitter()->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), REG_LNGRET_HI, lclNum,
+ genTypeSize(TYP_INT));
}
}
#endif // !defined(_TARGET_64BIT_)
regNumber otherRegNum; // The (second) register to use when passing this argument.
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc;
-#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+#elif defined(_TARGET_X86_)
+ __declspec(property(get = getIsStruct)) bool isStruct;
+ bool getIsStruct()
+ {
+ return varTypeIsStruct(node);
+ }
+#endif // _TARGET_X86_
#ifdef _TARGET_ARM_
void SetIsHfaRegArg(bool hfaRegArg)
{
return hasStackArgs;
}
+ bool AreArgsComplete() const
+ {
+ return argsComplete;
+ }
};
#ifdef DEBUG
GenTreeArgList* gtNewArgList(GenTreePtr op1, GenTreePtr op2);
GenTreeArgList* gtNewArgList(GenTreePtr op1, GenTreePtr op2, GenTreePtr op3);
- GenTreeArgList* gtNewAggregate(GenTree* element);
-
static fgArgTabEntryPtr gtArgEntryByArgNum(GenTreePtr call, unsigned argNum);
static fgArgTabEntryPtr gtArgEntryByNode(GenTreePtr call, GenTreePtr node);
fgArgTabEntryPtr gtArgEntryByLateArgIndex(GenTreePtr call, unsigned lateArgInx);
// for sufficiently small offsets, we can rely on OS page protection to implicitly null-check addresses that we
// know will be dereferenced. To know that reliance on implicit null checking is sound, we must further know that
// all offsets between the top-level indirection and the bottom are constant, and that their sum is sufficiently
- // small; hence the other fields of MorphAddrContext. Finally, the odd structure of GT_COPYBLK, in which the second
- // argument is a GT_LIST, requires us to "tell" that List node that its parent is a GT_COPYBLK, so it "knows" that
- // each of its arguments should be evaluated in MACK_Ind contexts. (This would not be true for GT_LIST nodes
- // representing method call argument lists.)
+ // small; hence the other fields of MorphAddrContext.
enum MorphAddrContextKind
{
MACK_Ind,
MACK_Addr,
- MACK_CopyBlock, // This is necessary so we know we have to start a new "Ind" context for each of the
- // addresses in the arg list.
};
struct MorphAddrContext
{
{
// If we are sequencing a node that does not appear in LIR,
// do not add it to the list.
- if (isLIR && (((tree->OperGet() == GT_LIST) && !tree->AsArgList()->IsAggregate()) || tree->OperGet() == GT_ARGPLACE))
+ if (isLIR)
{
- return;
+ if ((tree->OperGet() == GT_LIST) || (tree->OperGet() == GT_ARGPLACE) ||
+ (tree->OperGet() == GT_FIELD_LIST && !tree->AsFieldList()->IsFieldListHead()))
+ {
+ return;
+ }
}
/* Append to the node list */
break;
case GT_LIST:
- if ((op2 != nullptr) && op2->IsList())
+ case GT_FIELD_LIST:
+ if ((op2 != nullptr) && op2->OperIsAnyList())
{
ArrayStack<GenTree *> stack(this);
- while ((tree->gtGetOp2() != nullptr) && tree->gtGetOp2()->IsList())
+ while ((tree->gtGetOp2() != nullptr) && tree->gtGetOp2()->OperIsAnyList())
{
stack.Push(tree);
tree = tree->gtGetOp2();
GenTree::s_gtNodeSizes[GT_MOD] = TREE_NODE_SZ_LARGE;
GenTree::s_gtNodeSizes[GT_UMOD] = TREE_NODE_SZ_LARGE;
#endif
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
+ // TODO-Throughput: This should not need to be a large node. The object info should be
+ // obtained from the child node.
GenTree::s_gtNodeSizes[GT_PUTARG_STK] = TREE_NODE_SZ_LARGE;
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
assert(GenTree::s_gtNodeSizes[GT_RETURN] == GenTree::s_gtNodeSizes[GT_ASG]);
static_assert_no_msg(sizeof(GenTreeBox) <= TREE_NODE_SZ_LARGE); // *** large node
static_assert_no_msg(sizeof(GenTreeField) <= TREE_NODE_SZ_LARGE); // *** large node
static_assert_no_msg(sizeof(GenTreeArgList) <= TREE_NODE_SZ_SMALL);
+ static_assert_no_msg(sizeof(GenTreeFieldList) <= TREE_NODE_SZ_SMALL);
static_assert_no_msg(sizeof(GenTreeColon) <= TREE_NODE_SZ_SMALL);
static_assert_no_msg(sizeof(GenTreeCall) <= TREE_NODE_SZ_LARGE); // *** large node
static_assert_no_msg(sizeof(GenTreeCmpXchg) <= TREE_NODE_SZ_LARGE); // *** large node
static_assert_no_msg(sizeof(GenTreeLabel) <= TREE_NODE_SZ_SMALL);
static_assert_no_msg(sizeof(GenTreePhiArg) <= TREE_NODE_SZ_SMALL);
static_assert_no_msg(sizeof(GenTreeAllocObj) <= TREE_NODE_SZ_LARGE); // *** large node
-#ifndef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifndef FEATURE_PUT_STRUCT_ARG_STK
static_assert_no_msg(sizeof(GenTreePutArgStk) <= TREE_NODE_SZ_SMALL);
-#else // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#else // FEATURE_PUT_STRUCT_ARG_STK
+ // TODO-Throughput: This should not need to be a large node. The object info should be
+ // obtained from the child node.
static_assert_no_msg(sizeof(GenTreePutArgStk) <= TREE_NODE_SZ_LARGE);
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
#ifdef FEATURE_SIMD
static_assert_no_msg(sizeof(GenTreeSIMD) <= TREE_NODE_SZ_SMALL);
}
break;
- case GT_LIST:
+ case GT_FIELD_LIST:
{
- GenTreeArgList* list = tree->AsArgList();
- if (list->IsAggregate())
+ GenTreeFieldList* list = tree->AsFieldList();
+ if (list->IsFieldListHead())
{
+ GenTreeFieldList* list = tree->AsFieldList();
for (; list != nullptr; list = list->Rest())
{
result = fgWalkTreePostRec<computeStack>(&list->gtOp1, fgWalkData);
return result;
}
}
- break;
}
-
- // GT_LIST nodes that do not represent aggregate arguments intentionally fall through to the
- // default node processing below.
- __fallthrough;
+ break;
}
default:
}
}
+//-------------------------------------------------------------------------
+// AreArgsComplete: Determine if this GT_CALL node's arguments have been processed.
+//
+// Return Value:
+// Returns true if fgMorphArgs has processed the arguments.
+//
+bool GenTreeCall::AreArgsComplete() const
+{
+ if (fgArgInfo == nullptr)
+ {
+ return false;
+ }
+ if (fgArgInfo->AreArgsComplete())
+ {
+ assert((gtCallLateArgs != nullptr) || !fgArgInfo->HasRegArgs());
+ return true;
+ }
+ assert(gtCallArgs == nullptr);
+ return false;
+}
+
/*****************************************************************************
*
* Returns non-zero if the two trees are identical.
unsigned Compiler::gtSetListOrder(GenTree *list, bool isListCallArgs, bool callArgsInRegs)
{
- assert((list != nullptr) && list->IsList());
+ assert((list != nullptr) && list->OperIsAnyList());
assert(!callArgsInRegs || isListCallArgs);
ArrayStack<GenTree *> listNodes(this);
{
listNodes.Push(list);
list = list->gtOp.gtOp2;
- } while ((list != nullptr) && (list->IsList()));
+ } while ((list != nullptr) && (list->OperIsAnyList()));
unsigned nxtlvl = (list == nullptr) ? 0 : gtSetEvalOrder(list);
while (listNodes.Height() > 0)
#endif // FEATURE_STACK_FP_X87
list = listNodes.Pop();
- assert(list && list->IsList());
+ assert(list && list->OperIsAnyList());
GenTreePtr next = list->gtOp.gtOp2;
unsigned level = 0;
break;
case GT_LIST:
+ case GT_FIELD_LIST:
case GT_NOP:
costEx = 0;
costSz = 0;
goto DONE;
case GT_LIST:
-
+ case GT_FIELD_LIST:
{
const bool isListCallArgs = false;
const bool callArgsInRegs = false;
break;
case GT_LIST:
+ case GT_FIELD_LIST:
break;
case GT_SUB:
// 'parent' must be non-null
//
// Notes:
-// When FEATURE_MULTIREG_ARGS is defined we can get here with GT_LDOBJ tree.
+// When FEATURE_MULTIREG_ARGS is defined we can get here with GT_OBJ tree.
// This happens when we have a struct that is passed in multiple registers.
//
// Also note that when FEATURE_UNIX_AMD64_STRUCT_PASSING is defined the GT_LDOBJ
-// later gets converted to a GT_LIST with two GT_LCL_FLDs in Lower/LowerXArch.
+// later gets converted to a GT_FIELD_LIST with two GT_LCL_FLDs in Lower/LowerXArch.
//
GenTreePtr* GenTree::gtGetChildPointer(GenTreePtr parent)
return new (this, GT_LIST) GenTreeArgList(arg1, gtNewArgList(arg2));
}
-//------------------------------------------------------------------------
-// Compiler::gtNewAggregate:
-// Creates a new aggregate argument node. These nodes are used to
-// represent arguments that are composed of multiple values (e.g.
-// the lclVars that represent the fields of a promoted struct).
-//
-// Note that aggregate arguments are currently represented by GT_LIST
-// nodes that are marked with the GTF_LIST_AGGREGATE flag. This
-// representation may be changed in the future to instead use its own
-// node type (e.g. GT_AGGREGATE).
-//
-// Arguments:
-// firstElement - The first element in the aggregate's list of values.
-//
-// Returns:
-// The newly-created aggregate node.
-GenTreeArgList* Compiler::gtNewAggregate(GenTree* firstElement)
-{
- GenTreeArgList* agg = gtNewArgList(firstElement);
- agg->gtFlags |= GTF_LIST_AGGREGATE;
- return agg;
-}
-
/*****************************************************************************
*
* Create a list out of the three values.
#endif // PROTO_JIT
else if (curArgTabEntry->parent != nullptr)
{
- assert(curArgTabEntry->parent->IsList());
+ assert(curArgTabEntry->parent->OperIsList());
if (curArgTabEntry->parent->Current() == node)
{
return curArgTabEntry;
// The nodes below this are not bashed, so they can be allocated at their individual sizes.
case GT_LIST:
- // This is ridiculous, but would go away if we made a stronger distinction between argument lists, whose
- // second argument *must* be an arglist*, and the uses of LIST in copyblk and initblk.
- if (tree->gtOp.gtOp2 != nullptr && tree->gtOp.gtOp2->OperGet() == GT_LIST)
- {
- copy = new (this, GT_LIST) GenTreeArgList(tree->gtOp.gtOp1, tree->gtOp.gtOp2->AsArgList());
- }
- else
- {
- copy = new (this, GT_LIST) GenTreeOp(GT_LIST, TYP_VOID, tree->gtOp.gtOp1, tree->gtOp.gtOp2);
- }
+ assert((tree->gtOp.gtOp2 == nullptr) || tree->gtOp.gtOp2->OperIsList());
+ copy = new (this, GT_LIST) GenTreeArgList(tree->gtOp.gtOp1);
+ copy->gtOp.gtOp2 = tree->gtOp.gtOp2;
+ break;
+
+ case GT_FIELD_LIST:
+ copy = new (this, GT_FIELD_LIST) GenTreeFieldList(tree->gtOp.gtOp1, tree->AsFieldList()->gtFieldOffset, tree->AsFieldList()->gtFieldType, nullptr);
+ copy->gtOp.gtOp2 = tree->gtOp.gtOp2;
+ copy->gtFlags = (copy->gtFlags & ~GTF_FIELD_LIST_HEAD) | (tree->gtFlags & GTF_FIELD_LIST_HEAD);
break;
case GT_INDEX:
}
break;
- case GT_LIST:
- if (m_node->AsArgList()->IsAggregate())
- {
- // List nodes that represent aggregates are handled by MoveNextAggregateUseEdge.
- break;
- }
- __fallthrough;
+ case GT_FIELD_LIST:
+ // Field List nodes are handled by MoveToNextFieldUseEdge.
+ break;
default:
if (m_node->OperIsConst() || m_node->OperIsLeaf())
}
#endif // FEATURE_SIMD
-void GenTreeUseEdgeIterator::MoveToNextAggregateUseEdge()
+void GenTreeUseEdgeIterator::MoveToNextFieldUseEdge()
{
- assert(m_node->OperGet() == GT_LIST);
- assert(m_node->AsArgList()->IsAggregate());
+ assert(m_node->OperGet() == GT_FIELD_LIST);
for (;;)
{
}
else
{
- GenTreeArgList* aggNode = m_argList->AsArgList();
- m_edge = &aggNode->gtOp1;
- m_argList = aggNode->Rest();
+ GenTreeArgList* listNode = m_argList->AsArgList();
+ m_edge = &listNode->gtOp1;
+ m_argList = listNode->Rest();
return;
}
break;
MoveToNextSIMDUseEdge();
}
#endif
- else if ((op == GT_LIST) && (m_node->AsArgList()->IsAggregate()))
+ else if (op == GT_FIELD_LIST)
{
- MoveToNextAggregateUseEdge();
+ MoveToNextFieldUseEdge();
}
else
{
{
printf(" (init)");
}
+ else if (tree->OperIsFieldList())
+ {
+ printf(" %s at offset %d", varTypeName(tree->AsFieldList()->gtFieldType), tree->AsFieldList()->gtFieldOffset);
+ }
IndirectAssignmentAnnotation* pIndirAnnote;
if (tree->gtOper == GT_ASG && GetIndirAssignMap()->Lookup(tree, &pIndirAnnote))
// call - The call for which 'arg' is an argument
// arg - The argument for which a message should be constructed
// argNum - The ordinal number of the arg in the argument list
-// listCount - When printing in LIR form this is the count for a multireg GT_LIST
+// listCount - When printing in LIR form this is the count for a GT_FIELD_LIST
// or -1 if we are not printing in LIR form
// bufp - A pointer to the buffer into which the message is written
// bufLength - The length of the buffer pointed to by bufp
// call - The call for which 'arg' is an argument
// argx - The argument for which a message should be constructed
// lateArgIndex - The ordinal number of the arg in the lastArg list
-// listCount - When printing in LIR form this is the count for a multireg GT_LIST
+// listCount - When printing in LIR form this is the count for a multireg GT_FIELD_LIST
// or -1 if we are not printing in LIR form
// bufp - A pointer to the buffer into which the message is written
// bufLength - The length of the buffer pointed to by bufp
return op2;
}
- if (tree->gtOper == GT_LIST)
+ if (tree->OperIsAnyList())
{
return tree;
}
GenTreePtr args;
for (args = expr->gtCall.gtCallArgs; args; args = args->gtOp.gtOp2)
{
- assert(args->IsList());
+ assert(args->OperIsList());
gtExtractSideEffList(args->Current(), pList, flags);
}
for (args = expr->gtCall.gtCallLateArgs; args; args = args->gtOp.gtOp2)
{
- assert(args->IsList());
+ assert(args->OperIsList());
gtExtractSideEffList(args->Current(), pList, flags);
}
}
#endif // FEATURE_SIMD
//---------------------------------------------------------------------------------------
-// GenTreeArgList::Prepend:
-// Prepends an element to a GT_LIST.
-//
-// Arguments:
-// compiler - The compiler context.
-// element - The element to prepend.
-//
-// Returns:
-// The new head of the list.
-GenTreeArgList* GenTreeArgList::Prepend(Compiler* compiler, GenTree* element)
-{
- GenTreeArgList* head = compiler->gtNewListNode(element, this);
- head->gtFlags |= (gtFlags & GTF_LIST_AGGREGATE);
- gtFlags &= ~GTF_LIST_AGGREGATE;
- return head;
-}
-
-//---------------------------------------------------------------------------------------
// InitializeStructReturnType:
// Initialize the Return Type Descriptor for a method that returns a struct type
//
#define GTF_ARRLEN_ARR_IDX 0x80000000 // GT_ARR_LENGTH -- Length which feeds into an array index expression
-#define GTF_LIST_AGGREGATE 0x80000000 // GT_LIST -- Indicates that this list should be treated as an
- // anonymous aggregate value (e.g. a multi-value argument).
+#define GTF_FIELD_LIST_HEAD 0x80000000 // GT_FIELD_LIST -- Indicates that this is the first field in a list of
+ // struct fields constituting a single call argument.
//----------------------------------------------------------------
return gtType != TYP_VOID;
}
- if (gtOper == GT_LIST)
+ if (gtOper == GT_FIELD_LIST)
{
- return (gtFlags & GTF_LIST_AGGREGATE) != 0;
+ return (gtFlags & GTF_FIELD_LIST_HEAD) != 0;
}
return true;
return IsNothingNode();
case GT_ARGPLACE:
- // ARGPLACE nodes may not be present in a block's LIR sequence, but they may
+ case GT_LIST:
+ // ARGPLACE and LIST nodes may not be present in a block's LIR sequence, but they may
// be present as children of an LIR node.
return (gtNext == nullptr) && (gtPrev == nullptr);
- case GT_LIST:
- // LIST nodes may only be present in an LIR sequence if they represent aggregates.
- // They are always allowed, however, as children of an LIR node.
- return ((gtFlags & GTF_LIST_AGGREGATE) != 0) || ((gtNext == nullptr) && (gtPrev == nullptr));
+ case GT_FIELD_LIST:
+ // Only the head of the FIELD_LIST is present in the block's LIR sequence.
+ return (((gtFlags & GTF_FIELD_LIST_HEAD) != 0) || ((gtNext == nullptr) && (gtPrev == nullptr)));
case GT_ADDR:
{
return OperIsSIMD(gtOper);
}
- bool OperIsAggregate()
+ bool OperIsFieldListHead()
{
- return (gtOper == GT_LIST) && ((gtFlags & GTF_LIST_AGGREGATE) != 0);
+ return (gtOper == GT_FIELD_LIST) && ((gtFlags & GTF_FIELD_LIST_HEAD) != 0);
}
bool OperIsConditionalJump() const
switch (gtOper)
{
case GT_LIST:
+ case GT_FIELD_LIST:
case GT_INTRINSIC:
case GT_LEA:
#ifdef FEATURE_SIMD
}
static inline bool RequiresNonNullOp2(genTreeOps oper);
- bool IsListForMultiRegArg();
+ bool IsValidCallArgument();
#endif // DEBUG
inline bool IsFPZero();
inline bool IsBoxedValue();
- bool IsList() const
+ static bool OperIsList(genTreeOps gtOper)
{
return gtOper == GT_LIST;
}
+ bool OperIsList() const
+ {
+ return OperIsList(gtOper);
+ }
+
+ static bool OperIsFieldList(genTreeOps gtOper)
+ {
+ return gtOper == GT_FIELD_LIST;
+ }
+
+ bool OperIsFieldList() const
+ {
+ return OperIsFieldList(gtOper);
+ }
+
+ static bool OperIsAnyList(genTreeOps gtOper)
+ {
+ return OperIsList(gtOper) || OperIsFieldList(gtOper);
+ }
+
+ bool OperIsAnyList() const
+ {
+ return OperIsAnyList(gtOper);
+ }
+
inline GenTreePtr MoveNext();
inline GenTreePtr Current();
#ifdef FEATURE_SIMD
void MoveToNextSIMDUseEdge();
#endif
- void MoveToNextAggregateUseEdge();
+ void MoveToNextFieldUseEdge();
public:
GenTreeUseEdgeIterator();
// method names for the arguments.
struct GenTreeArgList : public GenTreeOp
{
- bool IsAggregate() const
- {
- return (gtFlags & GTF_LIST_AGGREGATE) != 0;
- }
-
GenTreePtr& Current()
{
return gtOp1;
}
GenTreeArgList*& Rest()
{
- assert(gtOp2 == nullptr || gtOp2->OperGet() == GT_LIST);
+ assert(gtOp2 == nullptr || gtOp2->OperIsAnyList());
return *reinterpret_cast<GenTreeArgList**>(>Op2);
}
{
}
- GenTreeArgList(GenTreePtr arg, GenTreeArgList* rest) : GenTreeOp(GT_LIST, TYP_VOID, arg, rest)
+ GenTreeArgList(GenTreePtr arg, GenTreeArgList* rest) : GenTreeArgList(GT_LIST, arg, rest)
{
- // With structs passed in multiple args we could have an arg
- // GT_LIST containing a list of LCL_FLDs, see IsListForMultiRegArg()
- //
- assert((arg != nullptr) && ((!arg->IsList()) || (arg->IsListForMultiRegArg())));
+ }
+
+ GenTreeArgList(genTreeOps oper, GenTreePtr arg, GenTreeArgList* rest) : GenTreeOp(oper, TYP_VOID, arg, rest)
+ {
+ assert(OperIsAnyList(oper));
+ assert((arg != nullptr) && arg->IsValidCallArgument());
gtFlags |= arg->gtFlags & GTF_ALL_EFFECT;
if (rest != nullptr)
{
gtFlags |= rest->gtFlags & GTF_ALL_EFFECT;
}
}
+};
+
+// Represents a list of fields constituting a struct, when it is passed as an argument.
+// The first field of the struct is marked with the GTF_FIELD_LIST_HEAD flag, and
+// in LIR form it is the only member of the list that is threaded into the execution
+// order.
+// It differs from the GenTreeArgList in a couple of ways:
+// - The entire list represents a single argument.
+// - It contains additional fields to provide the offset and type of the field.
+//
+struct GenTreeFieldList : public GenTreeArgList
+{
+ unsigned gtFieldOffset;
+ var_types gtFieldType;
+
+ bool IsFieldListHead() const
+ {
+ return (gtFlags & GTF_FIELD_LIST_HEAD) != 0;
+ }
+
+#if DEBUGGABLE_GENTREE
+ GenTreeFieldList() : GenTreeArgList()
+ {
+ }
+#endif
- GenTreeArgList* Prepend(Compiler* compiler, GenTree* element);
+ GenTreeFieldList*& Rest()
+ {
+ assert(gtOp2 == nullptr || gtOp2->OperGet() == GT_FIELD_LIST);
+ return *reinterpret_cast<GenTreeFieldList**>(>Op2);
+ }
+
+ GenTreeFieldList(GenTreePtr arg, unsigned fieldOffset, var_types fieldType, GenTreeFieldList* prevList)
+ : GenTreeArgList(GT_FIELD_LIST, arg, nullptr)
+ {
+ // While GT_FIELD_LIST can be in a GT_LIST, GT_FIELD_LISTs cannot be nested or have GT_LISTs.
+ assert(!arg->OperIsAnyList());
+ gtFieldOffset = fieldOffset;
+ gtFieldType = fieldType;
+ if (prevList == nullptr)
+ {
+ gtFlags |= GTF_FIELD_LIST_HEAD;
+ }
+ else
+ {
+ prevList->gtOp2 = this;
+ }
+ }
};
// There was quite a bit of confusion in the code base about which of gtOp1 and gtOp2 was the
void ReplaceCallOperand(GenTree** operandUseEdge, GenTree* replacement);
+ bool AreArgsComplete() const;
+
GenTreeCall(var_types type) : GenTree(GT_CALL, type)
{
+ fgArgInfo = nullptr;
}
#if DEBUGGABLE_GENTREE
GenTreeCall() : GenTree()
GenTreePutArgStk(genTreeOps oper,
var_types type,
- unsigned slotNum FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(unsigned numSlots)
- FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(bool isStruct),
+ unsigned slotNum PUT_STRUCT_ARG_STK_ONLY_ARG(unsigned numSlots)
+ PUT_STRUCT_ARG_STK_ONLY_ARG(bool isStruct),
bool _putInIncomingArgArea = false DEBUGARG(GenTreePtr callNode = nullptr)
DEBUGARG(bool largeNode = false))
: GenTreeUnOp(oper, type DEBUGARG(largeNode))
, gtSlotNum(slotNum)
, putInIncomingArgArea(_putInIncomingArgArea)
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
, gtPutArgStkKind(PutArgStkKindInvalid)
, gtNumSlots(numSlots)
, gtIsStruct(isStruct)
, gtNumberReferenceSlots(0)
, gtGcPtrs(nullptr)
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
{
#ifdef DEBUG
gtCall = callNode;
GenTreePutArgStk(genTreeOps oper,
var_types type,
GenTreePtr op1,
- unsigned slotNum FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(unsigned numSlots)
- FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(bool isStruct),
+ unsigned slotNum PUT_STRUCT_ARG_STK_ONLY_ARG(unsigned numSlots)
+ PUT_STRUCT_ARG_STK_ONLY_ARG(bool isStruct),
bool _putInIncomingArgArea = false DEBUGARG(GenTreePtr callNode = nullptr)
DEBUGARG(bool largeNode = false))
: GenTreeUnOp(oper, type, op1 DEBUGARG(largeNode))
, gtSlotNum(slotNum)
, putInIncomingArgArea(_putInIncomingArgArea)
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
, gtPutArgStkKind(PutArgStkKindInvalid)
, gtNumSlots(numSlots)
, gtIsStruct(isStruct)
, gtNumberReferenceSlots(0)
, gtGcPtrs(nullptr)
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
{
#ifdef DEBUG
gtCall = callNode;
GenTreePutArgStk(genTreeOps oper,
var_types type,
- unsigned slotNum FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(unsigned numSlots)
- FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(bool isStruct) DEBUGARG(GenTreePtr callNode = NULL)
+ unsigned slotNum PUT_STRUCT_ARG_STK_ONLY_ARG(unsigned numSlots)
+ PUT_STRUCT_ARG_STK_ONLY_ARG(bool isStruct) DEBUGARG(GenTreePtr callNode = NULL)
DEBUGARG(bool largeNode = false))
: GenTreeUnOp(oper, type DEBUGARG(largeNode))
, gtSlotNum(slotNum)
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
, gtPutArgStkKind(PutArgStkKindInvalid)
, gtNumSlots(numSlots)
, gtIsStruct(isStruct)
, gtNumberReferenceSlots(0)
, gtGcPtrs(nullptr)
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
{
#ifdef DEBUG
gtCall = callNode;
GenTreePutArgStk(genTreeOps oper,
var_types type,
GenTreePtr op1,
- unsigned slotNum FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(unsigned numSlots)
- FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(bool isStruct) DEBUGARG(GenTreePtr callNode = NULL)
+ unsigned slotNum PUT_STRUCT_ARG_STK_ONLY_ARG(unsigned numSlots)
+ PUT_STRUCT_ARG_STK_ONLY_ARG(bool isStruct) DEBUGARG(GenTreePtr callNode = NULL)
DEBUGARG(bool largeNode = false))
: GenTreeUnOp(oper, type, op1 DEBUGARG(largeNode))
, gtSlotNum(slotNum)
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
, gtPutArgStkKind(PutArgStkKindInvalid)
, gtNumSlots(numSlots)
, gtIsStruct(isStruct)
, gtNumberReferenceSlots(0)
, gtGcPtrs(nullptr)
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
{
#ifdef DEBUG
gtCall = callNode;
return gtSlotNum * TARGET_POINTER_SIZE;
}
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
unsigned getArgSize()
{
return gtNumSlots * TARGET_POINTER_SIZE;
}
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
//------------------------------------------------------------------------
// setGcPointers: Sets the number of references and the layout of the struct object returned by the VM.
//
gtNumberReferenceSlots = numPointers;
gtGcPtrs = pointers;
}
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
#ifdef DEBUG
GenTreePtr gtCall; // the call node to which this argument belongs
#endif
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
// Instruction selection: during codegen time, what code sequence we will be using
// to encode this operation.
+ // TODO-Throughput: The following information should be obtained from the child
+ // block node.
enum PutArgStkKind : __int8{
PutArgStkKindInvalid, PutArgStkKindRepInstr, PutArgStkKindUnroll,
bool gtIsStruct; // This stack arg is a struct.
unsigned gtNumberReferenceSlots; // Number of reference slots.
BYTE* gtGcPtrs; // gcPointers
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
#if DEBUGGABLE_GENTREE
GenTreePutArgStk() : GenTreeUnOp()
inline GenTreePtr GenTree::MoveNext()
{
- assert(IsList());
+ assert(OperIsAnyList());
return gtOp.gtOp2;
}
#ifdef DEBUG
//------------------------------------------------------------------------
-// IsListForMultiRegArg: Given an GenTree node that represents an argument
-// enforce (or don't enforce) the following invariant.
-//
-// For LEGACY_BACKEND or architectures that don't support MultiReg args
-// we don't allow a GT_LIST at all.
-//
-// Currently for AMD64 UNIX we allow a limited case where a GT_LIST is
-// allowed but every element must be a GT_LCL_FLD.
-//
-// For the future targets that allow for Multireg args (and this includes
-// the current ARM64 target) we allow a GT_LIST of arbitrary nodes, these
-// would typically start out as GT_LCL_VARs or GT_LCL_FLDS or GT_INDs,
-// but could be changed into constants or GT_COMMA trees by the later
-// optimization phases.
+// IsValidCallArgument: Given an GenTree node that represents an argument
+// enforce (or don't enforce) the following invariant.
//
// Arguments:
// instance method for a GenTree node
// true: the GenTree node is accepted as a valid argument
// false: the GenTree node is not accepted as a valid argumeny
//
-inline bool GenTree::IsListForMultiRegArg()
+// Notes:
+// For targets that don't support arguments as a list of fields, we do not support GT_FIELD_LIST.
+//
+// Currently for AMD64 UNIX we allow a limited case where a GT_FIELD_LIST is
+// allowed but every element must be a GT_LCL_FLD.
+//
+// For the future targets that allow for Multireg args (and this includes the current ARM64 target),
+// or that allow for passing promoted structs, we allow a GT_FIELD_LIST of arbitrary nodes.
+// These would typically start out as GT_LCL_VARs or GT_LCL_FLDS or GT_INDs,
+// but could be changed into constants or GT_COMMA trees by the later
+// optimization phases.
+
+inline bool GenTree::IsValidCallArgument()
{
- if (!IsList())
+ if (OperIsList())
{
- // We don't have a GT_LIST, so just return true.
- return true;
+ // GT_FIELD_LIST is the only list allowed.
+ return false;
}
- else // We do have a GT_LIST
+ if (OperIsFieldList())
{
#if defined(LEGACY_BACKEND) || !FEATURE_MULTIREG_ARGS
-
- // Not allowed to have a GT_LIST for an argument
+ // Not allowed to have a GT_FIELD_LIST for an argument
// unless we have a RyuJIT backend and FEATURE_MULTIREG_ARGS
return false;
#else // we have RyuJIT backend and FEATURE_MULTIREG_ARGS
#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
- // For UNIX ABI we currently only allow a GT_LIST of GT_LCL_FLDs nodes
+ // For UNIX ABI we currently only allow a GT_FIELD_LIST of GT_LCL_FLDs nodes
GenTree* gtListPtr = this;
while (gtListPtr != nullptr)
{
}
#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
- // Note that for non-UNIX ABI the GT_LIST may contain any node
+ // Note that for non-UNIX ABI the GT_FIELD_LIST may contain any node
//
- // We allow this GT_LIST as an argument
+ // We allow this GT_FIELD_LIST as an argument
return true;
-#endif // RyuJIT backend and FEATURE_MULTIREG_ARGS
+#endif // FEATURE_MULTIREG_ARGS
}
+ // We don't have either kind of list, so it satisfies the invariant.
+ return true;
}
#endif // DEBUG
inline GenTreePtr GenTree::Current()
{
- assert(IsList());
+ assert(OperIsAnyList());
return gtOp.gtOp1;
}
inline GenTreePtr* GenTree::pCurrent()
{
- assert(IsList());
+ assert(OperIsAnyList());
return &(gtOp.gtOp1);
}
GTNODE(JTRUE , "jmpTrue" ,GenTreeOp ,0,GTK_UNOP|GTK_NOVALUE)
GTNODE(JCC , "jcc" ,GenTreeJumpCC ,0,GTK_LEAF|GTK_NOVALUE)
-GTNODE(LIST , "<list>" ,GenTreeOp ,0,GTK_BINOP)
+GTNODE(LIST , "<list>" ,GenTreeArgList ,0,GTK_BINOP|GTK_NOVALUE)
+GTNODE(FIELD_LIST , "<fldList>" ,GenTreeFieldList ,0,GTK_BINOP) // List of fields of a struct, when passed as an argument
//-----------------------------------------------------------------------------
// Other nodes that have special structure:
GTSTRUCT_1(Box , GT_BOX)
GTSTRUCT_1(Field , GT_FIELD)
GTSTRUCT_1(Call , GT_CALL)
-GTSTRUCT_1(ArgList , GT_LIST)
+GTSTRUCT_2(ArgList , GT_LIST, GT_FIELD_LIST)
+GTSTRUCT_1(FieldList , GT_FIELD_LIST)
GTSTRUCT_1(Colon , GT_COLON)
GTSTRUCT_1(FptrVal , GT_FTN_ADDR)
GTSTRUCT_1(Intrinsic , GT_INTRINSIC)
// Get native TypeHandle argument to old helper
op1 = op1->gtCall.gtCallArgs;
- assert(op1->IsList());
+ assert(op1->OperIsList());
assert(op1->gtOp.gtOp2 == nullptr);
op1 = op1->gtOp.gtOp1;
retNode = op1;
}
END_DECLARE_TYPED_ENUM(instruction,unsigned)
-#if defined(_TARGET_XARCH_)
-#if defined(_TARGET_X86_)
-#define INS_r_movs_ptr INS_r_movsd
-#define INS_movs_ptr INS_movsd
-#elif defined(_TARGET_AMD64_)
-#define INS_r_movs_ptr INS_r_movsq
-#define INS_movs_ptr INS_movsq
-#else
-#error Unsupported xarch target
-#endif
-#endif
-
/*****************************************************************************/
enum insUpdateModes
#define FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY(x)
#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)|| (defined(_TARGET_X86_) && !defined(LEGACY_BACKEND))
+#define FEATURE_PUT_STRUCT_ARG_STK 1
+#define PUT_STRUCT_ARG_STK_ONLY_ARG(x) , x
+#define PUT_STRUCT_ARG_STK_ONLY(x) x
+#else // !(defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)|| (defined(_TARGET_X86_) && !defined(LEGACY_BACKEND)))
+#define PUT_STRUCT_ARG_STK_ONLY_ARG(x)
+#define PUT_STRUCT_ARG_STK_ONLY(x)
+#endif // !(defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)|| (defined(_TARGET_X86_) && !defined(LEGACY_BACKEND)))
+
#if defined(UNIX_AMD64_ABI)
#define UNIX_AMD64_ABI_ONLY_ARG(x) , x
#define UNIX_AMD64_ABI_ONLY(x) x
//
// Notes:
// For System V systems with native struct passing (i.e. FEATURE_UNIX_AMD64_STRUCT_PASSING defined)
-// this method allocates a single GT_PUTARG_REG for 1 eightbyte structs and a GT_LIST of two GT_PUTARG_REGs
+// this method allocates a single GT_PUTARG_REG for 1 eightbyte structs and a GT_FIELD_LIST of two GT_PUTARG_REGs
// for two eightbyte structs.
//
// For STK passed structs the method generates GT_PUTARG_STK tree. For System V systems with native struct passing
// In this case a new tree is created that is GT_PUTARG_REG
// with a op1 the original argument.
// 2. The struct is contained in 2 eightbytes:
- // in this case the arg comes as a GT_LIST of two GT_LCL_FLDs - the two eightbytes of the struct.
- // The code creates a GT_PUTARG_REG node for each GT_LCL_FLD in the GT_LIST
+ // in this case the arg comes as a GT_FIELD_LIST of two GT_LCL_FLDs - the two eightbytes of the struct.
+ // The code creates a GT_PUTARG_REG node for each GT_LCL_FLD in the GT_FIELD_LIST
// and splices it in the list with the corresponding original GT_LCL_FLD tree as op1.
assert(info->structDesc.eightByteCount != 0);
//
// clang-format on
- assert(arg->OperGet() == GT_LIST);
+ assert(arg->OperGet() == GT_FIELD_LIST);
- GenTreeArgList* argListPtr = arg->AsArgList();
- assert(argListPtr->IsAggregate());
+ GenTreeFieldList* fieldListPtr = arg->AsFieldList();
+ assert(fieldListPtr->IsFieldListHead());
- for (unsigned ctr = 0; argListPtr != nullptr; argListPtr = argListPtr->Rest(), ctr++)
+ for (unsigned ctr = 0; fieldListPtr != nullptr; fieldListPtr = fieldListPtr->Rest(), ctr++)
{
// Create a new GT_PUTARG_REG node with op1 the original GT_LCL_FLD.
GenTreePtr newOper = comp->gtNewOperNode(
GT_PUTARG_REG,
comp->GetTypeFromClassificationAndSizes(info->structDesc.eightByteClassifications[ctr],
info->structDesc.eightByteSizes[ctr]),
- argListPtr->gtOp.gtOp1);
+ fieldListPtr->gtOp.gtOp1);
- // Splice in the new GT_PUTARG_REG node in the GT_LIST
- ReplaceArgWithPutArgOrCopy(&argListPtr->gtOp.gtOp1, newOper);
+ // Splice in the new GT_PUTARG_REG node in the GT_FIELD_LIST
+ ReplaceArgWithPutArgOrCopy(&fieldListPtr->gtOp.gtOp1, newOper);
}
- // Just return arg. The GT_LIST is not replaced.
+ // Just return arg. The GT_FIELD_LIST is not replaced.
// Nothing more to do.
return arg;
}
else
#else // not defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
#if FEATURE_MULTIREG_ARGS
- if ((info->numRegs > 1) && (arg->OperGet() == GT_LIST))
+ if ((info->numRegs > 1) && (arg->OperGet() == GT_FIELD_LIST))
{
- assert(arg->OperGet() == GT_LIST);
+ assert(arg->OperGet() == GT_FIELD_LIST);
- GenTreeArgList* argListPtr = arg->AsArgList();
- assert(argListPtr->IsAggregate());
+ GenTreeFieldList* fieldListPtr = arg->AsFieldList();
+ assert(fieldListPtr->IsFieldListHead());
- for (unsigned ctr = 0; argListPtr != nullptr; argListPtr = argListPtr->Rest(), ctr++)
+ for (unsigned ctr = 0; fieldListPtr != nullptr; fieldListPtr = fieldListPtr->Rest(), ctr++)
{
- GenTreePtr curOp = argListPtr->gtOp.gtOp1;
+ GenTreePtr curOp = fieldListPtr->gtOp.gtOp1;
var_types curTyp = curOp->TypeGet();
// Create a new GT_PUTARG_REG node with op1
GenTreePtr newOper = comp->gtNewOperNode(GT_PUTARG_REG, curTyp, curOp);
- // Splice in the new GT_PUTARG_REG node in the GT_LIST
- ReplaceArgWithPutArgOrCopy(&argListPtr->gtOp.gtOp1, newOper);
+ // Splice in the new GT_PUTARG_REG node in the GT_FIELD_LIST
+ ReplaceArgWithPutArgOrCopy(&fieldListPtr->gtOp.gtOp1, newOper);
}
- // Just return arg. The GT_LIST is not replaced.
+ // Just return arg. The GT_FIELD_LIST is not replaced.
// Nothing more to do.
return arg;
}
// This provides the info to put this argument in in-coming arg area slot
// instead of in out-going arg area slot.
- FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY(assert(info->isStruct == varTypeIsStruct(type))); // Make sure state is
+ PUT_STRUCT_ARG_STK_ONLY(assert(info->isStruct == varTypeIsStruct(type))); // Make sure state is
// correct
#if FEATURE_FASTTAILCALL
putArg = new (comp, GT_PUTARG_STK)
GenTreePutArgStk(GT_PUTARG_STK, type, arg,
- info->slotNum FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(info->numSlots)
- FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(info->isStruct),
+ info->slotNum PUT_STRUCT_ARG_STK_ONLY_ARG(info->numSlots)
+ PUT_STRUCT_ARG_STK_ONLY_ARG(info->isStruct),
call->IsFastTailCall() DEBUGARG(call));
#else
putArg = new (comp, GT_PUTARG_STK)
GenTreePutArgStk(GT_PUTARG_STK, type, arg,
- info->slotNum FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(info->numSlots)
- FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(info->isStruct) DEBUGARG(call));
+ info->slotNum PUT_STRUCT_ARG_STK_ONLY_ARG(info->numSlots)
+ PUT_STRUCT_ARG_STK_ONLY_ARG(info->isStruct) DEBUGARG(call));
#endif
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
// If the ArgTabEntry indicates that this arg is a struct
// get and store the number of slots that are references.
// This is later used in the codegen for PUT_ARG_STK implementation
putArg->AsPutArgStk()->setGcPointers(numRefs, gcLayout);
}
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
}
if (arg->InReg())
putArg = NewPutArg(call, arg, info, type);
// In the case of register passable struct (in one or two registers)
- // the NewPutArg returns a new node (GT_PUTARG_REG or a GT_LIST with two GT_PUTARG_REGs.)
+ // the NewPutArg returns a new node (GT_PUTARG_REG or a GT_FIELD_LIST with two GT_PUTARG_REGs.)
// If an extra node is returned, splice it in the right place in the tree.
if (arg != putArg)
{
break;
#endif
- case GT_LIST:
+ case GT_FIELD_LIST:
{
- GenTreeArgList* list = arg->AsArgList();
- assert(list->IsAggregate());
+ GenTreeFieldList* list = arg->AsFieldList();
+ assert(list->IsFieldListHead());
for (; list != nullptr; list = list->Rest())
{
#ifdef _TARGET_ARM64_
void TreeNodeInfoInitPutArgStk(GenTree* argNode, fgArgTabEntryPtr info);
#endif // _TARGET_ARM64_
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
void TreeNodeInfoInitPutArgStk(GenTree* tree);
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
void TreeNodeInfoInitLclHeap(GenTree* tree);
void DumpNodeInfoMap();
__fallthrough;
case GT_LIST:
+ case GT_FIELD_LIST:
case GT_ARGPLACE:
case GT_NO_OP:
case GT_START_NONGC:
for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext())
{
- assert(list->IsList());
+ assert(list->OperIsList());
GenTreePtr argNode = list->Current();
argNode = argNode->gtEffectiveVal();
- // A GT_LIST has a TYP_VOID, but is used to represent a multireg struct
- if (varTypeIsStruct(argNode) || (argNode->gtOper == GT_LIST))
+ // A GT_FIELD_LIST has a TYP_VOID, but is used to represent a multireg struct
+ if (varTypeIsStruct(argNode) || (argNode->gtOper == GT_FIELD_LIST))
{
GenTreePtr actualArgNode = argNode;
unsigned originalSize = 0;
- if (argNode->gtOper == GT_LIST)
+ if (argNode->gtOper == GT_FIELD_LIST)
{
// There could be up to 2-4 PUTARG_REGs in the list (3 or 4 can only occur for HFAs)
- GenTreeArgList* argListPtr = argNode->AsArgList();
+ GenTreeFieldList* fieldListPtr = argNode->AsFieldList();
// Initailize the first register and the first regmask in our list
regNumber targetReg = argReg;
unsigned iterationNum = 0;
originalSize = 0;
- for (; argListPtr; argListPtr = argListPtr->Rest())
+ for (; fieldListPtr; fieldListPtr = fieldListPtr->Rest())
{
- GenTreePtr putArgRegNode = argListPtr->gtOp.gtOp1;
+ GenTreePtr putArgRegNode = fieldListPtr->Current();
assert(putArgRegNode->gtOper == GT_PUTARG_REG);
GenTreePtr putArgChild = putArgRegNode->gtOp.gtOp1;
argNode->gtLsraInfo.srcCount = 1;
argNode->gtLsraInfo.dstCount = 0;
- // Do we have a TYP_STRUCT argument (or a GT_LIST), if so it must be a multireg pass-by-value struct
- if ((putArgChild->TypeGet() == TYP_STRUCT) || (putArgChild->OperGet() == GT_LIST))
+ // Do we have a TYP_STRUCT argument (or a GT_FIELD_LIST), if so it must be a multireg pass-by-value struct
+ if ((putArgChild->TypeGet() == TYP_STRUCT) || (putArgChild->OperGet() == GT_FIELD_LIST))
{
// We will use store instructions that each write a register sized value
- if (putArgChild->OperGet() == GT_LIST)
+ if (putArgChild->OperGet() == GT_FIELD_LIST)
{
- // We consume all of the items in the GT_LIST
+ // We consume all of the items in the GT_FIELD_LIST
argNode->gtLsraInfo.srcCount = info->numSlots;
}
else
break;
case GT_LIST:
+ case GT_FIELD_LIST:
case GT_ARGPLACE:
case GT_NO_OP:
case GT_START_NONGC:
}
break;
-#ifdef _TARGET_X86_
- case GT_OBJ:
- NYI_X86("GT_OBJ");
-#elif !defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
+#if !defined(FEATURE_PUT_STRUCT_ARG_STK)
case GT_OBJ:
#endif
case GT_BLK:
info->dstCount = 0;
break;
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
case GT_PUTARG_STK:
TreeNodeInfoInitPutArgStk(tree);
break;
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
case GT_STORE_BLK:
case GT_STORE_OBJ:
// First, count reg args
for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext())
{
- assert(list->IsList());
+ assert(list->OperIsList());
GenTreePtr argNode = list->Current();
argNode->gtLsraInfo.srcCount = 1;
argNode->gtLsraInfo.dstCount = 0;
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
// If the node is TYP_STRUCT and it is put on stack with
// putarg_stk operation, we consume and produce no registers.
// In this case the embedded Obj node should not produce
argNode->gtOp.gtOp1->gtLsraInfo.dstCount = 0;
argNode->gtLsraInfo.srcCount = 0;
}
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
continue;
}
// If the struct arg is wrapped in CPYBLK the type of the param will be TYP_VOID.
// Use the curArgTabEntry's isStruct to get whether the param is a struct.
- if (varTypeIsStruct(argNode) FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY(|| curArgTabEntry->isStruct))
+ if (varTypeIsStruct(argNode) PUT_STRUCT_ARG_STK_ONLY(|| curArgTabEntry->isStruct))
{
unsigned originalSize = 0;
LclVarDsc* varDsc = nullptr;
{
originalSize = genTypeSize(argNode->gtType);
}
- else if (argNode->gtOper == GT_LIST)
+ else if (argNode->gtOper == GT_FIELD_LIST)
{
originalSize = 0;
// There could be up to 2 PUTARG_REGs in the list
- GenTreeArgList* argListPtr = argNode->AsArgList();
- unsigned iterationNum = 0;
- for (; argListPtr; argListPtr = argListPtr->Rest())
+ GenTreeFieldList* fieldListPtr = argNode->AsFieldList();
+ unsigned iterationNum = 0;
+ for (; fieldListPtr; fieldListPtr = fieldListPtr->Rest())
{
- GenTreePtr putArgRegNode = argListPtr->gtOp.gtOp1;
+ GenTreePtr putArgRegNode = fieldListPtr->Current();
assert(putArgRegNode->gtOper == GT_PUTARG_REG);
if (iterationNum == 0)
}
}
-#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
+#ifdef FEATURE_PUT_STRUCT_ARG_STK
//------------------------------------------------------------------------
// TreeNodeInfoInitPutArgStk: Set the NodeInfo for a GT_PUTARG_STK.
//
TreeNodeInfo* info = &(tree->gtLsraInfo);
LinearScan* l = m_lsra;
+#ifdef _TARGET_X86_
+ if (tree->gtOp.gtOp1->gtOper == GT_FIELD_LIST)
+ {
+ GenTreeFieldList* fieldListPtr = tree->gtOp.gtOp1->AsFieldList();
+ for (; fieldListPtr; fieldListPtr = fieldListPtr->Rest())
+ {
+ GenTree* fieldNode = fieldListPtr->Current();
+ if (fieldNode->OperGet() == GT_LONG)
+ {
+ NYI_X86("Promoted long field of TYP_STRUCT");
+ }
+ if (varTypeIsByte(fieldNode))
+ {
+ fieldNode->gtLsraInfo.setSrcCandidates(l, l->allRegs(TYP_INT) & ~RBM_NON_BYTE_REGS);
+ }
+ info->srcCount++;
+ }
+ info->dstCount = 0;
+ return;
+ }
+#endif // _TARGET_X86_
+
if (tree->TypeGet() != TYP_STRUCT)
{
TreeNodeInfoInitSimple(tree);
info->setInternalCandidates(l, regMask);
}
+#ifdef _TARGET_X86_
+ if (size >= 8)
+#else // !_TARGET_X86_
if (size >= XMM_REGSIZE_BYTES)
+#endif // !_TARGET_X86_
{
- // If we have a buffer larger than XMM_REGSIZE_BYTES,
- // reserve an XMM register to use it for a
+ // If we have a buffer larger than or equal to XMM_REGSIZE_BYTES on x64/ux,
+ // or larger than or equal to 8 bytes on x86, reserve an XMM register to use it for a
// series of 16-byte loads and stores.
info->internalFloatCount = 1;
info->addInternalCandidates(l, l->internalFloatRegCandidates());
info->srcCount -= 1;
}
}
-#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
+#endif // FEATURE_PUT_STRUCT_ARG_STK
//------------------------------------------------------------------------
// TreeNodeInfoInitLclHeap: Set the NodeInfo for a GT_LCLHEAP.
{
MakeSrcContained(tree, op1);
}
+ else if (op1->IsCnsIntOrI())
+ {
+ // TODO-CQ: We should be able to support swapping op1 and op2 to generate cmp reg, imm,
+ // but there is currently an assert in CodeGen::genCompareInt().
+ // https://github.com/dotnet/coreclr/issues/7270
+ SetRegOptional(op2);
+ }
else
{
// One of op1 or op2 could be marked as reg optional
operand->OperIsCompare());
return 0;
}
- else if (!operand->OperIsAggregate() && (operand->OperIsStore() || operand->TypeGet() == TYP_VOID))
+ else if (!operand->OperIsFieldListHead() && (operand->OperIsStore() || operand->TypeGet() == TYP_VOID))
{
// Stores and void-typed operands may be encountered when processing call nodes, which contain
// pointers to argument setup stores.
}
else
{
- // If an aggregate or non-void-typed operand is not an unsued value and does not have source registers,
+ // If a field list or non-void-typed operand is not an unused value and does not have source registers,
// that argument is contained within its parent and produces `sum(operand_dst_count)` registers.
int dstCount = 0;
for (GenTree* op : operand->Operands())
assert(!isRegPairType(tree->TypeGet()));
#endif // _TARGET_ARM_
- // The LIR traversal doesn't visit non-aggregate GT_LIST or GT_ARGPLACE nodes
+ // The LIR traversal doesn't visit GT_LIST or GT_ARGPLACE nodes
assert(tree->OperGet() != GT_ARGPLACE);
- assert((tree->OperGet() != GT_LIST) || tree->AsArgList()->IsAggregate());
+ assert(tree->OperGet() != GT_LIST);
+ // The LIR traversal visits only the first node in a GT_FIELD_LIST.
+ assert((tree->OperGet() != GT_FIELD_LIST) || tree->AsFieldList()->IsFieldListHead());
// These nodes are eliminated by the Rationalizer.
if (tree->OperGet() == GT_CLS_VAR)
#endif // FEATURE_PARTIAL_SIMD_CALLEE_SAVE
bool isContainedNode =
- !noAdd && consume == 0 && produce == 0 && (tree->OperIsAggregate() || (tree->TypeGet() != TYP_VOID && !tree->OperIsStore()));
+ !noAdd && consume == 0 && produce == 0 && (tree->OperIsFieldListHead() || ((tree->TypeGet() != TYP_VOID) && !tree->OperIsStore()));
if (isContainedNode)
{
// Contained nodes map to the concatenated lists of their operands.
{
/* Get hold of the next argument values for the oldCall and newCall */
- assert(newArgs->IsList());
+ assert(newArgs->OperIsList());
newCurr = newArgs->Current();
newArgs = newArgs->Rest();
- assert(oldArgs->IsList());
+ assert(oldArgs->OperIsList());
oldCurr = oldArgs->Current();
oldArgs = oldArgs->Rest();
argCount = oldArgInfo->argCount;
nextSlotNum = oldArgInfo->nextSlotNum;
+ hasRegArgs = oldArgInfo->hasRegArgs;
+ hasStackArgs = oldArgInfo->hasStackArgs;
argsComplete = true;
argsSorted = true;
}
GenTreePtr argx;
if (curArgTabEntry->parent != nullptr)
{
- assert(curArgTabEntry->parent->IsList());
+ assert(curArgTabEntry->parent->OperIsList());
argx = curArgTabEntry->parent->Current();
isRegArg = (argx->gtFlags & GTF_LATE_ARG) != 0;
}
if (curArgTabEntry->parent != nullptr)
{
- assert(curArgTabEntry->parent->IsList());
+ assert(curArgTabEntry->parent->OperIsList());
argx = curArgTabEntry->parent->Current();
isRegArg = (argx->gtFlags & GTF_LATE_ARG) != 0;
}
assert(curArgTabEntry->numSlots == numSlots);
assert(curArgTabEntry->alignment == alignment);
assert(curArgTabEntry->parent == parent);
- assert(parent->IsList());
+ assert(parent->OperIsList());
#if FEATURE_FIXED_OUT_ARGS
if (curArgTabEntry->node != node)
#ifndef LEGACY_BACKEND
#if FEATURE_MULTIREG_ARGS
- // For RyuJIT backend we will expand a Multireg arg into a GT_LIST
+ // For RyuJIT backend we will expand a Multireg arg into a GT_FIELD_LIST
// with multiple indirections, so here we consider spilling it into a tmp LclVar.
//
// Note that Arm32 is a LEGACY_BACKEND and it defines FEATURE_MULTIREG_ARGS
{
GenTreePtr parent = curArgTabEntry->parent;
/* a normal argument from the list */
- noway_assert(parent->IsList());
+ noway_assert(parent->OperIsList());
noway_assert(parent->gtOp.gtOp1 == argx);
parent->gtOp.gtOp1 = setupArg;
}
else
{
- noway_assert(tmpRegArgNext->IsList());
+ noway_assert(tmpRegArgNext->OperIsList());
noway_assert(tmpRegArgNext->Current());
tmpRegArgNext->gtOp.gtOp2 = compiler->gtNewArgList(defArg);
tmpRegArgNext = tmpRegArgNext->Rest();
unsigned argSlots = 0;
unsigned nonRegPassedStructSlots = 0;
- bool lateArgsComputed = (call->gtCallLateArgs != nullptr);
+ bool reMorphing = call->AreArgsComplete();
bool callHasRetBuffArg = call->HasRetBufArg();
#ifndef _TARGET_X86_ // i.e. _TARGET_AMD64_ or _TARGET_ARM_
// Process the late arguments (which were determined by a previous caller).
// Do this before resetting fgPtrArgCntCur as fgMorphTree(call->gtCallLateArgs)
// may need to refer to it.
- if (lateArgsComputed)
+ if (reMorphing)
{
// We need to reMorph the gtCallLateArgs early since that is what triggers
// the expression folding and we need to have the final folded gtCallLateArgs
//
// Since the late arguments are evaluated last we have pushed all of the
// other arguments on the stack before we evaluate these late arguments,
- // so we record the stack depth on the first morph call when lateArgsComputed
+ // so we record the stack depth on the first morph call when reMorphing
// was false (via RecordStkLevel) and then retrieve that value here (via RetrieveStkLevel)
//
- unsigned callStkLevel = call->fgArgInfo->RetrieveStkLevel();
- fgPtrArgCntCur += callStkLevel;
- call->gtCallLateArgs = fgMorphTree(call->gtCallLateArgs)->AsArgList();
- flagsSummary |= call->gtCallLateArgs->gtFlags;
- fgPtrArgCntCur -= callStkLevel;
- assert(call->fgArgInfo != nullptr);
- call->fgArgInfo->RemorphReset();
-
- numArgs = call->fgArgInfo->ArgCount();
+ unsigned callStkLevel = call->fgArgInfo->RetrieveStkLevel();
+ if (call->gtCallLateArgs != nullptr)
+ {
+ fgPtrArgCntCur += callStkLevel;
+ call->gtCallLateArgs = fgMorphTree(call->gtCallLateArgs)->AsArgList();
+ flagsSummary |= call->gtCallLateArgs->gtFlags;
+ fgPtrArgCntCur -= callStkLevel;
+ }
+ assert(call->fgArgInfo != nullptr);
+ call->fgArgInfo->RemorphReset();
+
+ numArgs = call->fgArgInfo->ArgCount();
}
else
{
{
args = call->gtCallArgs;
assert(args != nullptr);
- assert(args->IsList());
+ assert(args->OperIsList());
argx = call->gtCallArgs->Current();
/* We must fill in or update the argInfo table */
- if (lateArgsComputed)
+ if (reMorphing)
{
/* this is a register argument - possibly update it in the table */
call->fgArgInfo->RemorphRegArg(argIndex, argx, nullptr, genMapIntRegArgNumToRegNum(intArgRegNum), 1, 1);
*parentArgx = argx;
flagsSummary |= argx->gtFlags;
- assert(args->IsList());
+ assert(args->OperIsList());
assert(argx == args->Current());
#ifndef LEGACY_BACKEND
fgArgTabEntryPtr argEntry = nullptr;
- if (lateArgsComputed)
+ if (reMorphing)
{
argEntry = gtArgEntryByArgNum(call, argIndex);
}
#ifdef _TARGET_ARM_
bool passUsingIntRegs;
- if (lateArgsComputed)
+ if (reMorphing)
{
passUsingFloatRegs = isValidFloatArgReg(argEntry->regNum);
passUsingIntRegs = isValidIntArgReg(argEntry->regNum);
#elif defined(_TARGET_ARM64_)
- if (lateArgsComputed)
+ if (reMorphing)
{
passUsingFloatRegs = isValidFloatArgReg(argEntry->regNum);
}
#elif defined(_TARGET_AMD64_)
#if defined(UNIX_AMD64_ABI)
- if (lateArgsComputed)
+ if (reMorphing)
{
passUsingFloatRegs = isValidFloatArgReg(argEntry->regNum);
}
bool isStructArg = varTypeIsStruct(argx);
- if (lateArgsComputed)
+ if (reMorphing)
{
#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
// Get the struct description for the already completed struct argument.
// This size has now been computed
assert(size != 0);
}
- else // !lateArgsComputed
+ else // !reMorphing
{
//
// Figure out the size of the argument. This is either in number of registers, or number of
GenTreePtr argObj = argx;
GenTreePtr* parentOfArgObj = parentArgx;
- assert(args->IsList());
+ assert(args->OperIsList());
assert(argx == args->Current());
/* The GT_OBJ may be be a child of a GT_COMMA */
// the obj reading memory past the end of the valuetype
CLANG_FORMAT_COMMENT_ANCHOR;
-#if defined(_TARGET_X86_) && !defined(LEGACY_BACKEND)
- // TODO-X86-CQ: [1091733] Revisit for small structs, we should use push instruction
- copyBlkClass = objClass;
- size = roundupSize / TARGET_POINTER_SIZE; // Normalize size to number of pointer sized items
-#else // !defined(_TARGET_X86_) || defined(LEGACY_BACKEND)
if (roundupSize > originalSize)
{
copyBlkClass = objClass;
}
size = roundupSize / TARGET_POINTER_SIZE; // Normalize size to number of pointer sized items
-#endif // !defined(_TARGET_X86_) || defined(LEGACY_BACKEND)
}
}
}
}
#endif // defined(_TARGET_X86_) && !defined(LEGACY_BACKEND)
- } // end !lateArgsComputed
+ } // end !reMorphing
//
// Now we know if the argument goes in registers or not and how big it is,
#endif
fgArgTabEntryPtr newArgEntry;
- if (lateArgsComputed)
+ if (reMorphing)
{
// This is a register argument - possibly update it in the table
newArgEntry = call->fgArgInfo->RemorphRegArg(argIndex, argx, args, nextRegNum, size, argAlign);
// If the register arguments have not been determined then we must fill in the argInfo
- if (lateArgsComputed)
+ if (reMorphing)
{
// This is a stack argument - possibly update it in the table
call->fgArgInfo->RemorphStkArg(argIndex, argx, args, size, argAlign);
if (copyBlkClass != NO_CLASS_HANDLE)
{
- noway_assert(!lateArgsComputed);
+ noway_assert(!reMorphing);
fgMakeOutgoingStructArgCopy(call, args, argIndex,
copyBlkClass FEATURE_UNIX_AMD64_STRUCT_PASSING_ONLY_ARG(&structDesc));
// This can cause a GTF_EXCEPT flag to be set.
// TODO-CQ: Fix the cases where this happens. We shouldn't be adding any new flags.
// This currently occurs in the case where we are re-morphing the args on x86/RyuJIT, and
- // there are no register arguments. Then lateArgsComputed is never true, so we keep re-copying
+ // there are no register arguments. Then reMorphing is never true, so we keep re-copying
// any struct arguments.
// i.e. assert(((call->gtFlags & GTF_EXCEPT) != 0) || ((args->Current()->gtFlags & GTF_EXCEPT) == 0)
flagsSummary |= (args->Current()->gtFlags & GTF_EXCEPT);
NYI_X86("MKREFANY");
// 'Lower' the MKREFANY tree and insert it.
- noway_assert(!lateArgsComputed);
+ noway_assert(!reMorphing);
// Get a new temp
// Here we don't need unsafe value cls check since the addr of temp is used only in mkrefany
}
#endif // !LEGACY_BACKEND
+#if defined (_TARGET_X86_) && !defined(LEGACY_BACKEND)
+ if (isStructArg)
+ {
+ GenTree* lclNode = fgIsIndirOfAddrOfLocal(argx);
+ if ((lclNode != nullptr) &&
+ (lvaGetPromotionType(lclNode->AsLclVarCommon()->gtLclNum) == Compiler::PROMOTION_TYPE_INDEPENDENT))
+ {
+ // Make a GT_FIELD_LIST of the field lclVars.
+ GenTreeLclVarCommon* lcl = lclNode->AsLclVarCommon();
+ LclVarDsc* varDsc = &(lvaTable[lcl->gtLclNum]);
+ GenTreeFieldList* fieldList = nullptr;
+ for (unsigned fieldLclNum = varDsc->lvFieldLclStart;
+ fieldLclNum < varDsc->lvFieldLclStart + varDsc->lvFieldCnt; ++fieldLclNum)
+ {
+ LclVarDsc* fieldVarDsc = &lvaTable[fieldLclNum];
+ if (fieldList == nullptr)
+ {
+ lcl->SetLclNum(fieldLclNum);
+ lcl->ChangeOper(GT_LCL_VAR);
+ lcl->gtType = fieldVarDsc->lvType;
+ fieldList = new (this, GT_FIELD_LIST) GenTreeFieldList(lcl, fieldVarDsc->lvFldOffset, fieldVarDsc->lvType, nullptr);
+ fgArgTabEntryPtr fp = Compiler::gtArgEntryByNode(call, argx);
+ fp->node = fieldList;
+ args->gtOp.gtOp1 = fieldList;
+ }
+ else
+ {
+ GenTree* fieldLcl = gtNewLclvNode(fieldLclNum, fieldVarDsc->lvType);
+ fieldList = new (this, GT_FIELD_LIST) GenTreeFieldList(fieldLcl, fieldVarDsc->lvFldOffset, fieldVarDsc->lvType, fieldList);
+ }
+ }
+ }
+ }
+#endif // defined (_TARGET_X86_) && !defined(LEGACY_BACKEND)
+
#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
if (isStructArg && !isRegArg)
{
}
} // end foreach argument loop
- if (!lateArgsComputed)
+ if (!reMorphing)
{
call->fgArgInfo->ArgsComplete();
#ifdef LEGACY_BACKEND
// For UNIX_AMD64, the condition without hasStackArgCopy cannot catch
// all cases of fgMakeOutgoingStructArgCopy() being called. hasStackArgCopy
// is added to make sure to call EvalArgsToTemp.
- if (!lateArgsComputed && (call->fgArgInfo->HasRegArgs()
+ if (!reMorphing && (call->fgArgInfo->HasRegArgs()
#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING
|| hasStackArgCopy
#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING
// In the future we can migrate UNIX_AMD64 to use this
// method instead of fgMorphSystemVStructArgs
- // We only build GT_LISTs for MultiReg structs for the RyuJIT backend
+ // We only build GT_FIELD_LISTs for MultiReg structs for the RyuJIT backend
if (hasMultiregStructArgs)
{
fgMorphMultiregStructArgs(call);
{
for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext())
{
- assert(list->IsList());
+ assert(list->OperIsList());
GenTreePtr argNode = list->Current();
if (argx == argNode)
{
var_types originalType = type;
// If we have already processed the arg...
- if (arg->OperGet() == GT_LIST && varTypeIsStruct(arg))
+ if (arg->OperGet() == GT_FIELD_LIST && varTypeIsStruct(arg))
{
continue;
}
// Create LCL_FLD for each eightbyte.
argListCreated = true;
+ // First eightbyte.
+ arg->AsLclFld()->gtFieldSeq = FieldSeqStore::NotAField();
+ arg->gtType =
+ GetTypeFromClassificationAndSizes(fgEntryPtr->structDesc.eightByteClassifications[0],
+ fgEntryPtr->structDesc.eightByteSizes[0]);
+ GenTreeFieldList* fieldList =
+ new (this, GT_FIELD_LIST) GenTreeFieldList(arg, 0, originalType, nullptr);
+ fieldList->gtType = originalType; // Preserve the type. It is a special case.
+ arg = fieldList;
+
// Second eightbyte.
GenTreeLclFld* newLclField = new (this, GT_LCL_FLD)
GenTreeLclFld(GetTypeFromClassificationAndSizes(fgEntryPtr->structDesc
fgEntryPtr->structDesc.eightByteSizes[1]),
lclCommon->gtLclNum, fgEntryPtr->structDesc.eightByteOffsets[1]);
- GenTreeArgList* aggregate = gtNewAggregate(newLclField);
- aggregate->gtType = originalType; // Preserve the type. It is a special case.
+ fieldList = new (this, GT_FIELD_LIST) GenTreeFieldList(newLclField, 0, originalType, fieldList);
+ fieldList->gtType = originalType; // Preserve the type. It is a special case.
newLclField->gtFieldSeq = FieldSeqStore::NotAField();
-
- // First field
- arg->AsLclFld()->gtFieldSeq = FieldSeqStore::NotAField();
- arg->gtType =
- GetTypeFromClassificationAndSizes(fgEntryPtr->structDesc.eightByteClassifications[0],
- fgEntryPtr->structDesc.eightByteSizes[0]);
- arg = aggregate->Prepend(this, arg);
- arg->gtType = type; // Preserve the type. It is a special case.
}
else
{
{
for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext())
{
- assert(list->IsList());
+ assert(list->OperIsList());
GenTreePtr argNode = list->Current();
if (argx == argNode)
//
// Notes:
// We only call fgMorphMultiregStructArg for the register passed TYP_STRUCT arguments.
-// The call to fgMorphMultiregStructArg will mutate the argument into the GT_LIST form
-// whicj is only used for register arguments.
+// The call to fgMorphMultiregStructArg will mutate the argument into the GT_FIELD_LIST form
+// which is only used for struct arguments.
// If this method fails to find any TYP_STRUCT arguments it will assert.
//
void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call)
{
for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext())
{
- assert(list->IsList());
+ assert(list->OperIsList());
GenTreePtr argNode = list->Current();
if (argx == argNode)
//-----------------------------------------------------------------------------
// fgMorphMultiregStructArg: Given a multireg TYP_STRUCT arg from a call argument list
-// Morph the argument into a set of GT_LIST nodes.
+// Morph the argument into a set of GT_FIELD_LIST nodes.
//
// Arguments:
// arg - A GenTree node containing a TYP_STRUCT arg that
// for passing in multiple registers.
// If arg is a LclVar we check if it is struct promoted and has the right number of fields
// and if they are at the appropriate offsets we will use the struct promted fields
-// in the GT_LIST nodes that we create.
+// in the GT_FIELD_LIST nodes that we create.
// If we have a GT_LCL_VAR that isn't struct promoted or doesn't meet the requirements
// we will use a set of GT_LCL_FLDs nodes to access the various portions of the struct
// this also forces the struct to be stack allocated into the local frame.
// We should still have a TYP_STRUCT
assert(argValue->TypeGet() == TYP_STRUCT);
- GenTreeArgList* newArg = nullptr;
+ GenTreeFieldList* newArg = nullptr;
// Are we passing a struct LclVar?
//
// Create a new tree for 'arg'
// replace the existing LDOBJ(ADDR(LCLVAR))
- // with a LIST(LCLVAR-LO, LIST(LCLVAR-HI, nullptr))
+ // with a FIELD_LIST(LCLVAR-LO, FIELD_LIST(LCLVAR-HI, nullptr))
//
- newArg = gtNewAggregate(hiLclVar)->Prepend(this, loLclVar);
+ newArg = new (this, GT_FIELD_LIST) GenTreeFieldList(loLclVar, 0, loType, nullptr);
+ (void) new (this, GT_FIELD_LIST) GenTreeFieldList(hiLclVar, TARGET_POINTER_SIZE, hiType, newArg);
}
}
}
//
lvaSetVarDoNotEnregister(varNum DEBUG_ARG(DNER_LocalField));
- // Start building our list from the last element
- unsigned offset = lastOffset;
- unsigned inx = elemCount;
-
// Create a new tree for 'arg'
// replace the existing LDOBJ(ADDR(LCLVAR))
- // with a LIST(LCLFLD-LO, LIST(LCLFLD-HI, nullptr) ...)
+ // with a FIELD_LIST(LCLFLD-LO, FIELD_LIST(LCLFLD-HI, nullptr) ...)
//
- while (inx > 0)
+ unsigned offset = 0;
+ GenTreeFieldList* listEntry = nullptr;
+ for (unsigned inx = 0; inx < elemCount; inx++)
{
- inx--;
- offset -= elemSize;
+ elemSize = genTypeSize(type[inx]);
GenTreePtr nextLclFld = gtNewLclFldNode(varNum, type[inx], offset);
+ listEntry = new (this, GT_FIELD_LIST) GenTreeFieldList(nextLclFld, offset, type[inx], listEntry);
if (newArg == nullptr)
{
- newArg = gtNewAggregate(nextLclFld);
- }
- else
- {
- newArg = newArg->Prepend(this, nextLclFld);
+ newArg = listEntry;
}
+ offset += elemSize;
}
}
// Are we passing a GT_OBJ struct?
// Create a new tree for 'arg'
// replace the existing LDOBJ(EXPR)
- // with a LIST(IND(EXPR), LIST(IND(EXPR+8), nullptr) ...)
+ // with a FIELD_LIST(IND(EXPR), FIELD_LIST(IND(EXPR+8), nullptr) ...)
//
- // Start building our list from the last element
- unsigned offset = structSize;
- unsigned inx = elemCount;
- while (inx > 0)
+ unsigned offset = 0;
+ GenTreeFieldList* listEntry = nullptr;
+ for (unsigned inx = 0; inx < elemCount; inx++)
{
- inx--;
elemSize = genTypeSize(type[inx]);
- offset -= elemSize;
GenTreePtr curAddr = baseAddr;
if (offset != 0)
{
curAddr = baseAddr;
}
GenTreePtr curItem = gtNewOperNode(GT_IND, type[inx], curAddr);
+ listEntry = new (this, GT_FIELD_LIST) GenTreeFieldList(curItem, offset, type[inx], listEntry);
if (newArg == nullptr)
{
- newArg = gtNewAggregate(curItem);
- }
- else
- {
- newArg = newArg->Prepend(this, curItem);
+ newArg = listEntry;
}
+ offset += elemSize;
}
}
}
{
nCalleeArgs++;
- assert(args->IsList());
+ assert(args->OperIsList());
GenTreePtr argx = args->gtOp.gtOp1;
if (varTypeIsStruct(argx))
// Assume it's an Ind context to start.
MorphAddrContext subIndMac1(MACK_Ind);
MorphAddrContext* subMac1 = mac;
- if (subMac1 == nullptr || subMac1->m_kind == MACK_Ind || subMac1->m_kind == MACK_CopyBlock)
+ if (subMac1 == nullptr || subMac1->m_kind == MACK_Ind)
{
switch (tree->gtOper)
{
// If we are in the Valuenum CSE phase then don't morph away anything as these
// nodes may have CSE defs/uses in them.
//
- if (!optValnumCSE_phase && (oper != GT_ASG) && (oper != GT_COLON) && !tree->IsList())
+ if (!optValnumCSE_phase && (oper != GT_ASG) && (oper != GT_COLON) && !tree->OperIsAnyList())
{
/* Check for op1 as a GT_COMMA with a unconditional throw node */
if (op1 && fgIsCommaThrow(op1, true))
AXC_AddrWide, // The address being computed will be dereferenced by a block operation that operates
// on more bytes than the width of the storage location addressed. If this is a
// field of a promoted struct local, declare the entire struct local address-taken.
- AXC_InitBlk, // An GT_INITBLK is the immediate parent. The first argument is in an IND context.
- AXC_CopyBlk, // An GT_COPYBLK is the immediate parent. The first argument is in a GT_LIST, whose
- // args should be evaluated in an IND context.
AXC_IndAdd, // A GT_ADD is the immediate parent, and it was evaluated in an IND contxt.
// If one arg is a constant int, evaluate the other in an IND context. Otherwise, none.
};
return WALK_CONTINUE;
case GT_LIST:
- if (axc == AXC_InitBlk || axc == AXC_CopyBlk)
- {
- axcStack->Push(axc);
- }
- else
- {
- axcStack->Push(AXC_None);
- }
+ case GT_FIELD_LIST:
+ axcStack->Push(AXC_None);
return WALK_CONTINUE;
case GT_INDEX:
#endif // defined(LEGACY_BACKEND)
}
-// Static variables.
-Compiler::MorphAddrContext Compiler::s_CopyBlockMAC(Compiler::MACK_CopyBlock);
-
#ifdef FEATURE_SIMD
//-----------------------------------------------------------------------------------
const bool isLateArg = (node->gtFlags & GTF_LATE_ARG) != 0;
#endif
- // First, remove any preceeding GT_LIST nodes, which are not otherwise visited by the tree walk.
+ // First, remove any preceeding list nodes, which are not otherwise visited by the tree walk.
//
- // NOTE: GT_LIST nodes that are used as aggregates, by block ops, and by phi nodes will in fact be visited.
+ // NOTE: GT_FIELD_LIST head nodes, and GT_LIST nodes used by phi nodes will in fact be visited.
for (GenTree* prev = node->gtPrev;
- prev != nullptr && prev->OperGet() == GT_LIST && !(prev->AsArgList()->IsAggregate());
+ prev != nullptr && prev->OperIsAnyList() && !(prev->OperIsFieldListHead());
prev = node->gtPrev)
{
BlockRange().Remove(prev);
}
// In addition, remove the current node if it is a GT_LIST node that is not an aggregate.
- if (node->OperGet() == GT_LIST)
+ if (node->OperIsAnyList())
{
GenTreeArgList* list = node->AsArgList();
- if (!list->IsAggregate())
+ if (!list->OperIsFieldListHead())
{
BlockRange().Remove(list);
}
curArgMask = RBM_NONE; // Set of argument registers that are going to be setup by this arg
tmpMask = RBM_NONE; // Set of additional temp registers that are need only to setup the current arg
- assert(list->IsList());
+ assert(list->OperIsList());
args = list->Current();
list = list->Rest();
case GT_MKREFANY: // We can't evaluate these.
case GT_RETFILT:
case GT_LIST:
+ case GT_FIELD_LIST:
case GT_ARR_LENGTH:
return false;
case GT_MULHI:
case GT_JTRUE:
case GT_LIST:
+#ifndef LEGACY_BACKEND
+ case GT_FIELD_LIST:
+#endif // !LEGACY_BACKEND
// These nodes never need to have a ValueNumber
tree->gtVNPair.SetBoth(ValueNumStore::NoVN);
break;