/* -*- 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(testJitDCEinGVN_ins) {
  MinimalFunc func;
  MBasicBlock* block = func.createEntryBlock();

  // mul0 = p * p
  // mul1 = mul0 * mul0
  // return p
  MParameter* p = func.createParameter();
  block->add(p);
  MMul* mul0 = MMul::New(func.alloc, p, p, MIRType::Double);
  block->add(mul0);
  if (!mul0->typePolicy()->adjustInputs(func.alloc, mul0)) {
    return false;
  }
  MMul* mul1 = MMul::New(func.alloc, mul0, mul0, MIRType::Double);
  block->add(mul1);
  if (!mul1->typePolicy()->adjustInputs(func.alloc, mul1)) {
    return false;
  }
  MReturn* ret = MReturn::New(func.alloc, p);
  block->end(ret);

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

  // mul0 and mul1 should be deleted.
  for (MInstructionIterator ins = block->begin(); ins != block->end(); ins++) {
    CHECK(!ins->isMul() ||
          (ins->getOperand(0) != p && ins->getOperand(1) != p));
    CHECK(!ins->isMul() ||
          (ins->getOperand(0) != mul0 && ins->getOperand(1) != mul0));
  }
  return true;
}
END_TEST(testJitDCEinGVN_ins)

BEGIN_TEST(testJitDCEinGVN_phi) {
  MinimalFunc func;
  MBasicBlock* block = func.createEntryBlock();
  MBasicBlock* thenBlock1 = func.createBlock(block);
  MBasicBlock* thenBlock2 = func.createBlock(block);
  MBasicBlock* elifBlock = func.createBlock(block);
  MBasicBlock* elseBlock = func.createBlock(block);
  MBasicBlock* joinBlock = func.createBlock(block);

  // if (p) {
  //   x = 1.0;
  //   y = 3.0;
  // } else if (q) {
  //   x = 2.0;
  //   y = 4.0;
  // } else {
  //   x = 1.0;
  //   y = 5.0;
  // }
  // x = phi(1.0, 2.0, 1.0);
  // y = phi(3.0, 4.0, 5.0);
  // z = x * y;
  // return y;

  MConstant* c1 = MConstant::New(func.alloc, DoubleValue(1.0));
  block->add(c1);
  MPhi* x = MPhi::New(func.alloc);
  MPhi* y = MPhi::New(func.alloc);

  // if (p) {
  MParameter* p = func.createParameter();
  block->add(p);
  block->end(MTest::New(func.alloc, p, thenBlock1, elifBlock));

  //   x = 1.0
  //   y = 3.0;
  MOZ_RELEASE_ASSERT(x->addInputSlow(c1));
  MConstant* c3 = MConstant::New(func.alloc, DoubleValue(3.0));
  thenBlock1->add(c3);
  MOZ_RELEASE_ASSERT(y->addInputSlow(c3));
  thenBlock1->end(MGoto::New(func.alloc, joinBlock));
  MOZ_ALWAYS_TRUE(joinBlock->addPredecessor(func.alloc, thenBlock1));

  // } else if (q) {
  MParameter* q = func.createParameter();
  elifBlock->add(q);
  elifBlock->end(MTest::New(func.alloc, q, thenBlock2, elseBlock));

  //   x = 2.0
  //   y = 4.0;
  MConstant* c2 = MConstant::New(func.alloc, DoubleValue(2.0));
  thenBlock2->add(c2);
  MOZ_RELEASE_ASSERT(x->addInputSlow(c2));
  MConstant* c4 = MConstant::New(func.alloc, DoubleValue(4.0));
  thenBlock2->add(c4);
  MOZ_RELEASE_ASSERT(y->addInputSlow(c4));
  thenBlock2->end(MGoto::New(func.alloc, joinBlock));
  MOZ_ALWAYS_TRUE(joinBlock->addPredecessor(func.alloc, thenBlock2));

  // } else {
  //   x = 1.0
  //   y = 5.0;
  // }
  MOZ_RELEASE_ASSERT(x->addInputSlow(c1));
  MConstant* c5 = MConstant::New(func.alloc, DoubleValue(5.0));
  elseBlock->add(c5);
  MOZ_RELEASE_ASSERT(y->addInputSlow(c5));
  elseBlock->end(MGoto::New(func.alloc, joinBlock));
  MOZ_ALWAYS_TRUE(joinBlock->addPredecessor(func.alloc, elseBlock));

  // x = phi(1.0, 2.0, 1.0)
  // y = phi(3.0, 4.0, 5.0)
  // z = x * y
  // return y
  joinBlock->addPhi(x);
  joinBlock->addPhi(y);
  MMul* z = MMul::New(func.alloc, x, y, MIRType::Double);
  joinBlock->add(z);
  MReturn* ret = MReturn::New(func.alloc, y);
  joinBlock->end(ret);

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

  // c1 should be deleted.
  for (MInstructionIterator ins = block->begin(); ins != block->end(); ins++) {
    CHECK(!ins->isConstant() || (ins->toConstant()->numberToDouble() != 1.0));
  }
  return true;
}
END_TEST(testJitDCEinGVN_phi)
