From: Carol Eidt Date: Sat, 10 Sep 2016 15:24:51 +0000 (-0700) Subject: Support GT_OBJ for x86 X-Git-Tag: accepted/tizen/base/20180629.140029~3539^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e550cf35bd5ff8e7eefcb1a605219f71a3672d83;p=platform%2Fupstream%2Fcoreclr.git Support GT_OBJ for x86 Add support for GT_OBJ for x86, and allow them to be transformed into a list of fields (in morph) if it is a promoted struct. Add a new list type for this (GT_FIELD_LIST) with the type and offset, and use it for the multireg arg passing as well for consistency. Also refactor fgMorphArgs so that there is a positive check for reMorphing, rather than relying on gtCallLateArgs, which can be null if there are no register args. In codegenxarch, modify the struct passing (genPutStructArgStk) to work for both the x64/ux and x86 case, including the option of pushing fields onto the stack. Eliminate the redundant INS_movs_ptr, and replace with the pre-existing INS_movsp. --- diff --git a/src/jit/codegenarm.cpp b/src/jit/codegenarm.cpp index 4ce8230..4e7a149 100644 --- a/src/jit/codegenarm.cpp +++ b/src/jit/codegenarm.cpp @@ -1423,6 +1423,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) break; case GT_LIST: + case GT_FIELD_LIST: case GT_ARGPLACE: // Nothing to do break; diff --git a/src/jit/codegenarm64.cpp b/src/jit/codegenarm64.cpp index 14d2052..8865799 100644 --- a/src/jit/codegenarm64.cpp +++ b/src/jit/codegenarm64.cpp @@ -3397,6 +3397,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) break; case GT_LIST: + case GT_FIELD_LIST: case GT_ARGPLACE: // Nothing to do break; @@ -5321,7 +5322,7 @@ void CodeGen::genCallInstruction(GenTreePtr node) // Consume all the arg regs for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) { - assert(list->IsList()); + assert(list->OperIsList()); GenTreePtr argNode = list->Current(); @@ -6785,23 +6786,23 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode) { 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); diff --git a/src/jit/codegencommon.cpp b/src/jit/codegencommon.cpp index 64fb841..799498a 100755 --- a/src/jit/codegencommon.cpp +++ b/src/jit/codegencommon.cpp @@ -103,6 +103,10 @@ CodeGen::CodeGen(Compiler* theCompiler) : CodeGenInterface(theCompiler) 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(); diff --git a/src/jit/codegenlinear.h b/src/jit/codegenlinear.h index 793d6d2..1c14f66 100644 --- a/src/jit/codegenlinear.h +++ b/src/jit/codegenlinear.h @@ -130,10 +130,9 @@ void genConsumeBlockDst(GenTreeBlk* blkNode); 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); @@ -161,12 +160,17 @@ void genCodeForCpBlkRepMovs(GenTreeBlk* cpBlkNode); 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); @@ -219,6 +223,15 @@ bool genIsRegCandidateLocal(GenTreePtr tree) 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); diff --git a/src/jit/codegenxarch.cpp b/src/jit/codegenxarch.cpp index 03af2cd..44bf2cb 100644 --- a/src/jit/codegenxarch.cpp +++ b/src/jit/codegenxarch.cpp @@ -292,9 +292,9 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg) #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) { @@ -1315,9 +1315,9 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode) 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) { @@ -1386,17 +1386,17 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode) } 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); @@ -2567,8 +2567,8 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) 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); } @@ -2660,6 +2660,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) break; case GT_LIST: + case GT_FIELD_LIST: case GT_ARGPLACE: // Nothing to do break; @@ -3848,21 +3849,145 @@ void CodeGen::genCodeForCpBlkRepMovs(GenTreeBlk* cpBlkNode) 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(). @@ -3889,14 +4014,40 @@ void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode, unsigned baseV 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()); @@ -3908,11 +4059,10 @@ void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode, unsigned baseV 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; } @@ -3921,41 +4071,29 @@ void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode, unsigned baseV // 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); } } } @@ -3965,17 +4103,16 @@ void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode, unsigned baseV // // 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; @@ -3985,7 +4122,7 @@ void CodeGen::genStructPutArgRepMovs(GenTreePutArgStk* putArgNode, unsigned base 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); } @@ -4037,11 +4174,12 @@ void CodeGen::genClearStackVec3ArgUpperBits() } } #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) { @@ -4078,7 +4216,7 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode) bool dstOnStack = dstAddr->OperIsLocalAddr(); #ifdef DEBUG - bool isRepMovsPtrUsed = false; + bool isRepMovspUsed = false; assert(!dstAddr->isContained()); @@ -4086,8 +4224,8 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode) // 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(); @@ -4134,23 +4272,23 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode) 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--; } } @@ -4166,7 +4304,7 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode) 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; @@ -4178,12 +4316,12 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode) } 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--; } } @@ -4191,13 +4329,13 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode) { #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; @@ -4748,7 +4886,7 @@ void CodeGen::genCodeForShift(GenTreePtr tree) regNumber operandReg = operand->gtRegNum; GenTreePtr shiftBy = tree->gtGetOp2(); - + if (shiftBy->isContainedIntOrIImmed()) { // First, move the operand to the destination register and @@ -5390,7 +5528,7 @@ void CodeGen::genConsumeOperands(GenTreeOp* tree) } } -#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 @@ -5401,22 +5539,24 @@ void CodeGen::genConsumeOperands(GenTreeOp* tree) // 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()); @@ -5441,13 +5581,21 @@ void CodeGen::genConsumePutStructArgStk( // 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) { @@ -5476,10 +5624,10 @@ void CodeGen::genConsumePutStructArgStk( 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 @@ -6102,7 +6250,7 @@ void CodeGen::genCallInstruction(GenTreePtr node) // Consume all the arg regs for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) { - assert(list->IsList()); + assert(list->OperIsList()); GenTreePtr argNode = list->Current(); @@ -6116,13 +6264,13 @@ void CodeGen::genCallInstruction(GenTreePtr node) #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; @@ -6192,18 +6340,30 @@ void CodeGen::genCallInstruction(GenTreePtr node) { 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())); } @@ -6358,14 +6518,14 @@ void CodeGen::genCallInstruction(GenTreePtr node) 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()) { @@ -7407,7 +7567,9 @@ void CodeGen::genCompareInt(GenTreePtr treeNode) 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_ @@ -7657,7 +7819,7 @@ void CodeGen::genLongToIntCast(GenTree* cast) // 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 @@ -7666,7 +7828,7 @@ void CodeGen::genLongToIntCast(GenTree* cast) 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); @@ -7694,7 +7856,7 @@ void CodeGen::genLongToIntCast(GenTree* cast) genJumpToThrowHlpBlk(EJ_jne, SCK_OVERFLOW); } } - + if (dstReg != loSrcReg) { inst_RV_RV(INS_mov, dstReg, loSrcReg, TYP_INT, EA_4BYTE); @@ -7738,11 +7900,11 @@ void CodeGen::genIntToIntCast(GenTreePtr treeNode) } #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) @@ -8766,7 +8928,7 @@ unsigned CodeGen::getBaseVarForPutArgStk(GenTreePtr treeNode) #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 } @@ -8775,7 +8937,7 @@ unsigned CodeGen::getBaseVarForPutArgStk(GenTreePtr treeNode) } //--------------------------------------------------------------------- // -// 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 @@ -8788,7 +8950,24 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode) { 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())); @@ -8814,6 +8993,34 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode) 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"); @@ -8840,7 +9047,10 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode) 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 @@ -8876,7 +9086,90 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode) #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. @@ -8885,41 +9178,37 @@ void CodeGen::genPutArgStk(GenTreePtr treeNode) // // 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(); @@ -8931,7 +9220,7 @@ void CodeGen::genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum) // 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); @@ -8950,8 +9239,8 @@ void CodeGen::genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum) 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; @@ -8962,13 +9251,13 @@ void CodeGen::genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum) } 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--; } } @@ -8976,7 +9265,7 @@ void CodeGen::genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum) { getEmitter()->emitIns_R_I(INS_mov, EA_4BYTE, REG_RCX, nonGcSlotCount); copiedSlots += nonGcSlotCount; - instGen(INS_r_movs_ptr); + instGen(INS_r_movsp); } } break; @@ -8985,10 +9274,10 @@ void CodeGen::genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum) 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) @@ -9002,8 +9291,7 @@ void CodeGen::genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum) } 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. @@ -9029,7 +9317,7 @@ void CodeGen::genPutStructArgStk(GenTreePtr treeNode, unsigned baseVarNum) assert(gcPtrCount == 0); } } -#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#endif // defined(FEATURE_PUT_STRUCT_ARG_STK) /***************************************************************************** * @@ -9367,7 +9655,8 @@ void CodeGen::genStoreLongLclVar(GenTree* treeNode) 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)); } @@ -9377,7 +9666,8 @@ void CodeGen::genStoreLongLclVar(GenTree* treeNode) // 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_) diff --git a/src/jit/compiler.h b/src/jit/compiler.h index a3a20a2..2f062af 100644 --- a/src/jit/compiler.h +++ b/src/jit/compiler.h @@ -1164,7 +1164,13 @@ struct fgArgTabEntry 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) @@ -1293,6 +1299,10 @@ public: { return hasStackArgs; } + bool AreArgsComplete() const + { + return argsComplete; + } }; #ifdef DEBUG @@ -1939,8 +1949,6 @@ public: 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); @@ -4434,16 +4442,11 @@ private: // 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 { diff --git a/src/jit/flowgraph.cpp b/src/jit/flowgraph.cpp index 97eb5cd..bf51bc1 100644 --- a/src/jit/flowgraph.cpp +++ b/src/jit/flowgraph.cpp @@ -18028,9 +18028,13 @@ void Compiler::fgSetTreeSeqFinish(GenTreePtr tree, bool isLIR) { // 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 */ @@ -20328,10 +20332,11 @@ void Compiler::fgDebugCheckFlags(GenTreePtr tree) break; case GT_LIST: - if ((op2 != nullptr) && op2->IsList()) + case GT_FIELD_LIST: + if ((op2 != nullptr) && op2->OperIsAnyList()) { ArrayStack stack(this); - while ((tree->gtGetOp2() != nullptr) && tree->gtGetOp2()->IsList()) + while ((tree->gtGetOp2() != nullptr) && tree->gtGetOp2()->OperIsAnyList()) { stack.Push(tree); tree = tree->gtGetOp2(); diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp index 58b4616..89fee00 100644 --- a/src/jit/gentree.cpp +++ b/src/jit/gentree.cpp @@ -321,9 +321,11 @@ void GenTree::InitNodeSize() 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]); @@ -354,6 +356,7 @@ void GenTree::InitNodeSize() 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 @@ -378,11 +381,13 @@ void GenTree::InitNodeSize() 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); @@ -1067,11 +1072,12 @@ Compiler::fgWalkResult Compiler::fgWalkTreePostRec(GenTreePtr* pTree, fgWalkData } 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(&list->gtOp1, fgWalkData); @@ -1080,12 +1086,8 @@ Compiler::fgWalkResult Compiler::fgWalkTreePostRec(GenTreePtr* pTree, 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: @@ -1881,6 +1883,27 @@ void GenTreeCall::ReplaceCallOperand(GenTree** useEdge, GenTree* replacement) } } +//------------------------------------------------------------------------- +// 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. @@ -3392,7 +3415,7 @@ bool GenTree::gtIsValid64RsltMul() unsigned Compiler::gtSetListOrder(GenTree *list, bool isListCallArgs, bool callArgsInRegs) { - assert((list != nullptr) && list->IsList()); + assert((list != nullptr) && list->OperIsAnyList()); assert(!callArgsInRegs || isListCallArgs); ArrayStack listNodes(this); @@ -3401,7 +3424,7 @@ unsigned Compiler::gtSetListOrder(GenTree *list, bool isListCallArgs, { 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) @@ -3413,7 +3436,7 @@ unsigned Compiler::gtSetListOrder(GenTree *list, bool isListCallArgs, #endif // FEATURE_STACK_FP_X87 list = listNodes.Pop(); - assert(list && list->IsList()); + assert(list && list->OperIsAnyList()); GenTreePtr next = list->gtOp.gtOp2; unsigned level = 0; @@ -4160,6 +4183,7 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree) break; case GT_LIST: + case GT_FIELD_LIST: case GT_NOP: costEx = 0; costSz = 0; @@ -4852,7 +4876,7 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree) goto DONE; case GT_LIST: - + case GT_FIELD_LIST: { const bool isListCallArgs = false; const bool callArgsInRegs = false; @@ -5213,6 +5237,7 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree) break; case GT_LIST: + case GT_FIELD_LIST: break; case GT_SUB: @@ -5981,11 +6006,11 @@ bool GenTree::IsAddWithI32Const(GenTreePtr* addr, int* offset) // '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) @@ -6869,29 +6894,6 @@ GenTreeArgList* Compiler::gtNewArgList(GenTreePtr arg1, GenTreePtr arg2) 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. @@ -6962,7 +6964,7 @@ fgArgTabEntryPtr Compiler::gtArgEntryByNode(GenTreePtr call, GenTreePtr node) #endif // PROTO_JIT else if (curArgTabEntry->parent != nullptr) { - assert(curArgTabEntry->parent->IsList()); + assert(curArgTabEntry->parent->OperIsList()); if (curArgTabEntry->parent->Current() == node) { return curArgTabEntry; @@ -7793,16 +7795,15 @@ GenTreePtr Compiler::gtCloneExpr(GenTree* tree, // 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: @@ -9141,13 +9142,9 @@ GenTree** GenTreeUseEdgeIterator::GetNextUseEdge() const } 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()) @@ -9396,10 +9393,9 @@ void GenTreeUseEdgeIterator::MoveToNextSIMDUseEdge() } #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 (;;) { @@ -9417,9 +9413,9 @@ void GenTreeUseEdgeIterator::MoveToNextAggregateUseEdge() } 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; @@ -9465,9 +9461,9 @@ GenTreeUseEdgeIterator& GenTreeUseEdgeIterator::operator++() MoveToNextSIMDUseEdge(); } #endif - else if ((op == GT_LIST) && (m_node->AsArgList()->IsAggregate())) + else if (op == GT_FIELD_LIST) { - MoveToNextAggregateUseEdge(); + MoveToNextFieldUseEdge(); } else { @@ -11142,6 +11138,10 @@ void Compiler::gtDispTree(GenTreePtr tree, { 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)) @@ -11488,7 +11488,7 @@ void Compiler::gtDispTree(GenTreePtr tree, // 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 @@ -11544,7 +11544,7 @@ void Compiler::gtGetArgMsg( // 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 @@ -12810,7 +12810,7 @@ GenTreePtr Compiler::gtFoldExprConst(GenTreePtr tree) return op2; } - if (tree->gtOper == GT_LIST) + if (tree->OperIsAnyList()) { return tree; } @@ -14365,12 +14365,12 @@ void Compiler::gtExtractSideEffList(GenTreePtr expr, 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); } } @@ -16624,24 +16624,6 @@ bool GenTree::isCommutativeSIMDIntrinsic() #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 // diff --git a/src/jit/gentree.h b/src/jit/gentree.h index a4059bd..2e9f4b0 100644 --- a/src/jit/gentree.h +++ b/src/jit/gentree.h @@ -946,8 +946,8 @@ public: #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. //---------------------------------------------------------------- @@ -1015,9 +1015,9 @@ public: 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; @@ -1037,14 +1037,14 @@ public: 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: { @@ -1426,9 +1426,9 @@ public: 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 @@ -1468,6 +1468,7 @@ public: switch (gtOper) { case GT_LIST: + case GT_FIELD_LIST: case GT_INTRINSIC: case GT_LEA: #ifdef FEATURE_SIMD @@ -1480,7 +1481,7 @@ public: } static inline bool RequiresNonNullOp2(genTreeOps oper); - bool IsListForMultiRegArg(); + bool IsValidCallArgument(); #endif // DEBUG inline bool IsFPZero(); @@ -1488,11 +1489,36 @@ public: 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(); @@ -1956,7 +1982,7 @@ class GenTreeUseEdgeIterator final #ifdef FEATURE_SIMD void MoveToNextSIMDUseEdge(); #endif - void MoveToNextAggregateUseEdge(); + void MoveToNextFieldUseEdge(); public: GenTreeUseEdgeIterator(); @@ -2654,18 +2680,13 @@ struct GenTreeField : public GenTree // 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(>Op2); } @@ -2679,20 +2700,68 @@ struct GenTreeArgList : public GenTreeOp { } - 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(>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 @@ -3385,8 +3454,11 @@ struct GenTreeCall final : public GenTree void ReplaceCallOperand(GenTree** operandUseEdge, GenTree* replacement); + bool AreArgsComplete() const; + GenTreeCall(var_types type) : GenTree(GT_CALL, type) { + fgArgInfo = nullptr; } #if DEBUGGABLE_GENTREE GenTreeCall() : GenTree() @@ -4375,20 +4447,20 @@ struct GenTreePutArgStk : public GenTreeUnOp 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; @@ -4398,20 +4470,20 @@ struct GenTreePutArgStk : public GenTreeUnOp 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; @@ -4422,18 +4494,18 @@ struct GenTreePutArgStk : public GenTreeUnOp 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; @@ -4443,18 +4515,18 @@ struct GenTreePutArgStk : public GenTreeUnOp 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; @@ -4467,14 +4539,14 @@ struct GenTreePutArgStk : public GenTreeUnOp 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. // @@ -4496,15 +4568,17 @@ struct GenTreePutArgStk : public GenTreeUnOp 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, @@ -4516,7 +4590,7 @@ struct GenTreePutArgStk : public GenTreeUnOp 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() @@ -4801,26 +4875,14 @@ inline bool GenTree::IsBoxedValue() 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 @@ -4829,18 +4891,29 @@ inline GenTreePtr GenTree::MoveNext() // 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; @@ -4848,7 +4921,7 @@ inline bool GenTree::IsListForMultiRegArg() #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) { @@ -4866,25 +4939,27 @@ inline bool GenTree::IsListForMultiRegArg() } #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); } diff --git a/src/jit/gtlist.h b/src/jit/gtlist.h index 3bf3603..edd246d 100644 --- a/src/jit/gtlist.h +++ b/src/jit/gtlist.h @@ -202,7 +202,8 @@ GTNODE(SIMD , "simd" ,GenTreeSIMD ,0,GTK_BINOP|GTK_EX GTNODE(JTRUE , "jmpTrue" ,GenTreeOp ,0,GTK_UNOP|GTK_NOVALUE) GTNODE(JCC , "jcc" ,GenTreeJumpCC ,0,GTK_LEAF|GTK_NOVALUE) -GTNODE(LIST , "" ,GenTreeOp ,0,GTK_BINOP) +GTNODE(LIST , "" ,GenTreeArgList ,0,GTK_BINOP|GTK_NOVALUE) +GTNODE(FIELD_LIST , "" ,GenTreeFieldList ,0,GTK_BINOP) // List of fields of a struct, when passed as an argument //----------------------------------------------------------------------------- // Other nodes that have special structure: diff --git a/src/jit/gtstructs.h b/src/jit/gtstructs.h index e0e1ee5..ac91240 100644 --- a/src/jit/gtstructs.h +++ b/src/jit/gtstructs.h @@ -65,7 +65,8 @@ GTSTRUCT_1(Cast , GT_CAST) 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) diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp index dbc67c9..001a529 100644 --- a/src/jit/importer.cpp +++ b/src/jit/importer.cpp @@ -3534,7 +3534,7 @@ GenTreePtr Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, // 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; diff --git a/src/jit/instr.h b/src/jit/instr.h index 0425684..c38f8d2 100644 --- a/src/jit/instr.h +++ b/src/jit/instr.h @@ -56,18 +56,6 @@ DECLARE_TYPED_ENUM(instruction,unsigned) } 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 diff --git a/src/jit/jit.h b/src/jit/jit.h index 57c8b6d..7485b8b 100644 --- a/src/jit/jit.h +++ b/src/jit/jit.h @@ -259,6 +259,15 @@ struct CLRConfig #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 diff --git a/src/jit/lower.cpp b/src/jit/lower.cpp index be3429a..f21073d 100644 --- a/src/jit/lower.cpp +++ b/src/jit/lower.cpp @@ -715,7 +715,7 @@ void Lowering::ReplaceArgWithPutArgOrCopy(GenTree** argSlot, GenTree* putArgOrCo // // 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 @@ -776,8 +776,8 @@ GenTreePtr Lowering::NewPutArg(GenTreeCall* call, GenTreePtr arg, fgArgTabEntryP // 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); @@ -849,25 +849,25 @@ GenTreePtr Lowering::NewPutArg(GenTreeCall* call, GenTreePtr arg, fgArgTabEntryP // // 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; } @@ -880,26 +880,26 @@ GenTreePtr Lowering::NewPutArg(GenTreeCall* call, GenTreePtr arg, fgArgTabEntryP 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; } @@ -916,23 +916,23 @@ GenTreePtr Lowering::NewPutArg(GenTreeCall* call, GenTreePtr arg, fgArgTabEntryP // 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 @@ -958,7 +958,7 @@ GenTreePtr Lowering::NewPutArg(GenTreeCall* call, GenTreePtr arg, fgArgTabEntryP putArg->AsPutArgStk()->setGcPointers(numRefs, gcLayout); } -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING +#endif // FEATURE_PUT_STRUCT_ARG_STK } if (arg->InReg()) @@ -1091,7 +1091,7 @@ void Lowering::LowerArg(GenTreeCall* call, GenTreePtr* ppArg) 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) { @@ -4053,10 +4053,10 @@ void Lowering::CheckCallArg(GenTree* arg) 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()) { diff --git a/src/jit/lower.h b/src/jit/lower.h index 5ce21e4..61a270d 100644 --- a/src/jit/lower.h +++ b/src/jit/lower.h @@ -203,9 +203,9 @@ private: #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(); diff --git a/src/jit/lowerarm64.cpp b/src/jit/lowerarm64.cpp index d80691a..7c6164f 100644 --- a/src/jit/lowerarm64.cpp +++ b/src/jit/lowerarm64.cpp @@ -201,6 +201,7 @@ void Lowering::TreeNodeInfoInit(GenTree* tree) __fallthrough; case GT_LIST: + case GT_FIELD_LIST: case GT_ARGPLACE: case GT_NO_OP: case GT_START_NONGC: @@ -976,7 +977,7 @@ void Lowering::TreeNodeInfoInitCall(GenTreeCall* call) for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) { - assert(list->IsList()); + assert(list->OperIsList()); GenTreePtr argNode = list->Current(); @@ -1002,16 +1003,16 @@ void Lowering::TreeNodeInfoInitCall(GenTreeCall* call) 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; @@ -1019,9 +1020,9 @@ void Lowering::TreeNodeInfoInitCall(GenTreeCall* call) 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; @@ -1165,14 +1166,14 @@ void Lowering::TreeNodeInfoInitPutArgStk(GenTree* argNode, fgArgTabEntryPtr info 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 diff --git a/src/jit/lowerxarch.cpp b/src/jit/lowerxarch.cpp index 54942e3..5d6fc0a 100644 --- a/src/jit/lowerxarch.cpp +++ b/src/jit/lowerxarch.cpp @@ -241,6 +241,7 @@ void Lowering::TreeNodeInfoInit(GenTree* tree) break; case GT_LIST: + case GT_FIELD_LIST: case GT_ARGPLACE: case GT_NO_OP: case GT_START_NONGC: @@ -553,10 +554,7 @@ void Lowering::TreeNodeInfoInit(GenTree* tree) } 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: @@ -567,11 +565,11 @@ void Lowering::TreeNodeInfoInit(GenTree* tree) 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: @@ -1250,7 +1248,7 @@ void Lowering::TreeNodeInfoInitCall(GenTreeCall* call) // First, count reg args for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) { - assert(list->IsList()); + assert(list->OperIsList()); GenTreePtr argNode = list->Current(); @@ -1265,7 +1263,7 @@ void Lowering::TreeNodeInfoInitCall(GenTreeCall* call) 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 @@ -1277,7 +1275,7 @@ void Lowering::TreeNodeInfoInitCall(GenTreeCall* call) argNode->gtOp.gtOp1->gtLsraInfo.dstCount = 0; argNode->gtLsraInfo.srcCount = 0; } -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING +#endif // FEATURE_PUT_STRUCT_ARG_STK continue; } @@ -1307,7 +1305,7 @@ void Lowering::TreeNodeInfoInitCall(GenTreeCall* call) // 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; @@ -1329,16 +1327,16 @@ void Lowering::TreeNodeInfoInitCall(GenTreeCall* call) { 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) @@ -1895,7 +1893,7 @@ void Lowering::TreeNodeInfoInitBlockStore(GenTreeBlk* blkNode) } } -#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING +#ifdef FEATURE_PUT_STRUCT_ARG_STK //------------------------------------------------------------------------ // TreeNodeInfoInitPutArgStk: Set the NodeInfo for a GT_PUTARG_STK. // @@ -1910,6 +1908,28 @@ void Lowering::TreeNodeInfoInitPutArgStk(GenTree* tree) 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); @@ -1983,10 +2003,14 @@ void Lowering::TreeNodeInfoInitPutArgStk(GenTree* 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()); @@ -2022,7 +2046,7 @@ void Lowering::TreeNodeInfoInitPutArgStk(GenTree* tree) info->srcCount -= 1; } } -#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING +#endif // FEATURE_PUT_STRUCT_ARG_STK //------------------------------------------------------------------------ // TreeNodeInfoInitLclHeap: Set the NodeInfo for a GT_LCLHEAP. @@ -3332,6 +3356,13 @@ void Lowering::TreeNodeInfoInitCmp(GenTreePtr tree) { 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 diff --git a/src/jit/lsra.cpp b/src/jit/lsra.cpp index 1aef9b5..fa774b4 100644 --- a/src/jit/lsra.cpp +++ b/src/jit/lsra.cpp @@ -3313,7 +3313,7 @@ static int ComputeOperandDstCount(GenTree* operand) 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. @@ -3321,7 +3321,7 @@ static int ComputeOperandDstCount(GenTree* operand) } 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()) @@ -3368,9 +3368,11 @@ void LinearScan::buildRefPositionsForNode(GenTree* tree, 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) @@ -3959,7 +3961,7 @@ void LinearScan::buildRefPositionsForNode(GenTree* tree, #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. diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index 410d8a7..42fbb07 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -1019,12 +1019,12 @@ fgArgInfo::fgArgInfo(GenTreePtr newCall, GenTreePtr oldCall) { /* 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(); @@ -1056,6 +1056,8 @@ fgArgInfo::fgArgInfo(GenTreePtr newCall, GenTreePtr oldCall) argCount = oldArgInfo->argCount; nextSlotNum = oldArgInfo->nextSlotNum; + hasRegArgs = oldArgInfo->hasRegArgs; + hasStackArgs = oldArgInfo->hasStackArgs; argsComplete = true; argsSorted = true; } @@ -1197,7 +1199,7 @@ fgArgTabEntry* fgArgInfo::RemorphRegArg( 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; } @@ -1264,7 +1266,7 @@ void fgArgInfo::RemorphStkArg( if (curArgTabEntry->parent != nullptr) { - assert(curArgTabEntry->parent->IsList()); + assert(curArgTabEntry->parent->OperIsList()); argx = curArgTabEntry->parent->Current(); isRegArg = (argx->gtFlags & GTF_LATE_ARG) != 0; } @@ -1292,7 +1294,7 @@ void fgArgInfo::RemorphStkArg( 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) @@ -1521,7 +1523,7 @@ void fgArgInfo::ArgsComplete() #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 @@ -2373,7 +2375,7 @@ void fgArgInfo::EvalArgsToTemps() { 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; @@ -2396,7 +2398,7 @@ void fgArgInfo::EvalArgsToTemps() } else { - noway_assert(tmpRegArgNext->IsList()); + noway_assert(tmpRegArgNext->OperIsList()); noway_assert(tmpRegArgNext->Current()); tmpRegArgNext->gtOp.gtOp2 = compiler->gtNewArgList(defArg); tmpRegArgNext = tmpRegArgNext->Rest(); @@ -2612,7 +2614,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) 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_ @@ -2740,7 +2742,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // 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 @@ -2754,18 +2756,21 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // // 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 { @@ -2825,7 +2830,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) { args = call->gtCallArgs; assert(args != nullptr); - assert(args->IsList()); + assert(args->OperIsList()); argx = call->gtCallArgs->Current(); @@ -2944,7 +2949,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) /* 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); @@ -3090,7 +3095,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) *parentArgx = argx; flagsSummary |= argx->gtFlags; - assert(args->IsList()); + assert(args->OperIsList()); assert(argx == args->Current()); #ifndef LEGACY_BACKEND @@ -3135,7 +3140,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) fgArgTabEntryPtr argEntry = nullptr; - if (lateArgsComputed) + if (reMorphing) { argEntry = gtArgEntryByArgNum(call, argIndex); } @@ -3143,7 +3148,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) #ifdef _TARGET_ARM_ bool passUsingIntRegs; - if (lateArgsComputed) + if (reMorphing) { passUsingFloatRegs = isValidFloatArgReg(argEntry->regNum); passUsingIntRegs = isValidIntArgReg(argEntry->regNum); @@ -3194,7 +3199,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) #elif defined(_TARGET_ARM64_) - if (lateArgsComputed) + if (reMorphing) { passUsingFloatRegs = isValidFloatArgReg(argEntry->regNum); } @@ -3205,7 +3210,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) #elif defined(_TARGET_AMD64_) #if defined(UNIX_AMD64_ABI) - if (lateArgsComputed) + if (reMorphing) { passUsingFloatRegs = isValidFloatArgReg(argEntry->regNum); } @@ -3231,7 +3236,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) 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. @@ -3275,7 +3280,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // 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 @@ -3394,7 +3399,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) 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 */ @@ -3701,11 +3706,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // 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; @@ -3720,7 +3720,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } size = roundupSize / TARGET_POINTER_SIZE; // Normalize size to number of pointer sized items -#endif // !defined(_TARGET_X86_) || defined(LEGACY_BACKEND) } } } @@ -3879,7 +3878,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } #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, @@ -3958,7 +3957,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) #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); @@ -4068,7 +4067,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // 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); @@ -4083,14 +4082,14 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) 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); @@ -4106,7 +4105,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) 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 @@ -4135,6 +4134,41 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } #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) { @@ -4147,7 +4181,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } } // end foreach argument loop - if (!lateArgsComputed) + if (!reMorphing) { call->fgArgInfo->ArgsComplete(); #ifdef LEGACY_BACKEND @@ -4255,7 +4289,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // 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 @@ -4286,7 +4320,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // 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); @@ -4349,7 +4383,7 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen { for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) { - assert(list->IsList()); + assert(list->OperIsList()); GenTreePtr argNode = list->Current(); if (argx == argNode) @@ -4370,7 +4404,7 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen { 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; } @@ -4401,6 +4435,16 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen // 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 @@ -4408,17 +4452,9 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen 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 { @@ -4465,7 +4501,7 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen { for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) { - assert(list->IsList()); + assert(list->OperIsList()); GenTreePtr argNode = list->Current(); if (argx == argNode) @@ -4505,8 +4541,8 @@ void Compiler::fgMorphSystemVStructArgs(GenTreeCall* call, bool hasStructArgumen // // 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) @@ -4555,7 +4591,7 @@ 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) @@ -4603,7 +4639,7 @@ void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call) //----------------------------------------------------------------------------- // 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 @@ -4615,7 +4651,7 @@ void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call) // 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. @@ -4730,7 +4766,7 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f // We should still have a TYP_STRUCT assert(argValue->TypeGet() == TYP_STRUCT); - GenTreeArgList* newArg = nullptr; + GenTreeFieldList* newArg = nullptr; // Are we passing a struct LclVar? // @@ -4832,9 +4868,10 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f // 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); } } } @@ -4900,27 +4937,22 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f // 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? @@ -4933,17 +4965,14 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f // 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) { @@ -4956,14 +4985,12 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f 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; } } } @@ -6678,7 +6705,7 @@ bool Compiler::fgCanFastTailCall(GenTreeCall* callee) { nCalleeArgs++; - assert(args->IsList()); + assert(args->OperIsList()); GenTreePtr argx = args->gtOp.gtOp1; if (varTypeIsStruct(argx)) @@ -11057,7 +11084,7 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // 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) { @@ -13000,7 +13027,7 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // 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)) @@ -17470,9 +17497,6 @@ enum AddrExposedContext 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. }; @@ -17588,14 +17612,8 @@ Compiler::fgWalkResult Compiler::fgMarkAddrTakenLocalsPreCB(GenTreePtr* pTree, f 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: @@ -18099,9 +18117,6 @@ bool Compiler::fgShouldCreateAssignOp(GenTreePtr tree, bool* bReverse) #endif // defined(LEGACY_BACKEND) } -// Static variables. -Compiler::MorphAddrContext Compiler::s_CopyBlockMAC(Compiler::MACK_CopyBlock); - #ifdef FEATURE_SIMD //----------------------------------------------------------------------------------- diff --git a/src/jit/rationalize.cpp b/src/jit/rationalize.cpp index 19a5984..7e75354 100644 --- a/src/jit/rationalize.cpp +++ b/src/jit/rationalize.cpp @@ -614,21 +614,21 @@ Compiler::fgWalkResult Rationalizer::RewriteNode(GenTree** useEdge, ArrayStackgtFlags & 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); } diff --git a/src/jit/regalloc.cpp b/src/jit/regalloc.cpp index 9dd7299..a93003f 100644 --- a/src/jit/regalloc.cpp +++ b/src/jit/regalloc.cpp @@ -4525,7 +4525,7 @@ regMaskTP Compiler::rpPredictTreeRegUse(GenTreePtr tree, 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(); diff --git a/src/jit/valuenum.cpp b/src/jit/valuenum.cpp index 5bc96ed..54be240 100644 --- a/src/jit/valuenum.cpp +++ b/src/jit/valuenum.cpp @@ -2072,6 +2072,7 @@ bool ValueNumStore::CanEvalForConstantArgs(VNFunc vnf) 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: @@ -6522,6 +6523,9 @@ void Compiler::fgValueNumberTree(GenTreePtr tree, bool evalAsgLhsInd) 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;