[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:
parent
967a04634d
commit
ea2fbb7620
@ -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") \
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
19
test/message/syntactic-tail-call-in-try-catch-finally.js
Normal file
19
test/message/syntactic-tail-call-in-try-catch-finally.js
Normal 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();
|
||||
}
|
||||
}
|
@ -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.
|
21
test/message/syntactic-tail-call-in-try-try-catch-finally.js
Normal file
21
test/message/syntactic-tail-call-in-try-try-catch-finally.js
Normal 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
*%(basename)s:16: SyntaxError: Tail call expression in try block.
|
||||
return continue f();
|
||||
^
|
||||
SyntaxError: Tail call expression in try block.
|
16
test/message/syntactic-tail-call-in-try.js
Normal file
16
test/message/syntactic-tail-call-in-try.js
Normal 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) {
|
||||
}
|
||||
}
|
4
test/message/syntactic-tail-call-in-try.out
Normal file
4
test/message/syntactic-tail-call-in-try.out
Normal 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.
|
121
test/mjsunit/es7/syntactic-tail-call-simple.js
Normal file
121
test/mjsunit/es7/syntactic-tail-call-simple.js
Normal 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));
|
||||
})();
|
423
test/mjsunit/es7/syntactic-tail-call.js
Normal file
423
test/mjsunit/es7/syntactic-tail-call.js
Normal 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();
|
||||
})();
|
Loading…
Reference in New Issue
Block a user