diff options
Diffstat (limited to 'src/jit/codegenxarch.cpp')
-rw-r--r-- | src/jit/codegenxarch.cpp | 483 |
1 files changed, 249 insertions, 234 deletions
diff --git a/src/jit/codegenxarch.cpp b/src/jit/codegenxarch.cpp index 8e0af48799..e893da6035 100644 --- a/src/jit/codegenxarch.cpp +++ b/src/jit/codegenxarch.cpp @@ -226,7 +226,7 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg) genPopRegs(pushedRegs, byrefPushedRegs, norefPushedRegs); } -BasicBlock* CodeGen::genCallFinally(BasicBlock* block, BasicBlock* lblk) +BasicBlock* CodeGen::genCallFinally(BasicBlock* block) { #if FEATURE_EH_FUNCLETS // Generate a call to the finally, like this: @@ -263,10 +263,14 @@ BasicBlock* CodeGen::genCallFinally(BasicBlock* block, BasicBlock* lblk) } else { +// TODO-Linux-x86: Do we need to handle the GC information for this NOP or JMP specially, as is done for other +// architectures? +#ifndef JIT32_GCENCODER // Because of the way the flowgraph is connected, the liveness info for this one instruction // after the call is not (can not be) correct in cases where a variable has a last use in the // handler. So turn off GC reporting for this single instruction. getEmitter()->emitDisableGC(); +#endif // JIT32_GCENCODER // Now go to where the finally funclet needs to return to. if (block->bbNext->bbJumpDest == block->bbNext->bbNext) @@ -282,7 +286,9 @@ BasicBlock* CodeGen::genCallFinally(BasicBlock* block, BasicBlock* lblk) inst_JMP(EJ_jmp, block->bbNext->bbJumpDest); } +#ifndef JIT32_GCENCODER getEmitter()->emitEnableGC(); +#endif // JIT32_GCENCODER } #else // !FEATURE_EH_FUNCLETS @@ -348,8 +354,6 @@ BasicBlock* CodeGen::genCallFinally(BasicBlock* block, BasicBlock* lblk) if (!(block->bbFlags & BBF_RETLESS_CALL)) { assert(block->isBBCallAlwaysPair()); - - lblk = block; block = block->bbNext; } return block; @@ -515,13 +519,13 @@ void CodeGen::genCodeForMulHi(GenTreeOp* treeNode) GenTree* regOp = op1; GenTree* rmOp = op2; - // Set rmOp to the contained memory operand (if any) - if (op1->isContained() || (!op2->isContained() && (op2->gtRegNum == REG_RAX))) + // Set rmOp to the memory operand (if any) + if (op1->isUsedFromMemory() || (op2->isUsedFromReg() && (op2->gtRegNum == REG_RAX))) { regOp = op2; rmOp = op1; } - assert(!regOp->isContained()); + assert(regOp->isUsedFromReg()); // Setup targetReg when neither of the source operands was a matching register if (regOp->gtRegNum != REG_RAX) @@ -569,12 +573,12 @@ void CodeGen::genCodeForLongUMod(GenTreeOp* node) GenTree* const dividendLo = dividend->gtOp1; GenTree* const dividendHi = dividend->gtOp2; - assert(!dividendLo->isContained()); - assert(!dividendHi->isContained()); + assert(dividendLo->isUsedFromReg()); + assert(dividendHi->isUsedFromReg()); GenTree* const divisor = node->gtOp2; assert(divisor->gtSkipReloadOrCopy()->OperGet() == GT_CNS_INT); - assert(!divisor->gtSkipReloadOrCopy()->isContained()); + assert(divisor->gtSkipReloadOrCopy()->isUsedFromReg()); assert(divisor->gtSkipReloadOrCopy()->AsIntCon()->gtIconVal >= 2); assert(divisor->gtSkipReloadOrCopy()->AsIntCon()->gtIconVal <= 0x3fffffff); @@ -656,16 +660,16 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode) var_types targetType = treeNode->TypeGet(); emitter* emit = getEmitter(); - // dividend is not contained. - assert(!dividend->isContained()); + // dividend is in a register. + assert(dividend->isUsedFromReg()); genConsumeOperands(treeNode->AsOp()); if (varTypeIsFloating(targetType)) { - // divisor is not contained or if contained is a memory op. + // Check that divisor is a valid operand. // Note that a reg optional operand is a treated as a memory op // if no register is allocated to it. - assert(!divisor->isContained() || divisor->isMemoryOp() || divisor->IsCnsFltOrDbl() || + assert(divisor->isUsedFromReg() || divisor->isMemoryOp() || divisor->IsCnsFltOrDbl() || divisor->IsRegOptional()); // Floating point div/rem operation @@ -675,7 +679,7 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode) { emit->emitInsBinary(genGetInsForOper(treeNode->gtOper, targetType), size, treeNode, divisor); } - else if (!divisor->isContained() && divisor->gtRegNum == targetReg) + else if (divisor->isUsedFromReg() && divisor->gtRegNum == targetReg) { // It is not possible to generate 2-operand divss or divsd where reg2 = reg1 / reg2 // because divss/divsd reg1, reg2 will over-write reg1. Therefore, in case of AMD64 @@ -773,8 +777,8 @@ void CodeGen::genCodeForBinary(GenTree* treeNode) GenTreePtr op1 = treeNode->gtGetOp1(); GenTreePtr op2 = treeNode->gtGetOp2(); - // Commutative operations can mark op1 as contained to generate "op reg, memop/immed" - if (op1->isContained()) + // Commutative operations can mark op1 as contained or reg-optional to generate "op reg, memop/immed" + if (!op1->isUsedFromReg()) { assert(treeNode->OperIsCommutative()); assert(op1->isMemoryOp() || op1->IsCnsNonZeroFltOrDbl() || op1->IsIntCnsFitsInI32() || op1->IsRegOptional()); @@ -788,8 +792,8 @@ void CodeGen::genCodeForBinary(GenTree* treeNode) // The arithmetic node must be sitting in a register (since it's not contained) noway_assert(targetReg != REG_NA); - regNumber op1reg = op1->isContained() ? REG_NA : op1->gtRegNum; - regNumber op2reg = op2->isContained() ? REG_NA : op2->gtRegNum; + regNumber op1reg = op1->isUsedFromReg() ? op1->gtRegNum : REG_NA; + regNumber op2reg = op2->isUsedFromReg() ? op2->gtRegNum : REG_NA; GenTreePtr dst; GenTreePtr src; @@ -814,7 +818,7 @@ void CodeGen::genCodeForBinary(GenTree* treeNode) } // now we know there are 3 different operands so attempt to use LEA else if (oper == GT_ADD && !varTypeIsFloating(treeNode) && !treeNode->gtOverflowEx() // LEA does not set flags - && (op2->isContainedIntOrIImmed() || !op2->isContained()) && !treeNode->gtSetFlags()) + && (op2->isContainedIntOrIImmed() || op2->isUsedFromReg()) && !treeNode->gtSetFlags()) { if (op2->isContainedIntOrIImmed()) { @@ -936,7 +940,7 @@ void CodeGen::genStructReturn(GenTreePtr treeNode) { // Right now the only enregistrable structs supported are SIMD vector types. assert(varTypeIsSIMD(op1)); - assert(!op1->isContained()); + assert(op1->isUsedFromReg()); // This is a case of operand is in a single reg and needs to be // returned in multiple ABI return registers. @@ -974,7 +978,7 @@ void CodeGen::genStructReturn(GenTreePtr treeNode) } else { - assert(op1->isContained()); + assert(op1->isUsedFromMemory()); // Copy var on stack into ABI return registers int offset = 0; @@ -1328,7 +1332,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) else { GenTreePtr operand = treeNode->gtGetOp1(); - assert(!operand->isContained()); + assert(operand->isUsedFromReg()); regNumber operandReg = genConsumeReg(operand); if (operandReg != targetReg) @@ -1374,7 +1378,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) case GT_RSH_LO: // TODO-X86-CQ: This only handles the case where the operand being shifted is in a register. We don't // need sourceHi to be always in reg in case of GT_LSH_HI (because it could be moved from memory to - // targetReg if sourceHi is a contained mem-op). Similarly for GT_RSH_LO, sourceLo could be marked as + // targetReg if sourceHi is a memory operand). Similarly for GT_RSH_LO, sourceLo could be marked as // contained memory-op. Even if not a memory-op, we could mark it as reg-optional. genCodeForShiftLong(treeNode); break; @@ -1423,7 +1427,6 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) if (!treeNode->InReg() && !(treeNode->gtFlags & GTF_SPILLED)) { assert(!isRegCandidate); - #if defined(FEATURE_SIMD) && defined(_TARGET_X86_) // Loading of TYP_SIMD12 (i.e. Vector3) variable if (treeNode->TypeGet() == TYP_SIMD12) @@ -1486,10 +1489,11 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) // storing of TYP_SIMD12 (i.e. Vector3) field if (treeNode->TypeGet() == TYP_SIMD12) { - genStoreLclFldTypeSIMD12(treeNode); + genStoreLclTypeSIMD12(treeNode); break; } -#endif +#endif // FEATURE_SIMD + GenTreePtr op1 = treeNode->gtGetOp1(); genConsumeRegs(op1); emit->emitInsBinary(ins_Store(targetType), emitTypeSize(treeNode), treeNode, op1); @@ -1526,6 +1530,13 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) #endif // !defined(_TARGET_64BIT_) #ifdef FEATURE_SIMD + // storing of TYP_SIMD12 (i.e. Vector3) field + if (treeNode->TypeGet() == TYP_SIMD12) + { + genStoreLclTypeSIMD12(treeNode); + break; + } + if (varTypeIsSIMD(targetType) && (targetReg != REG_NA) && op1->IsCnsIntOrI()) { // This is only possible for a zero-init. @@ -1547,25 +1558,24 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) } else { - bool containedOp1 = op1->isContained(); // Look for the case where we have a constant zero which we've marked for reuse, // but which isn't actually in the register we want. In that case, it's better to create // zero in the target register, because an xor is smaller than a copy. Note that we could // potentially handle this in the register allocator, but we can't always catch it there // because the target may not have a register allocated for it yet. - if (!containedOp1 && (op1->gtRegNum != treeNode->gtRegNum) && + if (op1->isUsedFromReg() && (op1->gtRegNum != treeNode->gtRegNum) && (op1->IsIntegralConst(0) || op1->IsFPZero())) { op1->gtRegNum = REG_NA; op1->ResetReuseRegVal(); - containedOp1 = true; } - if (containedOp1) + if (!op1->isUsedFromReg()) { - // Currently, we assume that the contained source of a GT_STORE_LCL_VAR writing to a register - // must be a constant. However, in the future we might want to support a contained memory op. - // This is a bit tricky because we have to decide it's contained before register allocation, + // Currently, we assume that the non-reg source of a GT_STORE_LCL_VAR writing to a register + // must be a constant. However, in the future we might want to support an operand used from + // memory. This is a bit tricky because we have to decide it can be used from memory before + // register allocation, // and this would be a case where, once that's done, we need to mark that node as always // requiring a register - which we always assume now anyway, but once we "optimize" that // we'll have to take cases like this into account. @@ -1682,7 +1692,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) // CQ: When possible use LEA for mul by imm 3, 5 or 9 ssize_t imm = immOp->AsIntConCommon()->IconValue(); - if (!requiresOverflowCheck && !rmOp->isContained() && ((imm == 3) || (imm == 5) || (imm == 9))) + if (!requiresOverflowCheck && rmOp->isUsedFromReg() && ((imm == 3) || (imm == 5) || (imm == 9))) { // We will use the LEA instruction to perform this multiply // Note that an LEA with base=x, index=x and scale=(imm-1) computes x*imm when imm=3,5 or 9. @@ -1712,15 +1722,15 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) ins = genGetInsForOper(GT_MUL, targetType); } - // Set rmOp to the contain memory operand (if any) + // Set rmOp to the memory operand (if any) // or set regOp to the op2 when it has the matching target register for our multiply op // - if (op1->isContained() || (!op2->isContained() && (op2->gtRegNum == mulTargetReg))) + if (op1->isUsedFromMemory() || (op2->isUsedFromReg() && (op2->gtRegNum == mulTargetReg))) { regOp = op2; rmOp = op1; } - assert(!regOp->isContained()); + assert(regOp->isUsedFromReg()); // Setup targetReg when neither of the source operands was a matching register if (regOp->gtRegNum != mulTargetReg) @@ -1781,6 +1791,8 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) case GT_LE: case GT_GE: case GT_GT: + case GT_TEST_EQ: + case GT_TEST_NE: { // TODO-XArch-CQ: Check if we can use the currently set flags. // TODO-XArch-CQ: Check for the case where we can simply transfer the carry bit to a register @@ -2089,7 +2101,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) case GT_NULLCHECK: { - assert(!treeNode->gtOp.gtOp1->isContained()); + assert(treeNode->gtOp.gtOp1->isUsedFromReg()); regNumber reg = genConsumeReg(treeNode->gtOp.gtOp1); emit->emitIns_AR_R(INS_cmp, EA_4BYTE, reg, reg, 0); } @@ -2180,7 +2192,7 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) #if !defined(_TARGET_64BIT_) case GT_LONG: - assert(!treeNode->isContained()); + assert(treeNode->isUsedFromReg()); genConsumeRegs(treeNode); break; #endif @@ -2631,16 +2643,14 @@ void CodeGen::genLclHeap(GenTreePtr tree) // Loop: genDefineTempLabel(loop); -#if defined(_TARGET_AMD64_) - // Push two 8-byte zeros. This matches the 16-byte STACK_ALIGN value. - static_assert_no_msg(STACK_ALIGN == (REGSIZE_BYTES * 2)); - inst_IV(INS_push_hide, 0); // --- push 8-byte 0 - inst_IV(INS_push_hide, 0); // --- push 8-byte 0 -#elif defined(_TARGET_X86_) - // Push a single 4-byte zero. This matches the 4-byte STACK_ALIGN value. - static_assert_no_msg(STACK_ALIGN == REGSIZE_BYTES); - inst_IV(INS_push_hide, 0); // --- push 4-byte 0 -#endif // _TARGET_X86_ + static_assert_no_msg((STACK_ALIGN % REGSIZE_BYTES) == 0); + unsigned const count = (STACK_ALIGN / REGSIZE_BYTES); + + for (unsigned i = 0; i < count; i++) + { + inst_IV(INS_push_hide, 0); // --- push REG_SIZE bytes of 0 + } + // Note that the stack must always be aligned to STACK_ALIGN bytes // Decrement the loop counter and loop if not done. inst_RV(INS_dec, regCnt, TYP_I_IMPL); @@ -2841,8 +2851,8 @@ void CodeGen::genCodeForInitBlkRepStos(GenTreeBlk* initBlkNode) } #ifdef DEBUG - assert(!dstAddr->isContained()); - assert(!initVal->isContained()); + assert(dstAddr->isUsedFromReg()); + assert(initVal->isUsedFromReg()); #ifdef _TARGET_AMD64_ assert(size != 0); #endif @@ -2878,8 +2888,8 @@ void CodeGen::genCodeForInitBlkUnroll(GenTreeBlk* initBlkNode) initVal = initVal->gtGetOp1(); } - assert(!dstAddr->isContained()); - assert(!initVal->isContained() || (initVal->IsIntegralConst(0) && ((size & 0xf) == 0))); + assert(dstAddr->isUsedFromReg()); + assert(initVal->isUsedFromReg() || (initVal->IsIntegralConst(0) && ((size & 0xf) == 0))); assert(size != 0); assert(size <= INITBLK_UNROLL_LIMIT); assert(initVal->gtSkipReloadOrCopy()->IsCnsIntOrI()); @@ -2979,8 +2989,8 @@ void CodeGen::genCodeForInitBlk(GenTreeBlk* initBlkNode) initVal = initVal->gtGetOp1(); } - assert(!dstAddr->isContained()); - assert(!initVal->isContained()); + assert(dstAddr->isUsedFromReg()); + assert(initVal->isUsedFromReg()); if (blockSize != 0) { @@ -3064,7 +3074,7 @@ void CodeGen::genCodeForCpBlkUnroll(GenTreeBlk* cpBlkNode) if (source->gtOper == GT_IND) { srcAddr = source->gtGetOp1(); - if (!srcAddr->isContained()) + if (srcAddr->isUsedFromReg()) { genConsumeReg(srcAddr); } @@ -3086,7 +3096,7 @@ void CodeGen::genCodeForCpBlkUnroll(GenTreeBlk* cpBlkNode) srcAddr = source; } - if (!dstAddr->isContained()) + if (dstAddr->isUsedFromReg()) { genConsumeReg(dstAddr); } @@ -3171,7 +3181,7 @@ void CodeGen::genCodeForCpBlkRepMovs(GenTreeBlk* cpBlkNode) GenTreePtr srcAddr = nullptr; #ifdef DEBUG - assert(!dstAddr->isContained()); + assert(dstAddr->isUsedFromReg()); assert(source->isContained()); #ifdef _TARGET_X86_ @@ -3352,7 +3362,7 @@ void CodeGen::genStructPutArgUnroll(GenTreePutArgStk* putArgNode) assert(src->gtOper == GT_OBJ); - if (!src->gtOp.gtOp1->isContained()) + if (src->gtOp.gtOp1->isUsedFromReg()) { genConsumeReg(src->gtOp.gtOp1); } @@ -3544,7 +3554,7 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode) if (source->gtOper == GT_IND) { srcAddr = source->gtGetOp1(); - assert(!srcAddr->isContained()); + assert(srcAddr->isUsedFromReg()); } else { @@ -3557,7 +3567,7 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode) #ifdef DEBUG bool isRepMovspUsed = false; - assert(!dstAddr->isContained()); + assert(dstAddr->isUsedFromReg()); // If the GenTree node has data about GC pointers, this means we're dealing // with CpObj, so this requires special logic. @@ -3720,7 +3730,7 @@ void CodeGen::genCodeForCpBlk(GenTreeBlk* cpBlkNode) if (source->gtOper == GT_IND) { srcAddr = source->gtGetOp1(); - assert(!srcAddr->isContained()); + assert(srcAddr->isUsedFromReg()); } else { @@ -3863,16 +3873,16 @@ void CodeGen::genRangeCheck(GenTreePtr oper) GenTreeBoundsChk* bndsChk = oper->AsBoundsChk(); - GenTreePtr arrLen = bndsChk->gtArrLen; GenTreePtr arrIndex = bndsChk->gtIndex; + GenTreePtr arrLen = bndsChk->gtArrLen; GenTreePtr arrRef = nullptr; int lenOffset = 0; GenTree * src1, *src2; emitJumpKind jmpKind; - genConsumeRegs(arrLen); genConsumeRegs(arrIndex); + genConsumeRegs(arrLen); if (arrIndex->isContainedIntOrIImmed()) { @@ -3899,7 +3909,7 @@ void CodeGen::genRangeCheck(GenTreePtr oper) // cmp reg, [mem] (if arrLen is a memory op) // // That is only one of arrIndex or arrLen can be a memory op. - assert(!arrIndex->isContainedMemoryOp() || !arrLen->isContainedMemoryOp()); + assert(!arrIndex->isUsedFromMemory() || !arrLen->isUsedFromMemory()); src1 = arrIndex; src2 = arrLen; @@ -4211,7 +4221,7 @@ void CodeGen::genCodeForShift(GenTreePtr tree) { // Only the non-RMW case here. assert(tree->OperIsShiftOrRotate()); - assert(!tree->gtOp.gtOp1->isContained()); + assert(tree->gtOp.gtOp1->isUsedFromReg()); assert(tree->gtRegNum != REG_NA); genConsumeOperands(tree->AsOp()); @@ -4277,8 +4287,8 @@ void CodeGen::genCodeForShiftLong(GenTreePtr tree) GenTree* operand = tree->gtOp.gtOp1; assert(operand->OperGet() == GT_LONG); - assert(!operand->gtOp.gtOp1->isContained()); - assert(!operand->gtOp.gtOp2->isContained()); + assert(operand->gtOp.gtOp1->isUsedFromReg()); + assert(operand->gtOp.gtOp2->isUsedFromReg()); GenTree* operandLo = operand->gtGetOp1(); GenTree* operandHi = operand->gtGetOp2(); @@ -4334,7 +4344,7 @@ void CodeGen::genCodeForShiftRMW(GenTreeStoreInd* storeInd) assert(data->OperIsShiftOrRotate()); // This function only handles the RMW case. - assert(data->gtOp.gtOp1->isContained()); + assert(data->gtOp.gtOp1->isUsedFromMemory()); assert(data->gtOp.gtOp1->isIndir()); assert(Lowering::IndirsAreEquivalent(data->gtOp.gtOp1, storeInd)); assert(data->gtRegNum == REG_NA); @@ -4580,7 +4590,7 @@ void CodeGen::genStoreInd(GenTreePtr node) assert(storeInd->IsRMWDstOp1()); rmwSrc = data->gtGetOp1(); rmwDst = data->gtGetOp1(); - assert(rmwSrc->isContained()); + assert(rmwSrc->isUsedFromMemory()); } assert(rmwSrc != nullptr); @@ -4616,8 +4626,7 @@ void CodeGen::genStoreInd(GenTreePtr node) assert(rmwSrc == data->gtGetOp2()); genCodeForShiftRMW(storeInd); } - else if (!compiler->opts.compDbgCode && data->OperGet() == GT_ADD && - (rmwSrc->IsIntegralConst(1) || rmwSrc->IsIntegralConst(-1))) + else if (data->OperGet() == GT_ADD && (rmwSrc->IsIntegralConst(1) || rmwSrc->IsIntegralConst(-1))) { // Generate "inc/dec [mem]" instead of "add/sub [mem], 1". // @@ -4858,11 +4867,6 @@ void CodeGen::genCallInstruction(GenTreePtr node) if (arg->OperGet() != GT_ARGPLACE && !(arg->gtFlags & GTF_LATE_ARG)) { #if defined(_TARGET_X86_) - assert((arg->OperGet() == GT_PUTARG_STK) || (arg->OperGet() == GT_LONG)); - if (arg->OperGet() == GT_LONG) - { - 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); @@ -4886,9 +4890,9 @@ void CodeGen::genCallInstruction(GenTreePtr node) stackArgBytes += argBytes; } else - { #endif // FEATURE_PUT_STRUCT_ARG_STK + { stackArgBytes += genTypeSize(genActualType(arg->TypeGet())); } } @@ -5001,6 +5005,20 @@ void CodeGen::genCallInstruction(GenTreePtr node) #endif // defined(_TARGET_X86_) +#ifdef FEATURE_AVX_SUPPORT + // When it's a PInvoke call and the call type is USER function, we issue VZEROUPPER here + // if the function contains 256bit AVX instructions, this is to avoid AVX-256 to Legacy SSE + // transition penalty, assuming the user function contains legacy SSE instruction. + // To limit code size increase impact: we only issue VZEROUPPER before PInvoke call, not issue + // VZEROUPPER after PInvoke call because transition penalty from legacy SSE to AVX only happens + // when there's preceding 256-bit AVX to legacy SSE transition penalty. + if (call->IsPInvoke() && (call->gtCallType == CT_USER_FUNC) && getEmitter()->Contains256bitAVX()) + { + assert(compiler->getSIMDInstructionSet() == InstructionSet_AVX); + instGen(INS_vzeroupper); + } +#endif + if (target != nullptr) { #ifdef _TARGET_X86_ @@ -5020,7 +5038,7 @@ void CodeGen::genCallInstruction(GenTreePtr node) assert(target->OperGet() == GT_IND); GenTree* addr = target->AsIndir()->Addr(); - assert(!addr->isContained()); + assert(addr->isUsedFromReg()); genConsumeReg(addr); genCopyRegIfNeeded(addr, REG_VIRTUAL_STUB_TARGET); @@ -5113,6 +5131,15 @@ void CodeGen::genCallInstruction(GenTreePtr node) retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), ilOffset); } +#if defined(UNIX_X86_ABI) + // Put back the stack pointer if there was any padding for stack alignment + unsigned padStackAlign = call->fgArgInfo->GetPadStackAlign(); + if (padStackAlign != 0) + { + inst_RV_IV(INS_add, REG_SPBASE, padStackAlign * TARGET_POINTER_SIZE, EA_PTRSIZE); + } +#endif // UNIX_X86_ABI + // if it was a pinvoke we may have needed to get the address of a label if (genPendingCallLabel) { @@ -6064,7 +6091,7 @@ void CodeGen::genCompareInt(GenTreePtr treeNode) GenTreePtr op2 = tree->gtOp2; var_types op1Type = op1->TypeGet(); var_types op2Type = op2->TypeGet(); - regNumber targetReg = treeNode->gtRegNum; + regNumber targetReg = tree->gtRegNum; // Case of op1 == 0 or op1 != 0: // Optimize generation of 'test' instruction if op1 sets flags. @@ -6081,7 +6108,7 @@ void CodeGen::genCompareInt(GenTreePtr treeNode) assert(realOp1->gtSetZSFlags()); // Must be (in)equality against zero. - assert(tree->OperGet() == GT_EQ || tree->OperGet() == GT_NE); + assert(tree->OperIs(GT_EQ, GT_NE)); assert(op2->IsIntegralConst(0)); assert(op2->isContained()); @@ -6105,7 +6132,7 @@ void CodeGen::genCompareInt(GenTreePtr treeNode) // If we have GT_JTRUE(GT_EQ/NE(GT_SIMD((in)Equality, v1, v2), true/false)), // then we don't need to generate code for GT_EQ/GT_NE, since SIMD (in)Equality intrinsic // would set or clear Zero flag. - if ((targetReg == REG_NA) && (tree->OperGet() == GT_EQ || tree->OperGet() == GT_NE)) + if ((targetReg == REG_NA) && tree->OperIs(GT_EQ, GT_NE)) { // Is it a SIMD (in)Equality that doesn't need to materialize result into a register? if ((op1->gtRegNum == REG_NA) && op1->IsSIMDEqualityOrInequality()) @@ -6124,128 +6151,67 @@ void CodeGen::genCompareInt(GenTreePtr treeNode) genConsumeOperands(tree); - instruction ins; - emitAttr cmpAttr; - // 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_ - assert(!varTypeIsLong(op1Type) && !varTypeIsLong(op2Type)); -#endif // _TARGET_X86_ - - // By default we use an int32 sized cmp instruction - // - ins = INS_cmp; - var_types cmpType = TYP_INT; - - // In the if/then/else statement below we may change the - // 'cmpType' and/or 'ins' to generate a smaller instruction + instruction ins; - // Are we comparing two values that are the same size? - // - if (genTypeSize(op1Type) == genTypeSize(op2Type)) + if (tree->OperIs(GT_TEST_EQ, GT_TEST_NE)) { - if (op1Type == op2Type) - { - // If both types are exactly the same we can use that type - cmpType = op1Type; - } - else if (genTypeSize(op1Type) == 8) - { - // If we have two different int64 types we need to use a long compare - cmpType = TYP_LONG; - } - - cmpAttr = emitTypeSize(cmpType); + ins = INS_test; } - else // Here we know that (op1Type != op2Type) + else if (op1->isUsedFromReg() && op2->IsIntegralConst(0)) { - // Do we have a short compare against a constant in op2? - // - // We checked for this case in TreeNodeInfoInitCmp() and if we can perform a small - // compare immediate we labeled this compare with a GTF_RELOP_SMALL - // and for unsigned small non-equality compares the GTF_UNSIGNED flag. - // - if (op2->isContainedIntOrIImmed() && ((tree->gtFlags & GTF_RELOP_SMALL) != 0)) - { - assert(varTypeIsSmall(op1Type)); - cmpType = op1Type; - } -#ifdef _TARGET_AMD64_ - else // compare two different sized operands - { - // For this case we don't want any memory operands, only registers or immediates - // - assert(!op1->isContainedMemoryOp()); - assert(!op2->isContainedMemoryOp()); + // We're comparing a register to 0 so we can generate "test reg1, reg1" + // instead of the longer "cmp reg1, 0" + ins = INS_test; + op2 = op1; + } + else + { + ins = INS_cmp; + } - // Check for the case where one operand is an int64 type - // Lower should have placed 32-bit operand in a register - // for signed comparisons we will sign extend the 32-bit value in place. - // - bool op1Is64Bit = (genTypeSize(op1Type) == 8); - bool op2Is64Bit = (genTypeSize(op2Type) == 8); - if (op1Is64Bit) - { - cmpType = TYP_LONG; - if (!(tree->gtFlags & GTF_UNSIGNED) && !op2Is64Bit) - { - assert(op2->gtRegNum != REG_NA); - inst_RV_RV(INS_movsxd, op2->gtRegNum, op2->gtRegNum, op2Type); - } - } - else if (op2Is64Bit) - { - cmpType = TYP_LONG; - if (!(tree->gtFlags & GTF_UNSIGNED) && !op1Is64Bit) - { - assert(op1->gtRegNum != REG_NA); - } - } - } -#endif // _TARGET_AMD64_ + var_types type; - cmpAttr = emitTypeSize(cmpType); + if (op1Type == op2Type) + { + type = op1Type; } - - // See if we can generate a "test" instruction instead of a "cmp". - // For this to generate the correct conditional branch we must have - // a compare against zero. - // - if (op2->IsIntegralConst(0)) + else if (genTypeSize(op1Type) == genTypeSize(op2Type)) { - if (op1->isContained()) - { - // op1 can be a contained memory op - // or the special contained GT_AND that we created in Lowering::TreeNodeInfoInitCmp() - // - if ((op1->OperGet() == GT_AND) && op1->gtGetOp2()->isContainedIntOrIImmed() && - ((tree->OperGet() == GT_EQ) || (tree->OperGet() == GT_NE))) - { - ins = INS_test; // we will generate "test andOp1, andOp2CnsVal" - op2 = op1->gtOp.gtOp2; // must assign op2 before we overwrite op1 - op1 = op1->gtOp.gtOp1; // overwrite op1 - - if (op1->isContainedMemoryOp()) - { - // use the size andOp1 if it is a contained memoryop. - cmpAttr = emitTypeSize(op1->TypeGet()); - } - // fallthrough to emit->emitInsBinary(ins, cmpAttr, op1, op2); - } - } - else // op1 is not contained thus it must be in a register - { - ins = INS_test; - op2 = op1; // we will generate "test reg1,reg1" - // fallthrough to emit->emitInsBinary(ins, cmpAttr, op1, op2); - } + // If the types are different but have the same size then we'll use TYP_INT or TYP_LONG. + // This primarily deals with small type mixes (e.g. byte/ubyte) that need to be widened + // and compared as int. We should not get long type mixes here but handle that as well + // just in case. + type = genTypeSize(op1Type) == 8 ? TYP_LONG : TYP_INT; } - - getEmitter()->emitInsBinary(ins, cmpAttr, op1, op2); + else + { + // In the types are different simply use TYP_INT. This deals with small type/int type + // mixes (e.g. byte/short ubyte/int) that need to be widened and compared as int. + // Lowering is expected to handle any mixes that involve long types (e.g. int/long). + type = TYP_INT; + } + + // The common type cannot be larger than the machine word size + assert(genTypeSize(type) <= genTypeSize(TYP_I_IMPL)); + // The common type cannot be smaller than any of the operand types, we're probably mixing int/long + assert(genTypeSize(type) >= max(genTypeSize(op1Type), genTypeSize(op2Type))); + // TYP_UINT and TYP_ULONG should not appear here, only small types can be unsigned + assert(!varTypeIsUnsigned(type) || varTypeIsSmall(type)); + // Small unsigned int types (TYP_BOOL can use anything) should use unsigned comparisons + assert(!(varTypeIsSmallInt(type) && varTypeIsUnsigned(type)) || ((tree->gtFlags & GTF_UNSIGNED) != 0)); + // If op1 is smaller then it cannot be in memory, we're probably missing a cast + assert((genTypeSize(op1Type) >= genTypeSize(type)) || !op1->isUsedFromMemory()); + // If op2 is smaller then it cannot be in memory, we're probably missing a cast + assert((genTypeSize(op2Type) >= genTypeSize(type)) || !op2->isUsedFromMemory()); + // If op2 is a constant then it should fit in the common type + assert(!op2->IsCnsIntOrI() || genTypeCanRepresentValue(type, op2->AsIntCon()->IconValue())); + + getEmitter()->emitInsBinary(ins, emitTypeSize(type), op1, op2); // Are we evaluating this into a register? if (targetReg != REG_NA) @@ -6810,7 +6776,7 @@ void CodeGen::genFloatToFloatCast(GenTreePtr treeNode) GenTreePtr op1 = treeNode->gtOp.gtOp1; #ifdef DEBUG // If not contained, must be a valid float reg. - if (!op1->isContained()) + if (op1->isUsedFromReg()) { assert(genIsValidFloatReg(op1->gtRegNum)); } @@ -6821,7 +6787,7 @@ void CodeGen::genFloatToFloatCast(GenTreePtr treeNode) assert(varTypeIsFloating(srcType) && varTypeIsFloating(dstType)); genConsumeOperands(treeNode->AsOp()); - if (srcType == dstType && (!op1->isContained() && (targetReg == op1->gtRegNum))) + if (srcType == dstType && (op1->isUsedFromReg() && (targetReg == op1->gtRegNum))) { // source and destinations types are the same and also reside in the same register. // we just need to consume and produce the reg in this case. @@ -6861,7 +6827,7 @@ void CodeGen::genIntToFloatCast(GenTreePtr treeNode) GenTreePtr op1 = treeNode->gtOp.gtOp1; #ifdef DEBUG - if (!op1->isContained()) + if (op1->isUsedFromReg()) { assert(genIsValidIntReg(op1->gtRegNum)); } @@ -6936,7 +6902,7 @@ void CodeGen::genIntToFloatCast(GenTreePtr treeNode) // If we change the instructions below, FloatingPointUtils::convertUInt64ToDobule // should be also updated for consistent conversion result. assert(dstType == TYP_DOUBLE); - assert(!op1->isContained()); + assert(op1->isUsedFromReg()); // Set the flags without modifying op1. // test op1Reg, op1Reg @@ -6995,7 +6961,7 @@ void CodeGen::genFloatToIntCast(GenTreePtr treeNode) GenTreePtr op1 = treeNode->gtOp.gtOp1; #ifdef DEBUG - if (!op1->isContained()) + if (op1->isUsedFromReg()) { assert(genIsValidFloatReg(op1->gtRegNum)); } @@ -7374,7 +7340,7 @@ void CodeGen::genSSE2BitwiseOp(GenTreePtr treeNode) // Move operand into targetReg only if the reg reserved for // internal purpose is not the same as targetReg. GenTreePtr op1 = treeNode->gtOp.gtOp1; - assert(!op1->isContained()); + assert(op1->isUsedFromReg()); regNumber operandReg = genConsumeReg(op1); if (tmpReg != targetReg) { @@ -7497,7 +7463,7 @@ unsigned CodeGen::getBaseVarForPutArgStk(GenTreePtr treeNode) #ifdef _TARGET_X86_ //--------------------------------------------------------------------- -// adjustStackForPutArgStk: +// genAdjustStackForPutArgStk: // adjust the stack pointer for a putArgStk node if necessary. // // Arguments: @@ -7505,6 +7471,12 @@ unsigned CodeGen::getBaseVarForPutArgStk(GenTreePtr treeNode) // // Returns: true if the stack pointer was adjusted; false otherwise. // +// Notes: +// Sets `m_pushStkArg` to true if the stack arg needs to be pushed, +// false if the stack arg needs to be stored at the current stack +// pointer address. This is exactly the opposite of the return value +// of this function. +// bool CodeGen::genAdjustStackForPutArgStk(GenTreePutArgStk* putArgStk) { #ifdef FEATURE_SIMD @@ -7562,11 +7534,10 @@ bool CodeGen::genAdjustStackForPutArgStk(GenTreePutArgStk* putArgStk) } //--------------------------------------------------------------------- -// genPutArgStkFieldList - generate code for passing an arg on the stack. +// genPutArgStkFieldList - generate code for passing a GT_FIELD_LIST arg on the stack. // // Arguments -// treeNode - the GT_PUTARG_STK node -// targetType - the type of the treeNode +// treeNode - the GT_PUTARG_STK node whose op1 is a GT_FIELD_LIST // // Return value: // None @@ -7578,24 +7549,36 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) // Set m_pushStkArg and pre-adjust the stack if necessary. const bool preAdjustedStack = genAdjustStackForPutArgStk(putArgStk); + // For now, we only support the "push" case; we will push a full slot for the first field of each slot // within the struct. assert((putArgStk->isPushKind()) && !preAdjustedStack && m_pushStkArg); - // If we have pre-adjusted the stack and are simply storing the fields in order) set the offset to 0. + // If we have pre-adjusted the stack and are simply storing the fields in order, set the offset to 0. // (Note that this mode is not currently being used.) // If we are pushing the arguments (i.e. we have not pre-adjusted the stack), then we are pushing them // in reverse order, so we start with the current field offset at the size of the struct arg (which must be // a multiple of the target pointer size). unsigned currentOffset = (preAdjustedStack) ? 0 : putArgStk->getArgSize(); unsigned prevFieldOffset = currentOffset; - regNumber tmpReg = REG_NA; + regNumber intTmpReg = REG_NA; + regNumber simdTmpReg = REG_NA; if (putArgStk->gtRsvdRegs != RBM_NONE) { - assert(genCountBits(putArgStk->gtRsvdRegs) == 1); - tmpReg = genRegNumFromMask(putArgStk->gtRsvdRegs); - assert(genIsValidIntReg(tmpReg)); + regMaskTP rsvdRegs = putArgStk->gtRsvdRegs; + if ((rsvdRegs & RBM_ALLINT) != 0) + { + intTmpReg = genRegNumFromMask(rsvdRegs & RBM_ALLINT); + assert(genIsValidIntReg(intTmpReg)); + } + if ((rsvdRegs & RBM_ALLFLOAT) != 0) + { + simdTmpReg = genRegNumFromMask(rsvdRegs & RBM_ALLFLOAT); + assert(genIsValidFloatReg(simdTmpReg)); + } + assert(genCountBits(rsvdRegs) == (unsigned)((intTmpReg == REG_NA) ? 0 : 1) + ((simdTmpReg == REG_NA) ? 0 : 1)); } + for (GenTreeFieldList* current = fieldList; current != nullptr; current = current->Rest()) { GenTree* const fieldNode = current->Current(); @@ -7612,7 +7595,7 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) // assigned a register, and which is therefore contained. // Unlike genConsumeReg(), it handles the case where no registers are being consumed. genConsumeRegs(fieldNode); - regNumber argReg = fieldNode->isContainedSpillTemp() ? REG_NA : fieldNode->gtRegNum; + regNumber argReg = fieldNode->isUsedFromSpillTemp() ? REG_NA : fieldNode->gtRegNum; // If the field is slot-like, we can use a push instruction to store the entire register no matter the type. // @@ -7623,7 +7606,7 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) // able to detect stores into the outgoing argument area of the stack on x86. const bool fieldIsSlot = ((fieldOffset % 4) == 0) && ((prevFieldOffset - fieldOffset) >= 4); int adjustment = roundUp(currentOffset - fieldOffset, 4); - if (fieldIsSlot) + if (fieldIsSlot && !varTypeIsSIMD(fieldType)) { fieldType = genActualType(fieldType); unsigned pushSize = genTypeSize(fieldType); @@ -7641,12 +7624,13 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) else { m_pushStkArg = false; + // We always "push" floating point fields (i.e. they are full slot values that don't // require special handling). - assert(varTypeIsIntegralOrI(fieldNode)); + assert(varTypeIsIntegralOrI(fieldNode) || varTypeIsSIMD(fieldNode)); + // If we can't push this field, it needs to be in a register so that we can store // it to the stack location. - assert(tmpReg != REG_NA); if (adjustment != 0) { // This moves the stack pointer to fieldOffset. @@ -7658,15 +7642,16 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) } // Does it need to be in a byte register? - // If so, we'll use tmpReg, which must have been allocated as a byte register. + // If so, we'll use intTmpReg, which must have been allocated as a byte register. // If it's already in a register, but not a byteable one, then move it. if (varTypeIsByte(fieldType) && ((argReg == REG_NA) || ((genRegMask(argReg) & RBM_BYTE_REGS) == 0))) { - noway_assert((genRegMask(tmpReg) & RBM_BYTE_REGS) != 0); + assert(intTmpReg != REG_NA); + noway_assert((genRegMask(intTmpReg) & RBM_BYTE_REGS) != 0); if (argReg != REG_NA) { - inst_RV_RV(INS_mov, tmpReg, argReg, fieldType); - argReg = tmpReg; + inst_RV_RV(INS_mov, intTmpReg, argReg, fieldType); + argReg = intTmpReg; } } } @@ -7675,8 +7660,9 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) { if (m_pushStkArg) { - if (fieldNode->isContainedSpillTemp()) + if (fieldNode->isUsedFromSpillTemp()) { + assert(!varTypeIsSIMD(fieldType)); // Q: can we get here with SIMD? assert(fieldNode->IsRegOptional()); TempDsc* tmp = getSpillTempDsc(fieldNode); getEmitter()->emitIns_S(INS_push, emitActualTypeSize(fieldNode->TypeGet()), tmp->tdTempNum(), 0); @@ -7709,25 +7695,35 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk) } else { - // The stack has been adjusted and we will load the field to tmpReg and then store it on the stack. + // The stack has been adjusted and we will load the field to intTmpReg and then store it on the stack. assert(varTypeIsIntegralOrI(fieldNode)); switch (fieldNode->OperGet()) { case GT_LCL_VAR: - inst_RV_TT(INS_mov, tmpReg, fieldNode); + inst_RV_TT(INS_mov, intTmpReg, fieldNode); break; case GT_CNS_INT: - genSetRegToConst(tmpReg, fieldNode->TypeGet(), fieldNode); + genSetRegToConst(intTmpReg, fieldNode->TypeGet(), fieldNode); break; default: unreached(); } - genStoreRegToStackArg(fieldType, tmpReg, fieldOffset - currentOffset); + genStoreRegToStackArg(fieldType, intTmpReg, fieldOffset - currentOffset); } } else { - genStoreRegToStackArg(fieldType, argReg, fieldOffset - currentOffset); +#if defined(_TARGET_X86_) && defined(FEATURE_SIMD) + if (fieldType == TYP_SIMD12) + { + assert(genIsValidFloatReg(simdTmpReg)); + genStoreSIMD12ToStack(argReg, simdTmpReg); + } + else +#endif // defined(_TARGET_X86_) && defined(FEATURE_SIMD) + { + genStoreRegToStackArg(fieldType, argReg, fieldOffset - currentOffset); + } if (m_pushStkArg) { // We always push a slot-rounded size @@ -7762,13 +7758,15 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* putArgStk) #ifdef _TARGET_X86_ -#ifdef FEATURE_SIMD - if (targetType == TYP_SIMD12) +#if defined(UNIX_X86_ABI) + // For each call, first stack argument has the padding for alignment + // if this value is not zero, use it to adjust the ESP + unsigned argPadding = putArgStk->getArgPadding(); + if (argPadding != 0) { - genPutArgStkSIMD12(putArgStk); - return; + inst_RV_IV(INS_sub, REG_SPBASE, argPadding * TARGET_POINTER_SIZE, EA_PTRSIZE); } -#endif // FEATURE_SIMD +#endif if (varTypeIsStruct(targetType)) { @@ -7782,9 +7780,9 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* putArgStk) GenTreePtr data = putArgStk->gtOp1; - // On a 32-bit target, all of the long arguments have been decomposed into - // a separate putarg_stk for each of the upper and lower halves. - noway_assert(targetType != TYP_LONG); + // On a 32-bit target, all of the long arguments are handled with GT_FIELD_LIST, + // and the type of the putArgStk is TYP_VOID. + assert(targetType != TYP_LONG); const unsigned argSize = putArgStk->getArgSize(); assert((argSize % TARGET_POINTER_SIZE) == 0); @@ -7808,7 +7806,7 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* putArgStk) else { // We should not see any contained nodes that are not immediates. - assert(!data->isContained()); + assert(data->isUsedFromReg()); genConsumeReg(data); genPushReg(targetType, data->gtRegNum); } @@ -7844,13 +7842,14 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* putArgStk) GenTreePtr data = putArgStk->gtOp1; - if (data->isContained()) + if (data->isContainedIntOrIImmed()) { getEmitter()->emitIns_S_I(ins_Store(targetType), emitTypeSize(targetType), baseVarNum, argOffset, (int)data->AsIntConCommon()->IconValue()); } else { + assert(data->isUsedFromReg()); genConsumeReg(data); getEmitter()->emitIns_S_R(ins_Store(targetType), emitTypeSize(targetType), data->gtRegNum, baseVarNum, argOffset); @@ -7996,6 +7995,14 @@ void CodeGen::genPutStructArgStk(GenTreePutArgStk* putArgStk) { var_types targetType = putArgStk->TypeGet(); +#if defined(_TARGET_X86_) && defined(FEATURE_SIMD) + if (targetType == TYP_SIMD12) + { + genPutArgStkSIMD12(putArgStk); + return; + } +#endif // defined(_TARGET_X86_) && defined(FEATURE_SIMD) + if (varTypeIsSIMD(targetType)) { regNumber srcReg = genConsumeReg(putArgStk->gtGetOp1()); @@ -8078,7 +8085,7 @@ void CodeGen::genPutStructArgStk(GenTreePutArgStk* putArgStk) slotAttr = EA_BYREF; } - const unsigned offset = i * 4; + const unsigned offset = i * TARGET_POINTER_SIZE; if (srcAddrInReg) { getEmitter()->emitIns_AR_R(INS_push, slotAttr, REG_NA, srcRegNum, offset); @@ -8087,7 +8094,7 @@ void CodeGen::genPutStructArgStk(GenTreePutArgStk* putArgStk) { getEmitter()->emitIns_S(INS_push, slotAttr, srcLclNum, srcLclOffset + offset); } - genStackLevel += 4; + genStackLevel += TARGET_POINTER_SIZE; } #else // !defined(_TARGET_X86_) @@ -8175,11 +8182,11 @@ void CodeGen::genPutStructArgStk(GenTreePutArgStk* putArgStk) * * Create and record GC Info for the function. */ -#ifdef _TARGET_AMD64_ +#ifndef JIT32_GCENCODER void -#else // !_TARGET_AMD64_ +#else // !JIT32_GCENCODER void* -#endif // !_TARGET_AMD64_ +#endif // !JIT32_GCENCODER CodeGen::genCreateAndStoreGCInfo(unsigned codeSize, unsigned prologSize, unsigned epilogSize DEBUGARG(void* codePtr)) { #ifdef JIT32_GCENCODER @@ -8381,6 +8388,14 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize gcInfoEncoder->SetSizeOfEditAndContinuePreservedArea(preservedAreaSize); } + if (compiler->opts.IsReversePInvoke()) + { + unsigned reversePInvokeFrameVarNumber = compiler->lvaReversePInvokeFrameVar; + assert(reversePInvokeFrameVarNumber != BAD_VAR_NUM && reversePInvokeFrameVarNumber < compiler->lvaRefCount); + LclVarDsc& reversePInvokeFrameVar = compiler->lvaTable[reversePInvokeFrameVarNumber]; + gcInfoEncoder->SetReversePInvokeFrameSlot(reversePInvokeFrameVar.lvStkOffs); + } + gcInfoEncoder->Build(); // GC Encoder automatically puts the GC info in the right spot using ICorJitInfo::allocGCInfo(size_t) |