// Copyright 2017 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.

// Flags: --allow-natives-syntax --opt --no-always-opt

var global = this;

// TODO(ishell): update the test once const->mutable migration does not
// create a new map.
var IS_INPLACE_MAP_MODIFICATION_SUPPORTED = false;

var unique_id = 0;
// Creates a function with unique SharedFunctionInfo to ensure the feedback
// vector is unique for each test case.
function MakeFunctionWithUniqueSFI(...args) {
  assertTrue(args.length > 0);
  var body = `/* Unique comment: ${unique_id++} */ ` + args.pop();
  return new Function(...args, body);
}


//
// Load constant field from constant object directly.
//
function TestLoadFromConstantFieldOfAConstantObject(the_value, other_value) {
  function A(v) { this.v = v; }
  function O() { this.a = new A(the_value); }
  var the_object = new O();

  // Ensure that {the_object.a}'s map is not stable to complicate compiler's
  // life.
  new A(the_value).blah = 0;

  // Ensure that constant tracking is enabled for {contant_object}.
  delete global.constant_object;
  global.constant_object = the_object;
  assertEquals(the_object, constant_object);

  assertTrue(%HasFastProperties(the_object));

  // {constant_object} is known to the compiler via global property cell
  // tracking.
  var load = MakeFunctionWithUniqueSFI("return constant_object.a.v;");
  load();
  load();
  %OptimizeFunctionOnNextCall(load);
  assertEquals(the_value, load());
  assertOptimized(load);
  if (IS_INPLACE_MAP_MODIFICATION_SUPPORTED) {
    var a = new A(other_value);
    assertTrue(%HaveSameMap(a, the_object.a));
    // Make constant field mutable by assigning another value
    // to some other instance of A.
    new A(the_value).v = other_value;
    assertTrue(%HaveSameMap(a, new A(the_value)));
    assertTrue(%HaveSameMap(a, the_object.a));
    assertUnoptimized(load);
    assertEquals(the_value, load());
  } else {
    var a = new A(other_value);
    assertTrue(%HaveSameMap(a, the_object.a));
    // Make constant field mutable by assigning another value
    // to some other instance of A.
    new A(the_value).v = other_value;
    assertOptimized(load);
    assertTrue(!%HaveSameMap(a, new A(the_value)));

    assertTrue(%HaveSameMap(a, the_object.a));
    // Ensure the {the_object.a} migrated to an up-to date version of a map
    // by loading a property through IC.
    assertEquals(the_value, the_object.a.v);
    assertTrue(!%HaveSameMap(a, the_object.a));
    assertOptimized(load);

    // Now attempt to call load should deoptimize because of failed map check.
    assertEquals(the_value, load());
  }
  assertUnoptimized(load);
  assertEquals(the_value, load());
}

// Test constant tracking with Smi value.
(function() {
  var the_value = 42;
  var other_value = 153;
  TestLoadFromConstantFieldOfAConstantObject(the_value, other_value);
})();

// Test constant tracking with double value.
(function() {
  var the_value = 0.9;
  var other_value = 0.42;
  TestLoadFromConstantFieldOfAConstantObject(the_value, other_value);
})();

// Test constant tracking with function value.
(function() {
  var the_value = function V() {};
  var other_value = function W() {};
  TestLoadFromConstantFieldOfAConstantObject(the_value, other_value);
})();

// Test constant tracking with heap object value.
(function() {
  function V() {}
  var the_value = new V();
  var other_value = new V();
  TestLoadFromConstantFieldOfAConstantObject(the_value, other_value);
})();


//
// Load constant field from a prototype.
//
function TestLoadFromConstantFieldOfAPrototype(the_value, other_value) {
  function Proto() { this.v = the_value; }
  var the_prototype = new Proto();

  function O() {}
  O.prototype = the_prototype;
  var the_object = new O();

  // Ensure O.prototype is in fast mode by loading from its field.
  function warmup() { return new O().v; }
  warmup(); warmup(); warmup();
  assertTrue(%HasFastProperties(O.prototype));

  // The parameter object is not constant but all the values have the same
  // map and therefore the compiler knows the prototype object and can
  // optimize load of "v".
  var load = MakeFunctionWithUniqueSFI("o", "return o.v;");
  load(new O());
  load(new O());
  %OptimizeFunctionOnNextCall(load);
  assertEquals(the_value, load(new O()));
  assertOptimized(load);
  if (IS_INPLACE_MAP_MODIFICATION_SUPPORTED) {
    // Invalidation of mutability should trigger deoptimization with a
    // "field-owner" reason.
    the_prototype.v = other_value;
  } else {
    // Invalidation of mutability should trigger deoptimization with a
    // "prototype-check" (stability) reason.
    the_prototype.v = other_value;
  }
  assertUnoptimized(load);
}

// Test constant tracking with Smi value.
(function() {
  var the_value = 42;
  var other_value = 153;
  TestLoadFromConstantFieldOfAPrototype(the_value, other_value);
})();

// Test constant tracking with double value.
(function() {
  var the_value = 0.9;
  var other_value = 0.42;
  TestLoadFromConstantFieldOfAPrototype(the_value, other_value);
})();

// Test constant tracking with function value.
(function() {
  var the_value = function V() {};
  var other_value = function W() {};
  TestLoadFromConstantFieldOfAPrototype(the_value, other_value);
})();

// Test constant tracking with heap object value.
(function() {
  function V() {}
  var the_value = new V();
  var other_value = new V();
  TestLoadFromConstantFieldOfAPrototype(the_value, other_value);
})();


//
// Store to constant field of a constant object.
//
function TestStoreToConstantFieldOfConstantObject(the_value, other_value) {
  function A(v) { this.v = v; }
  function O() { this.a = new A(the_value); }
  var the_object = new O();

  // Ensure that {the_object.a}'s map is not stable to complicate compiler's
  // life.
  new A(the_value).blah = 0;

  // Ensure that constant tracking is enabled for {contant_object}.
  delete global.constant_object;
  global.constant_object = the_object;
  assertEquals(the_object, constant_object);

  assertTrue(%HasFastProperties(the_object));

  // {constant_object} is known to the compiler via global property cell
  // tracking.
  var store = MakeFunctionWithUniqueSFI("v", "constant_object.a.v = v;");
  store(the_value);
  store(the_value);
  %OptimizeFunctionOnNextCall(store);
  store(the_value);
  assertEquals(the_value, constant_object.a.v);
  assertOptimized(store);
  // Storing of the same value does not deoptimize.
  store(the_value);
  assertEquals(the_value, constant_object.a.v);
  assertOptimized(store);

  if (IS_INPLACE_MAP_MODIFICATION_SUPPORTED) {
    var a = new A(other_value);

    if (typeof the_value == "function" || typeof the_value == "object") {
      // For heap object fields "field-owner" dependency is installed for
      // any access of the field, therefore making constant field mutable by
      // assigning other value to some other instance of A should already
      // trigger deoptimization.
      assertTrue(%HaveSameMap(a, the_object.a));
      new A(the_value).v = other_value;
      assertTrue(%HaveSameMap(a, new A(the_value)));
      assertTrue(%HaveSameMap(a, the_object.a));
      assertUnoptimized(store);
    } else {
      assertOptimized(store);
    }
    // Storing other value deoptimizes because of failed value check.
    store(other_value);
    assertUnoptimized(store);
    assertEquals(other_value, constant_object.a.v);
  } else {
    // Storing other value deoptimizes because of failed value check.
    store(other_value);
    assertUnoptimized(store);
    assertEquals(other_value, constant_object.a.v);
  }
}

// Test constant tracking with Smi values.
(function() {
  var the_value = 42;
  var other_value = 153;
  TestStoreToConstantFieldOfConstantObject(the_value, other_value);
})();

// Test constant tracking with double values.
(function() {
  var the_value = 0.9;
  var other_value = 0.42
  TestStoreToConstantFieldOfConstantObject(the_value, other_value);
})();

// Test constant tracking with function values.
(function() {
  var the_value = function V() {};
  var other_value = function W() {};
  TestStoreToConstantFieldOfConstantObject(the_value, other_value);
})();

// Test constant tracking with heap object values.
(function() {
  function V() {}
  var the_value = new V();
  var other_value = new V();
  TestStoreToConstantFieldOfConstantObject(the_value, other_value);
})();