Javascript  |  423行  |  12.21 KB

// Copyright 2016 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 --harmony-tailcalls


Error.prepareStackTrace = (error,stack) => {
  error.strace = stack;
  return error.message + "\n    at " + stack.join("\n    at ");
}

var verbose = typeof(arguments) !== "undefined" && arguments.indexOf("-v") >= 0;

function checkStackTrace(expected) {
  var e = new Error();
  e.stack;  // prepare stack trace
  var stack = e.strace;
  assertEquals("checkStackTrace", stack[0].getFunctionName());
  for (var i = 0; i < expected.length; i++) {
    assertEquals(expected[i].name, stack[i + 1].getFunctionName());
  }
}


var CAN_INLINE_COMMENT  = "// Let it be inlined.";
var DONT_INLINE_COMMENT = (function() {
  var line = "// Don't inline. Don't inline. Don't inline. Don't inline.";
  for (var i = 0; i < 4; i++) {
    line += "\n  " + line;
  }
  return line;
})();


function ident_source(source, ident) {
  ident = " ".repeat(ident);
  return ident + source.replace(/\n/gi, "\n" + ident);
}

var SHARDS_COUNT = 10;

function run_tests(shard) {
  function inlinable_comment(inlinable) {
    return inlinable ? CAN_INLINE_COMMENT : DONT_INLINE_COMMENT;
  }

  // Check arguments manually to avoid bailing out with reason "bad value
  // context for arguments value".
  function check_arguments_template(expected_name) {
    var lines = [
      `  assertEquals_(${expected_name}.length, arguments.length);`,
      `  for (var i = 0; i < ${expected_name}.length; i++) {`,
      `    assertEquals_(${expected_name}[i], arguments[i]);`,
      `  }`,
    ];
    return lines.join("\n");
  }
  var check_arguments = check_arguments_template("expected_args");

  function deopt_template(deopt_mode) {
    switch(deopt_mode) {
      case "none":
        return "  // Don't deoptimize";
      case "f":
      case "g":
      case "test":
        return `  %DeoptimizeFunction(${deopt_mode});`;
      default:
        assertUnreachable();
    }
  }

  var f_cfg_sloppy = {
    func_name: 'f',
    source_template: function(cfg) {
      var receiver = cfg.f_receiver != undefined ? cfg.f_receiver
                                                 : "global";
      var do_checks = [
        `  assertEquals_(${receiver}, this);`,
        `  ${!cfg.check_new_target ? "// " : ""}assertEquals_(undefined, new.target);`,
        check_arguments,
        `  checkStackTrace_([f, test]);`,
      ].join("\n");

      var lines = [
        `function f(a) {`,
        `  ${inlinable_comment(cfg.f_inlinable)}`,
        `  counter++;`,
        `  var expected_args = [${cfg.f_args}];`,
        do_checks,
        deopt_template(cfg.deopt_mode),
        do_checks,
        `  return 42;`,
        `}`,
      ];
      return lines.join("\n");
    },
  };

  var f_cfg_strict = {
    func_name: 'f',
    source_template: function(cfg) {
      var receiver = cfg.f_receiver != undefined ? cfg.f_receiver
                                                 : "undefined";
      var do_checks = [
        `  assertEquals_(${receiver}, this);`,
        `  ${!cfg.check_new_target ? "// " : ""}assertEquals_(undefined, new.target);`,
        check_arguments,
        `  checkStackTrace_([f, test]);`,
      ].join("\n");

      var lines = [
        `function f(a) {`,
        `  "use strict";`,
        `  ${inlinable_comment(cfg.f_inlinable)}`,
        `  counter++;`,
        `  var expected_args = [${cfg.f_args}];`,
        do_checks,
        deopt_template(cfg.deopt_mode),
        do_checks,
        `  return 42;`,
        `}`,
      ];
      return lines.join("\n");
    },
  };

  var f_cfg_possibly_eval = {
    func_name: 'eval',
    source_template: function(cfg) {
      var receiver = cfg.f_receiver != undefined ? cfg.f_receiver
                                                 : "global";
      var do_checks = [
        `  assertEquals_(${receiver}, this);`,
        `  ${!cfg.check_new_target ? "// " : ""}assertEquals_(undefined, new.target);`,
        check_arguments,
        `  checkStackTrace_([f, test]);`,
      ].join("\n");

      var lines = [
        `function f(a) {`,
        `  ${inlinable_comment(cfg.f_inlinable)}`,
        `  counter++;`,
        `  var expected_args = [${cfg.f_args}];`,
        do_checks,
        deopt_template(cfg.deopt_mode),
        do_checks,
        `  return 42;`,
        `}`,
        `var eval = f;`,
      ];
      return lines.join("\n");
    },
  };

  var f_cfg_bound = {
    func_name: 'bound',
    source_template: function(cfg) {
      var do_checks = [
        `  assertEquals_(receiver, this);`,
        `  ${!cfg.check_new_target ? "// " : ""}assertEquals_(undefined, new.target);`,
        check_arguments,
        `  checkStackTrace_([f, test]);`,
      ].join("\n");

      var lines = [
        `function f(a) {`,
        `  "use strict";`,
        `  ${inlinable_comment(cfg.f_inlinable)}`,
        `  counter++;`,
        `  var expected_args = [${cfg.f_args}];`,
        do_checks,
        deopt_template(cfg.deopt_mode),
        do_checks,
        `  return 42;`,
        `}`,
        `var receiver = {a: 153};`,
        `var bound = f.bind(receiver);`,
      ];
      return lines.join("\n");
    },
  };

  var f_cfg_proxy = {
    func_name: 'p',
    source_template: function(cfg) {
      var receiver = cfg.f_receiver != undefined ? cfg.f_receiver
                                                 : "global";
      var do_checks = [
        `  assertEquals_(${receiver}, this);`,
        `  ${!cfg.check_new_target ? "// " : ""}assertEquals_(undefined, new.target);`,
        check_arguments,
        `  checkStackTrace_([f, test]);`,
      ].join("\n");

      var lines = [
        `function f(a) {`,
        `  ${inlinable_comment(cfg.f_inlinable)}`,
        `  counter++;`,
        `  var expected_args = [${cfg.f_args}];`,
        do_checks,
        deopt_template(cfg.deopt_mode),
        do_checks,
        `  return 42;`,
        `}`,
        `var p = new Proxy(f, {});`,
      ];
      return lines.join("\n");
    },
  };

  var g_cfg_normal = {
    receiver: undefined,
    source_template: function(cfg) {
      var lines = [
        `function g(a) {`,
        `  "use strict";`,
        `  ${inlinable_comment(cfg.g_inlinable)}`,
        `  var expected_args = [${cfg.g_args}];`,
        check_arguments,
        `  return ${cfg.f_name}(${cfg.f_args});`,
        `}`,
      ];
      return lines.join("\n");
    },
  };


  var g_cfg_reflect_apply = {
    receiver: "the_receiver",
    source_template: function(cfg) {
      var lines = [
        `function g(a) {`,
        `  "use strict";`,
        `  ${inlinable_comment(cfg.g_inlinable)}`,
        `  var expected_args = [${cfg.g_args}];`,
        check_arguments,
        `  return Reflect.apply(${cfg.f_name}, the_receiver, [${cfg.f_args}]);`,
        `}`,
      ];
      return lines.join("\n");
    },
  };


  var g_cfg_function_apply = {
    receiver: "the_receiver",
    source_template: function(cfg) {
      var lines = [
        `function g(a) {`,
        `  "use strict";`,
        `  ${inlinable_comment(cfg.g_inlinable)}`,
        `  var expected_args = [${cfg.g_args}];`,
        check_arguments,
        `  return ${cfg.f_name}.apply(the_receiver, [${cfg.f_args}]);`,
        `}`,
      ];
      return lines.join("\n");
    },
  };


  var g_cfg_function_apply_arguments_object = {
    receiver: "the_receiver",
    source_template: function(cfg) {
      cfg.f_args = cfg.g_args;
      var lines = [
        `function g(a) {`,
        `  "use strict";`,
        `  ${inlinable_comment(cfg.g_inlinable)}`,
        `  var expected_args = [${cfg.g_args}];`,
        check_arguments,
        `  return ${cfg.f_name}.apply(the_receiver, arguments);`,
        `}`,
      ];
      return lines.join("\n");
    },
  };


  var g_cfg_function_call = {
    receiver: "the_receiver",
    source_template: function(cfg) {
      var f_args = "the_receiver";
      if (cfg.f_args !== "") f_args += ", ";
      f_args += cfg.f_args;

      var lines = [
        `function g(a) {`,
        `  "use strict";`,
        `  ${inlinable_comment(cfg.g_inlinable)}`,
        `  var expected_args = [${cfg.g_args}];`,
        check_arguments,
        `  return ${cfg.f_name}.call(${f_args});`,
        `}`,
      ];
      return lines.join("\n");
    },
  };


  function test_template(cfg) {
    // Note: g_source_template modifies cfg.f_args in some cases.
    var g_source = cfg.g_source_template(cfg);
    g_source = ident_source(g_source, 2);

    var f_source = cfg.f_source_template(cfg);
    f_source = ident_source(f_source, 2);

    var lines = [
      `(function() {`,
      `  // Avoid bailing out because of "Reference to a variable which requires dynamic lookup".`,
      `  var assertEquals_ = assertEquals;`,
      `  var checkStackTrace_ = checkStackTrace;`,
      `  var undefined = void 0;`,
      `  var global = Function('return this')();`,
      `  var the_receiver = {receiver: 1};`,
      `  var counter = 0;`,
      ``,
      `  // Don't inline helper functions`,
      `  %NeverOptimizeFunction(assertEquals);`,
      `  %NeverOptimizeFunction(checkStackTrace);`,
      ``,
      f_source,
      g_source,
      `  function test() {`,
      `    "use strict";`,
      `    assertEquals_(42, g(${cfg.g_args}));`,
      `  }`,
      `  ${"test();".repeat(cfg.test_warmup_count)}`,
      `  ${cfg.f_inlinable ? "%SetForceInlineFlag(f)" : "%OptimizeFunctionOnNextCall(f)"};`,
      `  ${cfg.g_inlinable ? "%SetForceInlineFlag(g)" : "%OptimizeFunctionOnNextCall(g)"};`,
      `  %OptimizeFunctionOnNextCall(test);`,
      `  test();`,
      `  assertEquals(${1 + cfg.test_warmup_count}, counter);`,
      `})();`,
      ``,
    ];
    var source = lines.join("\n");
    return source;
  }

  var f_args_variants = [/*"", "1",*/ "1, 2"];
  var g_args_variants = [/*"", "10",*/ "10, 20"];
  var f_inlinable_variants = [true, false];
  var g_inlinable_variants = [true, false];
  // This is to avoid bailing out because of referencing new.target.
  var check_new_target_variants = [/*true,*/ false];
  var deopt_mode_variants = ["none", "f", "g", "test"];
  var f_variants = [
      f_cfg_sloppy,
      f_cfg_strict,
      f_cfg_bound,
      f_cfg_proxy,
//      f_cfg_possibly_eval,
  ];
  var g_variants = [
      g_cfg_normal,
//      g_cfg_reflect_apply,
      g_cfg_function_apply,
//      g_cfg_function_apply_arguments_object,
      g_cfg_function_call,
  ];
  var test_warmup_counts = [0, 1, 2];

  var iter = 0;
  var tests_executed = 0;
  if (verbose && shard !== undefined) {
    print("Running shard #" + shard);
  }
  f_variants.forEach((f_cfg) => {
    check_new_target_variants.forEach((check_new_target) => {
      deopt_mode_variants.forEach((deopt_mode) => {
        g_variants.forEach((g_cfg) => {
          f_args_variants.forEach((f_args) => {
            g_args_variants.forEach((g_args) => {
              f_inlinable_variants.forEach((f_inlinable) => {
                g_inlinable_variants.forEach((g_inlinable) => {
                  test_warmup_counts.forEach((test_warmup_count) => {
                    if (shard !== undefined && (iter++) % SHARDS_COUNT != shard) {
                      if (verbose) {
                        print("skipping...");
                      }
                      return;
                    }
                    tests_executed++;
                    var cfg = {
                      f_source_template: f_cfg.source_template,
                      f_inlinable,
                      f_args,
                      f_name: f_cfg.func_name,
                      f_receiver: g_cfg.receiver,
                      g_source_template: g_cfg.source_template,
                      g_inlinable,
                      g_args,
                      test_warmup_count,
                      check_new_target,
                      deopt_mode,
                    };
                    var source = test_template(cfg);
                    if (verbose) {
                      // print("====================");
                      // print(source);
                    }
                    eval(source);
                  });
                });
              });
            });
          });
        });
      });
    });
  });
  if (verbose) {
    print("Number of tests executed: " + tests_executed);
  }
}

// Uncomment to run all the tests at once or use shard runners.
//run_tests();