From ea2fbb7620bdd48ef53196b2dbc79554bb48389a Mon Sep 17 00:00:00 2001 From: ishell Date: Tue, 26 Apr 2016 10:30:21 -0700 Subject: [PATCH] [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} --- src/messages.h | 3 + src/parsing/parser-base.h | 72 ++- src/parsing/parser.cc | 75 ++-- src/parsing/parser.h | 2 - src/parsing/preparser.cc | 39 +- ...yntactic-tail-call-in-try-catch-finally.js | 19 + ...ntactic-tail-call-in-try-catch-finally.out | 4 + ...ctic-tail-call-in-try-try-catch-finally.js | 21 + ...tic-tail-call-in-try-try-catch-finally.out | 4 + test/message/syntactic-tail-call-in-try.js | 16 + test/message/syntactic-tail-call-in-try.out | 4 + .../mjsunit/es7/syntactic-tail-call-simple.js | 121 +++++ test/mjsunit/es7/syntactic-tail-call.js | 423 ++++++++++++++++++ 13 files changed, 753 insertions(+), 50 deletions(-) create mode 100644 test/message/syntactic-tail-call-in-try-catch-finally.js create mode 100644 test/message/syntactic-tail-call-in-try-catch-finally.out create mode 100644 test/message/syntactic-tail-call-in-try-try-catch-finally.js create mode 100644 test/message/syntactic-tail-call-in-try-try-catch-finally.out create mode 100644 test/message/syntactic-tail-call-in-try.js create mode 100644 test/message/syntactic-tail-call-in-try.out create mode 100644 test/mjsunit/es7/syntactic-tail-call-simple.js create mode 100644 test/mjsunit/es7/syntactic-tail-call.js diff --git a/src/messages.h b/src/messages.h index 9728ef3596..30451c6a7b 100644 --- a/src/messages.h +++ b/src/messages.h @@ -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") \ diff --git a/src/parsing/parser-base.h b/src/parsing/parser-base.h index bcff25bcd4..212502052a 100644 --- a/src/parsing/parser-base.h +++ b/src/parsing/parser-base.h @@ -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& expressions_in_tail_position() { + List& 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 destructuring_assignments_to_rewrite_; - List expressions_in_tail_position_; + List expressions_in_tail_position_; bool collect_expressions_in_tail_position_; ZoneList 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* 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* 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::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::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::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); } } diff --git a/src/parsing/parser.cc b/src/parsing/parser.cc index 6e05fcbb01..ea5072e27d 100644 --- a/src/parsing/parser.cc +++ b/src/parsing/parser.cc @@ -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* 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* 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 expressions_in_tail_position_in_catch_block; + List 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* Parser::ParseEagerFunctionBody( // ES6 14.6.1 Static Semantics: IsInTailPosition // Mark collected return expressions that are in tail call position. - const List& expressions_in_tail_position = + const List& 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; } diff --git a/src/parsing/parser.h b/src/parsing/parser.h index 3df630a4ef..b429fbef3f 100644 --- a/src/parsing/parser.h +++ b/src/parsing/parser.h @@ -920,8 +920,6 @@ class Parser : public ParserBase { Statement* ParseForStatement(ZoneList* labels, bool* ok); Statement* ParseThrowStatement(bool* ok); Expression* MakeCatchContext(Handle 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 diff --git a/src/parsing/preparser.cc b/src/parsing/preparser.cc index b5d5c28728..f1ef8ab60b 100644 --- a/src/parsing/preparser.cc +++ b/src/parsing/preparser.cc @@ -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 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(); } diff --git a/test/message/syntactic-tail-call-in-try-catch-finally.js b/test/message/syntactic-tail-call-in-try-catch-finally.js new file mode 100644 index 0000000000..f2edf6e183 --- /dev/null +++ b/test/message/syntactic-tail-call-in-try-catch-finally.js @@ -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(); + } +} diff --git a/test/message/syntactic-tail-call-in-try-catch-finally.out b/test/message/syntactic-tail-call-in-try-catch-finally.out new file mode 100644 index 0000000000..ad138aa3f3 --- /dev/null +++ b/test/message/syntactic-tail-call-in-try-catch-finally.out @@ -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. diff --git a/test/message/syntactic-tail-call-in-try-try-catch-finally.js b/test/message/syntactic-tail-call-in-try-try-catch-finally.js new file mode 100644 index 0000000000..202f4cc2bb --- /dev/null +++ b/test/message/syntactic-tail-call-in-try-try-catch-finally.js @@ -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(); + } +} diff --git a/test/message/syntactic-tail-call-in-try-try-catch-finally.out b/test/message/syntactic-tail-call-in-try-try-catch-finally.out new file mode 100644 index 0000000000..0c7b4b12d6 --- /dev/null +++ b/test/message/syntactic-tail-call-in-try-try-catch-finally.out @@ -0,0 +1,4 @@ +*%(basename)s:16: SyntaxError: Tail call expression in try block. + return continue f(); + ^ +SyntaxError: Tail call expression in try block. diff --git a/test/message/syntactic-tail-call-in-try.js b/test/message/syntactic-tail-call-in-try.js new file mode 100644 index 0000000000..97eb4f757a --- /dev/null +++ b/test/message/syntactic-tail-call-in-try.js @@ -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) { + } +} diff --git a/test/message/syntactic-tail-call-in-try.out b/test/message/syntactic-tail-call-in-try.out new file mode 100644 index 0000000000..9de8d52f04 --- /dev/null +++ b/test/message/syntactic-tail-call-in-try.out @@ -0,0 +1,4 @@ +*%(basename)s:13: SyntaxError: Tail call expression in try block. + return continue f(); + ^ +SyntaxError: Tail call expression in try block. diff --git a/test/mjsunit/es7/syntactic-tail-call-simple.js b/test/mjsunit/es7/syntactic-tail-call-simple.js new file mode 100644 index 0000000000..02f235e6d3 --- /dev/null +++ b/test/mjsunit/es7/syntactic-tail-call-simple.js @@ -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)); +})(); diff --git a/test/mjsunit/es7/syntactic-tail-call.js b/test/mjsunit/es7/syntactic-tail-call.js new file mode 100644 index 0000000000..b65fc45059 --- /dev/null +++ b/test/mjsunit/es7/syntactic-tail-call.js @@ -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(); +})();