summaryrefslogtreecommitdiff
path: root/src/jit
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit')
-rw-r--r--src/jit/codegenarm.cpp9
-rw-r--r--src/jit/codegenarm64.cpp154
-rw-r--r--src/jit/codegenarmarch.cpp210
-rw-r--r--src/jit/compiler.h19
-rw-r--r--src/jit/earlyprop.cpp28
-rw-r--r--src/jit/emit.cpp12
-rw-r--r--src/jit/emit.h9
-rw-r--r--src/jit/emitarm64.cpp122
-rw-r--r--src/jit/emitarm64.h15
-rw-r--r--src/jit/gentree.cpp10
-rw-r--r--src/jit/importer.cpp2
-rw-r--r--src/jit/lir.cpp139
-rw-r--r--src/jit/lowerarmarch.cpp3
-rw-r--r--src/jit/lsraarm.cpp16
-rw-r--r--src/jit/lsraarm64.cpp4
-rw-r--r--src/jit/lsraarmarch.cpp6
-rw-r--r--src/jit/lsraxarch.cpp17
-rw-r--r--src/jit/morph.cpp3
-rw-r--r--src/jit/target.h7
-rw-r--r--src/jit/utils.cpp28
-rw-r--r--src/jit/valuenum.cpp12
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;