Commit c67b5096 authored by ishell's avatar ishell Committed by Commit bot

[turbofan] Fixing ES6 tail calls in Turbofan.

In case when F inlined normal call to G which tail calls H we should not write translation for G for the tail call site.
Otherwise we will see G in a stack trace inside H.

This CL also adds a "megatest" which tests product of the following cases:
1) tail caller is inlined/not-inlined
2) tail callee is inlined/not-inlined
3) tail caller has an arguments adaptor frame above or not
4) tail callee has an arguments adaptor frame above or not
5) tail callee is a normal/bound/proxy function

Note that tests for not yet supported cases are not run for now.

BUG=v8:4698
LOG=N

Review URL: https://codereview.chromium.org/1709583002

Cr-Commit-Position: refs/heads/master@{#34108}
parent f8e068e9
......@@ -578,13 +578,29 @@ void InstructionSelector::InitializeCallBuffer(Node* call, CallBuffer* buffer,
size_t frame_state_entries = 0;
USE(frame_state_entries); // frame_state_entries is only used for debug.
if (buffer->frame_state_descriptor != nullptr) {
Node* frame_state =
call->InputAt(static_cast<int>(buffer->descriptor->InputCount()));
// If it was a syntactic tail call we need to drop the current frame and
// an arguments adaptor frame on top of it (if the latter is present).
if (buffer->descriptor->SupportsTailCalls()) {
frame_state = NodeProperties::GetFrameStateInput(frame_state, 0);
buffer->frame_state_descriptor =
buffer->frame_state_descriptor->outer_state();
if (buffer->frame_state_descriptor != nullptr &&
buffer->frame_state_descriptor->type() ==
FrameStateType::kArgumentsAdaptor) {
frame_state = NodeProperties::GetFrameStateInput(frame_state, 0);
buffer->frame_state_descriptor =
buffer->frame_state_descriptor->outer_state();
}
}
InstructionSequence::StateId state_id =
sequence()->AddFrameStateDescriptor(buffer->frame_state_descriptor);
buffer->instruction_args.push_back(g.TempImmediate(state_id.ToInt()));
Node* frame_state =
call->InputAt(static_cast<int>(buffer->descriptor->InputCount()));
StateObjectDeduplicator deduplicator(instruction_zone());
frame_state_entries =
......
// 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
"use strict";
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());
}
}
%NeverOptimizeFunction(CheckStackTrace);
function CheckArguments(expected, args) {
args = Array.prototype.slice.call(args);
assertEquals(expected, args);
}
%NeverOptimizeFunction(CheckArguments);
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 f_template_normal(f_inlinable, f_args) {
var f_comment = f_inlinable ? CAN_INLINE_COMMENT : DONT_INLINE_COMMENT;
var lines = [
`function f(a) {`,
` ${f_comment}`,
` assertEquals(undefined, this);`,
` CheckArguments([${f_args}], arguments);`,
` CheckStackTrace([f, test]);`,
` %DeoptimizeNow();`,
` CheckArguments([${f_args}], arguments);`,
` CheckStackTrace([f, test]);`,
` return 42;`,
`}`,
];
return lines.join("\n");
}
function f_template_bound(f_inlinable, f_args) {
var f_comment = f_inlinable ? CAN_INLINE_COMMENT : DONT_INLINE_COMMENT;
var lines = [
`function ff(a) {`,
` ${f_comment}`,
` assertEquals(153, this.a);`,
` CheckArguments([${f_args}], arguments);`,
` CheckStackTrace([ff, test]);`,
` %DeoptimizeNow();`,
` CheckArguments([${f_args}], arguments);`,
` CheckStackTrace([ff, test]);`,
` return 42;`,
`}`,
`var f = ff.bind({a: 153});`,
];
return lines.join("\n");
}
function f_template_proxy(f_inlinable, f_args) {
var f_comment = f_inlinable ? CAN_INLINE_COMMENT : DONT_INLINE_COMMENT;
var lines = [
`function ff(a) {`,
` ${f_comment}`,
` assertEquals(undefined, this);`,
` CheckArguments([${f_args}], arguments);`,
` CheckStackTrace([f, test]);`,
` %DeoptimizeNow();`,
` CheckArguments([${f_args}], arguments);`,
` CheckStackTrace([f, test]);`,
` return 42;`,
`}`,
`var f = new Proxy(ff, {});`,
];
return lines.join("\n");
}
function g_template(g_inlinable, f_args, g_args) {
var g_comment = g_inlinable ? CAN_INLINE_COMMENT : DONT_INLINE_COMMENT;
var lines = [
`function g(a) {`,
` ${g_comment}`,
` CheckArguments([${g_args}], arguments);`,
` return f(${f_args});`,
`}`,
];
return lines.join("\n");
}
function test_template(f_source, g_source, g_args,
f_inlinable, g_inlinable) {
f_source = ident_source(f_source, 2);
g_source = ident_source(g_source, 2);
var lines = [
`(function() {`,
f_source,
g_source,
` function test() {`,
` assertEquals(42, g(${g_args}));`,
` }`,
` ${f_inlinable ? "%SetForceInlineFlag(f)" : ""};`,
` ${g_inlinable ? "%SetForceInlineFlag(g)" : ""};`,
``,
` test();`,
` %OptimizeFunctionOnNextCall(test);`,
` try { %OptimizeFunctionOnNextCall(f); } catch(e) {}`,
` try { %OptimizeFunctionOnNextCall(ff); } catch(e) {}`,
` %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_template_normal,
f_template_bound,
f_template_proxy
];
f_variants.forEach((f_template) => {
f_args_variants.forEach((f_args) => {
g_args_variants.forEach((g_args) => {
f_inlinable_variants.forEach((f_inlinable) => {
g_inlinable_variants.forEach((g_inlinable) => {
var f_source = f_template(f_inlinable, f_args);
var g_source = g_template(g_inlinable, f_args, g_args);
var source = test_template(f_source, g_source, g_args,
f_inlinable, g_inlinable);
print("====================");
print(source);
eval(source);
});
});
});
});
});
}
run_tests();
......@@ -5,10 +5,16 @@
// Flags: --allow-natives-syntax --harmony-tailcalls
"use strict";
Error.prepareStackTrace = (e,s) => s;
Error.prepareStackTrace = (error,stack) => {
error.strace = stack;
return error.message + "\n at " + stack.join("\n at ");
}
function CheckStackTrace(expected) {
var stack = (new Error()).stack;
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());
......@@ -27,14 +33,13 @@ function f_153(expected_call_stack, a) {
// Tail call when caller does not have an arguments adaptor frame.
(function test() {
(function() {
// Caller and callee have same number of arguments.
function f1(a) {
CheckStackTrace([f1, test]);
return 10 + a;
}
function g1(a) { return f1(2); }
assertEquals(12, g1(1));
// Caller has more arguments than callee.
function f2(a) {
......@@ -42,7 +47,6 @@ function f_153(expected_call_stack, a) {
return 10 + a;
}
function g2(a, b, c) { return f2(2); }
assertEquals(12, g2(1, 2, 3));
// Caller has less arguments than callee.
function f3(a, b, c) {
......@@ -50,7 +54,6 @@ function f_153(expected_call_stack, a) {
return 10 + a + b + c;
}
function g3(a) { return f3(2, 3, 4); }
assertEquals(19, g3(1));
// Callee has arguments adaptor frame.
function f4(a, b, c) {
......@@ -58,19 +61,27 @@ function f_153(expected_call_stack, a) {
return 10 + a;
}
function g4(a) { return f4(2); }
assertEquals(12, g4(1));
function test() {
assertEquals(12, g1(1));
assertEquals(12, g2(1, 2, 3));
assertEquals(19, g3(1));
assertEquals(12, g4(1));
}
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Tail call when caller has an arguments adaptor frame.
(function test() {
(function() {
// Caller and callee have same number of arguments.
function f1(a) {
CheckStackTrace([f1, test]);
return 10 + a;
}
function g1(a) { return f1(2); }
assertEquals(12, g1());
// Caller has more arguments than callee.
function f2(a) {
......@@ -78,7 +89,6 @@ function f_153(expected_call_stack, a) {
return 10 + a;
}
function g2(a, b, c) { return f2(2); }
assertEquals(12, g2());
// Caller has less arguments than callee.
function f3(a, b, c) {
......@@ -86,7 +96,6 @@ function f_153(expected_call_stack, a) {
return 10 + a + b + c;
}
function g3(a) { return f3(2, 3, 4); }
assertEquals(19, g3());
// Callee has arguments adaptor frame.
function f4(a, b, c) {
......@@ -94,13 +103,22 @@ function f_153(expected_call_stack, a) {
return 10 + a;
}
function g4(a) { return f4(2); }
assertEquals(12, g4());
function test() {
assertEquals(12, g1());
assertEquals(12, g2());
assertEquals(19, g3());
assertEquals(12, g4());
}
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Tail call bound function when caller does not have an arguments
// adaptor frame.
(function test() {
(function() {
// Caller and callee have same number of arguments.
function f1(a) {
assertEquals(153, this.a);
......@@ -109,7 +127,6 @@ function f_153(expected_call_stack, a) {
}
var b1 = f1.bind({a: 153});
function g1(a) { return b1(2); }
assertEquals(12, g1(1));
// Caller has more arguments than callee.
function f2(a) {
......@@ -119,7 +136,6 @@ function f_153(expected_call_stack, a) {
}
var b2 = f2.bind({a: 153});
function g2(a, b, c) { return b2(2); }
assertEquals(12, g2(1, 2, 3));
// Caller has less arguments than callee.
function f3(a, b, c) {
......@@ -129,7 +145,6 @@ function f_153(expected_call_stack, a) {
}
var b3 = f3.bind({a: 153});
function g3(a) { return b3(2, 3, 4); }
assertEquals(19, g3(1));
// Callee has arguments adaptor frame.
function f4(a, b, c) {
......@@ -139,12 +154,21 @@ function f_153(expected_call_stack, a) {
}
var b4 = f4.bind({a: 153});
function g4(a) { return b4(2); }
assertEquals(12, g4(1));
function test() {
assertEquals(12, g1(1));
assertEquals(12, g2(1, 2, 3));
assertEquals(19, g3(1));
assertEquals(12, g4(1));
}
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Tail call bound function when caller has an arguments adaptor frame.
(function test() {
(function() {
// Caller and callee have same number of arguments.
function f1(a) {
assertEquals(153, this.a);
......@@ -153,7 +177,6 @@ function f_153(expected_call_stack, a) {
}
var b1 = f1.bind({a: 153});
function g1(a) { return b1(2); }
assertEquals(12, g1());
// Caller has more arguments than callee.
function f2(a) {
......@@ -163,7 +186,6 @@ function f_153(expected_call_stack, a) {
}
var b2 = f2.bind({a: 153});
function g2(a, b, c) { return b2(2); }
assertEquals(12, g2());
// Caller has less arguments than callee.
function f3(a, b, c) {
......@@ -173,7 +195,6 @@ function f_153(expected_call_stack, a) {
}
var b3 = f3.bind({a: 153});
function g3(a) { return b3(2, 3, 4); }
assertEquals(19, g3());
// Callee has arguments adaptor frame.
function f4(a, b, c) {
......@@ -183,34 +204,46 @@ function f_153(expected_call_stack, a) {
}
var b4 = f4.bind({a: 153});
function g4(a) { return b4(2); }
assertEquals(12, g4());
function test() {
assertEquals(12, g1());
assertEquals(12, g2());
assertEquals(19, g3());
assertEquals(12, g4());
}
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Tail calling via various expressions.
(function test() {
(function() {
function g1(a) {
return f([f, g1, test], false) || f([f, test], true);
}
assertEquals(true, g1());
function g2(a) {
return f([f, g2, test], true) && f([f, test], true);
}
assertEquals(true, g2());
function g3(a) {
return f([f, g3, test], 13), f([f, test], 153);
}
assertEquals(153, g3());
function test() {
assertEquals(true, g1());
assertEquals(true, g2());
assertEquals(153, g3());
}
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Test tail calls from try-catch-finally constructs.
(function test() {
//
// try-catch
//
// Test tail calls from try-catch constructs.
(function() {
function tc1(a) {
try {
f_153([f_153, tc1, test]);
......@@ -219,7 +252,6 @@ function f_153(expected_call_stack, a) {
f_153([f_153, tc1, test]);
}
}
assertEquals(153, tc1());
function tc2(a) {
try {
......@@ -230,7 +262,6 @@ function f_153(expected_call_stack, a) {
return f_153([f_153, test]);
}
}
assertEquals(153, tc2());
function tc3(a) {
try {
......@@ -242,11 +273,20 @@ function f_153(expected_call_stack, a) {
f_153([f_153, tc3, test]);
return f_153([f_153, test]);
}
assertEquals(153, tc3());
//
// try-finally
//
function test() {
assertEquals(153, tc1());
assertEquals(153, tc2());
assertEquals(153, tc3());
}
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Test tail calls from try-finally constructs.
(function() {
function tf1(a) {
try {
f_153([f_153, tf1, test]);
......@@ -255,7 +295,6 @@ function f_153(expected_call_stack, a) {
f_153([f_153, tf1, test]);
}
}
assertEquals(153, tf1());
function tf2(a) {
try {
......@@ -266,7 +305,6 @@ function f_153(expected_call_stack, a) {
return f_153([f_153, test]);
}
}
assertEquals(153, tf2());
function tf3(a) {
try {
......@@ -276,11 +314,20 @@ function f_153(expected_call_stack, a) {
}
return f_153([f_153, test]);
}
assertEquals(153, tf3());
//
// try-catch-finally
//
function test() {
assertEquals(153, tf1());
assertEquals(153, tf2());
assertEquals(153, tf3());
}
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Test tail calls from try-catch-finally constructs.
(function() {
function tcf1(a) {
try {
f_153([f_153, tcf1, test]);
......@@ -290,7 +337,6 @@ function f_153(expected_call_stack, a) {
f_153([f_153, tcf1, test]);
}
}
assertEquals(153, tcf1());
function tcf2(a) {
try {
......@@ -303,7 +349,6 @@ function f_153(expected_call_stack, a) {
f_153([f_153, tcf2, test]);
}
}
assertEquals(153, tcf2());
function tcf3(a) {
try {
......@@ -316,7 +361,6 @@ function f_153(expected_call_stack, a) {
return f_153([f_153, test]);
}
}
assertEquals(153, tcf3());
function tcf4(a) {
try {
......@@ -329,5 +373,14 @@ function f_153(expected_call_stack, a) {
}
return f_153([f_153, test]);
}
assertEquals(153, tcf4());
function test() {
assertEquals(153, tcf1());
assertEquals(153, tcf2());
assertEquals(153, tcf3());
assertEquals(153, tcf4());
}
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment