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

load('../base.js');

const BENCHMARK_NAME = arguments[0];
const TEST_TYPE = arguments[1];
const optimize_param = arguments[2];
let optimize;
if (optimize_param == "opt") {
  optimize = true;
} else if (optimize_param == "noopt"){
  optimize = false;
} else {
  throw new Error("Test configuration error");
}

const DETERMINISTIC_RUNS = 1;
const LOCAL_ITERATIONS = 10000;
new BenchmarkSuite(BENCHMARK_NAME, [1000], [
  new Benchmark(BENCHMARK_NAME, false, false, DETERMINISTIC_RUNS, runBenchmark)
]);

// Classes for monomorphic super property access.
class A { };
A.prototype.super_prop_a0 = 10;
A.prototype.super_prop_a1 = 10;
A.prototype.super_prop_a2 = 10;
A.prototype.super_prop_a3 = 10;
A.prototype.super_prop_a4 = 10;
A.prototype.super_prop_a5 = 10;
A.prototype.super_prop_a6 = 10;
A.prototype.super_prop_a7 = 10;
A.prototype.super_prop_a8 = 10;
A.prototype.super_prop_a9 = 10;

class B extends A { }
B.prototype.super_prop_b0 = 10;
B.prototype.super_prop_b1 = 10;
B.prototype.super_prop_b2 = 10;
B.prototype.super_prop_b3 = 10;
B.prototype.super_prop_b4 = 10;
B.prototype.super_prop_b5 = 10;
B.prototype.super_prop_b6 = 10;
B.prototype.super_prop_b7 = 10;
B.prototype.super_prop_b8 = 10;
B.prototype.super_prop_b9 = 10;

class C extends B {
  test_super_a(unused) {
    // Do many property accesses to increase the time taken by the thing we want
    // to measure, in comparison to the overhead of calling this function.
    return (super.super_prop_a0 + super.super_prop_a1 + super.super_prop_a2 +
      super.super_prop_a3 + super.super_prop_a4 + super.super_prop_a5 +
      super.super_prop_a6 + super.super_prop_a7 + super.super_prop_a8 +
      super.super_prop_a9);
  }
  test_super_b(unused) {
    return (super.super_prop_b0 + super.super_prop_b1 + super.super_prop_b2 +
        super.super_prop_b3 + super.super_prop_b4 + super.super_prop_b5 +
        super.super_prop_b6 + super.super_prop_b7 + super.super_prop_b8 +
        super.super_prop_b9);
  }
};

// Classes for megamorphic super property access.
function createClasses(base) {
  class B extends base { };
  B.prototype.super_prop_b0 = 10;
  B.prototype.super_prop_b1 = 10;
  B.prototype.super_prop_b2 = 10;
  B.prototype.super_prop_b3 = 10;
  B.prototype.super_prop_b4 = 10;
  B.prototype.super_prop_b5 = 10;
  B.prototype.super_prop_b6 = 10;
  B.prototype.super_prop_b7 = 10;
  B.prototype.super_prop_b8 = 10;
  B.prototype.super_prop_b9 = 10;

  class C extends B {
    test_super_a(unused) {
      return (super.super_prop_a0 + super.super_prop_a1 + super.super_prop_a2 +
        super.super_prop_a3 + super.super_prop_a4 + super.super_prop_a5 +
        super.super_prop_a6 + super.super_prop_a7 + super.super_prop_a8 +
        super.super_prop_a9);
    }
    test_super_b(unused) {
      return (super.super_prop_b0 + super.super_prop_b1 + super.super_prop_b2 +
        super.super_prop_b3 + super.super_prop_b4 + super.super_prop_b5 +
        super.super_prop_b6 + super.super_prop_b7 + super.super_prop_b8 +
        super.super_prop_b9);
    }
  }
  return C;
}

function test_property_access(o) {
  return (o.prop0 + o.prop1 + o.prop2 + o.prop3 + o.prop4 + o.prop5 + o.prop6 +
     o.prop7 + o.prop8 + o.prop9);
}

// Set up "objects" and "tested_functions" based on which test we're running.
let objects;
let tested_functions;

switch (TEST_TYPE) {
  case "super_1":
    // Monomorphic super property lookup, property is a constant, found 1 step
    // above the lookup start object in the prototype chain.
    objects = [new C()];
    tested_functions = [C.prototype.test_super_a];
    break;
  case "normal_1":
    // Monomorphic normal property lookup, property is a constant, found 1 step
    // above the lookup start object in the prototype chain.
    objects = [{__proto__: {"prop0": 10, "prop1": 10, "prop2": 10, "prop3": 10,
                            "prop4": 10, "prop5": 10, "prop6": 10, "prop7": 10,
                            "prop8": 10, "prop9": 10}}];
    tested_functions = [test_property_access];
    break;
  case "super_2":
    // Monomorphic super property lookup, property is a constant, found in the
    // lookup start object.
    objects = [new C()];
    tested_functions = [C.prototype.test_super_b];
    break;
  case "normal_2":
    // Monomorphic normal property lookup, property is a constant, found in the
    // lookup start object.
    objects = [{"prop0": 10, "prop1": 10, "prop2": 10, "prop3": 10, "prop4": 10,
                "prop5": 10, "prop6": 10, "prop7": 10, "prop8": 10,
                "prop9": 10}];
    tested_functions = [test_property_access];
    break;
  case "super_3":
    // Megamorphic super property lookup, property is a constant, found 1 step
    // above the lookup start object in the prototype chain. The holder is
    // the same in all cases though.
    objects = [];
    tested_functions = [];
    for (let i = 0; i < 5; ++i) {
      const c = createClasses(A);
      objects.push(new c());
      tested_functions.push(c.prototype.test_super_a);
    }
    break;
  case "normal_3":
    // Megamorphic normal property lookup, property is a constant, found 1 step
    // above the lookup start object in the prototype chain. The holder is
    // the same in all cases though.
    const proto = {"prop0": 10, "prop1": 10, "prop2": 10, "prop3": 10,
                   "prop4": 10, "prop5": 10, "prop6": 10, "prop7": 10,
                   "prop8": 10, "prop9": 10};
    objects = [{__proto__: proto}, {__proto__: proto, "a": 1},
               {__proto__: proto, "a": 1, "b": 1},
               {__proto__: proto, "a": 1, "b": 1, "c": 1},
               {__proto__: proto, "a": 1, "b": 1, "c": 1, "d": 1}];
    tested_functions = [test_property_access, test_property_access,
                        test_property_access, test_property_access,
                        test_property_access];
    break;
  case "super_4":
    // Megamorphic super property lookup, property is a constant, found in the
    // lookup start object. The holder is always a different object.
    objects = [];
    tested_functions = [];
    for (let i = 0; i < 5; ++i) {
      const c = createClasses(A);
      objects.push(new c());
      tested_functions.push(c.prototype.test_super_b);
    }
    break;
  case "normal_4":
    // Megamorphic normal property lookup, property is a constant, found in the
    // lookup start object. The holder is always a different object.
    objects = [{"prop0": 10, "prop1": 10, "prop2": 10, "prop3": 10, "prop4": 10,
                "prop5": 10, "prop6": 10, "prop7": 10, "prop8": 10,
                "prop9": 10},
               {"a": 0, "prop0": 10, "prop1": 10, "prop2": 10, "prop3": 10,
                "prop4": 10, "prop5": 10, "prop6": 10, "prop7": 10, "prop8": 10,
                "prop9": 10},
               {"a": 0, "b": 0, "prop0": 10, "prop1": 10, "prop2": 10,
                "prop3": 10, "prop4": 10, "prop5": 10, "prop6": 10, "prop7": 10,
                "prop8": 10, "prop9": 10},
               {"a": 0, "b": 0, "c": 0, "prop0": 10, "prop1": 10, "prop2": 10,
                "prop3": 10, "prop4": 10, "prop5": 10, "prop6": 10, "prop7": 10,
                "prop8": 10, "prop9": 10},
               {"a": 0, "b": 0, "c": 0, "d": 0, "prop0": 10, "prop1": 10,
                "prop2": 10, "prop3": 10, "prop4": 10, "prop5": 10, "prop6": 10,
                "prop7": 10, "prop8": 10, "prop9": 10}];

    tested_functions = [test_property_access, test_property_access,
                        test_property_access, test_property_access,
                        test_property_access];
    break;
  default:
    throw new Error("Test configuration error");
}

for (f of tested_functions) {
  if (optimize) {
    %PrepareFunctionForOptimization(f);
  } else {
    %NeverOptimizeFunction(f);
  }
}

function runBenchmark() {
  const expected_value = 10 * 10;
  let ix = 0;
  for (let i = 0; i < LOCAL_ITERATIONS; ++i) {
    const object = objects[ix];
    const r = tested_functions[ix].call(object, object);
    if (r != expected_value) {
      throw new Error("Test error");
    }
    if (++ix == objects.length) {
      ix = 0;
      if (optimize) {
        for (f of tested_functions) {
          %OptimizeFunctionOnNextCall(f);
        }
      }
    }
  }
}

var success = true;

function PrintResult(name, result) {
  print(name + '(Score): ' + result);
}

function PrintError(name, error) {
  PrintResult(name, error);
  success = false;
}

BenchmarkSuite.config.doWarmup = false;
BenchmarkSuite.config.doDeterministic = true;

BenchmarkSuite.RunSuites({NotifyResult: PrintResult, NotifyError: PrintError});
