[es8] Initial set of changes to support syntactic tail calls.

The syntax is "return continue expr;".

BUG=v8:4915
LOG=Y

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

Cr-Commit-Position: refs/heads/master@{#35799}
This commit is contained in:
ishell 2016-04-26 10:30:21 -07:00 committed by Commit bot
parent 967a04634d
commit ea2fbb7620
13 changed files with 753 additions and 50 deletions

View File

@ -423,6 +423,9 @@ class CallSite {
"inside a block.") \
T(StrictOctalLiteral, "Octal literals are not allowed in strict mode.") \
T(StrictWith, "Strict mode code may not include a with statement") \
T(TailCallInCatchBlock, \
"Tail call expression in catch block when finally block is also present.") \
T(TailCallInTryBlock, "Tail call expression in try block.") \
T(TemplateOctalLiteral, \
"Octal literals are not allowed in template strings.") \
T(ThisFormalParameter, "'this' is not a valid formal parameter name") \

View File

@ -190,6 +190,14 @@ class ParserBase : public Traits {
Scope* scope;
};
struct TailCallExpression {
TailCallExpression(ExpressionT expression, int pos)
: expression(expression), pos(pos) {}
ExpressionT expression;
int pos;
};
class FunctionState BASE_EMBEDDED {
public:
FunctionState(FunctionState** function_state_stack, Scope** scope_stack,
@ -247,12 +255,12 @@ class ParserBase : public Traits {
return destructuring_assignments_to_rewrite_;
}
List<ExpressionT>& expressions_in_tail_position() {
List<TailCallExpression>& expressions_in_tail_position() {
return expressions_in_tail_position_;
}
void AddExpressionInTailPosition(ExpressionT expression) {
void AddExpressionInTailPosition(ExpressionT expression, int pos) {
if (collect_expressions_in_tail_position_) {
expressions_in_tail_position_.Add(expression);
expressions_in_tail_position_.Add(TailCallExpression(expression, pos));
}
}
@ -315,7 +323,7 @@ class ParserBase : public Traits {
Scope* outer_scope_;
List<DestructuringAssignment> destructuring_assignments_to_rewrite_;
List<ExpressionT> expressions_in_tail_position_;
List<TailCallExpression> expressions_in_tail_position_;
bool collect_expressions_in_tail_position_;
ZoneList<ExpressionT> non_patterns_to_rewrite_;
@ -334,6 +342,42 @@ class ParserBase : public Traits {
friend class Checkpoint;
};
// This scope disables collecting of expressions at tail call position.
class DontCollectExpressionsInTailPositionScope {
public:
explicit DontCollectExpressionsInTailPositionScope(
FunctionState* function_state)
: function_state_(function_state),
old_value_(function_state->collect_expressions_in_tail_position()) {
function_state->set_collect_expressions_in_tail_position(false);
}
~DontCollectExpressionsInTailPositionScope() {
function_state_->set_collect_expressions_in_tail_position(old_value_);
}
private:
FunctionState* function_state_;
bool old_value_;
};
// Collects all return expressions at tail call position in this scope
// to a separate list.
class CollectExpressionsInTailPositionToListScope {
public:
CollectExpressionsInTailPositionToListScope(FunctionState* function_state,
List<TailCallExpression>* list)
: function_state_(function_state), list_(list) {
function_state->expressions_in_tail_position().Swap(list_);
}
~CollectExpressionsInTailPositionToListScope() {
function_state_->expressions_in_tail_position().Swap(list_);
}
private:
FunctionState* function_state_;
List<TailCallExpression>* list_;
};
// Annoyingly, arrow functions first parse as comma expressions, then when we
// see the => we have to go back and reinterpret the arguments as being formal
// parameters. To do so we need to reset some of the parser state back to
@ -1893,6 +1937,7 @@ ParserBase<Traits>::ParseAssignmentExpression(bool accept_IN,
// ArrowFunction
// YieldExpression
// LeftHandSideExpression AssignmentOperator AssignmentExpression
// TailCallExpression
bool is_destructuring_assignment = false;
int lhs_beg_pos = peek_position();
@ -2860,6 +2905,22 @@ ParserBase<Traits>::ParseArrowFunctionLiteral(
// Single-expression body
int pos = position();
ExpressionClassifier classifier(this);
bool is_tail_call_expression;
if (FLAG_harmony_explicit_tailcalls) {
// TODO(ishell): update chapter number.
// ES8 XX.YY.ZZ
if (peek() == Token::CONTINUE) {
Consume(Token::CONTINUE);
pos = position();
is_tail_call_expression = true;
} else {
is_tail_call_expression = false;
}
} else {
// ES6 14.6.1 Static Semantics: IsInTailPosition
is_tail_call_expression =
allow_tailcalls() && !is_sloppy(language_mode());
}
ExpressionT expression =
ParseAssignmentExpression(accept_IN, &classifier, CHECK_OK);
Traits::RewriteNonPattern(&classifier, CHECK_OK);
@ -2868,8 +2929,7 @@ ParserBase<Traits>::ParseArrowFunctionLiteral(
body->Add(factory()->NewReturnStatement(expression, pos), zone());
materialized_literal_count = function_state.materialized_literal_count();
expected_property_count = function_state.expected_property_count();
// ES6 14.6.1 Static Semantics: IsInTailPosition
if (allow_tailcalls() && !is_sloppy(language_mode())) {
if (is_tail_call_expression) {
this->MarkTailPosition(expression);
}
}

View File

@ -2573,6 +2573,13 @@ Statement* Parser::ParseReturnStatement(bool* ok) {
function_state_->set_return_location(loc);
Token::Value tok = peek();
int tail_call_position = -1;
if (FLAG_harmony_explicit_tailcalls && tok == Token::CONTINUE) {
Consume(Token::CONTINUE);
tail_call_position = position();
tok = peek();
}
Statement* result;
Expression* return_value;
if (scanner()->HasAnyLineTerminatorBeforeNext() ||
@ -2629,9 +2636,21 @@ Statement* Parser::ParseReturnStatement(bool* ok) {
is_object_conditional, pos);
}
// ES6 14.6.1 Static Semantics: IsInTailPosition
if (allow_tailcalls() && !is_sloppy(language_mode())) {
function_state_->AddExpressionInTailPosition(return_value);
// TODO(ishell): update chapter number.
// ES8 XX.YY.ZZ
if (tail_call_position >= 0) {
if (!function_state_->collect_expressions_in_tail_position()) {
Scanner::Location loc(tail_call_position, tail_call_position + 1);
ReportMessageAt(loc, MessageTemplate::kTailCallInTryBlock);
*ok = false;
return NULL;
}
function_state_->AddExpressionInTailPosition(return_value,
tail_call_position);
} else if (allow_tailcalls() && !is_sloppy(language_mode())) {
// ES6 14.6.1 Static Semantics: IsInTailPosition
function_state_->AddExpressionInTailPosition(return_value, pos);
}
}
ExpectSemicolon(CHECK_OK);
@ -2811,40 +2830,6 @@ Statement* Parser::ParseThrowStatement(bool* ok) {
factory()->NewThrow(exception, pos), pos);
}
class Parser::DontCollectExpressionsInTailPositionScope {
public:
DontCollectExpressionsInTailPositionScope(
Parser::FunctionState* function_state)
: function_state_(function_state),
old_value_(function_state->collect_expressions_in_tail_position()) {
function_state->set_collect_expressions_in_tail_position(false);
}
~DontCollectExpressionsInTailPositionScope() {
function_state_->set_collect_expressions_in_tail_position(old_value_);
}
private:
Parser::FunctionState* function_state_;
bool old_value_;
};
// Collects all return expressions at tail call position in this scope
// to a separate list.
class Parser::CollectExpressionsInTailPositionToListScope {
public:
CollectExpressionsInTailPositionToListScope(
Parser::FunctionState* function_state, List<Expression*>* list)
: function_state_(function_state), list_(list) {
function_state->expressions_in_tail_position().Swap(list_);
}
~CollectExpressionsInTailPositionToListScope() {
function_state_->expressions_in_tail_position().Swap(list_);
}
private:
Parser::FunctionState* function_state_;
List<Expression*>* list_;
};
TryStatement* Parser::ParseTryStatement(bool* ok) {
// TryStatement ::
@ -2877,7 +2862,7 @@ TryStatement* Parser::ParseTryStatement(bool* ok) {
Scope* catch_scope = NULL;
Variable* catch_variable = NULL;
Block* catch_block = NULL;
List<Expression*> expressions_in_tail_position_in_catch_block;
List<TailCallExpression> expressions_in_tail_position_in_catch_block;
if (tok == Token::CATCH) {
Consume(Token::CATCH);
@ -2993,6 +2978,16 @@ TryStatement* Parser::ParseTryStatement(bool* ok) {
result = factory()->NewTryCatchStatement(try_block, catch_scope,
catch_variable, catch_block, pos);
} else {
if (FLAG_harmony_explicit_tailcalls &&
expressions_in_tail_position_in_catch_block.length() > 0) {
// TODO(ishell): update chapter number.
// ES8 XX.YY.ZZ
int pos = expressions_in_tail_position_in_catch_block[0].pos;
ReportMessageAt(Scanner::Location(pos, pos + 1),
MessageTemplate::kTailCallInCatchBlock);
*ok = false;
return NULL;
}
DCHECK(finally_block != NULL);
result = factory()->NewTryFinallyStatement(try_block, finally_block, pos);
}
@ -4573,10 +4568,10 @@ ZoneList<Statement*>* Parser::ParseEagerFunctionBody(
// ES6 14.6.1 Static Semantics: IsInTailPosition
// Mark collected return expressions that are in tail call position.
const List<Expression*>& expressions_in_tail_position =
const List<TailCallExpression>& expressions_in_tail_position =
function_state_->expressions_in_tail_position();
for (int i = 0; i < expressions_in_tail_position.length(); ++i) {
MarkTailPosition(expressions_in_tail_position[i]);
MarkTailPosition(expressions_in_tail_position[i].expression);
}
return result;
}

View File

@ -920,8 +920,6 @@ class Parser : public ParserBase<ParserTraits> {
Statement* ParseForStatement(ZoneList<const AstRawString*>* labels, bool* ok);
Statement* ParseThrowStatement(bool* ok);
Expression* MakeCatchContext(Handle<String> id, VariableProxy* value);
class DontCollectExpressionsInTailPositionScope;
class CollectExpressionsInTailPositionToListScope;
TryStatement* ParseTryStatement(bool* ok);
DebuggerStatement* ParseDebuggerStatement(bool* ok);
// Parse a SubStatement in strict mode, or with an extra block scope in

View File

@ -12,8 +12,8 @@
#include "src/hashmap.h"
#include "src/list.h"
#include "src/parsing/parser-base.h"
#include "src/parsing/preparse-data.h"
#include "src/parsing/preparse-data-format.h"
#include "src/parsing/preparse-data.h"
#include "src/parsing/preparser.h"
#include "src/unicode.h"
#include "src/utils.h"
@ -681,11 +681,27 @@ PreParser::Statement PreParser::ParseReturnStatement(bool* ok) {
// This is not handled during preparsing.
Token::Value tok = peek();
int tail_call_position = -1;
if (FLAG_harmony_explicit_tailcalls && tok == Token::CONTINUE) {
Consume(Token::CONTINUE);
tail_call_position = position();
tok = peek();
}
if (!scanner()->HasAnyLineTerminatorBeforeNext() &&
tok != Token::SEMICOLON &&
tok != Token::RBRACE &&
tok != Token::EOS) {
ParseExpression(true, CHECK_OK);
if (tail_call_position >= 0) {
if (!function_state_->collect_expressions_in_tail_position()) {
Scanner::Location loc(tail_call_position, tail_call_position + 1);
ReportMessageAt(loc, MessageTemplate::kTailCallInTryBlock);
*ok = false;
return Statement::Default();
}
function_state_->AddExpressionInTailPosition(
PreParserExpression::Default(), tail_call_position);
}
}
ExpectSemicolon(CHECK_OK);
return Statement::Jump();
@ -936,7 +952,10 @@ PreParser::Statement PreParser::ParseTryStatement(bool* ok) {
Expect(Token::TRY, CHECK_OK);
ParseBlock(CHECK_OK);
{
DontCollectExpressionsInTailPositionScope no_tail_calls(function_state_);
ParseBlock(CHECK_OK);
}
Token::Value tok = peek();
if (tok != Token::CATCH && tok != Token::FINALLY) {
@ -944,6 +963,8 @@ PreParser::Statement PreParser::ParseTryStatement(bool* ok) {
*ok = false;
return Statement::Default();
}
List<TailCallExpression> expressions_in_tail_position_in_catch_block;
bool catch_block_exists = false;
if (tok == Token::CATCH) {
Consume(Token::CATCH);
Expect(Token::LPAREN, CHECK_OK);
@ -953,6 +974,9 @@ PreParser::Statement PreParser::ParseTryStatement(bool* ok) {
ValidateBindingPattern(&pattern_classifier, CHECK_OK);
Expect(Token::RPAREN, CHECK_OK);
{
CollectExpressionsInTailPositionToListScope
collect_expressions_in_tail_position_scope(
function_state_, &expressions_in_tail_position_in_catch_block);
BlockState block_state(&scope_, catch_scope);
Scope* block_scope = NewScope(scope_, BLOCK_SCOPE);
{
@ -960,11 +984,22 @@ PreParser::Statement PreParser::ParseTryStatement(bool* ok) {
ParseBlock(CHECK_OK);
}
}
catch_block_exists = true;
tok = peek();
}
if (tok == Token::FINALLY) {
Consume(Token::FINALLY);
ParseBlock(CHECK_OK);
if (FLAG_harmony_explicit_tailcalls && catch_block_exists &&
expressions_in_tail_position_in_catch_block.length() > 0) {
// TODO(ishell): update chapter number.
// ES8 XX.YY.ZZ
int pos = expressions_in_tail_position_in_catch_block[0].pos;
ReportMessageAt(Scanner::Location(pos, pos + 1),
MessageTemplate::kTailCallInCatchBlock);
*ok = false;
return Statement::Default();
}
}
return Statement::Default();
}

View File

@ -0,0 +1,19 @@
// 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: --harmony-explicit-tailcalls
function f() {
return 1;
}
function g() {
try {
f();
} catch(e) {
return continue f();
} finally {
f();
}
}

View File

@ -0,0 +1,4 @@
*%(basename)s:15: SyntaxError: Tail call expression in catch block when finally block is also present.
return continue f();
^
SyntaxError: Tail call expression in catch block when finally block is also present.

View File

@ -0,0 +1,21 @@
// 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: --harmony-explicit-tailcalls
function f() {
return 1;
}
function g() {
try {
try {
f();
} catch(e) {
return continue f();
}
} finally {
f();
}
}

View File

@ -0,0 +1,4 @@
*%(basename)s:16: SyntaxError: Tail call expression in try block.
return continue f();
^
SyntaxError: Tail call expression in try block.

View File

@ -0,0 +1,16 @@
// 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: --harmony-explicit-tailcalls
function f() {
return 1;
}
function g() {
try {
return continue f();
} catch(e) {
}
}

View File

@ -0,0 +1,4 @@
*%(basename)s:13: SyntaxError: Tail call expression in try block.
return continue f();
^
SyntaxError: Tail call expression in try block.

View File

@ -0,0 +1,121 @@
// 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-explicit-tailcalls --stack-size=100
//
// Tail call normal functions.
//
(function() {
function f(n) {
if (n <= 0) {
return "foo";
}
return continue f(n - 1);
}
assertEquals("foo", f(1e5));
%OptimizeFunctionOnNextCall(f);
assertEquals("foo", f(1e5));
})();
(function() {
function f(n) {
if (n <= 0) {
return "foo";
}
return continue f(n - 1, 42); // Call with arguments adaptor.
}
assertEquals("foo", f(1e5));
%OptimizeFunctionOnNextCall(f);
assertEquals("foo", f(1e5));
})();
(function() {
function f(n){
if (n <= 0) {
return "foo";
}
return continue g(n - 1);
}
function g(n){
if (n <= 0) {
return "bar";
}
return continue f(n - 1);
}
assertEquals("foo", f(1e5));
assertEquals("bar", f(1e5 + 1));
%OptimizeFunctionOnNextCall(f);
assertEquals("foo", f(1e5));
assertEquals("bar", f(1e5 + 1));
})();
(function() {
function f(n){
if (n <= 0) {
return "foo";
}
return continue g(n - 1, 42); // Call with arguments adaptor.
}
function g(n){
if (n <= 0) {
return "bar";
}
return continue f(n - 1, 42); // Call with arguments adaptor.
}
assertEquals("foo", f(1e5));
assertEquals("bar", f(1e5 + 1));
%OptimizeFunctionOnNextCall(f);
assertEquals("foo", f(1e5));
assertEquals("bar", f(1e5 + 1));
})();
//
// Tail call bound functions.
//
(function() {
function f0(n) {
if (n <= 0) {
return "foo";
}
return continue f_bound(n - 1);
}
var f_bound = f0.bind({});
function f(n) {
return continue f_bound(n);
}
assertEquals("foo", f(1e5));
%OptimizeFunctionOnNextCall(f);
assertEquals("foo", f(1e5));
})();
(function() {
function f0(n){
if (n <= 0) {
return "foo";
}
return continue g_bound(n - 1);
}
function g0(n){
if (n <= 0) {
return "bar";
}
return continue f_bound(n - 1);
}
var f_bound = f0.bind({});
var g_bound = g0.bind({});
function f(n) {
return continue f_bound(n);
}
assertEquals("foo", f(1e5));
assertEquals("bar", f(1e5 + 1));
%OptimizeFunctionOnNextCall(f);
assertEquals("foo", f(1e5));
assertEquals("bar", f(1e5 + 1));
})();

View File

@ -0,0 +1,423 @@
// 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-explicit-tailcalls
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());
}
}
function f(expected_call_stack, a, b) {
CheckStackTrace(expected_call_stack);
return a;
}
function f_153(expected_call_stack, a) {
CheckStackTrace(expected_call_stack);
return 153;
}
// Tail call when caller does not have an arguments adaptor frame.
(function() {
// Caller and callee have same number of arguments.
function f1(a) {
CheckStackTrace([f1, test]);
return 10 + a;
}
function g1(a) { return continue f1(2); }
// Caller has more arguments than callee.
function f2(a) {
CheckStackTrace([f2, test]);
return 10 + a;
}
function g2(a, b, c) { return continue f2(2); }
// Caller has less arguments than callee.
function f3(a, b, c) {
CheckStackTrace([f3, test]);
return 10 + a + b + c;
}
function g3(a) { return continue f3(2, 3, 4); }
// Callee has arguments adaptor frame.
function f4(a, b, c) {
CheckStackTrace([f4, test]);
return 10 + a;
}
function g4(a) { return continue f4(2); }
function test() {
assertEquals(12, g1(1));
assertEquals(12, g2(1, 2, 3));
assertEquals(19, g3(1));
assertEquals(12, g4(1));
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Tail call when caller has an arguments adaptor frame.
(function() {
// Caller and callee have same number of arguments.
function f1(a) {
CheckStackTrace([f1, test]);
return 10 + a;
}
function g1(a) { return continue f1(2); }
// Caller has more arguments than callee.
function f2(a) {
CheckStackTrace([f2, test]);
return 10 + a;
}
function g2(a, b, c) { return continue f2(2); }
// Caller has less arguments than callee.
function f3(a, b, c) {
CheckStackTrace([f3, test]);
return 10 + a + b + c;
}
function g3(a) { return continue f3(2, 3, 4); }
// Callee has arguments adaptor frame.
function f4(a, b, c) {
CheckStackTrace([f4, test]);
return 10 + a;
}
function g4(a) { return continue f4(2); }
function test() {
assertEquals(12, g1());
assertEquals(12, g2());
assertEquals(19, g3());
assertEquals(12, g4());
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Tail call bound function when caller does not have an arguments
// adaptor frame.
(function() {
// Caller and callee have same number of arguments.
function f1(a) {
assertEquals(153, this.a);
CheckStackTrace([f1, test]);
return 10 + a;
}
var b1 = f1.bind({a: 153});
function g1(a) { return continue b1(2); }
// Caller has more arguments than callee.
function f2(a) {
assertEquals(153, this.a);
CheckStackTrace([f2, test]);
return 10 + a;
}
var b2 = f2.bind({a: 153});
function g2(a, b, c) { return continue b2(2); }
// Caller has less arguments than callee.
function f3(a, b, c) {
assertEquals(153, this.a);
CheckStackTrace([f3, test]);
return 10 + a + b + c;
}
var b3 = f3.bind({a: 153});
function g3(a) { return continue b3(2, 3, 4); }
// Callee has arguments adaptor frame.
function f4(a, b, c) {
assertEquals(153, this.a);
CheckStackTrace([f4, test]);
return 10 + a;
}
var b4 = f4.bind({a: 153});
function g4(a) { return continue b4(2); }
function test() {
assertEquals(12, g1(1));
assertEquals(12, g2(1, 2, 3));
assertEquals(19, g3(1));
assertEquals(12, g4(1));
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Tail call bound function when caller has an arguments adaptor frame.
(function() {
// Caller and callee have same number of arguments.
function f1(a) {
assertEquals(153, this.a);
CheckStackTrace([f1, test]);
return 10 + a;
}
var b1 = f1.bind({a: 153});
function g1(a) { return continue b1(2); }
// Caller has more arguments than callee.
function f2(a) {
assertEquals(153, this.a);
CheckStackTrace([f2, test]);
return 10 + a;
}
var b2 = f2.bind({a: 153});
function g2(a, b, c) { return continue b2(2); }
// Caller has less arguments than callee.
function f3(a, b, c) {
assertEquals(153, this.a);
CheckStackTrace([f3, test]);
return 10 + a + b + c;
}
var b3 = f3.bind({a: 153});
function g3(a) { return continue b3(2, 3, 4); }
// Callee has arguments adaptor frame.
function f4(a, b, c) {
assertEquals(153, this.a);
CheckStackTrace([f4, test]);
return 10 + a;
}
var b4 = f4.bind({a: 153});
function g4(a) { return continue b4(2); }
function test() {
assertEquals(12, g1());
assertEquals(12, g2());
assertEquals(19, g3());
assertEquals(12, g4());
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Tail calling via various expressions.
(function() {
function g1(a) {
return continue f([f, g1, test], false) || f([f, test], true);
}
function g2(a) {
return continue f([f, g2, test], true) && f([f, test], true);
}
function g3(a) {
return continue f([f, g3, test], 13), f([f, test], 153);
}
function test() {
assertEquals(true, g1());
assertEquals(true, g2());
assertEquals(153, g3());
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Test tail calls from try-catch constructs.
(function() {
function tc1(a) {
try {
f_153([f_153, tc1, test]);
return f_153([f_153, tc1, test]);
} catch(e) {
f_153([f_153, tc1, test]);
}
}
function tc2(a) {
try {
f_153([f_153, tc2, test]);
throw new Error("boom");
} catch(e) {
f_153([f_153, tc2, test]);
return continue f_153([f_153, test]);
}
}
function tc3(a) {
try {
f_153([f_153, tc3, test]);
throw new Error("boom");
} catch(e) {
f_153([f_153, tc3, test]);
}
f_153([f_153, tc3, test]);
return continue f_153([f_153, test]);
}
function test() {
assertEquals(153, tc1());
assertEquals(153, tc2());
assertEquals(153, tc3());
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Test tail calls from try-finally constructs.
(function() {
function tf1(a) {
try {
f_153([f_153, tf1, test]);
return f_153([f_153, tf1, test]);
} finally {
f_153([f_153, tf1, test]);
}
}
function tf2(a) {
try {
f_153([f_153, tf2, test]);
throw new Error("boom");
} finally {
f_153([f_153, tf2, test]);
return continue f_153([f_153, test]);
}
}
function tf3(a) {
try {
f_153([f_153, tf3, test]);
} finally {
f_153([f_153, tf3, test]);
}
return continue f_153([f_153, test]);
}
function test() {
assertEquals(153, tf1());
assertEquals(153, tf2());
assertEquals(153, tf3());
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Test tail calls from try-catch-finally constructs.
(function() {
function tcf1(a) {
try {
f_153([f_153, tcf1, test]);
return f_153([f_153, tcf1, test]);
} catch(e) {
} finally {
f_153([f_153, tcf1, test]);
}
}
function tcf2(a) {
try {
f_153([f_153, tcf2, test]);
throw new Error("boom");
} catch(e) {
f_153([f_153, tcf2, test]);
return f_153([f_153, tcf2, test]);
} finally {
f_153([f_153, tcf2, test]);
}
}
function tcf3(a) {
try {
f_153([f_153, tcf3, test]);
throw new Error("boom");
} catch(e) {
f_153([f_153, tcf3, test]);
} finally {
f_153([f_153, tcf3, test]);
return continue f_153([f_153, test]);
}
}
function tcf4(a) {
try {
f_153([f_153, tcf4, test]);
throw new Error("boom");
} catch(e) {
f_153([f_153, tcf4, test]);
} finally {
f_153([f_153, tcf4, test]);
}
return continue f_153([f_153, test]);
}
function test() {
assertEquals(153, tcf1());
assertEquals(153, tcf2());
assertEquals(153, tcf3());
assertEquals(153, tcf4());
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Test tail calls from arrow functions.
(function () {
function g1(a) {
return continue (() => { return continue f_153([f_153, test]); })();
}
function g2(a) {
return continue (() => continue f_153([f_153, test]))();
}
function g3(a) {
var closure = () => continue f([f, closure, test], true)
? f_153([f_153, test])
: f_153([f_153, test]);
return continue closure();
}
function test() {
assertEquals(153, g1());
assertEquals(153, g2());
assertEquals(153, g3());
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();