diff options
Diffstat (limited to 'src/jit')
-rw-r--r-- | src/jit/codegenarm.cpp | 9 | ||||
-rw-r--r-- | src/jit/codegenarm64.cpp | 154 | ||||
-rw-r--r-- | src/jit/codegenarmarch.cpp | 210 | ||||
-rw-r--r-- | src/jit/compiler.h | 19 | ||||
-rw-r--r-- | src/jit/earlyprop.cpp | 28 | ||||
-rw-r--r-- | src/jit/emit.cpp | 12 | ||||
-rw-r--r-- | src/jit/emit.h | 9 | ||||
-rw-r--r-- | src/jit/emitarm64.cpp | 122 | ||||
-rw-r--r-- | src/jit/emitarm64.h | 15 | ||||
-rw-r--r-- | src/jit/gentree.cpp | 10 | ||||
-rw-r--r-- | src/jit/importer.cpp | 2 | ||||
-rw-r--r-- | src/jit/lir.cpp | 139 | ||||
-rw-r--r-- | src/jit/lowerarmarch.cpp | 3 | ||||
-rw-r--r-- | src/jit/lsraarm.cpp | 16 | ||||
-rw-r--r-- | src/jit/lsraarm64.cpp | 4 | ||||
-rw-r--r-- | src/jit/lsraarmarch.cpp | 6 | ||||
-rw-r--r-- | src/jit/lsraxarch.cpp | 17 | ||||
-rw-r--r-- | src/jit/morph.cpp | 3 | ||||
-rw-r--r-- | src/jit/target.h | 7 | ||||
-rw-r--r-- | src/jit/utils.cpp | 28 | ||||
-rw-r--r-- | src/jit/valuenum.cpp | 12 |
21 files changed, 517 insertions, 308 deletions
diff --git a/src/jit/codegenarm.cpp b/src/jit/codegenarm.cpp index 1a94789d19..bc91b5d8d2 100644 --- a/src/jit/codegenarm.cpp +++ b/src/jit/codegenarm.cpp @@ -772,15 +772,6 @@ instruction CodeGen::genGetInsForOper(genTreeOps oper, var_types type) return ins; } -// Generates CpBlk code by performing a loop unroll -// Preconditions: -// The size argument of the CpBlk node is a constant and <= 64 bytes. -// This may seem small but covers >95% of the cases in several framework assemblies. -void CodeGen::genCodeForCpBlkUnroll(GenTreeBlk* cpBlkNode) -{ - NYI_ARM("genCodeForCpBlkUnroll"); -} - // Generate code for InitBlk by performing a loop unroll // Preconditions: // a) Both the size and fill byte value are integer constants. diff --git a/src/jit/codegenarm64.cpp b/src/jit/codegenarm64.cpp index 9c9f82369f..4ecba7da00 100644 --- a/src/jit/codegenarm64.cpp +++ b/src/jit/codegenarm64.cpp @@ -1292,7 +1292,7 @@ BasicBlock* CodeGen::genCallFinally(BasicBlock* block) if ((block->bbNext == nullptr) || !BasicBlock::sameEHRegion(block, block->bbNext)) { - instGen(INS_BREAKPOINT); // This should never get executed + instGen(INS_bkpt); // This should never get executed } } else @@ -1849,7 +1849,7 @@ void CodeGen::genLclHeap(GenTreePtr tree) BasicBlock* esp_check = genCreateTempLabel(); emitJumpKind jmpEqual = genJumpKindForOper(GT_EQ, CK_SIGNED); inst_JMP(jmpEqual, esp_check); - getEmitter()->emitIns(INS_BREAKPOINT); + getEmitter()->emitIns(INS_bkpt); genDefineTempLabel(esp_check); } #endif @@ -1888,7 +1888,7 @@ void CodeGen::genLclHeap(GenTreePtr tree) // If 0 bail out by returning null in targetReg genConsumeRegAndCopy(size, targetReg); endLabel = genCreateTempLabel(); - getEmitter()->emitIns_R_R(INS_TEST, easz, targetReg, targetReg); + getEmitter()->emitIns_R_R(INS_tst, easz, targetReg, targetReg); emitJumpKind jmpEqual = genJumpKindForOper(GT_EQ, CK_SIGNED); inst_JMP(jmpEqual, endLabel); @@ -1912,7 +1912,7 @@ void CodeGen::genLclHeap(GenTreePtr tree) // Align to STACK_ALIGN // regCnt will be the total number of bytes to localloc inst_RV_IV(INS_add, regCnt, (STACK_ALIGN - 1), emitActualTypeSize(type)); - inst_RV_IV(INS_AND, regCnt, ~(STACK_ALIGN - 1), emitActualTypeSize(type)); + inst_RV_IV(INS_and, regCnt, ~(STACK_ALIGN - 1), emitActualTypeSize(type)); } stackAdjustment = 0; @@ -2379,25 +2379,6 @@ void CodeGen::genCodeForInitBlkUnroll(GenTreeBlk* initBlkNode) } } -// Generate code for a load from some address + offset -// base: tree node which can be either a local address or arbitrary node -// offset: distance from the base from which to load -void CodeGen::genCodeForLoadOffset(instruction ins, emitAttr size, regNumber dst, GenTree* base, unsigned offset) -{ - emitter* emit = getEmitter(); - - if (base->OperIsLocalAddr()) - { - if (base->gtOper == GT_LCL_FLD_ADDR) - offset += base->gtLclFld.gtLclOffs; - emit->emitIns_R_S(ins, size, dst, base->gtLclVarCommon.gtLclNum, offset); - } - else - { - emit->emitIns_R_R_I(ins, size, dst, base->gtRegNum, offset); - } -} - // Generate code for a load pair from some address + offset // base: tree node which can be either a local address or arbitrary node // offset: distance from the base from which to load @@ -2418,25 +2399,6 @@ void CodeGen::genCodeForLoadPairOffset(regNumber dst, regNumber dst2, GenTree* b } } -// Generate code for a store to some address + offset -// base: tree node which can be either a local address or arbitrary node -// offset: distance from the base from which to load -void CodeGen::genCodeForStoreOffset(instruction ins, emitAttr size, regNumber src, GenTree* base, unsigned offset) -{ - emitter* emit = getEmitter(); - - if (base->OperIsLocalAddr()) - { - if (base->gtOper == GT_LCL_FLD_ADDR) - offset += base->gtLclFld.gtLclOffs; - emit->emitIns_S_R(ins, size, src, base->gtLclVarCommon.gtLclNum, offset); - } - else - { - emit->emitIns_R_R_I(ins, size, src, base->gtRegNum, offset); - } -} - // Generate code for a store pair to some address + offset // base: tree node which can be either a local address or arbitrary node // offset: distance from the base from which to load @@ -2457,114 +2419,6 @@ void CodeGen::genCodeForStorePairOffset(regNumber src, regNumber src2, GenTree* } } -// Generates CpBlk code by performing a loop unroll -// Preconditions: -// The size argument of the CpBlk node is a constant and <= 64 bytes. -// This may seem small but covers >95% of the cases in several framework assemblies. -void CodeGen::genCodeForCpBlkUnroll(GenTreeBlk* cpBlkNode) -{ - // Make sure we got the arguments of the cpblk operation in the right registers - unsigned size = cpBlkNode->Size(); - GenTreePtr dstAddr = cpBlkNode->Addr(); - GenTreePtr source = cpBlkNode->Data(); - GenTreePtr srcAddr = nullptr; - - assert((size != 0) && (size <= CPBLK_UNROLL_LIMIT)); - - emitter* emit = getEmitter(); - - if (dstAddr->isUsedFromReg()) - { - genConsumeReg(dstAddr); - } - - if (cpBlkNode->gtFlags & GTF_BLK_VOLATILE) - { - // issue a full memory barrier before a volatile CpBlkUnroll operation - instGen_MemoryBarrier(); - } - - if (source->gtOper == GT_IND) - { - srcAddr = source->gtGetOp1(); - if (srcAddr->isUsedFromReg()) - { - genConsumeReg(srcAddr); - } - } - else - { - noway_assert(source->IsLocal()); - // TODO-Cleanup: Consider making the addrForm() method in Rationalize public, e.g. in GenTree. - // OR: transform source to GT_IND(GT_LCL_VAR_ADDR) - if (source->OperGet() == GT_LCL_VAR) - { - source->SetOper(GT_LCL_VAR_ADDR); - } - else - { - assert(source->OperGet() == GT_LCL_FLD); - source->SetOper(GT_LCL_FLD_ADDR); - } - srcAddr = source; - } - - unsigned offset = 0; - - // Grab the integer temp register to emit the loads and stores. - regNumber tmpReg = cpBlkNode->ExtractTempReg(RBM_ALLINT); - - if (size >= 2 * REGSIZE_BYTES) - { - regNumber tmp2Reg = cpBlkNode->ExtractTempReg(RBM_ALLINT); - - size_t slots = size / (2 * REGSIZE_BYTES); - - while (slots-- > 0) - { - // Load - genCodeForLoadPairOffset(tmpReg, tmp2Reg, srcAddr, offset); - // Store - genCodeForStorePairOffset(tmpReg, tmp2Reg, dstAddr, offset); - offset += 2 * REGSIZE_BYTES; - } - } - - // Fill the remainder (15 bytes or less) if there's one. - if ((size & 0xf) != 0) - { - if ((size & 8) != 0) - { - genCodeForLoadOffset(INS_ldr, EA_8BYTE, tmpReg, srcAddr, offset); - genCodeForStoreOffset(INS_str, EA_8BYTE, tmpReg, dstAddr, offset); - offset += 8; - } - if ((size & 4) != 0) - { - genCodeForLoadOffset(INS_ldr, EA_4BYTE, tmpReg, srcAddr, offset); - genCodeForStoreOffset(INS_str, EA_4BYTE, tmpReg, dstAddr, offset); - offset += 4; - } - if ((size & 2) != 0) - { - genCodeForLoadOffset(INS_ldrh, EA_2BYTE, tmpReg, srcAddr, offset); - genCodeForStoreOffset(INS_strh, EA_2BYTE, tmpReg, dstAddr, offset); - offset += 2; - } - if ((size & 1) != 0) - { - genCodeForLoadOffset(INS_ldrb, EA_1BYTE, tmpReg, srcAddr, offset); - genCodeForStoreOffset(INS_strb, EA_1BYTE, tmpReg, dstAddr, offset); - } - } - - if (cpBlkNode->gtFlags & GTF_BLK_VOLATILE) - { - // issue a INS_BARRIER_ISHLD after a volatile CpBlkUnroll operation - instGen_MemoryBarrier(INS_BARRIER_ISHLD); - } -} - // Generate code for CpObj nodes wich copy structs that have interleaved // GC pointers. // For this case we'll generate a sequence of loads/stores in the case of struct diff --git a/src/jit/codegenarmarch.cpp b/src/jit/codegenarmarch.cpp index b7b9312cb5..0c609b222d 100644 --- a/src/jit/codegenarmarch.cpp +++ b/src/jit/codegenarmarch.cpp @@ -246,8 +246,6 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) genCodeForJumpTrue(treeNode); break; -#ifdef _TARGET_ARM_ - case GT_JCC: genCodeForJcc(treeNode->AsCC()); break; @@ -256,8 +254,6 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) genCodeForSetcc(treeNode->AsCC()); break; -#endif // _TARGET_ARM_ - case GT_RETURNTRAP: genCodeForReturnTrap(treeNode->AsOp()); break; @@ -449,7 +445,7 @@ void CodeGen::genIntrinsic(GenTreePtr treeNode) assert(varTypeIsFloating(srcNode)); assert(srcNode->TypeGet() == treeNode->TypeGet()); - // Right now only Abs/Round/Sqrt are treated as math intrinsics. + // Right now only Abs/Ceiling/Floor/Round/Sqrt are treated as math intrinsics. // switch (treeNode->gtIntrinsic.gtIntrinsicId) { @@ -458,6 +454,17 @@ void CodeGen::genIntrinsic(GenTreePtr treeNode) getEmitter()->emitInsBinary(INS_ABS, emitTypeSize(treeNode), treeNode, srcNode); break; +#ifdef _TARGET_ARM64_ + case CORINFO_INTRINSIC_Ceiling: + genConsumeOperands(treeNode->AsOp()); + getEmitter()->emitInsBinary(INS_frintp, emitActualTypeSize(treeNode), treeNode, srcNode); + break; + + case CORINFO_INTRINSIC_Floor: + genConsumeOperands(treeNode->AsOp()); + getEmitter()->emitInsBinary(INS_frintm, emitActualTypeSize(treeNode), treeNode, srcNode); + break; +#endif case CORINFO_INTRINSIC_Round: NYI_ARM("genIntrinsic for round - not implemented yet"); genConsumeOperands(treeNode->AsOp()); @@ -1760,6 +1767,153 @@ void CodeGen::genCodeForCpBlk(GenTreeBlk* cpBlkNode) } } +//---------------------------------------------------------------------------------- +// genCodeForCpBlkUnroll: Generates CpBlk code by performing a loop unroll +// +// Arguments: +// cpBlkNode - Copy block node +// +// Return Value: +// None +// +// Assumption: +// The size argument of the CpBlk node is a constant and <= CPBLK_UNROLL_LIMIT bytes. +// +void CodeGen::genCodeForCpBlkUnroll(GenTreeBlk* cpBlkNode) +{ + // Make sure we got the arguments of the cpblk operation in the right registers + unsigned size = cpBlkNode->Size(); + GenTreePtr dstAddr = cpBlkNode->Addr(); + GenTreePtr source = cpBlkNode->Data(); + GenTreePtr srcAddr = nullptr; + + assert((size != 0) && (size <= CPBLK_UNROLL_LIMIT)); + + emitter* emit = getEmitter(); + + if (dstAddr->isUsedFromReg()) + { + genConsumeReg(dstAddr); + } + + if (cpBlkNode->gtFlags & GTF_BLK_VOLATILE) + { + // issue a full memory barrier before a volatile CpBlkUnroll operation + instGen_MemoryBarrier(); + } + + if (source->gtOper == GT_IND) + { + srcAddr = source->gtGetOp1(); + if (srcAddr->isUsedFromReg()) + { + genConsumeReg(srcAddr); + } + } + else + { + noway_assert(source->IsLocal()); + // TODO-Cleanup: Consider making the addrForm() method in Rationalize public, e.g. in GenTree. + // OR: transform source to GT_IND(GT_LCL_VAR_ADDR) + if (source->OperGet() == GT_LCL_VAR) + { + source->SetOper(GT_LCL_VAR_ADDR); + } + else + { + assert(source->OperGet() == GT_LCL_FLD); + source->SetOper(GT_LCL_FLD_ADDR); + } + srcAddr = source; + } + + unsigned offset = 0; + + // Grab the integer temp register to emit the loads and stores. + regNumber tmpReg = cpBlkNode->ExtractTempReg(RBM_ALLINT); + +#ifdef _TARGET_ARM64_ + if (size >= 2 * REGSIZE_BYTES) + { + regNumber tmp2Reg = cpBlkNode->ExtractTempReg(RBM_ALLINT); + + size_t slots = size / (2 * REGSIZE_BYTES); + + while (slots-- > 0) + { + // Load + genCodeForLoadPairOffset(tmpReg, tmp2Reg, srcAddr, offset); + // Store + genCodeForStorePairOffset(tmpReg, tmp2Reg, dstAddr, offset); + offset += 2 * REGSIZE_BYTES; + } + } + + // Fill the remainder (15 bytes or less) if there's one. + if ((size & 0xf) != 0) + { + if ((size & 8) != 0) + { + genCodeForLoadOffset(INS_ldr, EA_8BYTE, tmpReg, srcAddr, offset); + genCodeForStoreOffset(INS_str, EA_8BYTE, tmpReg, dstAddr, offset); + offset += 8; + } + if ((size & 4) != 0) + { + genCodeForLoadOffset(INS_ldr, EA_4BYTE, tmpReg, srcAddr, offset); + genCodeForStoreOffset(INS_str, EA_4BYTE, tmpReg, dstAddr, offset); + offset += 4; + } + if ((size & 2) != 0) + { + genCodeForLoadOffset(INS_ldrh, EA_2BYTE, tmpReg, srcAddr, offset); + genCodeForStoreOffset(INS_strh, EA_2BYTE, tmpReg, dstAddr, offset); + offset += 2; + } + if ((size & 1) != 0) + { + genCodeForLoadOffset(INS_ldrb, EA_1BYTE, tmpReg, srcAddr, offset); + genCodeForStoreOffset(INS_strb, EA_1BYTE, tmpReg, dstAddr, offset); + } + } +#else // !_TARGET_ARM64_ + size_t slots = size / REGSIZE_BYTES; + while (slots-- > 0) + { + genCodeForLoadOffset(INS_ldr, EA_4BYTE, tmpReg, srcAddr, offset); + genCodeForStoreOffset(INS_str, EA_4BYTE, tmpReg, dstAddr, offset); + offset += REGSIZE_BYTES; + } + + // Fill the remainder (3 bytes or less) if there's one. + if ((size & 0x03) != 0) + { + if ((size & 2) != 0) + { + genCodeForLoadOffset(INS_ldrh, EA_2BYTE, tmpReg, srcAddr, offset); + genCodeForStoreOffset(INS_strh, EA_2BYTE, tmpReg, dstAddr, offset); + offset += 2; + } + if ((size & 1) != 0) + { + genCodeForLoadOffset(INS_ldrb, EA_1BYTE, tmpReg, srcAddr, offset); + genCodeForStoreOffset(INS_strb, EA_1BYTE, tmpReg, dstAddr, offset); + } + } +#endif // !_TARGET_ARM64_ + + if (cpBlkNode->gtFlags & GTF_BLK_VOLATILE) + { +#ifdef _TARGET_ARM64_ + // issue a INS_BARRIER_ISHLD after a volatile CpBlkUnroll operation + instGen_MemoryBarrier(INS_BARRIER_ISHLD); +#else + // issue a full memory barrier after a volatile CpBlk operation + instGen_MemoryBarrier(); +#endif // !_TARGET_ARM64_ + } +} + // Generates code for InitBlk by calling the VM memset helper function. // Preconditions: // a) The size argument of the InitBlk is not an integer constant. @@ -1804,6 +1958,44 @@ void CodeGen::genCodeForInitBlk(GenTreeBlk* initBlkNode) genEmitHelperCall(CORINFO_HELP_MEMSET, 0, EA_UNKNOWN); } +// Generate code for a load from some address + offset +// base: tree node which can be either a local address or arbitrary node +// offset: distance from the base from which to load +void CodeGen::genCodeForLoadOffset(instruction ins, emitAttr size, regNumber dst, GenTree* base, unsigned offset) +{ + emitter* emit = getEmitter(); + + if (base->OperIsLocalAddr()) + { + if (base->gtOper == GT_LCL_FLD_ADDR) + offset += base->gtLclFld.gtLclOffs; + emit->emitIns_R_S(ins, size, dst, base->gtLclVarCommon.gtLclNum, offset); + } + else + { + emit->emitIns_R_R_I(ins, size, dst, base->gtRegNum, offset); + } +} + +// Generate code for a store to some address + offset +// base: tree node which can be either a local address or arbitrary node +// offset: distance from the base from which to load +void CodeGen::genCodeForStoreOffset(instruction ins, emitAttr size, regNumber src, GenTree* base, unsigned offset) +{ + emitter* emit = getEmitter(); + + if (base->OperIsLocalAddr()) + { + if (base->gtOper == GT_LCL_FLD_ADDR) + offset += base->gtLclFld.gtLclOffs; + emit->emitIns_S_R(ins, size, src, base->gtLclVarCommon.gtLclNum, offset); + } + else + { + emit->emitIns_R_R_I(ins, size, src, base->gtRegNum, offset); + } +} + //------------------------------------------------------------------------ // genRegCopy: Generate a register copy. // @@ -3099,8 +3291,6 @@ void CodeGen::genCodeForJumpTrue(GenTreePtr tree) } } -#if defined(_TARGET_ARM_) - //------------------------------------------------------------------------ // genCodeForJcc: Produce code for a GT_JCC node. // @@ -3140,6 +3330,9 @@ void CodeGen::genCodeForSetcc(GenTreeCC* setcc) // Make sure nobody is setting GTF_RELOP_NAN_UN on this node as it is ignored. assert((setcc->gtFlags & GTF_RELOP_NAN_UN) == 0); +#ifdef _TARGET_ARM64_ + inst_SET(jumpKind, dstReg); +#else // Emit code like that: // ... // bgt True @@ -3161,12 +3354,11 @@ void CodeGen::genCodeForSetcc(GenTreeCC* setcc) genDefineTempLabel(labelTrue); getEmitter()->emitIns_R_I(INS_mov, emitActualTypeSize(setcc->TypeGet()), dstReg, 1); genDefineTempLabel(labelNext); +#endif genProduceReg(setcc); } -#endif // defined(_TARGET_ARM_) - //------------------------------------------------------------------------ // genCodeForStoreBlk: Produce code for a GT_STORE_OBJ/GT_STORE_DYN_BLK/GT_STORE_BLK node. // diff --git a/src/jit/compiler.h b/src/jit/compiler.h index 0997154b65..5c1438ee00 100644 --- a/src/jit/compiler.h +++ b/src/jit/compiler.h @@ -10287,23 +10287,8 @@ const instruction INS_SQRT = INS_vsqrt; #ifdef _TARGET_ARM64_ -const instruction INS_SHIFT_LEFT_LOGICAL = INS_lsl; -const instruction INS_SHIFT_RIGHT_LOGICAL = INS_lsr; -const instruction INS_SHIFT_RIGHT_ARITHM = INS_asr; - -const instruction INS_AND = INS_and; -const instruction INS_OR = INS_orr; -const instruction INS_XOR = INS_eor; -const instruction INS_NEG = INS_neg; -const instruction INS_TEST = INS_tst; -const instruction INS_MUL = INS_mul; -const instruction INS_MULADD = INS_madd; -const instruction INS_SIGNED_DIVIDE = INS_sdiv; -const instruction INS_UNSIGNED_DIVIDE = INS_udiv; -const instruction INS_BREAKPOINT = INS_bkpt; -const instruction INS_ADDC = INS_adc; -const instruction INS_SUBC = INS_sbc; -const instruction INS_NOT = INS_mvn; +const instruction INS_MULADD = INS_madd; +const instruction INS_BREAKPOINT = INS_bkpt; const instruction INS_ABS = INS_fabs; const instruction INS_ROUND = INS_frintn; diff --git a/src/jit/earlyprop.cpp b/src/jit/earlyprop.cpp index 50c696df97..8cb51a3fbd 100644 --- a/src/jit/earlyprop.cpp +++ b/src/jit/earlyprop.cpp @@ -296,6 +296,34 @@ bool Compiler::optEarlyPropRewriteTree(GenTreePtr tree) // array length argument, but the type of GT_ARR_LENGTH is always INT32. return false; } + + // When replacing GT_ARR_LENGTH nodes with constants we can end up with GT_ARR_BOUNDS_CHECK + // nodes that have constant operands and thus can be trivially proved to be useless. It's + // better to remove these range checks here, otherwise they'll pass through assertion prop + // (creating useless (c1 < c2)-like assertions) and reach RangeCheck where they are finally + // removed. Common patterns like new int[] { x, y, z } benefit from this. + + if ((tree->gtNext != nullptr) && tree->gtNext->OperIs(GT_ARR_BOUNDS_CHECK)) + { + GenTreeBoundsChk* check = tree->gtNext->AsBoundsChk(); + + if ((check->gtArrLen == tree) && check->gtIndex->IsCnsIntOrI() && + (check->gtIndex->AsIntCon()->IconValue() >= 0) && + (check->gtIndex->AsIntCon()->IconValue() < actualVal->AsIntCon()->IconValue())) + { + GenTree* comma = check->gtGetParent(nullptr); + + if ((comma != nullptr) && comma->OperIs(GT_COMMA) && (comma->gtGetOp1() == check)) + { + GenTree* next = check->gtNext; + optRemoveRangeCheck(comma, root); + // Both `tree` and `check` have been removed from the statement. Ensure that optEarlyProp + // can process the rest of the statment by changing tree->gtNext appropriately. + tree->gtNext = next; + return true; + } + } + } } else if (propKind == optPropKind::OPK_OBJ_GETTYPE) { diff --git a/src/jit/emit.cpp b/src/jit/emit.cpp index f579aee5ba..4fc97bbd44 100644 --- a/src/jit/emit.cpp +++ b/src/jit/emit.cpp @@ -3646,9 +3646,11 @@ AGAIN: if (emitIsCondJump(jmp)) { - ssz = JCC_SIZE_SMALL; - nsd = JCC_DIST_SMALL_MAX_NEG; - psd = JCC_DIST_SMALL_MAX_POS; + ssz = JCC_SIZE_SMALL; + bool isTest = (jmp->idIns() == INS_tbz) || (jmp->idIns() == INS_tbnz); + + nsd = (isTest) ? TB_DIST_SMALL_MAX_NEG : JCC_DIST_SMALL_MAX_POS; + psd = (isTest) ? TB_DIST_SMALL_MAX_POS : JCC_DIST_SMALL_MAX_POS; } else if (emitIsUncondJump(jmp)) { @@ -3656,10 +3658,6 @@ AGAIN: assert(jmp->idjShort); ssz = JMP_SIZE_SMALL; } - else if (emitIsCmpJump(jmp)) - { - NYI("branch shortening compare-and-branch instructions"); - } else if (emitIsLoadLabel(jmp)) { ssz = LBL_SIZE_SMALL; diff --git a/src/jit/emit.h b/src/jit/emit.h index a9dc076958..8c2b825fe7 100644 --- a/src/jit/emit.h +++ b/src/jit/emit.h @@ -2382,11 +2382,12 @@ inline emitAttr emitTypeSize(T type) extern const unsigned short emitTypeActSz[TYP_COUNT]; -inline emitAttr emitActualTypeSize(var_types type) +template <class T> +inline emitAttr emitActualTypeSize(T type) { - assert(type < TYP_COUNT); - assert(emitTypeActSz[type] > 0); - return (emitAttr)emitTypeActSz[type]; + assert(TypeGet(type) < TYP_COUNT); + assert(emitTypeActSz[TypeGet(type)] > 0); + return (emitAttr)emitTypeActSz[TypeGet(type)]; } /***************************************************************************** diff --git a/src/jit/emitarm64.cpp b/src/jit/emitarm64.cpp index 6c9d61ca25..d3c051a601 100644 --- a/src/jit/emitarm64.cpp +++ b/src/jit/emitarm64.cpp @@ -6691,7 +6691,19 @@ void emitter::emitSetShortJump(instrDescJmp* id) insFormat fmt = IF_NONE; if (emitIsCondJump(id)) { - fmt = IF_BI_0B; + switch (id->idIns()) + { + case INS_cbz: + case INS_cbnz: + fmt = IF_BI_1A; + break; + case INS_tbz: + case INS_tbnz: + fmt = IF_BI_1B; + break; + default: + fmt = IF_BI_0B; + } } else if (emitIsLoadLabel(id)) { @@ -6784,7 +6796,77 @@ void emitter::emitIns_R_D(instruction ins, emitAttr attr, unsigned offs, regNumb void emitter::emitIns_J_R(instruction ins, emitAttr attr, BasicBlock* dst, regNumber reg) { - NYI("emitIns_J_R"); + assert((ins == INS_cbz) || (ins == INS_cbnz)); + + assert(dst && (dst->bbFlags & BBF_JMP_TARGET)); + + insFormat fmt = IF_LARGEJMP; + + instrDescJmp* id = emitNewInstrJmp(); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idReg1(reg); + id->idjShort = false; + id->idOpSize(EA_SIZE(attr)); + + id->idAddr()->iiaBBlabel = dst; + id->idjKeepLong = emitComp->fgInDifferentRegions(emitComp->compCurBB, dst); + + /* Record the jump's IG and offset within it */ + + id->idjIG = emitCurIG; + id->idjOffs = emitCurIGsize; + + /* Append this jump to this IG's jump list */ + + id->idjNext = emitCurIGjmpList; + emitCurIGjmpList = id; + +#if EMITTER_STATS + emitTotalIGjmps++; +#endif + + dispIns(id); + appendToCurIG(id); +} + +void emitter::emitIns_J_R_I(instruction ins, emitAttr attr, BasicBlock* dst, regNumber reg, int imm) +{ + assert((ins == INS_tbz) || (ins == INS_tbnz)); + + assert(dst && (dst->bbFlags & BBF_JMP_TARGET)); + + insFormat fmt = IF_LARGEJMP; + + instrDescJmp* id = emitNewInstrJmp(); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idReg1(reg); + id->idjShort = false; + id->idSmallCns(imm & 0x3f); + id->idOpSize(EA_SIZE(attr)); + + id->idAddr()->iiaBBlabel = dst; + id->idjKeepLong = emitComp->fgInDifferentRegions(emitComp->compCurBB, dst); + + /* Record the jump's IG and offset within it */ + + id->idjIG = emitCurIG; + id->idjOffs = emitCurIGsize; + + /* Append this jump to this IG's jump list */ + + id->idjNext = emitCurIGjmpList; + emitCurIGjmpList = id; + +#if EMITTER_STATS + emitTotalIGjmps++; +#endif + + dispIns(id); + appendToCurIG(id); } void emitter::emitIns_J(instruction ins, BasicBlock* dst, int instrCount) @@ -8253,7 +8335,7 @@ BYTE* emitter::emitOutputLJ(insGroup* ig, BYTE* dst, instrDesc* i) // Short conditional/unconditional jump assert(!id->idjKeepLong); assert(emitJumpCrossHotColdBoundary(srcOffs, dstOffs) == false); - assert((fmt == IF_BI_0A) || (fmt == IF_BI_0B)); + assert((fmt == IF_BI_0A) || (fmt == IF_BI_0B) || (fmt == IF_BI_1A) || (fmt == IF_BI_1B)); } else { @@ -8278,13 +8360,39 @@ BYTE* emitter::emitOutputLJ(insGroup* ig, BYTE* dst, instrDesc* i) // the correct offset. Note also that this works for both integer and floating-point conditions, because // the condition inversion takes ordered/unordered into account, preserving NaN behavior. For example, // "GT" (greater than) is inverted to "LE" (less than, equal, or unordered). + + instruction revereIns; + insFormat reverseFmt; + + switch (ins) + { + case INS_cbz: + revereIns = INS_cbnz; + reverseFmt = IF_BI_1A; + break; + case INS_cbnz: + revereIns = INS_cbz; + reverseFmt = IF_BI_1A; + break; + case INS_tbz: + revereIns = INS_tbnz; + reverseFmt = IF_BI_1B; + break; + case INS_tbnz: + revereIns = INS_tbz; + reverseFmt = IF_BI_1B; + break; + default: + revereIns = emitJumpKindToIns(emitReverseJumpKind(emitInsToJumpKind(ins))); + reverseFmt = IF_BI_0B; + } + dst = emitOutputShortBranch(dst, - emitJumpKindToIns(emitReverseJumpKind( - emitInsToJumpKind(ins))), // reverse the conditional instruction - IF_BI_0B, + revereIns, // reverse the conditional instruction + reverseFmt, 8, /* 8 bytes from start of this large conditional pseudo-instruction to L_not. */ - nullptr /* only used for tbz/tbnzcbz/cbnz */); + id); // Now, pretend we've got a normal unconditional branch, and fall through to the code to emit that. ins = INS_b; diff --git a/src/jit/emitarm64.h b/src/jit/emitarm64.h index ebebf51bdd..87d99952cf 100644 --- a/src/jit/emitarm64.h +++ b/src/jit/emitarm64.h @@ -781,6 +781,8 @@ void emitIns_R_D(instruction ins, emitAttr attr, unsigned offs, regNumber reg); void emitIns_J_R(instruction ins, emitAttr attr, BasicBlock* dst, regNumber reg); +void emitIns_J_R_I(instruction ins, emitAttr attr, BasicBlock* dst, regNumber reg, int imm); + void emitIns_I_AR( instruction ins, emitAttr attr, int val, regNumber reg, int offs, int memCookie = 0, void* clsCookie = NULL); @@ -856,17 +858,8 @@ BYTE* emitOutputShortConstant( inline bool emitIsCondJump(instrDesc* jmp) { - return ((jmp->idInsFmt() == IF_BI_0B) || (jmp->idInsFmt() == IF_LARGEJMP)); -} - -/***************************************************************************** - * - * Given an instrDesc, return true if it's a compare and jump. - */ - -inline bool emitIsCmpJump(instrDesc* jmp) -{ - return ((jmp->idInsFmt() == IF_BI_1A) || (jmp->idInsFmt() == IF_BI_1B)); + return ((jmp->idInsFmt() == IF_BI_0B) || (jmp->idInsFmt() == IF_BI_1A) || (jmp->idInsFmt() == IF_BI_1B) || + (jmp->idInsFmt() == IF_LARGEJMP)); } /***************************************************************************** diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp index bcf8d36254..17d86f805f 100644 --- a/src/jit/gentree.cpp +++ b/src/jit/gentree.cpp @@ -15663,16 +15663,6 @@ bool GenTree::isContained() const assert(!isMarkedContained); } -#if !defined(_TARGET_64BIT_) - if (OperGet() == GT_LONG) - { - // GT_LONG nodes are normally contained. The only exception is when the result - // of a TYP_LONG operation is not used and this can only happen if the GT_LONG - // has no parent. - assert(isMarkedContained || (gtGetParent(nullptr) == nullptr)); - } -#endif - // if it's contained it better have a user if (isMarkedContained) { diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp index ddee0aba5b..e10ae6a3c4 100644 --- a/src/jit/importer.cpp +++ b/src/jit/importer.cpp @@ -18701,6 +18701,8 @@ bool Compiler::IsTargetIntrinsic(CorInfoIntrinsics intrinsicId) case CORINFO_INTRINSIC_Sqrt: case CORINFO_INTRINSIC_Abs: case CORINFO_INTRINSIC_Round: + case CORINFO_INTRINSIC_Floor: + case CORINFO_INTRINSIC_Ceiling: return true; default: diff --git a/src/jit/lir.cpp b/src/jit/lir.cpp index 02ba714ad2..a2343ad313 100644 --- a/src/jit/lir.cpp +++ b/src/jit/lir.cpp @@ -1407,6 +1407,105 @@ LIR::ReadOnlyRange LIR::Range::GetRangeOfOperandTrees(GenTree* root, bool* isClo #ifdef DEBUG //------------------------------------------------------------------------ +// CheckLclVarSemanticsHelper checks lclVar semantics. +// +// Specifically, ensure that an unaliasable lclVar is not redefined between the +// point at which a use appears in linear order and the point at which it is used by its user. +// This ensures that it is always safe to treat a lclVar use as happening at the user (rather than at +// the lclVar node). +class CheckLclVarSemanticsHelper +{ +public: + //------------------------------------------------------------------------ + // CheckLclVarSemanticsHelper constructor: Init arguments for the helper. + // + // This needs unusedDefs because unused lclVar reads may otherwise appear as outstanding reads + // and produce false indications that a write to a lclVar occurs while outstanding reads of that lclVar + // exist. + // + // Arguments: + // compiler - A compiler context. + // range - a range to do the check. + // unusedDefs - map of defs that do no have users. + // + CheckLclVarSemanticsHelper(Compiler* compiler, + const LIR::Range* range, + SmallHashTable<GenTreePtr, bool, 32U>& unusedDefs) + : compiler(compiler), range(range), unusedDefs(unusedDefs), unusedLclVarReads(compiler) + { + } + + //------------------------------------------------------------------------ + // Check: do the check. + // Return Value: + // 'true' if the Local variables semantics for the specified range is legal. + bool Check() + { + for (GenTreePtr node : *range) + { + if (!node->isContained()) // a contained node reads operands in the parent. + { + UseNodeOperands(node); + } + + AliasSet::NodeInfo nodeInfo(compiler, node); + if (nodeInfo.IsLclVarRead() && !unusedDefs.Contains(node)) + { + int count = 0; + unusedLclVarReads.TryGetValue(nodeInfo.LclNum(), &count); + unusedLclVarReads.AddOrUpdate(nodeInfo.LclNum(), count + 1); + } + + // If this node is a lclVar write, it must be to a lclVar that does not have an outstanding read. + assert(!nodeInfo.IsLclVarWrite() || !unusedLclVarReads.Contains(nodeInfo.LclNum())); + } + + return true; + } + +private: + //------------------------------------------------------------------------ + // UseNodeOperands: mark the node's operands as used. + // + // Arguments: + // node - the node to use operands from. + void UseNodeOperands(GenTreePtr node) + { + for (GenTreePtr operand : node->Operands()) + { + if (!operand->IsLIR()) + { + // ARGPLACE nodes are not represented in the LIR sequence. Ignore them. + assert(operand->OperIs(GT_ARGPLACE)); + continue; + } + if (operand->isContained()) + { + UseNodeOperands(operand); + } + AliasSet::NodeInfo operandInfo(compiler, operand); + if (operandInfo.IsLclVarRead()) + { + int count; + const bool removed = unusedLclVarReads.TryRemove(operandInfo.LclNum(), &count); + assert(removed); + + if (count > 1) + { + unusedLclVarReads.AddOrUpdate(operandInfo.LclNum(), count - 1); + } + } + } + } + +private: + Compiler* compiler; + const LIR::Range* range; + SmallHashTable<GenTree*, bool, 32U>& unusedDefs; + SmallHashTable<int, int, 32U> unusedLclVarReads; +}; + +//------------------------------------------------------------------------ // LIR::Range::CheckLIR: Performs a set of correctness checks on the LIR // contained in this range. // @@ -1561,44 +1660,8 @@ bool LIR::Range::CheckLIR(Compiler* compiler, bool checkUnusedValues) const } } - // Check lclVar semantics: specifically, ensure that an unaliasable lclVar is not redefined between the - // point at which a use appears in linear order and the point at which that use is consumed by its user. - // This ensures that it is always safe to treat a lclVar use as happening at the user (rather than at - // the lclVar node). - // - // This happens as a second pass because unused lclVar reads may otherwise appear as outstanding reads - // and produce false indications that a write to a lclVar occurs while outstanding reads of that lclVar - // exist. - SmallHashTable<int, int, 32> unconsumedLclVarReads(compiler); - for (GenTree* node : *this) - { - for (GenTree* operand : node->Operands()) - { - AliasSet::NodeInfo operandInfo(compiler, operand); - if (operandInfo.IsLclVarRead()) - { - int count; - const bool removed = unconsumedLclVarReads.TryRemove(operandInfo.LclNum(), &count); - assert(removed); - - if (count > 1) - { - unconsumedLclVarReads.AddOrUpdate(operandInfo.LclNum(), count - 1); - } - } - } - - AliasSet::NodeInfo nodeInfo(compiler, node); - if (nodeInfo.IsLclVarRead() && !unusedDefs.Contains(node)) - { - int count = 0; - unconsumedLclVarReads.TryGetValue(nodeInfo.LclNum(), &count); - unconsumedLclVarReads.AddOrUpdate(nodeInfo.LclNum(), count + 1); - } - - // If this node is a lclVar write, it must be to a lclVar that does not have an outstanding read. - assert(!nodeInfo.IsLclVarWrite() || !unconsumedLclVarReads.Contains(nodeInfo.LclNum())); - } + CheckLclVarSemanticsHelper checkLclVarSemanticsHelper(compiler, this, unusedDefs); + assert(checkLclVarSemanticsHelper.Check()); return true; } diff --git a/src/jit/lowerarmarch.cpp b/src/jit/lowerarmarch.cpp index e44e6ce068..4cee63b006 100644 --- a/src/jit/lowerarmarch.cpp +++ b/src/jit/lowerarmarch.cpp @@ -351,18 +351,15 @@ void Lowering::LowerBlockStore(GenTreeBlk* blkNode) } else // CopyBlk { -#ifdef _TARGET_ARM64_ // In case of a CpBlk with a constant size and less than CPBLK_UNROLL_LIMIT size // we should unroll the loop to improve CQ. // For reference see the code in lowerxarch.cpp. - // TODO-ARM-CQ: cpblk loop unrolling is currently not implemented. if ((size != 0) && (size <= INITBLK_UNROLL_LIMIT)) { blkNode->gtBlkOpKind = GenTreeBlk::BlkOpKindUnroll; } else -#endif // _TARGET_ARM64_ { // In case we have a constant integer this means we went beyond // CPBLK_UNROLL_LIMIT bytes of size, still we should never have the case of diff --git a/src/jit/lsraarm.cpp b/src/jit/lsraarm.cpp index 00d466e923..a0ec93a524 100644 --- a/src/jit/lsraarm.cpp +++ b/src/jit/lsraarm.cpp @@ -441,19 +441,11 @@ void LinearScan::TreeNodeInfoInit(GenTree* tree) break; case GT_LONG: - if (tree->IsUnusedValue()) - { - // An unused GT_LONG node needs to consume its sources. - info->srcCount = 2; - info->dstCount = 0; - } - else - { - // Passthrough. Should have been marked contained. - info->srcCount = 0; - assert(info->dstCount == 0); - } + assert(tree->IsUnusedValue()); // Contained nodes are already processed, only unused GT_LONG can reach here. + // An unused GT_LONG node needs to consume its sources. + info->srcCount = 2; + info->dstCount = 0; break; case GT_CNS_DBL: diff --git a/src/jit/lsraarm64.cpp b/src/jit/lsraarm64.cpp index e39c0d9332..0b6891ae65 100644 --- a/src/jit/lsraarm64.cpp +++ b/src/jit/lsraarm64.cpp @@ -260,9 +260,9 @@ void LinearScan::TreeNodeInfoInit(GenTree* tree) case GT_INTRINSIC: { - // TODO-ARM64-NYI - // Right now only Abs/Round/Sqrt are treated as math intrinsics noway_assert((tree->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Abs) || + (tree->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Ceiling) || + (tree->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Floor) || (tree->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Round) || (tree->gtIntrinsic.gtIntrinsicId == CORINFO_INTRINSIC_Sqrt)); diff --git a/src/jit/lsraarmarch.cpp b/src/jit/lsraarmarch.cpp index ac722e27bf..a739cc9e80 100644 --- a/src/jit/lsraarmarch.cpp +++ b/src/jit/lsraarmarch.cpp @@ -890,24 +890,20 @@ void LinearScan::TreeNodeInfoInitBlockStore(GenTreeBlk* blkNode) if (blkNode->gtBlkOpKind == GenTreeBlk::BlkOpKindUnroll) { - // TODO-ARM-CQ: cpblk loop unrolling is currently not implemented. // In case of a CpBlk with a constant size and less than CPBLK_UNROLL_LIMIT size // we should unroll the loop to improve CQ. // For reference see the code in lsraxarch.cpp. - NYI_ARM("cpblk loop unrolling is currently not implemented."); - -#ifdef _TARGET_ARM64_ internalIntCount = 1; internalIntCandidates = RBM_ALLINT; +#ifdef _TARGET_ARM64_ if (size >= 2 * REGSIZE_BYTES) { // We will use ldp/stp to reduce code size and improve performance // so we need to reserve an extra internal register internalIntCount++; } - #endif // _TARGET_ARM64_ } else diff --git a/src/jit/lsraxarch.cpp b/src/jit/lsraxarch.cpp index 80172577b1..8ac8e235b5 100644 --- a/src/jit/lsraxarch.cpp +++ b/src/jit/lsraxarch.cpp @@ -193,18 +193,11 @@ void LinearScan::TreeNodeInfoInit(GenTree* tree) #if !defined(_TARGET_64BIT_) case GT_LONG: - if (tree->IsUnusedValue()) - { - // An unused GT_LONG node needs to consume its sources. - info->srcCount = 2; - info->dstCount = 0; - } - else - { - // Passthrough. Should have been marked contained. - info->srcCount = 0; - assert(info->dstCount == 0); - } + assert(tree->IsUnusedValue()); // Contained nodes are already processed, only unused GT_LONG can reach here. + + // An unused GT_LONG node needs to consume its sources. + info->srcCount = 2; + info->dstCount = 0; break; #endif // !defined(_TARGET_64BIT_) diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index b384d792f3..471fe8a663 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -502,6 +502,9 @@ OPTIMIZECAST: /* Reset the call flag */ tree->gtFlags &= ~GTF_CALL; + /* Reset the assignment flag */ + tree->gtFlags &= ~GTF_ASG; + /* unless we have an overflow cast, reset the except flag */ if (!tree->gtOverflow()) { diff --git a/src/jit/target.h b/src/jit/target.h index 68b27f22f9..59bbdb492b 100644 --- a/src/jit/target.h +++ b/src/jit/target.h @@ -1183,6 +1183,10 @@ typedef unsigned short regPairNoSmall; // arm: need 12 bits #define ROUND_FLOAT 0 // Do not round intermed float expression results #define CPU_HAS_BYTE_REGS 0 #define CPU_USES_BLOCK_MOVE 0 + + #define CPBLK_UNROLL_LIMIT 32 // Upper bound to let the code generator to loop unroll CpBlk. + #define INITBLK_UNROLL_LIMIT 32 // Upper bound to let the code generator to loop unroll InitBlk. + #define FEATURE_WRITE_BARRIER 1 // Generate the proper WriteBarrier calls for GC #define FEATURE_FIXED_OUT_ARGS 1 // Preallocate the outgoing arg area in the prolog #define FEATURE_STRUCTPROMOTE 1 // JIT Optimization to promote fields of structs into registers @@ -1804,6 +1808,9 @@ typedef unsigned short regPairNoSmall; // arm: need 12 bits #define JCC_DIST_SMALL_MAX_NEG (-1048576) #define JCC_DIST_SMALL_MAX_POS (+1048575) + #define TB_DIST_SMALL_MAX_NEG (-32768) + #define TB_DIST_SMALL_MAX_POS (+32767) + #define JCC_SIZE_SMALL (4) #define JCC_SIZE_LARGE (8) diff --git a/src/jit/utils.cpp b/src/jit/utils.cpp index ffc9a753bf..85aec5464c 100644 --- a/src/jit/utils.cpp +++ b/src/jit/utils.cpp @@ -1769,13 +1769,16 @@ unsigned __int64 FloatingPointUtils::convertDoubleToUInt64(double d) // Rounds a double-precision floating-point value to the nearest integer, // and rounds midpoint values to the nearest even number. -// Note this should align with classlib in floatdouble.cpp -// Specializing for x86 using a x87 instruction is optional since -// this outcome is identical across targets. double FloatingPointUtils::round(double x) { + // ************************************************************************************ + // IMPORTANT: Do not change this implementation without also updating Math.Round(double), + // MathF.Round(float), and FloatingPointUtils::round(float) + // ************************************************************************************ + // If the number has no fractional part do nothing // This shortcut is necessary to workaround precision loss in borderline cases on some platforms + if (x == (double)((INT64)x)) { return x; @@ -1784,10 +1787,9 @@ double FloatingPointUtils::round(double x) // We had a number that was equally close to 2 integers. // We need to return the even one. - double tempVal = (x + 0.5); - double flrTempVal = floor(tempVal); + double flrTempVal = floor(x + 0.5); - if ((flrTempVal == tempVal) && (fmod(tempVal, 2.0) != 0)) + if ((x == (floor(x) + 0.5)) && (fmod(flrTempVal, 2.0) != 0)) { flrTempVal -= 1.0; } @@ -1809,13 +1811,16 @@ double FloatingPointUtils::round(double x) // Rounds a single-precision floating-point value to the nearest integer, // and rounds midpoint values to the nearest even number. -// Note this should align with classlib in floatsingle.cpp -// Specializing for x86 using a x87 instruction is optional since -// this outcome is identical across targets. float FloatingPointUtils::round(float x) { + // ************************************************************************************ + // IMPORTANT: Do not change this implementation without also updating MathF.Round(float), + // Math.Round(double), and FloatingPointUtils::round(double) + // ************************************************************************************ + // If the number has no fractional part do nothing // This shortcut is necessary to workaround precision loss in borderline cases on some platforms + if (x == (float)((INT32)x)) { return x; @@ -1824,10 +1829,9 @@ float FloatingPointUtils::round(float x) // We had a number that was equally close to 2 integers. // We need to return the even one. - float tempVal = (x + 0.5f); - float flrTempVal = floorf(tempVal); + float flrTempVal = floorf(x + 0.5f); - if ((flrTempVal == tempVal) && (fmodf(tempVal, 2.0f) != 0)) + if ((x == (floorf(x) + 0.5f)) && (fmodf(flrTempVal, 2.0f) != 0)) { flrTempVal -= 1.0f; } diff --git a/src/jit/valuenum.cpp b/src/jit/valuenum.cpp index e4990d635a..473f0cec8e 100644 --- a/src/jit/valuenum.cpp +++ b/src/jit/valuenum.cpp @@ -3514,6 +3514,12 @@ ValueNum ValueNumStore::EvalMathFuncUnary(var_types typ, CorInfoIntrinsics gtMat case CORINFO_INTRINSIC_Abs: res = fabs(arg0Val); break; + case CORINFO_INTRINSIC_Ceiling: + res = ceil(arg0Val); + break; + case CORINFO_INTRINSIC_Floor: + res = floor(arg0Val); + break; case CORINFO_INTRINSIC_Round: res = FloatingPointUtils::round(arg0Val); break; @@ -3544,6 +3550,12 @@ ValueNum ValueNumStore::EvalMathFuncUnary(var_types typ, CorInfoIntrinsics gtMat case CORINFO_INTRINSIC_Abs: res = fabsf(arg0Val); break; + case CORINFO_INTRINSIC_Ceiling: + res = ceilf(arg0Val); + break; + case CORINFO_INTRINSIC_Floor: + res = floorf(arg0Val); + break; case CORINFO_INTRINSIC_Round: res = FloatingPointUtils::round(arg0Val); break; |