// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/compiler/js-builtin-reducer.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/typer.h"
#include "src/isolate-inl.h"
#include "test/unittests/compiler/graph-unittest.h"
#include "test/unittests/compiler/node-test-utils.h"
#include "testing/gmock-support.h"

using testing::BitEq;
using testing::Capture;

namespace v8 {
namespace internal {
namespace compiler {

class JSBuiltinReducerTest : public TypedGraphTest {
 public:
  JSBuiltinReducerTest() : javascript_(zone()) {}

 protected:
  Reduction Reduce(Node* node, MachineOperatorBuilder::Flags flags =
                                   MachineOperatorBuilder::Flag::kNoFlags) {
    MachineOperatorBuilder machine(zone(), MachineType::PointerRepresentation(),
                                   flags);
    SimplifiedOperatorBuilder simplified(zone());
    JSGraph jsgraph(isolate(), graph(), common(), javascript(), &simplified,
                    &machine);
    // TODO(titzer): mock the GraphReducer here for better unit testing.
    GraphReducer graph_reducer(zone(), graph());
    JSBuiltinReducer reducer(&graph_reducer, &jsgraph);
    return reducer.Reduce(node);
  }

  Node* MathFunction(const char* name) {
    Handle<Object> m =
        JSObject::GetProperty(isolate()->global_object(),
                              isolate()->factory()->NewStringFromAsciiChecked(
                                  "Math")).ToHandleChecked();
    Handle<JSFunction> f = Handle<JSFunction>::cast(
        JSObject::GetProperty(
            m, isolate()->factory()->NewStringFromAsciiChecked(name))
            .ToHandleChecked());
    return HeapConstant(f);
  }

  JSOperatorBuilder* javascript() { return &javascript_; }

 private:
  JSOperatorBuilder javascript_;
};


namespace {

Type* const kIntegral32Types[] = {Type::UnsignedSmall(), Type::Negative32(),
                                  Type::Unsigned31(),    Type::SignedSmall(),
                                  Type::Signed32(),      Type::Unsigned32(),
                                  Type::Integral32()};


const LanguageMode kLanguageModes[] = {SLOPPY, STRICT, STRONG};


// TODO(mstarzinger): Find a common place and unify with test-js-typed-lowering.
Type* const kNumberTypes[] = {
    Type::UnsignedSmall(), Type::Negative32(),  Type::Unsigned31(),
    Type::SignedSmall(),   Type::Signed32(),    Type::Unsigned32(),
    Type::Integral32(),    Type::MinusZero(),   Type::NaN(),
    Type::OrderedNumber(), Type::PlainNumber(), Type::Number()};

}  // namespace


// -----------------------------------------------------------------------------
// Math.max


TEST_F(JSBuiltinReducerTest, MathMax0) {
  Node* function = MathFunction("max");

  Node* effect = graph()->start();
  Node* control = graph()->start();
  Node* context = UndefinedConstant();
  Node* frame_state = graph()->start();
  TRACED_FOREACH(LanguageMode, language_mode, kLanguageModes) {
    Node* call = graph()->NewNode(javascript()->CallFunction(2, language_mode),
                                  function, UndefinedConstant(), context,
                                  frame_state, frame_state, effect, control);
    Reduction r = Reduce(call);

    ASSERT_TRUE(r.Changed());
    EXPECT_THAT(r.replacement(), IsNumberConstant(-V8_INFINITY));
  }
}


TEST_F(JSBuiltinReducerTest, MathMax1) {
  Node* function = MathFunction("max");

  Node* effect = graph()->start();
  Node* control = graph()->start();
  Node* context = UndefinedConstant();
  Node* frame_state = graph()->start();
  TRACED_FOREACH(LanguageMode, language_mode, kLanguageModes) {
    TRACED_FOREACH(Type*, t0, kNumberTypes) {
      Node* p0 = Parameter(t0, 0);
      Node* call =
          graph()->NewNode(javascript()->CallFunction(3, language_mode),
                           function, UndefinedConstant(), p0, context,
                           frame_state, frame_state, effect, control);
      Reduction r = Reduce(call);

      ASSERT_TRUE(r.Changed());
      EXPECT_THAT(r.replacement(), p0);
    }
  }
}


TEST_F(JSBuiltinReducerTest, MathMax2) {
  Node* function = MathFunction("max");

  Node* effect = graph()->start();
  Node* control = graph()->start();
  Node* context = UndefinedConstant();
  Node* frame_state = graph()->start();
  TRACED_FOREACH(LanguageMode, language_mode, kLanguageModes) {
    TRACED_FOREACH(Type*, t0, kIntegral32Types) {
      TRACED_FOREACH(Type*, t1, kIntegral32Types) {
        Node* p0 = Parameter(t0, 0);
        Node* p1 = Parameter(t1, 1);
        Node* call =
            graph()->NewNode(javascript()->CallFunction(4, language_mode),
                             function, UndefinedConstant(), p0, p1, context,
                             frame_state, frame_state, effect, control);
        Reduction r = Reduce(call);

        ASSERT_TRUE(r.Changed());
        EXPECT_THAT(r.replacement(),
                    IsSelect(MachineRepresentation::kNone,
                             IsNumberLessThan(p1, p0), p0, p1));
      }
    }
  }
}


// -----------------------------------------------------------------------------
// Math.imul


TEST_F(JSBuiltinReducerTest, MathImul) {
  Node* function = MathFunction("imul");

  Node* effect = graph()->start();
  Node* control = graph()->start();
  Node* context = UndefinedConstant();
  Node* frame_state = graph()->start();
  TRACED_FOREACH(LanguageMode, language_mode, kLanguageModes) {
    TRACED_FOREACH(Type*, t0, kIntegral32Types) {
      TRACED_FOREACH(Type*, t1, kIntegral32Types) {
        Node* p0 = Parameter(t0, 0);
        Node* p1 = Parameter(t1, 1);
        Node* call =
            graph()->NewNode(javascript()->CallFunction(4, language_mode),
                             function, UndefinedConstant(), p0, p1, context,
                             frame_state, frame_state, effect, control);
        Reduction r = Reduce(call);

        ASSERT_TRUE(r.Changed());
        EXPECT_THAT(r.replacement(), IsInt32Mul(p0, p1));
      }
    }
  }
}


// -----------------------------------------------------------------------------
// Math.fround


TEST_F(JSBuiltinReducerTest, MathFround) {
  Node* function = MathFunction("fround");

  Node* effect = graph()->start();
  Node* control = graph()->start();
  Node* context = UndefinedConstant();
  Node* frame_state = graph()->start();
  TRACED_FOREACH(LanguageMode, language_mode, kLanguageModes) {
    TRACED_FOREACH(Type*, t0, kNumberTypes) {
      Node* p0 = Parameter(t0, 0);
      Node* call =
          graph()->NewNode(javascript()->CallFunction(3, language_mode),
                           function, UndefinedConstant(), p0, context,
                           frame_state, frame_state, effect, control);
      Reduction r = Reduce(call);

      ASSERT_TRUE(r.Changed());
      EXPECT_THAT(r.replacement(), IsTruncateFloat64ToFloat32(p0));
    }
  }
}

}  // namespace compiler
}  // namespace internal
}  // namespace v8