[turbofan] Move Number.isInteger to JSCallReducer

This also adds ObjectIsInteger and NumberIsInteger
operators.

Bug: v8:7340, v8:7250
Change-Id: I8067276d12c8532931f90e6397f8435362c2f9af
Reviewed-on: https://chromium-review.googlesource.com/951602
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51991}
diff --git a/src/compiler/effect-control-linearizer.cc b/src/compiler/effect-control-linearizer.cc
index 4c28f00..6d81483 100644
--- a/src/compiler/effect-control-linearizer.cc
+++ b/src/compiler/effect-control-linearizer.cc
@@ -872,6 +872,12 @@
     case IrOpcode::kObjectIsFiniteNumber:
       result = LowerObjectIsFiniteNumber(node);
       break;
+    case IrOpcode::kNumberIsInteger:
+      result = LowerNumberIsInteger(node);
+      break;
+    case IrOpcode::kObjectIsInteger:
+      result = LowerObjectIsInteger(node);
+      break;
     case IrOpcode::kCheckFloat64Hole:
       result = LowerCheckFloat64Hole(node, frame_state);
       break;
@@ -2169,15 +2175,15 @@
 
   auto done = __ MakeLabel(MachineRepresentation::kBit);
 
-  // Check if {value} is a Smi.
+  // Check if {object} is a Smi.
   __ GotoIf(ObjectIsSmi(object), &done, one);
 
-  // Check if {value} is a HeapNumber.
+  // Check if {object} is a HeapNumber.
   Node* value_map = __ LoadField(AccessBuilder::ForMap(), object);
   __ GotoIfNot(__ WordEqual(value_map, __ HeapNumberMapConstant()), &done,
                zero);
 
-  // Value is a HeapNumber.
+  // {object} is a HeapNumber.
   Node* value = __ LoadField(AccessBuilder::ForHeapNumberValue(), object);
   Node* diff = __ Float64Sub(value, value);
   Node* check = __ Float64Equal(diff, diff);
@@ -2187,6 +2193,40 @@
   return done.PhiAt(0);
 }
 
+Node* EffectControlLinearizer::LowerNumberIsInteger(Node* node) {
+  Node* number = node->InputAt(0);
+  Node* trunc = BuildFloat64RoundTruncate(number);
+  Node* diff = __ Float64Sub(number, trunc);
+  Node* check = __ Float64Equal(diff, __ Float64Constant(0));
+  return check;
+}
+
+Node* EffectControlLinearizer::LowerObjectIsInteger(Node* node) {
+  Node* object = node->InputAt(0);
+  Node* zero = __ Int32Constant(0);
+  Node* one = __ Int32Constant(1);
+
+  auto done = __ MakeLabel(MachineRepresentation::kBit);
+
+  // Check if {object} is a Smi.
+  __ GotoIf(ObjectIsSmi(object), &done, one);
+
+  // Check if {object} is a HeapNumber.
+  Node* value_map = __ LoadField(AccessBuilder::ForMap(), object);
+  __ GotoIfNot(__ WordEqual(value_map, __ HeapNumberMapConstant()), &done,
+               zero);
+
+  // {object} is a HeapNumber.
+  Node* value = __ LoadField(AccessBuilder::ForHeapNumberValue(), object);
+  Node* trunc = BuildFloat64RoundTruncate(value);
+  Node* diff = __ Float64Sub(value, trunc);
+  Node* check = __ Float64Equal(diff, __ Float64Constant(0));
+  __ Goto(&done, check);
+
+  __ Bind(&done);
+  return done.PhiAt(0);
+}
+
 Node* EffectControlLinearizer::LowerObjectIsMinusZero(Node* node) {
   Node* value = node->InputAt(0);
   Node* zero = __ Int32Constant(0);
@@ -4218,9 +4258,8 @@
 }
 
 Node* EffectControlLinearizer::BuildFloat64RoundDown(Node* value) {
-  Node* round_down = __ Float64RoundDown(value);
-  if (round_down != nullptr) {
-    return round_down;
+  if (machine()->Float64RoundDown().IsSupported()) {
+    return __ Float64RoundDown(value);
   }
 
   Node* const input = value;
@@ -4364,14 +4403,10 @@
   return Just(done.PhiAt(0));
 }
 
-Maybe<Node*> EffectControlLinearizer::LowerFloat64RoundTruncate(Node* node) {
-  // Nothing to be done if a fast hardware instruction is available.
+Node* EffectControlLinearizer::BuildFloat64RoundTruncate(Node* input) {
   if (machine()->Float64RoundTruncate().IsSupported()) {
-    return Nothing<Node*>();
+    return __ Float64RoundTruncate(input);
   }
-
-  Node* const input = node->InputAt(0);
-
   // General case for trunc.
   //
   //   if 0.0 < input then
@@ -4452,7 +4487,17 @@
     __ Goto(&done, input);
   }
   __ Bind(&done);
-  return Just(done.PhiAt(0));
+  return done.PhiAt(0);
+}
+
+Maybe<Node*> EffectControlLinearizer::LowerFloat64RoundTruncate(Node* node) {
+  // Nothing to be done if a fast hardware instruction is available.
+  if (machine()->Float64RoundTruncate().IsSupported()) {
+    return Nothing<Node*>();
+  }
+
+  Node* const input = node->InputAt(0);
+  return Just(BuildFloat64RoundTruncate(input));
 }
 
 Node* EffectControlLinearizer::LowerFindOrderedHashMapEntry(Node* node) {
diff --git a/src/compiler/effect-control-linearizer.h b/src/compiler/effect-control-linearizer.h
index d9dd556..dce5b4e 100644
--- a/src/compiler/effect-control-linearizer.h
+++ b/src/compiler/effect-control-linearizer.h
@@ -107,6 +107,8 @@
   Node* LowerNumberIsFloat64Hole(Node* node);
   Node* LowerNumberIsFinite(Node* node);
   Node* LowerObjectIsFiniteNumber(Node* node);
+  Node* LowerNumberIsInteger(Node* node);
+  Node* LowerObjectIsInteger(Node* node);
   Node* LowerArgumentsFrame(Node* node);
   Node* LowerArgumentsLength(Node* node);
   Node* LowerNewDoubleElements(Node* node);
@@ -169,6 +171,7 @@
                                                  Node* value,
                                                  Node* frame_state);
   Node* BuildFloat64RoundDown(Node* value);
+  Node* BuildFloat64RoundTruncate(Node* input);
   Node* ComputeIntegerHash(Node* value);
   Node* LowerStringComparison(Callable const& callable, Node* node);
   Node* IsElementsKindGreaterThan(Node* kind, ElementsKind reference_kind);
diff --git a/src/compiler/graph-assembler.cc b/src/compiler/graph-assembler.cc
index 676860f..37d6c94 100644
--- a/src/compiler/graph-assembler.cc
+++ b/src/compiler/graph-assembler.cc
@@ -86,10 +86,13 @@
 #undef CHECKED_BINOP_DEF
 
 Node* GraphAssembler::Float64RoundDown(Node* value) {
-  if (machine()->Float64RoundDown().IsSupported()) {
-    return graph()->NewNode(machine()->Float64RoundDown().op(), value);
-  }
-  return nullptr;
+  CHECK(machine()->Float64RoundDown().IsSupported());
+  return graph()->NewNode(machine()->Float64RoundDown().op(), value);
+}
+
+Node* GraphAssembler::Float64RoundTruncate(Node* value) {
+  CHECK(machine()->Float64RoundTruncate().IsSupported());
+  return graph()->NewNode(machine()->Float64RoundTruncate().op(), value);
 }
 
 Node* GraphAssembler::Projection(int index, Node* value) {
diff --git a/src/compiler/graph-assembler.h b/src/compiler/graph-assembler.h
index f3dd4e7..bc793df 100644
--- a/src/compiler/graph-assembler.h
+++ b/src/compiler/graph-assembler.h
@@ -196,6 +196,7 @@
   Node* Unreachable();
 
   Node* Float64RoundDown(Node* value);
+  Node* Float64RoundTruncate(Node* value);
 
   Node* ToNumber(Node* value);
   Node* BitcastWordToTagged(Node* value);
diff --git a/src/compiler/js-builtin-reducer.cc b/src/compiler/js-builtin-reducer.cc
index 3e90aeb..1cf94bc 100644
--- a/src/compiler/js-builtin-reducer.cc
+++ b/src/compiler/js-builtin-reducer.cc
@@ -754,22 +754,6 @@
   return Replace(value);
 }
 
-// ES6 section 20.1.2.3 Number.isInteger ( number )
-Reduction JSBuiltinReducer::ReduceNumberIsInteger(Node* node) {
-  JSCallReduction r(node);
-  if (r.InputsMatchOne(Type::Number())) {
-    // Number.isInteger(x:number) -> NumberEqual(NumberSubtract(x, x'), #0)
-    // where x' = NumberTrunc(x)
-    Node* input = r.GetJSCallInput(0);
-    Node* trunc = graph()->NewNode(simplified()->NumberTrunc(), input);
-    Node* diff = graph()->NewNode(simplified()->NumberSubtract(), input, trunc);
-    Node* value = graph()->NewNode(simplified()->NumberEqual(), diff,
-                                   jsgraph()->ZeroConstant());
-    return Replace(value);
-  }
-  return NoChange();
-}
-
 // ES6 section 20.1.2.4 Number.isNaN ( number )
 Reduction JSBuiltinReducer::ReduceNumberIsNaN(Node* node) {
   JSCallReduction r(node);
@@ -982,8 +966,6 @@
       return ReduceCollectionIteratorNext(
           node, OrderedHashMap::kEntrySize, factory()->empty_ordered_hash_map(),
           FIRST_MAP_ITERATOR_TYPE, LAST_MAP_ITERATOR_TYPE);
-    case kNumberIsInteger:
-      reduction = ReduceNumberIsInteger(node);
       break;
     case kNumberIsNaN:
       reduction = ReduceNumberIsNaN(node);
diff --git a/src/compiler/js-builtin-reducer.h b/src/compiler/js-builtin-reducer.h
index 7b81d03..438a2d8 100644
--- a/src/compiler/js-builtin-reducer.h
+++ b/src/compiler/js-builtin-reducer.h
@@ -57,7 +57,6 @@
   Reduction ReduceGlobalIsNaN(Node* node);
   Reduction ReduceMapHas(Node* node);
   Reduction ReduceMapGet(Node* node);
-  Reduction ReduceNumberIsInteger(Node* node);
   Reduction ReduceNumberIsNaN(Node* node);
   Reduction ReduceNumberIsSafeInteger(Node* node);
   Reduction ReduceNumberParseInt(Node* node);
diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc
index 29b5ce5..2db6fd9 100644
--- a/src/compiler/js-call-reducer.cc
+++ b/src/compiler/js-call-reducer.cc
@@ -3426,6 +3426,8 @@
                               jsgraph()->Constant(V8_INFINITY));
     case Builtins::kNumberIsFinite:
       return ReduceNumberIsFinite(node);
+    case Builtins::kNumberIsInteger:
+      return ReduceNumberIsInteger(node);
     case Builtins::kReturnReceiver:
       return ReduceReturnReceiver(node);
     case Builtins::kStringPrototypeIndexOf:
@@ -5894,6 +5896,19 @@
   return Replace(value);
 }
 
+// ES #sec-number.isfinite
+Reduction JSCallReducer::ReduceNumberIsInteger(Node* node) {
+  if (node->op()->ValueInputCount() < 3) {
+    Node* value = jsgraph()->FalseConstant();
+    ReplaceWithValue(node, value);
+    return Replace(value);
+  }
+  Node* input = NodeProperties::GetValueInput(node, 2);
+  Node* value = graph()->NewNode(simplified()->ObjectIsInteger(), input);
+  ReplaceWithValue(node, value);
+  return Replace(value);
+}
+
 Graph* JSCallReducer::graph() const { return jsgraph()->graph(); }
 
 Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }
diff --git a/src/compiler/js-call-reducer.h b/src/compiler/js-call-reducer.h
index cbbed17..3ff341a 100644
--- a/src/compiler/js-call-reducer.h
+++ b/src/compiler/js-call-reducer.h
@@ -151,6 +151,7 @@
   Reduction ReduceMathMinMax(Node* node, const Operator* op, Node* empty_value);
 
   Reduction ReduceNumberIsFinite(Node* node);
+  Reduction ReduceNumberIsInteger(Node* node);
 
   // Returns the updated {to} node, and updates control and effect along the
   // way.
diff --git a/src/compiler/opcodes.h b/src/compiler/opcodes.h
index 7c60183..aa6c074 100644
--- a/src/compiler/opcodes.h
+++ b/src/compiler/opcodes.h
@@ -386,6 +386,8 @@
   V(NumberIsFloat64Hole)                \
   V(NumberIsFinite)                     \
   V(ObjectIsFiniteNumber)               \
+  V(NumberIsInteger)                    \
+  V(ObjectIsInteger)                    \
   V(ObjectIsArrayBufferView)            \
   V(ObjectIsBigInt)                     \
   V(ObjectIsCallable)                   \
diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc
index 15d7093..defe1cc 100644
--- a/src/compiler/simplified-lowering.cc
+++ b/src/compiler/simplified-lowering.cc
@@ -2771,6 +2771,36 @@
       case IrOpcode::kNumberIsFinite: {
         UNREACHABLE();
       }
+      case IrOpcode::kObjectIsInteger: {
+        Type* const input_type = GetUpperBound(node->InputAt(0));
+        // Ranges are always integers.
+        if (input_type->IsRange()) {
+          VisitUnop(node, UseInfo::None(), MachineRepresentation::kBit);
+          if (lower()) {
+            DeferReplacement(node, lowering->jsgraph()->Int32Constant(1));
+          }
+        } else if (!input_type->Maybe(Type::Number())) {
+          VisitUnop(node, UseInfo::Any(), MachineRepresentation::kBit);
+          if (lower()) {
+            DeferReplacement(node, lowering->jsgraph()->Int32Constant(0));
+          }
+        } else if (input_type->Is(Type::Number())) {
+          VisitUnop(node, UseInfo::TruncatingFloat64(),
+                    MachineRepresentation::kBit);
+          if (lower()) {
+            NodeProperties::ChangeOp(node,
+                                     lowering->simplified()->NumberIsInteger());
+          }
+        } else {
+          VisitUnop(node, UseInfo::AnyTagged(), MachineRepresentation::kBit);
+        }
+        return;
+      }
+      case IrOpcode::kNumberIsInteger: {
+        VisitUnop(node, UseInfo::TruncatingFloat64(),
+                  MachineRepresentation::kBit);
+        return;
+      }
       case IrOpcode::kObjectIsMinusZero: {
         Type* const input_type = GetUpperBound(node->InputAt(0));
         if (input_type->Is(Type::MinusZero())) {
diff --git a/src/compiler/simplified-operator.cc b/src/compiler/simplified-operator.cc
index 78f99fa..7a52bed 100644
--- a/src/compiler/simplified-operator.cc
+++ b/src/compiler/simplified-operator.cc
@@ -711,6 +711,8 @@
   V(NumberIsFloat64Hole, Operator::kNoProperties, 1, 0)          \
   V(NumberIsFinite, Operator::kNoProperties, 1, 0)               \
   V(ObjectIsFiniteNumber, Operator::kNoProperties, 1, 0)         \
+  V(NumberIsInteger, Operator::kNoProperties, 1, 0)              \
+  V(ObjectIsInteger, Operator::kNoProperties, 1, 0)              \
   V(ConvertTaggedHoleToUndefined, Operator::kNoProperties, 1, 0) \
   V(SameValue, Operator::kCommutative, 2, 0)                     \
   V(ReferenceEqual, Operator::kCommutative, 2, 0)                \
diff --git a/src/compiler/simplified-operator.h b/src/compiler/simplified-operator.h
index 00d8d5a..2984104 100644
--- a/src/compiler/simplified-operator.h
+++ b/src/compiler/simplified-operator.h
@@ -624,6 +624,8 @@
   const Operator* NumberIsFloat64Hole();
   const Operator* NumberIsFinite();
   const Operator* ObjectIsFiniteNumber();
+  const Operator* NumberIsInteger();
+  const Operator* ObjectIsInteger();
 
   const Operator* ArgumentsFrame();
   const Operator* ArgumentsLength(int formal_parameter_count,
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index 0ae9ed0..60c7fad 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -2182,6 +2182,12 @@
   return Type::Boolean();
 }
 
+Type* Typer::Visitor::TypeNumberIsInteger(Node* node) { UNREACHABLE(); }
+
+Type* Typer::Visitor::TypeObjectIsInteger(Node* node) {
+  return Type::Boolean();
+}
+
 Type* Typer::Visitor::TypeObjectIsNaN(Node* node) {
   return TypeUnaryOp(node, ObjectIsNaN);
 }
diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc
index 244eae0..742f30d 100644
--- a/src/compiler/verifier.cc
+++ b/src/compiler/verifier.cc
@@ -1174,6 +1174,14 @@
       CheckValueInputIs(node, 0, Type::Any());
       CheckTypeIs(node, Type::Boolean());
       break;
+    case IrOpcode::kNumberIsInteger:
+      CheckValueInputIs(node, 0, Type::Number());
+      CheckTypeIs(node, Type::Boolean());
+      break;
+    case IrOpcode::kObjectIsInteger:
+      CheckValueInputIs(node, 0, Type::Any());
+      CheckTypeIs(node, Type::Boolean());
+      break;
     case IrOpcode::kFindOrderedHashMapEntry:
       CheckValueInputIs(node, 0, Type::Any());
       CheckTypeIs(node, Type::SignedSmall());
diff --git a/test/mjsunit/compiler/number-isinteger.js b/test/mjsunit/compiler/number-isinteger.js
index 8999569..aae172e 100644
--- a/test/mjsunit/compiler/number-isinteger.js
+++ b/test/mjsunit/compiler/number-isinteger.js
@@ -12,6 +12,9 @@
   assertTrue(f(Number.MIN_SAFE_INTEGER - 13));
   assertTrue(f(Number.MAX_SAFE_INTEGER));
   assertTrue(f(Number.MAX_SAFE_INTEGER + 23));
+  assertTrue(f(0));
+  assertTrue(f(-1));
+  assertTrue(f(123456));
   assertFalse(f(Number.NaN));
   assertFalse(f(Number.POSITIVE_INFINITY));
   assertFalse(f(Number.NEGATIVE_INFINITY));
@@ -28,3 +31,32 @@
 test(f);
 %OptimizeFunctionOnNextCall(f);
 test(f);
+
+
+function test2(f) {
+  assertTrue(f(0));
+  assertFalse(f(Number.MIN_VALUE));
+  assertTrue(f(Number.MAX_VALUE));
+  assertTrue(f(Number.MIN_SAFE_INTEGER));
+  assertTrue(f(Number.MIN_SAFE_INTEGER - 13));
+  assertTrue(f(Number.MAX_SAFE_INTEGER));
+  assertTrue(f(Number.MAX_SAFE_INTEGER + 23));
+  assertTrue(f(0));
+  assertTrue(f(-1));
+  assertTrue(f(123456));
+  assertFalse(f(Number.NaN));
+  assertFalse(f(Number.POSITIVE_INFINITY));
+  assertFalse(f(Number.NEGATIVE_INFINITY));
+  assertFalse(f(1 / 0));
+  assertFalse(f(-1 / 0));
+  assertFalse(f(Number.EPSILON));
+}
+
+function f2(x) {
+  return Number.isInteger(x);
+}
+
+test2(f2);
+test2(f2);
+%OptimizeFunctionOnNextCall(f2);
+test2(f2);
diff --git a/test/unittests/compiler/js-builtin-reducer-unittest.cc b/test/unittests/compiler/js-builtin-reducer-unittest.cc
index 85f9ce3..7fc85be 100644
--- a/test/unittests/compiler/js-builtin-reducer-unittest.cc
+++ b/test/unittests/compiler/js-builtin-reducer-unittest.cc
@@ -195,31 +195,6 @@
                                          IsPlainPrimitiveToNumber(p0))));
 }
 
-
-// -----------------------------------------------------------------------------
-// Number.isInteger
-
-TEST_F(JSBuiltinReducerTest, NumberIsIntegerWithNumber) {
-  Node* function = NumberFunction("isInteger");
-
-  Node* effect = graph()->start();
-  Node* control = graph()->start();
-  Node* context = UndefinedConstant();
-  Node* frame_state = graph()->start();
-  TRACED_FOREACH(Type*, t0, kNumberTypes) {
-    Node* p0 = Parameter(t0, 0);
-    Node* call =
-        graph()->NewNode(javascript()->Call(3), function, UndefinedConstant(),
-                         p0, context, frame_state, effect, control);
-    Reduction r = Reduce(call);
-
-    ASSERT_TRUE(r.Changed());
-    EXPECT_THAT(r.replacement(),
-                IsNumberEqual(IsNumberSubtract(p0, IsNumberTrunc(p0)),
-                              IsNumberConstant(0.0)));
-  }
-}
-
 // -----------------------------------------------------------------------------
 // Number.isNaN
 
diff --git a/test/unittests/compiler/js-call-reducer-unittest.cc b/test/unittests/compiler/js-call-reducer-unittest.cc
index 652455e..af41b70 100644
--- a/test/unittests/compiler/js-call-reducer-unittest.cc
+++ b/test/unittests/compiler/js-call-reducer-unittest.cc
@@ -471,6 +471,26 @@
   EXPECT_THAT(r.replacement(), IsObjectIsFiniteNumber(p0));
 }
 
+// -----------------------------------------------------------------------------
+// Number.isInteger
+
+TEST_F(JSCallReducerTest, NumberIsIntegerWithNumber) {
+  Node* function = NumberFunction("isInteger");
+
+  Node* effect = graph()->start();
+  Node* control = graph()->start();
+  Node* context = UndefinedConstant();
+  Node* frame_state = graph()->start();
+  Node* p0 = Parameter(Type::Any(), 0);
+  Node* call =
+      graph()->NewNode(javascript()->Call(3), function, UndefinedConstant(), p0,
+                       context, frame_state, effect, control);
+  Reduction r = Reduce(call);
+
+  ASSERT_TRUE(r.Changed());
+  EXPECT_THAT(r.replacement(), IsObjectIsInteger(p0));
+}
+
 }  // namespace compiler
 }  // namespace internal
 }  // namespace v8
diff --git a/test/unittests/compiler/node-test-utils.cc b/test/unittests/compiler/node-test-utils.cc
index a96da1a..d48bd09 100644
--- a/test/unittests/compiler/node-test-utils.cc
+++ b/test/unittests/compiler/node-test-utils.cc
@@ -2181,6 +2181,7 @@
 IS_UNOP_MATCHER(NumberToUint32)
 IS_UNOP_MATCHER(PlainPrimitiveToNumber)
 IS_UNOP_MATCHER(ObjectIsFiniteNumber)
+IS_UNOP_MATCHER(ObjectIsInteger)
 IS_UNOP_MATCHER(ObjectIsNaN)
 IS_UNOP_MATCHER(ObjectIsReceiver)
 IS_UNOP_MATCHER(ObjectIsSmi)
diff --git a/test/unittests/compiler/node-test-utils.h b/test/unittests/compiler/node-test-utils.h
index 9aabce7..8072e43 100644
--- a/test/unittests/compiler/node-test-utils.h
+++ b/test/unittests/compiler/node-test-utils.h
@@ -312,6 +312,7 @@
                               const Matcher<Node*>& control_matcher);
 
 Matcher<Node*> IsObjectIsFiniteNumber(const Matcher<Node*>& value_matcher);
+Matcher<Node*> IsObjectIsInteger(const Matcher<Node*>& value_matcher);
 Matcher<Node*> IsObjectIsNaN(const Matcher<Node*>& value_matcher);
 Matcher<Node*> IsObjectIsReceiver(const Matcher<Node*>& value_matcher);
 Matcher<Node*> IsObjectIsSmi(const Matcher<Node*>& value_matcher);