// Copyright 2015 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

function PrintDesc(desc, s) {
  var json;
  if (desc) {
    json = JSON.stringify(desc);
  } else {
    json = "<no such property>";
  }
  if (s === undefined) {
    print(json);
  } else {
    print(s + ": " + json);
  }
}


var counters;
var test_realm;
var cfg;


function GetDescriptor() {
  var code = 'Object.getOwnPropertyDescriptor(global, "x")';
  var desc = Realm.eval(test_realm, code);
//  PrintDesc(desc);
  return desc;
}

function SetUp() {
  counters = {};
  Realm.shared = {counters: counters};
  test_realm = Realm.create();
  Realm.eval(test_realm, 'var global = Realm.global(Realm.current());');
  print("=====================");
  print("Test realm: " + test_realm);
  assertEquals(undefined, GetDescriptor());
}

function TearDown() {
  Realm.dispose(test_realm);
  print("OK");
}


function AddStrict(code, cfg) {
  return cfg.strict ? '"use strict"; ' + code : code;
}

function ForceMutablePropertyCellType() {
  Realm.eval(test_realm, 'global.x = {}; global.x = undefined;');
}

function DeclareVar() {
  var code = 'var x;';
  return Realm.eval(test_realm, AddStrict(code, cfg));
}

function DefineVar(v) {
  var code = 'var x = ' + v;
  return Realm.eval(test_realm, AddStrict(code, cfg));
}

function DefineLoadVar() {
  var name = 'LoadVar_' + test_realm;
  var code =
      'var x;' +
      'function ' + name + '() {' +
      '  return x;' +
      '};';
  return Realm.eval(test_realm, AddStrict(code, cfg));
}

function LoadVar() {
  var name = 'LoadVar_' + test_realm;
  var code =
      (cfg.optimize ? '%OptimizeFunctionOnNextCall(' + name + ');' : '') +
      name + '();';
  return Realm.eval(test_realm, AddStrict(code, cfg));
}

function DefineStoreVar() {
  var name = 'StoreVar_' + test_realm;
  var code = 'var g = (Function("return this"))();' +
      'var x;' +
      'function ' + name + '(v) {' +
//      '  %DebugPrint(g);' +
      '  return x = v;' +
      '};';
  return Realm.eval(test_realm, AddStrict(code, cfg));
}

function StoreVar(v) {
  var name = 'StoreVar_' + test_realm;
  var code =
      (cfg.optimize ? '%OptimizeFunctionOnNextCall(' + name + ');' : '') +
      name + '(' + v + ');';
  return Realm.eval(test_realm, AddStrict(code, cfg));
}

// It does 13 iterations which results in 27 loads
// and 14 stores.
function LoadStoreLoop() {
  var code = 'for(var x = 0; x < 13; x++);';
  return Realm.eval(test_realm, AddStrict(code, cfg));
}

function DefineRWDataProperty() {
  var code =
      'Object.defineProperty(global, "x", { ' +
      '  value: 42, ' +
      '  writable: true, ' +
      '  enumerable: true, ' +
      '  configurable: true ' +
      '});';
  return Realm.eval(test_realm, AddStrict(code, cfg));
}

function DefineRODataProperty() {
  var code =
      'Object.defineProperty(global, "x", { ' +
      '  value: 42, ' +
      '  writable: false, ' +
      '  enumerable: true, ' +
      '  configurable: true ' +
      '});';
  return Realm.eval(test_realm, AddStrict(code, cfg));
}

function SetX_(v) {
  var code =
      'global.x_ = ' + v + '; ';
  return Realm.eval(test_realm, code);
}

function DefineRWAccessorProperty() {
  var code =
      'Object.defineProperty(global, "x", {' +
      '  get: function() { Realm.shared.counters.get_count++; return this.x_; },' +
      '  set: function(v) { Realm.shared.counters.set_count++; this.x_ = v; },' +
      '  enumerable: true, configurable: true' +
      '});';
  counters.get_count = 0;
  counters.set_count = 0;
  return Realm.eval(test_realm, AddStrict(code, cfg));
}

function DefineROAccessorProperty() {
  var code =
      'Object.defineProperty(global, "x", {' +
      '  get: function() { Realm.shared.counters.get_count++; return this.x_; },' +
      '  enumerable: true, configurable: true' +
      '});';
  counters.get_count = 0;
  counters.set_count = 0;
  return Realm.eval(test_realm, AddStrict(code, cfg));
}


function testSuite(opt_cfg) {
  //
  // Non strict.
  //

  (function() {
    SetUp();
    cfg = {optimize: opt_cfg.optimize, strict: false};
    DeclareVar();
    DefineLoadVar();
    DefineStoreVar();
    assertEquals(undefined, LoadVar());
    assertEquals(false, GetDescriptor().configurable);

    // Force property cell type to kMutable.
    DefineVar(undefined);
    DefineVar(153);
    assertEquals(false, GetDescriptor().configurable);

    assertEquals(153, LoadVar());
    assertEquals(113, StoreVar(113));
    assertEquals(113, LoadVar());
    LoadStoreLoop();
    assertEquals(13, LoadVar());
    TearDown();
  })();


  (function() {
    SetUp();
    cfg = {optimize: opt_cfg.optimize, strict: false};
    ForceMutablePropertyCellType();
    DefineLoadVar();
    DefineStoreVar();
    DefineRWDataProperty();
    assertEquals(42, LoadVar());
    assertEquals(true, GetDescriptor().configurable);

    DefineVar(153);
    assertEquals(true, GetDescriptor().configurable);

    assertEquals(153, LoadVar());
    assertEquals(113, StoreVar(113));
    assertEquals(113, LoadVar());
    LoadStoreLoop();
    assertEquals(13, LoadVar());

    // Now reconfigure to accessor.
    DefineRWAccessorProperty();
    assertEquals(undefined, GetDescriptor().value);
    assertEquals(true, GetDescriptor().configurable);
    assertEquals(0, counters.get_count);
    assertEquals(0, counters.set_count);

    assertEquals(undefined, LoadVar());
    assertEquals(1, counters.get_count);
    assertEquals(0, counters.set_count);

    LoadStoreLoop();
    assertEquals(28, counters.get_count);
    assertEquals(14, counters.set_count);

    assertEquals(13, LoadVar());
    assertEquals(29, counters.get_count);
    assertEquals(14, counters.set_count);

    TearDown();
  })();


  (function() {
    SetUp();
    cfg = {optimize: opt_cfg.optimize, strict: false};
    ForceMutablePropertyCellType();
    DefineLoadVar();
    DefineStoreVar();
    DefineRODataProperty();
    assertEquals(42, LoadVar());
    assertEquals(true, GetDescriptor().configurable);

    DefineVar(153);

    assertEquals(42, LoadVar());
    assertEquals(113, StoreVar(113));
    assertEquals(42, LoadVar());
    LoadStoreLoop();
    assertEquals(42, LoadVar());

    // Now reconfigure to accessor property.
    DefineRWAccessorProperty();
    assertEquals(undefined, GetDescriptor().value);
    assertEquals(true, GetDescriptor().configurable);
    assertEquals(0, counters.get_count);
    assertEquals(0, counters.set_count);

    assertEquals(undefined, LoadVar());
    assertEquals(1, counters.get_count);
    assertEquals(0, counters.set_count);

    LoadStoreLoop();
    assertEquals(28, counters.get_count);
    assertEquals(14, counters.set_count);

    assertEquals(13, LoadVar());
    assertEquals(29, counters.get_count);
    assertEquals(14, counters.set_count);

    TearDown();
  })();


  (function() {
    SetUp();
    cfg = {optimize: opt_cfg.optimize, strict: false};
    ForceMutablePropertyCellType();
    DefineLoadVar();
    DefineStoreVar();
    DefineRWAccessorProperty();
    assertEquals(0, counters.get_count);
    assertEquals(0, counters.set_count);
    assertEquals(true, GetDescriptor().configurable);

    assertEquals(undefined, LoadVar());
    assertEquals(1, counters.get_count);
    assertEquals(0, counters.set_count);

    DefineVar(153);
    assertEquals(true, GetDescriptor().configurable);
    assertEquals(1, counters.get_count);
    assertEquals(1, counters.set_count);

    assertEquals(153, LoadVar());
    assertEquals(2, counters.get_count);
    assertEquals(1, counters.set_count);

    assertEquals(113, StoreVar(113));
    assertEquals(2, counters.get_count);
    assertEquals(2, counters.set_count);

    assertEquals(113, LoadVar());
    assertEquals(3, counters.get_count);
    assertEquals(2, counters.set_count);

    LoadStoreLoop();
    assertEquals(30, counters.get_count);
    assertEquals(16, counters.set_count);

    assertEquals(13, LoadVar());
    assertEquals(31, counters.get_count);
    assertEquals(16, counters.set_count);

    // Now reconfigure to data property.
    DefineRWDataProperty();
    assertEquals(42, GetDescriptor().value);
    assertEquals(42, LoadVar());
    assertEquals(113, StoreVar(113));
    assertEquals(31, counters.get_count);
    assertEquals(16, counters.set_count);

    TearDown();
  })();


  (function() {
    SetUp();
    cfg = {optimize: opt_cfg.optimize, strict: false};
    ForceMutablePropertyCellType();
    DefineLoadVar();
    DefineStoreVar();
    DefineROAccessorProperty();
    assertEquals(0, counters.get_count);
    assertEquals(0, counters.set_count);
    assertEquals(true, GetDescriptor().configurable);

    assertEquals(undefined, LoadVar());
    assertEquals(1, counters.get_count);
    assertEquals(0, counters.set_count);

    SetX_(42);
    assertEquals(42, LoadVar());
    assertEquals(2, counters.get_count);
    assertEquals(0, counters.set_count);

    DefineVar(153);
    assertEquals(true, GetDescriptor().configurable);
    assertEquals(2, counters.get_count);
    assertEquals(0, counters.set_count);

    assertEquals(42, LoadVar());
    assertEquals(3, counters.get_count);
    assertEquals(0, counters.set_count);

    assertEquals(113, StoreVar(113));
    assertEquals(3, counters.get_count);
    assertEquals(0, counters.set_count);

    assertEquals(42, LoadVar());
    assertEquals(4, counters.get_count);
    assertEquals(0, counters.set_count);

    LoadStoreLoop();
    assertEquals(5, counters.get_count);
    assertEquals(0, counters.set_count);

    assertEquals(42, LoadVar());
    assertEquals(6, counters.get_count);
    assertEquals(0, counters.set_count);

    // Now reconfigure to data property.
    DefineRWDataProperty();
    assertEquals(42, GetDescriptor().value);
    assertEquals(42, LoadVar());
    assertEquals(113, StoreVar(113));
    assertEquals(6, counters.get_count);
    assertEquals(0, counters.set_count);

    TearDown();
  })();


  //
  // Strict.
  //

  (function() {
    SetUp();
    cfg = {optimize: opt_cfg.optimize, strict: true};
    DeclareVar();
    DefineLoadVar();
    DefineStoreVar();
    assertEquals(undefined, LoadVar());
    assertEquals(false, GetDescriptor().configurable);

    // Force property cell type to kMutable.
    DefineVar(undefined);
    DefineVar(153);
    assertEquals(false, GetDescriptor().configurable);

    assertEquals(153, LoadVar());
    assertEquals(113, StoreVar(113));
    assertEquals(113, LoadVar());
    LoadStoreLoop();
    assertEquals(13, LoadVar());
    TearDown();
  })();


  (function() {
    SetUp();
    cfg = {optimize: opt_cfg.optimize, strict: true};
    ForceMutablePropertyCellType();
    DefineLoadVar();
    DefineStoreVar();
    DefineRWDataProperty();
    assertEquals(42, LoadVar());
    assertEquals(true, GetDescriptor().configurable);

    DefineVar(153);
    assertEquals(true, GetDescriptor().configurable);

    assertEquals(153, LoadVar());
    assertEquals(113, StoreVar(113));
    assertEquals(113, LoadVar());
    LoadStoreLoop();
    assertEquals(13, LoadVar());
    TearDown();
  })();


  (function() {
    SetUp();
    cfg = {optimize: opt_cfg.optimize, strict: true};
    ForceMutablePropertyCellType();
    DefineLoadVar();
    DefineStoreVar();
    DefineRWDataProperty();
    assertEquals(true, GetDescriptor().configurable);
    assertEquals(true, GetDescriptor().writable);
    assertEquals(113, StoreVar(113));

    DefineRODataProperty();
    assertEquals(true, GetDescriptor().configurable);
    assertEquals(false, GetDescriptor().writable);

    assertEquals(42, LoadVar());
    assertEquals(true, GetDescriptor().configurable);
    assertThrows('DefineVar(153)');
    assertEquals(42, LoadVar());
    assertThrows('StoreVar(113)');
    assertThrows('StoreVar(113)');
    assertEquals(42, LoadVar());
    assertThrows('StoreVar(42)');
    assertEquals(42, LoadVar());
    assertThrows('LoadStoreLoop()');
    assertEquals(42, LoadVar());
    TearDown();
  })();


  (function() {
    SetUp();
    cfg = {optimize: opt_cfg.optimize, strict: true};
    ForceMutablePropertyCellType();
    DefineLoadVar();
    DefineStoreVar();
    DefineRWAccessorProperty();
    assertEquals(0, counters.get_count);
    assertEquals(0, counters.set_count);
    assertEquals(true, GetDescriptor().configurable);

    assertEquals(undefined, LoadVar());
    assertEquals(1, counters.get_count);
    assertEquals(0, counters.set_count);

    DefineVar(153);
    assertEquals(true, GetDescriptor().configurable);
    assertEquals(1, counters.get_count);
    assertEquals(1, counters.set_count);

    assertEquals(153, LoadVar());
    assertEquals(2, counters.get_count);
    assertEquals(1, counters.set_count);

    assertEquals(113, StoreVar(113));
    assertEquals(2, counters.get_count);
    assertEquals(2, counters.set_count);

    assertEquals(113, LoadVar());
    assertEquals(3, counters.get_count);
    assertEquals(2, counters.set_count);

    LoadStoreLoop();
    assertEquals(30, counters.get_count);
    assertEquals(16, counters.set_count);

    assertEquals(13, LoadVar());
    assertEquals(31, counters.get_count);
    assertEquals(16, counters.set_count);

    // Now reconfigure to data property.
    DefineRWDataProperty();
    assertEquals(42, GetDescriptor().value);
    assertEquals(42, LoadVar());
    assertEquals(113, StoreVar(113));
    assertEquals(31, counters.get_count);
    assertEquals(16, counters.set_count);

    TearDown();
  })();


  (function() {
    SetUp();
    cfg = {optimize: opt_cfg.optimize, strict: true};
    ForceMutablePropertyCellType();
    DefineLoadVar();
    DefineStoreVar();
    DefineROAccessorProperty();
    assertEquals(0, counters.get_count);
    assertEquals(0, counters.set_count);
    assertEquals(true, GetDescriptor().configurable);

    assertEquals(undefined, LoadVar());
    assertEquals(1, counters.get_count);
    assertEquals(0, counters.set_count);

    SetX_(42);
    assertEquals(42, LoadVar());
    assertEquals(2, counters.get_count);
    assertEquals(0, counters.set_count);

    assertThrows('DefineVar(153)');
    assertEquals(true, GetDescriptor().configurable);
    assertEquals(2, counters.get_count);
    assertEquals(0, counters.set_count);

    assertEquals(42, LoadVar());
    assertEquals(3, counters.get_count);
    assertEquals(0, counters.set_count);

    assertThrows('StoreVar(113)');
    assertEquals(3, counters.get_count);
    assertEquals(0, counters.set_count);

    assertEquals(42, LoadVar());
    assertEquals(4, counters.get_count);
    assertEquals(0, counters.set_count);

    assertThrows('LoadStoreLoop()');
    assertEquals(4, counters.get_count);
    assertEquals(0, counters.set_count);

    assertEquals(42, LoadVar());
    assertEquals(5, counters.get_count);
    assertEquals(0, counters.set_count);

    // Now reconfigure to data property.
    DefineRWDataProperty();
    assertEquals(42, GetDescriptor().value);
    assertEquals(42, LoadVar());
    assertEquals(113, StoreVar(113));
    assertEquals(5, counters.get_count);
    assertEquals(0, counters.set_count);

    TearDown();
  })();

}  // testSuite


testSuite({optimize: false});
testSuite({optimize: true});