/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "jit/IonAnalysis.h"
#include "jit/MIRGenerator.h"
#include "jit/MIRGraph.h"
#include "jit/ValueNumbering.h"

#include "jsapi-tests/testJitMinimalFunc.h"
#include "jsapi-tests/tests.h"

using namespace js;
using namespace js::jit;

BEGIN_TEST(testJitFoldsTo_DivReciprocal) {
  MinimalFunc func;
  MBasicBlock* block = func.createEntryBlock();

  // return p / 4.0
  MParameter* p = func.createParameter();
  block->add(p);
  MConstant* c = MConstant::New(func.alloc, DoubleValue(4.0));
  block->add(c);
  MDiv* div = MDiv::New(func.alloc, p, c, MIRType::Double);
  block->add(div);
  if (!div->typePolicy()->adjustInputs(func.alloc, div)) {
    return false;
  }
  MDefinition* left = div->getOperand(0);
  MReturn* ret = MReturn::New(func.alloc, div);
  block->end(ret);

  if (!func.runGVN()) {
    return false;
  }

  // Test that the div got folded to p * 0.25.
  MDefinition* op = ret->getOperand(0);
  CHECK(op->isMul());
  CHECK(op->getOperand(0) == left);
  CHECK(op->getOperand(1)->isConstant());
  CHECK(op->getOperand(1)->toConstant()->numberToDouble() == 0.25);
  return true;
}
END_TEST(testJitFoldsTo_DivReciprocal)

BEGIN_TEST(testJitFoldsTo_NoDivReciprocal) {
  MinimalFunc func;
  MBasicBlock* block = func.createEntryBlock();

  // return p / 5.0
  MParameter* p = func.createParameter();
  block->add(p);
  MConstant* c = MConstant::New(func.alloc, DoubleValue(5.0));
  block->add(c);
  MDiv* div = MDiv::New(func.alloc, p, c, MIRType::Double);
  block->add(div);
  if (!div->typePolicy()->adjustInputs(func.alloc, div)) {
    return false;
  }
  MDefinition* left = div->getOperand(0);
  MDefinition* right = div->getOperand(1);
  MReturn* ret = MReturn::New(func.alloc, div);
  block->end(ret);

  if (!func.runGVN()) {
    return false;
  }

  // Test that the div didn't get folded.
  MDefinition* op = ret->getOperand(0);
  CHECK(op->isDiv());
  CHECK(op->getOperand(0) == left);
  CHECK(op->getOperand(1) == right);
  return true;
}
END_TEST(testJitFoldsTo_NoDivReciprocal)

BEGIN_TEST(testJitNotNot) {
  MinimalFunc func;
  MBasicBlock* block = func.createEntryBlock();

  // return Not(Not(p))
  MParameter* p = func.createParameter();
  block->add(p);
  MNot* not0 = MNot::New(func.alloc, p);
  block->add(not0);
  MNot* not1 = MNot::New(func.alloc, not0);
  block->add(not1);
  MReturn* ret = MReturn::New(func.alloc, not1);
  block->end(ret);

  if (!func.runGVN()) {
    return false;
  }

  // Test that the nots did not get folded.
  MDefinition* op = ret->getOperand(0);
  CHECK(op->isNot());
  CHECK(op->getOperand(0)->isNot());
  CHECK(op->getOperand(0)->getOperand(0) == p);
  return true;
}
END_TEST(testJitNotNot)

BEGIN_TEST(testJitNotNotNot) {
  MinimalFunc func;
  MBasicBlock* block = func.createEntryBlock();

  // return Not(Not(Not(p)))
  MParameter* p = func.createParameter();
  block->add(p);
  MNot* not0 = MNot::New(func.alloc, p);
  block->add(not0);
  MNot* not1 = MNot::New(func.alloc, not0);
  block->add(not1);
  MNot* not2 = MNot::New(func.alloc, not1);
  block->add(not2);
  MReturn* ret = MReturn::New(func.alloc, not2);
  block->end(ret);

  if (!func.runGVN()) {
    return false;
  }

  // Test that the nots got folded.
  MDefinition* op = ret->getOperand(0);
  CHECK(op->isNot());
  CHECK(op->getOperand(0) == p);
  return true;
}
END_TEST(testJitNotNotNot)

BEGIN_TEST(testJitNotTest) {
  MinimalFunc func;
  MBasicBlock* block = func.createEntryBlock();
  MBasicBlock* then = func.createBlock(block);
  MBasicBlock* else_ = func.createBlock(block);
  MBasicBlock* exit = func.createBlock(block);

  // MTest(Not(p))
  MParameter* p = func.createParameter();
  block->add(p);
  MNot* not0 = MNot::New(func.alloc, p);
  block->add(not0);
  MTest* test = MTest::New(func.alloc, not0, then, else_);
  block->end(test);

  then->end(MGoto::New(func.alloc, exit));

  else_->end(MGoto::New(func.alloc, exit));

  MReturn* ret = MReturn::New(func.alloc, p);
  exit->end(ret);

  MOZ_ALWAYS_TRUE(exit->addPredecessorWithoutPhis(then));

  if (!func.runGVN()) {
    return false;
  }

  // Test that the not got folded.
  test = block->lastIns()->toTest();
  CHECK(test->getOperand(0) == p);
  CHECK(test->getSuccessor(0) == else_);
  CHECK(test->getSuccessor(1) == then);
  return true;
}
END_TEST(testJitNotTest)

BEGIN_TEST(testJitNotNotTest) {
  MinimalFunc func;
  MBasicBlock* block = func.createEntryBlock();
  MBasicBlock* then = func.createBlock(block);
  MBasicBlock* else_ = func.createBlock(block);
  MBasicBlock* exit = func.createBlock(block);

  // MTest(Not(Not(p)))
  MParameter* p = func.createParameter();
  block->add(p);
  MNot* not0 = MNot::New(func.alloc, p);
  block->add(not0);
  MNot* not1 = MNot::New(func.alloc, not0);
  block->add(not1);
  MTest* test = MTest::New(func.alloc, not1, then, else_);
  block->end(test);

  then->end(MGoto::New(func.alloc, exit));

  else_->end(MGoto::New(func.alloc, exit));

  MReturn* ret = MReturn::New(func.alloc, p);
  exit->end(ret);

  MOZ_ALWAYS_TRUE(exit->addPredecessorWithoutPhis(then));

  if (!func.runGVN()) {
    return false;
  }

  // Test that the nots got folded.
  test = block->lastIns()->toTest();
  CHECK(test->getOperand(0) == p);
  CHECK(test->getSuccessor(0) == then);
  CHECK(test->getSuccessor(1) == else_);
  return true;
}
END_TEST(testJitNotNotTest)

BEGIN_TEST(testJitFoldsTo_UnsignedDiv) {
  MinimalFunc func;
  MBasicBlock* block = func.createEntryBlock();

  // return 1.0 / 0xffffffff
  MConstant* c0 = MConstant::New(func.alloc, Int32Value(1));
  block->add(c0);
  MConstant* c1 = MConstant::New(func.alloc, Int32Value(0xffffffff));
  block->add(c1);
  MDiv* div = MDiv::New(func.alloc, c0, c1, MIRType::Int32, /*unsignd=*/true);
  block->add(div);
  MReturn* ret = MReturn::New(func.alloc, div);
  block->end(ret);

  if (!func.runGVN()) {
    return false;
  }

  // Test that the div got folded to 0.
  MConstant* op = ret->getOperand(0)->toConstant();
  CHECK(mozilla::NumbersAreIdentical(op->numberToDouble(), 0.0));
  return true;
}
END_TEST(testJitFoldsTo_UnsignedDiv)

BEGIN_TEST(testJitFoldsTo_UnsignedMod) {
  MinimalFunc func;
  MBasicBlock* block = func.createEntryBlock();

  // return 1.0 % 0xffffffff
  MConstant* c0 = MConstant::New(func.alloc, Int32Value(1));
  block->add(c0);
  MConstant* c1 = MConstant::New(func.alloc, Int32Value(0xffffffff));
  block->add(c1);
  MMod* mod = MMod::New(func.alloc, c0, c1, MIRType::Int32, /*unsignd=*/true);
  block->add(mod);
  MReturn* ret = MReturn::New(func.alloc, mod);
  block->end(ret);

  if (!func.runGVN()) {
    return false;
  }

  // Test that the mod got folded to 1.
  MConstant* op = ret->getOperand(0)->toConstant();
  CHECK(mozilla::NumbersAreIdentical(op->numberToDouble(), 1.0));
  return true;
}
END_TEST(testJitFoldsTo_UnsignedMod)