diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 91c0e0cbc93ed5..ff1fa2e20c44b0 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3641,6 +3641,7 @@ class Compiler GenTree* gtFoldExprConst(GenTree* tree); GenTree* gtFoldIndirConst(GenTreeIndir* indir); GenTree* gtFoldExprSpecial(GenTree* tree); + GenTree* gtFoldExprSpecialFloating(GenTree* tree); GenTree* gtFoldBoxNullable(GenTree* tree); GenTree* gtFoldExprCompare(GenTree* tree); GenTree* gtFoldExprConditional(GenTree* tree); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index ab5798358ed81b..61146e1177fbd3 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -13786,7 +13786,7 @@ GenTree* Compiler::gtFoldExprCompare(GenTree* tree) /* Filter out cases that cannot be folded here */ - /* Do not fold floats or doubles (e.g. NaN != Nan) */ + /* Do not fold floats or doubles (e.g. NaN != NaN) */ if (varTypeIsFloating(op1->TypeGet())) { @@ -14277,6 +14277,7 @@ CORINFO_CLASS_HANDLE Compiler::gtGetHelperArgClassHandle(GenTree* tree) // GenTree* Compiler::gtFoldExprSpecial(GenTree* tree) { + var_types type = tree->TypeGet(); GenTree* op1 = tree->AsOp()->gtOp1; GenTree* op2 = tree->AsOp()->gtOp2; genTreeOps oper = tree->OperGet(); @@ -14296,8 +14297,12 @@ GenTree* Compiler::gtFoldExprSpecial(GenTree* tree) /* We only consider TYP_INT for folding * Do not fold pointer arithmetic (e.g. addressing modes!) */ - if (oper != GT_QMARK && !varTypeIsIntOrI(tree->gtType)) + if (oper != GT_QMARK && !varTypeIsIntOrI(type)) { + if (varTypeIsFloating(type)) + { + return gtFoldExprSpecialFloating(tree); + } return tree; } @@ -14630,6 +14635,233 @@ GenTree* Compiler::gtFoldExprSpecial(GenTree* tree) return op; } +//------------------------------------------------------------------------ +// gtFoldExprSpecialFloating -- optimize floating-point binary ops with one constant operand +// +// Arguments: +// tree - tree to optimize +// +// Return value: +// Tree (possibly modified at root or below), or a new tree +// Any new tree is fully morphed, if necessary. +// +GenTree* Compiler::gtFoldExprSpecialFloating(GenTree* tree) +{ + assert(varTypeIsFloating(tree->TypeGet())); + assert(tree->OperKind() & GTK_BINOP); + + GenTree* op1 = tree->AsOp()->gtOp1; + GenTree* op2 = tree->AsOp()->gtOp2; + genTreeOps oper = tree->OperGet(); + + GenTree* op; + GenTree* cons; + double val; + + /* Filter out operators that cannot be folded here */ + if (oper == GT_CAST) + { + return tree; + } + + /* Find out which is the constant node */ + if (op1->IsCnsFltOrDbl()) + { + op = op2; + cons = op1; + } + else if (op2->IsCnsFltOrDbl()) + { + op = op1; + cons = op2; + } + else + { + return tree; + } + + /* Get the constant value */ + val = cons->AsDblCon()->DconValue(); + + // Helper function that creates a new IntCon node and morphs it, if required + auto NewMorphedIntConNode = [&](int value) -> GenTreeIntCon* { + GenTreeIntCon* icon = gtNewIconNode(value); + if (fgGlobalMorph) + { + INDEBUG(icon->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED); + } + return icon; + }; + + // Here `op` is the non-constant operand, `cons` is the constant operand + // and `val` is the constant value. + + switch (oper) + { + case GT_ADD: + { + // Handle `x + NaN == NaN` and `NaN + x == NaN` + // This is safe for all floats since we do not fault for sNaN + + if (FloatingPointUtils::isNaN(val)) + { + op = gtWrapWithSideEffects(cons, op, GTF_ALL_EFFECT); + goto DONE_FOLD; + } + + // Handle `x + -0 == x` and `-0 + x == x` + + if (FloatingPointUtils::isNegativeZero(val)) + { + goto DONE_FOLD; + } + + // We cannot handle `x + 0 == x` or `0 + x == x` since `-0 + 0 == 0` + break; + } + + case GT_DIV: + { + // Handle `x / NaN == NaN` and `NaN / x == NaN` + // This is safe for all floats since we do not fault for sNaN + + if (FloatingPointUtils::isNaN(val)) + { + op = gtWrapWithSideEffects(cons, op, GTF_ALL_EFFECT); + goto DONE_FOLD; + } + + // Handle `x / 1 == x`. + // This is safe for all floats since we do not fault for sNaN + + if ((op2 == cons) && (val == 1.0)) + { + goto DONE_FOLD; + } + break; + } + + case GT_EQ: + { + assert((tree->gtFlags & GTF_RELOP_NAN_UN) == 0); + + if (FloatingPointUtils::isNaN(val)) + { + // Comparison with NaN is always false + op = gtWrapWithSideEffects(NewMorphedIntConNode(0), op, GTF_ALL_EFFECT); + goto DONE_FOLD; + } + break; + } + + case GT_GE: + case GT_GT: + case GT_LE: + case GT_LT: + { + if (FloatingPointUtils::isNaN(val)) + { + if ((tree->gtFlags & GTF_RELOP_NAN_UN) != 0) + { + // Unordered comparison with NaN is always true + op = gtWrapWithSideEffects(NewMorphedIntConNode(1), op, GTF_ALL_EFFECT); + } + else + { + // Comparison with NaN is always false + op = gtWrapWithSideEffects(NewMorphedIntConNode(0), op, GTF_ALL_EFFECT); + } + goto DONE_FOLD; + } + break; + } + + case GT_MUL: + { + // Handle `x * NaN == NaN` and `NaN * x == NaN` + // This is safe for all floats since we do not fault for sNaN + + if (FloatingPointUtils::isNaN(val)) + { + op = gtWrapWithSideEffects(cons, op, GTF_ALL_EFFECT); + goto DONE_FOLD; + } + + // Handle `x * 1 == x` and `1 * x == x` + // This is safe for all floats since we do not fault for sNaN + + if (val == 1.0) + { + goto DONE_FOLD; + } + + // We cannot handle `x * 0 == 0` or ` 0 * x == 0` since `-0 * 0 == -0` + // We cannot handle `x * -0 == -0` or `-0 * x == -0` since `-0 * -0 == 0` + break; + } + + case GT_NE: + { + assert((tree->gtFlags & GTF_RELOP_NAN_UN) == 0); + + if (FloatingPointUtils::isNaN(val)) + { + // Comparison with NaN is always true + op = gtWrapWithSideEffects(NewMorphedIntConNode(1), op, GTF_ALL_EFFECT); + goto DONE_FOLD; + } + break; + } + + case GT_SUB: + { + // Handle `x - NaN == NaN` and `NaN - x == NaN` + // This is safe for all floats since we do not fault for sNaN + + if (FloatingPointUtils::isNaN(val)) + { + op = gtWrapWithSideEffects(cons, op, GTF_ALL_EFFECT); + goto DONE_FOLD; + } + + // Handle `x - 0 == x` + + if ((cons == op2) && FloatingPointUtils::isPositiveZero(val)) + { + goto DONE_FOLD; + } + + // We cannot handle `x - -0 == x` since `-0 - -0 == 0` + break; + } + + default: + { + break; + } + } + + /* The node is not foldable */ + + return tree; + +DONE_FOLD: + + JITDUMP("\nFolding binary operator with a constant operand:\n"); + DISPTREE(tree); + JITDUMP("Transformed into:\n"); + DISPTREE(op); + + if (fgGlobalMorph) + { + // We can sometimes produce a comma over the constant if the original op + // had a side effect, so just ensure we set the flag (which will be already + // set for the operands otherwise). + INDEBUG(op->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED); + } + return op; +} + //------------------------------------------------------------------------ // gtFoldBoxNullable -- optimize a boxed nullable feeding a compare to zero // diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 0c715dd6d54df3..9df5073bd80464 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -4823,38 +4823,82 @@ ValueNum ValueNumStore::EvalUsingMathIdentity(var_types typ, VNFunc func, ValueN return resultVN; // return the unsuccessful value } - // (0 + x) == x - // (x + 0) == x - // This identity does not apply for floating point (when x == -0.0). + ValueNum cnsVN = NoVN; + ValueNum opVN = NoVN; + + if (IsVNConstant(arg0VN)) + { + cnsVN = arg0VN; + opVN = arg1VN; + } + else if (IsVNConstant(arg1VN)) + { + cnsVN = arg1VN; + opVN = arg0VN; + } + auto identityForAddition = [=]() -> ValueNum { + ValueNum ZeroVN = VNZeroForType(typ); + if (!varTypeIsFloating(typ)) { - ValueNum ZeroVN = VNZeroForType(typ); - if (arg0VN == ZeroVN) + // Handle `0 + x == x` and `x + 0 == x` + if (cnsVN == ZeroVN) { - return arg1VN; + return opVN; } - else if (arg1VN == ZeroVN) + } + else if (cnsVN == NoVN) + { + return NoVN; + } + else + { + double val; + + if (typ == TYP_FLOAT) { - return arg0VN; + val = GetConstantSingle(cnsVN); + } + else + { + assert(typ == TYP_DOUBLE); + val = GetConstantDouble(cnsVN); + } + + // Handle `x + NaN == NaN` and `NaN + x == NaN` + // This is safe for all floats since we do not fault for sNaN + + if (FloatingPointUtils::isNaN(val)) + { + return cnsVN; + } + + // Handle `x + -0 == x` and `-0 + x == x` + + if (FloatingPointUtils::isNegativeZero(val)) + { + return opVN; } - } + // We cannot handle `x + 0 == x` or `0 + x == x` since `-0 + 0 == 0` + } return NoVN; }; - // (x - 0) == x - // (x - x) == 0 - // This identity does not apply for floating point (when x == -0.0). auto identityForSubtraction = [=](bool ovf) -> ValueNum { + ValueNum ZeroVN = VNZeroForType(typ); + if (!varTypeIsFloating(typ)) { - ValueNum ZeroVN = VNZeroForType(typ); + // Handle `x - 0 == x` if (arg1VN == ZeroVN) { return arg0VN; } - else if (arg0VN == arg1VN) + + // Handle `x - x == 0` + if (arg0VN == arg1VN) { return ZeroVN; } @@ -4892,110 +4936,179 @@ ValueNum ValueNumStore::EvalUsingMathIdentity(var_types typ, VNFunc func, ValueN } } } + else if (cnsVN == NoVN) + { + return NoVN; + } + else + { + double val; + + if (typ == TYP_FLOAT) + { + val = GetConstantSingle(cnsVN); + } + else + { + assert(typ == TYP_DOUBLE); + val = GetConstantDouble(cnsVN); + } + + // Handle `x - NaN == NaN` and `NaN - x == NaN` + // This is safe for all floats since we do not fault for sNaN + if (FloatingPointUtils::isNaN(val)) + { + return cnsVN; + } + + // Handle `x - 0 == x` + + if ((cnsVN == arg1VN) && FloatingPointUtils::isPositiveZero(val)) + { + return opVN; + } + + // We cannot handle `x - -0 == x` since `-0 - -0 == 0` + // We cannot handle `x - x == 0` since `NaN - NaN == NaN` + // We cannot handle other scenarios since floating-point rounds after each operation + } return NoVN; }; - // These identities do not apply for floating point. auto identityForMultiplication = [=]() -> ValueNum { + ValueNum ZeroVN = VNZeroForType(typ); + + // Handle `x * 1 == x` and `1 * x == x` + // This is safe for all floats since we do not fault for sNaN + + if (cnsVN == VNOneForType(typ)) + { + return opVN; + } + if (!varTypeIsFloating(typ)) { - // (0 * x) == 0 - // (x * 0) == 0 - // This identity does not apply for floating-point (when x == -0.0, NaN, +Inf, -Inf) - ValueNum ZeroVN = VNZeroForType(typ); - if (arg0VN == ZeroVN) - { - return ZeroVN; - } - else if (arg1VN == ZeroVN) + // Handle `0 * x == 0` and `x * 0 == 0` + if (cnsVN == ZeroVN) { return ZeroVN; } } - - // (x * 1) == x - // (1 * x) == x - // This is safe for all floats since we do not fault for sNaN - ValueNum OneVN = VNOneForType(typ); - if (arg0VN == OneVN) + else if (cnsVN == NoVN) { - return arg1VN; + return NoVN; } - else if (arg1VN == OneVN) + else { - return arg0VN; - } + double val; + + if (typ == TYP_FLOAT) + { + val = GetConstantSingle(cnsVN); + } + else + { + assert(typ == TYP_DOUBLE); + val = GetConstantDouble(cnsVN); + } + + // Handle `x * NaN == NaN` and `NaN * x == NaN` + // This is safe for all floats since we do not fault for sNaN + if (FloatingPointUtils::isNaN(val)) + { + return cnsVN; + } + + // We cannot handle `x * 0 == 0` or ` 0 * x == 0` since `-0 * 0 == -0` + // We cannot handle `x * -0 == -0` or `-0 * x == -0` since `-0 * -0 == 0` + } return NoVN; }; // We have ways of evaluating some binary functions. if (func < VNF_Boundary) { - ValueNum ZeroVN; - ValueNum OneVN; - ValueNum AllBitsVN; - switch (genTreeOps(func)) { case GT_ADD: + { resultVN = identityForAddition(); break; + } case GT_SUB: + { resultVN = identityForSubtraction(/* ovf */ false); break; + } case GT_MUL: + { resultVN = identityForMultiplication(); break; + } case GT_DIV: case GT_UDIV: { - // (x / 1) == x + // Handle `x / 1 == x` // This is safe for all floats since we do not fault for sNaN - OneVN = VNOneForType(typ); - if (arg1VN == OneVN) + if (arg1VN == VNOneForType(typ)) { resultVN = arg0VN; } - break; - } - case GT_OR: - { - // (0 | x) == x - // (x | 0) == x - ZeroVN = VNZeroForType(typ); - if (arg0VN == ZeroVN) + if (cnsVN == NoVN) { - resultVN = arg1VN; break; } - else if (arg1VN == ZeroVN) + else if (varTypeIsFloating(typ)) { - resultVN = arg0VN; - break; + double val; + + if (typ == TYP_FLOAT) + { + val = GetConstantSingle(cnsVN); + } + else + { + assert(typ == TYP_DOUBLE); + val = GetConstantDouble(cnsVN); + } + + // Handle `x / NaN == NaN` and `NaN / x == NaN` + // This is safe for all floats since we do not fault for sNaN + + if (FloatingPointUtils::isNaN(val)) + { + return cnsVN; + } } + break; + } + + case GT_OR: + { + ValueNum ZeroVN = VNZeroForType(typ); - // (x | ~0) == ~0 - // (~0 | x) == ~0 - AllBitsVN = VNAllBitsForType(typ); - if (arg0VN == AllBitsVN) + // Handle `0 | x == x` and `x | 0 == x` + if (cnsVN == ZeroVN) { - resultVN = AllBitsVN; + resultVN = opVN; break; } - else if (arg1VN == AllBitsVN) + + // Handle `x | AllBitsSet == AllBitsSet` and `AllBitsSet | x == AllBitsSet` + if (cnsVN == VNAllBitsForType(typ)) { - resultVN = AllBitsVN; + resultVN = cnsVN; break; } - // x | x == x + // Handle `x | x == x` if (arg0VN == arg1VN) { resultVN = arg0VN; @@ -5005,21 +5118,16 @@ ValueNum ValueNumStore::EvalUsingMathIdentity(var_types typ, VNFunc func, ValueN case GT_XOR: { - // (0 ^ x) == x - // (x ^ 0) == x - ZeroVN = VNZeroForType(typ); - if (arg0VN == ZeroVN) - { - resultVN = arg1VN; - break; - } - else if (arg1VN == ZeroVN) + ValueNum ZeroVN = VNZeroForType(typ); + + // Handle `0 ^ x == x` and `x ^ 0 == x` + if (cnsVN == ZeroVN) { - resultVN = arg0VN; + resultVN = opVN; break; } - // x ^ x == 0 + // Handle `x ^ x == 0` if (arg0VN == arg1VN) { resultVN = ZeroVN; @@ -5029,35 +5137,23 @@ ValueNum ValueNumStore::EvalUsingMathIdentity(var_types typ, VNFunc func, ValueN case GT_AND: { - // (x & 0) == 0 - // (0 & x) == 0 - ZeroVN = VNZeroForType(typ); - if (arg0VN == ZeroVN) - { - resultVN = ZeroVN; - break; - } - else if (arg1VN == ZeroVN) + ValueNum ZeroVN = VNZeroForType(typ); + + // Handle `x & 0 == 0` and `0 & x == 0` + if (cnsVN == ZeroVN) { resultVN = ZeroVN; break; } - // (x & ~0) == x - // (~0 & x) == x - AllBitsVN = VNAllBitsForType(typ); - if (arg0VN == AllBitsVN) + // Handle `x & AllBitsSet == x` and `AllBitsSet & x == x` + if (cnsVN == VNAllBitsForType(typ)) { - resultVN = arg1VN; - break; - } - else if (arg1VN == AllBitsVN) - { - resultVN = arg0VN; + resultVN = opVN; break; } - // x & x == x + // Handle `x & x == x` if (arg0VN == arg1VN) { resultVN = arg0VN; @@ -5071,20 +5167,16 @@ ValueNum ValueNumStore::EvalUsingMathIdentity(var_types typ, VNFunc func, ValueN case GT_ROL: case GT_ROR: { - // (x << 0) == x - // (x >> 0) == x - // (x rol 0) == x - // (x ror 0) == x - ZeroVN = VNZeroForType(typ); + ValueNum ZeroVN = VNZeroForType(typ); + + // Handle `x << 0 == x`, `x >> 0 == x`, `x rol 0 == x`, and `x ror 0 == x` if (arg1VN == ZeroVN) { resultVN = arg0VN; + break; } - // (0 << x) == 0 - // (0 >> x) == 0 - // (0 rol x) == 0 - // (0 ror x) == 0 + // Handle `0 << x == 0`, `0 >> x == 0`, `0 rol x == 0`, and `0 ror x == 0` if (arg0VN == ZeroVN) { resultVN = ZeroVN; @@ -5093,156 +5185,237 @@ ValueNum ValueNumStore::EvalUsingMathIdentity(var_types typ, VNFunc func, ValueN } case GT_EQ: - // (null == non-null) == false - // (non-null == null) == false - if (((arg0VN == VNForNull()) && IsKnownNonNull(arg1VN)) || - ((arg1VN == VNForNull()) && IsKnownNonNull(arg0VN))) + { + ValueNum ZeroVN = VNZeroForType(typ); + + // Handle `(null == non-null) == false` and `(non-null == null) == false` + if ((cnsVN == VNForNull()) && IsKnownNonNull(opVN)) { resultVN = VNZeroForType(typ); break; } - // (relop == 0) == !relop - ZeroVN = VNZeroForType(typ); - if (IsVNRelop(arg0VN) && (arg1VN == ZeroVN)) + + if (IsVNRelop(opVN)) { - ValueNum rev0VN = GetRelatedRelop(arg0VN, VN_RELATION_KIND::VRK_Reverse); - if (rev0VN != NoVN) + // Handle `(relop == 1) == relop` and `(1 == relop) == relop` + if (cnsVN == VNOneForType(typ)) { - resultVN = rev0VN; + resultVN = opVN; break; } + + // Handle `(relop == 0) == !relop` and `(0 == relop) == !relop` + if (cnsVN == ZeroVN) + { + ValueNum revVN = GetRelatedRelop(opVN, VN_RELATION_KIND::VRK_Reverse); + if (revVN != NoVN) + { + resultVN = revVN; + break; + } + } } - else if (IsVNRelop(arg1VN) && (arg0VN == ZeroVN)) + + // (x == x) == true (integer only) + FALLTHROUGH; + } + + case GT_GE: + case GT_LE: + { + ValueNum ZeroVN = VNZeroForType(typ); + var_types opTyp = TypeOfVN(arg0VN); + + if (varTypeIsFloating(opTyp)) { - ValueNum rev1VN = GetRelatedRelop(arg1VN, VN_RELATION_KIND::VRK_Reverse); - if (rev1VN != NoVN) + if (cnsVN == NoVN) { - resultVN = rev1VN; break; } + + double val; + + if (opTyp == TYP_FLOAT) + { + val = GetConstantSingle(cnsVN); + } + else + { + assert(opTyp == TYP_DOUBLE); + val = GetConstantDouble(cnsVN); + } + + if (FloatingPointUtils::isNaN(val)) + { + // Comparison with NaN is always false + resultVN = ZeroVN; + } + break; } - // (relop == 1) == relop - OneVN = VNOneForType(typ); - if (IsVNRelop(arg0VN) && (arg1VN == OneVN)) + + // The remaining only apply to integers + assert(varTypeIsIntegralOrI(TypeOfVN(arg0VN))); + + // Handle (x == x) == true + if (arg0VN == arg1VN) { - resultVN = arg0VN; + resultVN = VNOneForType(typ); break; } - else if (IsVNRelop(arg1VN) && (arg0VN == OneVN)) + + if (genTreeOps(func) == GT_GE) { - resultVN = arg1VN; - break; + // (never negative) >= 0 == true + if ((arg1VN == VNZeroForType(TypeOfVN(arg1VN))) && IsVNNeverNegative(arg0VN)) + { + resultVN = VNOneForType(typ); + } } - // (x == x) == true (integer only) - FALLTHROUGH; - case GT_GE: - case GT_LE: - // (x <= x) == true (integer only) - // (x >= x) == true (integer only) - if ((arg0VN == arg1VN) && varTypeIsIntegralOrI(TypeOfVN(arg0VN))) + else if (genTreeOps(func) == GT_LE) { - resultVN = VNOneForType(typ); + // 0 <= (never negative) == true + if ((arg0VN == VNZeroForType(TypeOfVN(arg0VN))) && IsVNNeverNegative(arg1VN)) + { + resultVN = VNOneForType(typ); + } } - else if (varTypeIsIntegralOrI(TypeOfVN(arg0VN))) + break; + } + + case GT_NE: + { + ValueNum ZeroVN = VNZeroForType(typ); + + // Handle `(relop != 0) == relop` and `(0 != relop) == relop` + if (IsVNRelop(opVN)) { - ZeroVN = VNZeroForType(typ); - if (genTreeOps(func) == GT_GE) + if (cnsVN == ZeroVN) { - // (never negative) >= 0 == true - if ((arg1VN == ZeroVN) && IsVNNeverNegative(arg0VN)) - { - resultVN = VNOneForType(typ); - } + resultVN = opVN; + break; } - else if (genTreeOps(func) == GT_LE) + + // Handle `(relop != 1) == !relop` and `(1 != relop) == !relop` + if (cnsVN == VNOneForType(typ)) { - // 0 <= (never negative) == true - if ((arg0VN == ZeroVN) && IsVNNeverNegative(arg1VN)) + ValueNum revVN = GetRelatedRelop(opVN, VN_RELATION_KIND::VRK_Reverse); + if (revVN != NoVN) { - resultVN = VNOneForType(typ); + resultVN = revVN; + break; } } } - break; - case GT_NE: - // (null != non-null) == true - // (non-null != null) == true - if (((arg0VN == VNForNull()) && IsKnownNonNull(arg1VN)) || - ((arg1VN == VNForNull()) && IsKnownNonNull(arg0VN))) + var_types opTyp = TypeOfVN(arg0VN); + + if (varTypeIsFloating(opTyp)) { - resultVN = VNOneForType(typ); + if (cnsVN == NoVN) + { + break; + } + + double val; + + if (opTyp == TYP_FLOAT) + { + val = GetConstantSingle(cnsVN); + } + else + { + assert(opTyp == TYP_DOUBLE); + val = GetConstantDouble(cnsVN); + } + + if (FloatingPointUtils::isNaN(val)) + { + // Comparison with NaN is always true + resultVN = VNOneForType(typ); + } break; } - // (x != x) == false (integer only) - else if ((arg0VN == arg1VN) && varTypeIsIntegralOrI(TypeOfVN(arg0VN))) + + // The remaining only apply to integers + assert(varTypeIsIntegralOrI(TypeOfVN(arg0VN))); + + // Handle `(null != non-null) == true` and `(non-null != null) == true` + if ((cnsVN == VNForNull()) && IsKnownNonNull(opVN)) { - resultVN = VNZeroForType(typ); + resultVN = VNOneForType(typ); break; } - // (relop != 0) == relop - ZeroVN = VNZeroForType(typ); - if (IsVNRelop(arg0VN) && (arg1VN == ZeroVN)) + + // Handle `(x != x) == false` + if (arg0VN == arg1VN) { - resultVN = arg0VN; + resultVN = VNZeroForType(typ); break; } - else if (IsVNRelop(arg1VN) && (arg0VN == ZeroVN)) + break; + } + + case GT_GT: + case GT_LT: + { + ValueNum ZeroVN = VNZeroForType(typ); + + // Handle `(x > x) == false` and `x < x == false` + if (arg0VN == arg1VN) { - resultVN = arg1VN; - break; + resultVN = ZeroVN; } - // (relop != 1) == !relop - OneVN = VNOneForType(typ); - if (IsVNRelop(arg0VN) && (arg1VN == OneVN)) + + var_types opTyp = TypeOfVN(arg0VN); + + if (varTypeIsFloating(opTyp)) { - ValueNum rev0VN = GetRelatedRelop(arg0VN, VN_RELATION_KIND::VRK_Reverse); - if (rev0VN != NoVN) + if (cnsVN == NoVN) { - resultVN = rev0VN; break; } - } - else if (IsVNRelop(arg1VN) && (arg0VN == OneVN)) - { - ValueNum rev1VN = GetRelatedRelop(arg1VN, VN_RELATION_KIND::VRK_Reverse); - if (rev1VN != NoVN) + + double val; + + if (opTyp == TYP_FLOAT) { - resultVN = rev1VN; - break; + val = GetConstantSingle(cnsVN); + } + else + { + assert(opTyp == TYP_DOUBLE); + val = GetConstantDouble(cnsVN); } - } - break; - case GT_GT: - case GT_LT: - // (x > x) == false (integer & floating point) - // (x < x) == false (integer & floating point) - if (arg0VN == arg1VN) - { - resultVN = VNZeroForType(typ); + if (FloatingPointUtils::isNaN(val)) + { + // Comparison with NaN is always false + resultVN = ZeroVN; + } + break; } - else if (varTypeIsIntegralOrI(TypeOfVN(arg0VN))) + + // The remaining only apply to integers + assert(varTypeIsIntegralOrI(TypeOfVN(arg0VN))); + + if (genTreeOps(func) == GT_LT) { - ZeroVN = VNZeroForType(typ); - if (genTreeOps(func) == GT_LT) + // (never negative) < 0 == false + if ((arg1VN == VNZeroForType(TypeOfVN(arg1VN))) && IsVNNeverNegative(arg0VN)) { - // (never negative) < 0 == false - if ((arg1VN == ZeroVN) && IsVNNeverNegative(arg0VN)) - { - resultVN = ZeroVN; - } + resultVN = ZeroVN; } - else if (genTreeOps(func) == GT_GT) + } + else if (genTreeOps(func) == GT_GT) + { + // 0 > (never negative) == false + if ((arg0VN == VNZeroForType(TypeOfVN(arg0VN))) && IsVNNeverNegative(arg1VN)) { - // 0 > (never negative) == false - if ((arg0VN == ZeroVN) && IsVNNeverNegative(arg1VN)) - { - resultVN = ZeroVN; - } + resultVN = ZeroVN; } } break; + } default: break; @@ -5254,53 +5427,101 @@ ValueNum ValueNumStore::EvalUsingMathIdentity(var_types typ, VNFunc func, ValueN { case VNF_ADD_OVF: case VNF_ADD_UN_OVF: + { resultVN = identityForAddition(); break; + } case VNF_SUB_OVF: case VNF_SUB_UN_OVF: + { resultVN = identityForSubtraction(/* ovf */ true); break; + } case VNF_MUL_OVF: case VNF_MUL_UN_OVF: + { resultVN = identityForMultiplication(); break; + } case VNF_LT_UN: - // (x < 0) == false - // (x < x) == false + { + // Handle `(x < 0) == false` and `(x < x) == false` std::swap(arg0VN, arg1VN); FALLTHROUGH; + } + case VNF_GT_UN: - // (0 > x) == false - // (x > x) == false - // None of the above identities apply to floating point comparisons. - // For example, (NaN > NaN) is true instead of false because these are - // unordered comparisons. - if (varTypeIsIntegralOrI(TypeOfVN(arg0VN)) && - ((arg0VN == VNZeroForType(TypeOfVN(arg0VN))) || (arg0VN == arg1VN))) + { + ValueNum ZeroVN = VNZeroForType(typ); + + // Handle `(0 > x) == false` and `(x > x) == false` + + var_types opTyp = TypeOfVN(arg0VN); + + if (varTypeIsFloating(opTyp)) { - resultVN = VNZeroForType(typ); + if (cnsVN == NoVN) + { + break; + } + + double val; + + if (opTyp == TYP_FLOAT) + { + val = GetConstantSingle(cnsVN); + } + else + { + assert(opTyp == TYP_DOUBLE); + val = GetConstantDouble(cnsVN); + } + + if (FloatingPointUtils::isNaN(val)) + { + // Unordered comparison with NaN is always true + resultVN = VNOneForType(typ); + } + break; + } + + assert(varTypeIsIntegralOrI(TypeOfVN(arg0VN))); + + if (((arg0VN == VNZeroForType(TypeOfVN(arg0VN))) || (arg0VN == arg1VN))) + { + resultVN = ZeroVN; } break; + } case VNF_GE_UN: - // (x >= 0) == true - // (x >= x) == true + { + // Handle `(x >= 0) == true` and `(x >= x) == true` std::swap(arg0VN, arg1VN); FALLTHROUGH; + } + case VNF_LE_UN: - // (0 <= x) == true - // (x <= x) == true - // Unlike (x < x) and (x > x), (x >= x) and (x <= x) also apply to floating - // point comparisons: x is either equal to itself or is unordered if it's NaN. - if ((varTypeIsIntegralOrI(TypeOfVN(arg0VN)) && (arg0VN == VNZeroForType(TypeOfVN(arg0VN)))) || - (arg0VN == arg1VN)) + { + // Handle `(0 <= x) == true` and `(x <= x) == true` + + if (arg0VN == arg1VN) { + // This is safe for floating-point since unordered comparison with NaN is always true resultVN = VNOneForType(typ); + break; + } + + if (varTypeIsIntegralOrI(TypeOfVN(arg0VN)) && (arg0VN == VNZeroForType(TypeOfVN(arg0VN)))) + { + resultVN = VNOneForType(typ); + break; } break; + } default: break;