// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Flags: --allow-natives-syntax --expose-gc


// Test stores on a join path.
(function testJoin() {
  function constructor() {
    this.a = 0;
  }
  function join(mode, expected) {
    var object = new constructor();
    if (mode) {
      object.a = 1;
    } else {
      object.a = 2;
    }
    assertEquals(expected, object.a);
  }
  %PrepareFunctionForOptimization(join);
  join(true, 1); join(true, 1);
  join(false, 2); join(false, 2);
  %OptimizeFunctionOnNextCall(join);
  join(true, 1); join(false, 2);
})();


// Test loads and stores inside a loop.
(function testLoop() {
  function constructor() {
    this.a = 0;
    this.b = 23;
  }
  function loop() {
    var object = new constructor();
    for (var i = 1; i < 10; i++) {
      object.a = object.a + i;
      assertEquals(i*(i+1)/2, object.a);
      assertEquals(23, object.b);
    }
    assertEquals(45, object.a);
    assertEquals(23, object.b);
  }
  %PrepareFunctionForOptimization(loop);
  loop(); loop();
  %OptimizeFunctionOnNextCall(loop);
  loop(); loop();
})();


// Test loads and stores inside nested loop.
(function testNested() {
  function constructor() {
    this.a = 0;
    this.b = 0;
    this.c = 23;
  }
  function nested() {
    var object = new constructor();
    for (var i = 1; i < 10; i++) {
      object.a = object.a + i;
      assertEquals(i*(i+1)/2, object.a);
      assertEquals((i-1)*6, object.b);
      assertEquals(23, object.c);
      for (var j = 1; j < 4; j++) {
        object.b = object.b + j;
        assertEquals(i*(i+1)/2, object.a);
        assertEquals((i-1)*6+j*(j+1)/2, object.b);
        assertEquals(23, object.c);
      }
      assertEquals(i*(i+1)/2, object.a);
      assertEquals(i*6, object.b);
      assertEquals(23, object.c);
    }
    assertEquals(45, object.a);
    assertEquals(54, object.b);
    assertEquals(23, object.c);
  }
  %PrepareFunctionForOptimization(nested);
  nested(); nested();
  %OptimizeFunctionOnNextCall(nested);
  nested(); nested();
})();


// Test deoptimization with captured objects in local variables.
(function testDeoptLocal() {
  var deopt = { deopt:false };
  function constructor1() {
    this.a = 1.0;
    this.b = 2.3;
    this.c = 3.0;
  }
  function constructor2(o) {
    this.d = o;
    this.e = 4.5;
  }
  function func() {
    var o1 = new constructor1();
    var o2 = new constructor2(o1);
    deopt.deopt;
    assertEquals(1.0, o1.a);
    assertEquals(2.3, o2.d.b);
    assertEquals(3.0, o2.d.c);
    assertEquals(4.5, o2.e);
  }
  %PrepareFunctionForOptimization(func);
  func(); func();
  %OptimizeFunctionOnNextCall(func);
  func(); func();
  delete deopt.deopt;
  func(); func();
})();


// Test deoptimization with captured objects on operand stack.
(function testDeoptOperand() {
  var deopt = { deopt:false };
  function constructor1() {
    this.a = 1.0;
    this.b = 2.3;
    deopt.deopt;
    assertEquals(1.0, this.a);
    assertEquals(2.3, this.b);
    this.b = 2.7;
    this.c = 3.0;
    this.d = 4.5;
  }
  function constructor2() {
    this.e = 5.0;
    this.f = new constructor1();
    assertEquals(1.0, this.f.a);
    assertEquals(2.7, this.f.b);
    assertEquals(3.0, this.f.c);
    assertEquals(4.5, this.f.d);
    assertEquals(5.0, this.e);
    this.e = 5.9;
    this.g = 6.7;
  }
  function func() {
    var o = new constructor2();
    assertEquals(1.0, o.f.a);
    assertEquals(2.7, o.f.b);
    assertEquals(3.0, o.f.c);
    assertEquals(4.5, o.f.d);
    assertEquals(5.9, o.e);
    assertEquals(6.7, o.g);
  }
  %PrepareFunctionForOptimization(func);
  func(); func();
  %OptimizeFunctionOnNextCall(func);
  func(); func();
  delete deopt.deopt;
  func(); func();
})();


// Test map checks on captured objects.
(function testMapCheck() {
  var sum = 0;
  function getter() { return 27; }
  function setter(v) { sum += v; }
  function constructor() {
    this.x = 23;
    this.y = 42;
  }
  function check(x, y) {
    var o = new constructor();
    assertEquals(x, o.x);
    assertEquals(y, o.y);
  }
  var monkey = Object.create(null, {
    x: { get:getter, set:setter },
    y: { get:getter, set:setter }
  });
  %PrepareFunctionForOptimization(check);
  check(23, 42); check(23, 42);
  %OptimizeFunctionOnNextCall(check);
  check(23, 42); check(23, 42);
  constructor.prototype = monkey;
  check(27, 27); check(27, 27);
  assertEquals(130, sum);
})();


// Test OSR into a loop with captured objects.
(function testOSR() {
  function constructor() {
    this.a = 23;
  }
  function osr1(length) {
    assertEquals(23, (new constructor()).a);
    var result = 0;
    for (var i = 0; i < length; i++) {
      result = (result + i) % 99;
    }
    return result;
  }
  function osr2(length) {
    var result = 0;
    for (var i = 0; i < length; i++) {
      result = (result + i) % 99;
    }
    assertEquals(23, (new constructor()).a);
    return result;
  }
  function osr3(length) {
    var result = 0;
    var o = new constructor();
    for (var i = 0; i < length; i++) {
      result = (result + i) % 99;
    }
    assertEquals(23, o.a);
    return result;
  }
  function test(closure) {
    assertEquals(45, closure(10));
    assertEquals(45, closure(10));
    assertEquals(10, closure(50000));
  }
  test(osr1);
  test(osr2);
  test(osr3);
})();


// Test out-of-bounds access on captured objects.
(function testOOB() {
  function cons1() {
    this.x = 1;
    this.y = 2;
    this.z = 3;
  }
  function cons2() {
    this.a = 7;
  }
  function oob(constructor, branch) {
    var o = new constructor();
    if (branch) {
      return o.a;
    } else {
      return o.z;
    }
  }
  %PrepareFunctionForOptimization(oob);
  assertEquals(3, oob(cons1, false));
  assertEquals(3, oob(cons1, false));
  assertEquals(7, oob(cons2, true));
  assertEquals(7, oob(cons2, true));
  gc();  // Clears type feedback of constructor call.
  assertEquals(7, oob(cons2, true));
  assertEquals(7, oob(cons2, true));
  %OptimizeFunctionOnNextCall(oob);
  assertEquals(7, oob(cons2, true));
})();


// Test non-shallow nested graph of captured objects.
(function testDeep() {
  var deopt = { deopt:false };
  function constructor1() {
    this.x = 23;
  }
  function constructor2(nested) {
    this.a = 17;
    this.b = nested;
    this.c = 42;
  }
  function deep() {
    var o1 = new constructor1();
    var o2 = new constructor2(o1);
    assertEquals(17, o2.a);
    assertEquals(23, o2.b.x);
    assertEquals(42, o2.c);
    o1.x = 99;
    deopt.deopt;
    assertEquals(99, o1.x);
    assertEquals(99, o2.b.x);
  }
  %PrepareFunctionForOptimization(deep);
  deep(); deep();
  %OptimizeFunctionOnNextCall(deep);
  deep(); deep();
  delete deopt.deopt;
  deep(); deep();
})();


// Test non-shallow nested graph of captured objects with duplicates
(function testDeepDuplicate() {
  function constructor1() {
    this.x = 23;
  }
  function constructor2(nested) {
    this.a = 17;
    this.b = nested;
    this.c = 42;
  }
  function deep(shouldDeopt) {
    var o1 = new constructor1();
    var o2 = new constructor2(o1);
    var o3 = new constructor2(o1);
    assertEquals(17, o2.a);
    assertEquals(23, o2.b.x);
    assertEquals(42, o2.c);
    o3.c = 54;
    o1.x = 99;
    if (shouldDeopt) %DeoptimizeFunction(deep);
    assertEquals(99, o1.x);
    assertEquals(99, o2.b.x);
    assertEquals(99, o3.b.x);
    assertEquals(54, o3.c);
    assertEquals(17, o3.a);
    assertEquals(42, o2.c);
    assertEquals(17, o2.a);
    o3.b.x = 1;
    assertEquals(1, o1.x);
  }
  %PrepareFunctionForOptimization(deep);
  deep(false); deep(false);
  %OptimizeFunctionOnNextCall(deep);
  deep(false); deep(false);
  deep(true); deep(true);
})();


// Test non-shallow nested graph of captured objects with inline
(function testDeepInline() {
  function h() {
    return { y : 3 };
  }

  function g(x) {
    var u = { x : h() };
    %DeoptimizeFunction(f);
    return u;
  }

  function f() {
    var l = { dummy : { } };
    var r = g(l);
    assertEquals(3, r.x.y);
  }

  %PrepareFunctionForOptimization(f);
  f(); f(); f();
  %OptimizeFunctionOnNextCall(f);
  f();
})();


// Test two nested objects
(function testTwoNestedObjects() {
  function f() {
    var l = { x : { y : 111 } };
    var l2 = { x : { y : 111 } };
    %DeoptimizeFunction(f);
    assertEquals(111, l.x.y);
    assertEquals(111, l2.x.y);
  }

  %PrepareFunctionForOptimization(f);
  f(); f(); f();
  %OptimizeFunctionOnNextCall(f);
  f();
})();


// Test a nested object and a duplicate
(function testTwoObjectsWithDuplicate() {
  function f() {
    var l = { x : { y : 111 } };
    var dummy = { d : 0 };
    var l2 = l.x;
    %DeoptimizeFunction(f);
    assertEquals(111, l.x.y);
    assertEquals(111, l2.y);
    assertEquals(0, dummy.d);
  }

  %PrepareFunctionForOptimization(f);
  f(); f(); f();
  %OptimizeFunctionOnNextCall(f);
  f();
})();


// Test materialization of a field that requires a Smi value.
(function testSmiField() {
  var deopt = { deopt:false };
  function constructor() {
    this.x = 1;
  }
  function field(x) {
    var o = new constructor();
    o.x = x;
    deopt.deopt
    assertEquals(x, o.x);
  }
  %PrepareFunctionForOptimization(field);
  field(1); field(2);
  %OptimizeFunctionOnNextCall(field);
  field(3); field(4);
  delete deopt.deopt;
  field(5.5); field(6.5);
})();


// Test materialization of a field that requires a heap object value.
(function testHeapObjectField() {
  var deopt = { deopt:false };
  function constructor() {
    this.x = {};
  }
  function field(x) {
    var o = new constructor();
    o.x = x;
    deopt.deopt
    assertEquals(x, o.x);
  }
  %PrepareFunctionForOptimization(field);
  field({}); field({});
  %OptimizeFunctionOnNextCall(field);
  field({}); field({});
  delete deopt.deopt;
  field(1); field(2);
})();
