2aae579cf0
In case when F tail calls G we should also remove the potential arguments adaptor frame for F. This CL introduces two new machine instructions ArchTailCallCodeObjectFromJSFunction and ArchTailCallJSFunctionFromJSFunction which (unlike existing ArchTailCallCodeObject and ArchTailCallJSFunction) also drop arguments adaptor frame if it exists right before jumping to the target function. BUG=v8:4698 LOG=N Review URL: https://codereview.chromium.org/1702423002 Cr-Commit-Position: refs/heads/master@{#34566}
361 lines
10 KiB
JavaScript
361 lines
10 KiB
JavaScript
// 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 --no-turbo-inlining
|
|
// TODO(v8:4698), TODO(ishell): support these cases.
|
|
// Flags: --nostress-opt
|
|
|
|
|
|
Error.prepareStackTrace = (error,stack) => {
|
|
error.strace = stack;
|
|
return error.message + "\n at " + stack.join("\n at ");
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
function run_tests() {
|
|
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");
|
|
|
|
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);`,
|
|
` assertEquals_(undefined, new.target);`,
|
|
check_arguments,
|
|
` checkStackTrace_([f, test]);`,
|
|
].join("\n");
|
|
|
|
var lines = [
|
|
`function f(a) {`,
|
|
` ${inlinable_comment(cfg.f_inlinable)}`,
|
|
` var expected_args = [${cfg.f_args}];`,
|
|
do_checks,
|
|
` %DeoptimizeNow();`,
|
|
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);`,
|
|
` assertEquals_(undefined, new.target);`,
|
|
check_arguments,
|
|
` checkStackTrace_([f, test]);`,
|
|
].join("\n");
|
|
|
|
var lines = [
|
|
`function f(a) {`,
|
|
` "use strict";`,
|
|
` ${inlinable_comment(cfg.f_inlinable)}`,
|
|
` var expected_args = [${cfg.f_args}];`,
|
|
do_checks,
|
|
` %DeoptimizeNow();`,
|
|
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);`,
|
|
` assertEquals_(undefined, new.target);`,
|
|
check_arguments,
|
|
` checkStackTrace_([f, test]);`,
|
|
].join("\n");
|
|
|
|
var lines = [
|
|
`function f(a) {`,
|
|
` ${inlinable_comment(cfg.f_inlinable)}`,
|
|
` var expected_args = [${cfg.f_args}];`,
|
|
do_checks,
|
|
` %DeoptimizeNow();`,
|
|
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);`,
|
|
` assertEquals_(undefined, new.target);`,
|
|
check_arguments,
|
|
` checkStackTrace_([f, test]);`,
|
|
].join("\n");
|
|
|
|
var lines = [
|
|
`function f(a) {`,
|
|
` "use strict";`,
|
|
` ${inlinable_comment(cfg.f_inlinable)}`,
|
|
` var expected_args = [${cfg.f_args}];`,
|
|
do_checks,
|
|
` %DeoptimizeNow();`,
|
|
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);`,
|
|
` assertEquals_(undefined, new.target);`,
|
|
check_arguments,
|
|
` checkStackTrace_([f, test]);`,
|
|
].join("\n");
|
|
|
|
var lines = [
|
|
`function f(a) {`,
|
|
` ${inlinable_comment(cfg.f_inlinable)}`,
|
|
` var expected_args = [${cfg.f_args}];`,
|
|
do_checks,
|
|
` %DeoptimizeNow();`,
|
|
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_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};`,
|
|
``,
|
|
` // Don't inline helper functions`,
|
|
` %NeverOptimizeFunction(assertEquals);`,
|
|
` %NeverOptimizeFunction(checkStackTrace);`,
|
|
``,
|
|
f_source,
|
|
g_source,
|
|
` function test() {`,
|
|
` "use strict";`,
|
|
` assertEquals_(42, g(${cfg.g_args}));`,
|
|
` }`,
|
|
` ${cfg.f_inlinable ? "%SetForceInlineFlag(f)" : ""};`,
|
|
` ${cfg.g_inlinable ? "%SetForceInlineFlag(g)" : ""};`,
|
|
` ${"test();".repeat(cfg.test_warmup_count)}`,
|
|
` %OptimizeFunctionOnNextCall(test);`,
|
|
` %OptimizeFunctionOnNextCall(f);`,
|
|
` %OptimizeFunctionOnNextCall(g);`,
|
|
` test();`,
|
|
`})();`,
|
|
``,
|
|
];
|
|
var source = lines.join("\n");
|
|
return source;
|
|
}
|
|
|
|
// TODO(v8:4698), TODO(ishell): support all commented cases.
|
|
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];
|
|
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_function_call,
|
|
g_cfg_function_apply,
|
|
g_cfg_function_apply_arguments_object,
|
|
];
|
|
var test_warmup_counts = [0, 1, 2];
|
|
|
|
f_variants.forEach((f_cfg) => {
|
|
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) => {
|
|
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,
|
|
};
|
|
var source = test_template(cfg);
|
|
print("====================");
|
|
print(source);
|
|
eval(source);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
run_tests();
|