[es6] Implement for-of iterator finalization

Implements iterator finalisation by desugaring for-of loops with an additional try-finally wrapper. See comment in parser.cc for details.

Also improved some AST printing facilities while there.

@Ross, I had to disable the bytecode generation test for for-of, because it got completely out of hand after this change (the new bytecode has 150+ lines). See the TODO that I assigned to you.

Patch set 1 is WIP patch by Georg (http://crrev.com/1695583003), patch set 2 relative changes.

@Georg, FYI, I changed the following:

- Moved try-finally out of the loop body, for performance, and in order to be able to handle `continue` correctly.
- Fixed scope management in ParseForStatement, which was the cause for the variable allocation failure.
- Fixed pre-existing zone initialisation bug in rewriter, which caused the crashes.
- Enabled all tests, adjusted a few others, added a couple more.

BUG=v8:2214
LOG=Y

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

Cr-Commit-Position: refs/heads/master@{#34111}
This commit is contained in:
rossberg 2016-02-18 02:49:07 -08:00 committed by Commit bot
parent 504796e916
commit cb1bf4af3c
18 changed files with 877 additions and 116 deletions

View File

@ -255,6 +255,7 @@ class AstValue : public ZoneObject {
F(dot_catch, ".catch") \
F(empty, "") \
F(eval, "eval") \
F(function, "function") \
F(get_space, "get ") \
F(let, "let") \
F(native, "native") \

View File

@ -36,10 +36,10 @@ AST_NODE_LIST(DECL_ACCEPT)
#ifdef DEBUG
void AstNode::PrintAst() { PrintAst(Isolate::Current()); }
void AstNode::Print() { Print(Isolate::Current()); }
void AstNode::PrintAst(Isolate* isolate) {
void AstNode::Print(Isolate* isolate) {
AstPrinter::PrintOut(isolate, this);
}

View File

@ -199,8 +199,8 @@ class AstNode: public ZoneObject {
#ifdef DEBUG
void PrettyPrint(Isolate* isolate);
void PrettyPrint();
void PrintAst(Isolate* isolate);
void PrintAst();
void Print(Isolate* isolate);
void Print();
#endif // DEBUG
// Type testing & conversion functions overridden by concrete subclasses.
@ -883,11 +883,13 @@ class ForOfStatement final : public ForEachStatement {
void Initialize(Expression* each,
Expression* subject,
Statement* body,
Variable* iterator,
Expression* assign_iterator,
Expression* next_result,
Expression* result_done,
Expression* assign_each) {
ForEachStatement::Initialize(each, subject, body);
iterator_ = iterator;
assign_iterator_ = assign_iterator;
next_result_ = next_result;
result_done_ = result_done;
@ -898,6 +900,10 @@ class ForOfStatement final : public ForEachStatement {
return subject();
}
Variable* iterator() const {
return iterator_;
}
// iterator = subject[Symbol.iterator]()
Expression* assign_iterator() const {
return assign_iterator_;
@ -932,6 +938,7 @@ class ForOfStatement final : public ForEachStatement {
protected:
ForOfStatement(Zone* zone, ZoneList<const AstRawString*>* labels, int pos)
: ForEachStatement(zone, labels, pos),
iterator_(NULL),
assign_iterator_(NULL),
next_result_(NULL),
result_done_(NULL),
@ -941,6 +948,7 @@ class ForOfStatement final : public ForEachStatement {
private:
int local_id(int n) const { return base_id() + parent_num_ids() + n; }
Variable* iterator_;
Expression* assign_iterator_;
Expression* next_result_;
Expression* result_done_;

View File

@ -1203,6 +1203,14 @@ const char* AstPrinter::PrintProgram(FunctionLiteral* program) {
}
void AstPrinter::PrintOut(Isolate* isolate, AstNode* node) {
AstPrinter printer(isolate);
printer.Init();
printer.Visit(node);
PrintF("%s", printer.Output());
}
void AstPrinter::PrintDeclarations(ZoneList<Declaration*>* declarations) {
if (declarations->length() > 0) {
IndentedScope indent(this, "DECLS");
@ -1390,6 +1398,10 @@ void AstPrinter::VisitForOfStatement(ForOfStatement* node) {
PrintIndentedVisit("FOR", node->each());
PrintIndentedVisit("OF", node->iterable());
PrintIndentedVisit("BODY", node->body());
PrintIndentedVisit("INIT", node->assign_iterator());
PrintIndentedVisit("NEXT", node->next_result());
PrintIndentedVisit("EACH", node->assign_each());
PrintIndentedVisit("DONE", node->result_done());
}
@ -1542,31 +1554,36 @@ void AstPrinter::VisitArrayLiteral(ArrayLiteral* node) {
void AstPrinter::VisitVariableProxy(VariableProxy* node) {
Variable* var = node->var();
EmbeddedVector<char, 128> buf;
int pos =
FormatSlotNode(&buf, node, "VAR PROXY", node->VariableFeedbackSlot());
switch (var->location()) {
case VariableLocation::UNALLOCATED:
break;
case VariableLocation::PARAMETER:
SNPrintF(buf + pos, " parameter[%d]", var->index());
break;
case VariableLocation::LOCAL:
SNPrintF(buf + pos, " local[%d]", var->index());
break;
case VariableLocation::CONTEXT:
SNPrintF(buf + pos, " context[%d]", var->index());
break;
case VariableLocation::GLOBAL:
SNPrintF(buf + pos, " global[%d]", var->index());
break;
case VariableLocation::LOOKUP:
SNPrintF(buf + pos, " lookup");
break;
if (!node->is_resolved()) {
SNPrintF(buf + pos, " unresolved");
PrintLiteralWithModeIndented(buf.start(), nullptr, node->name());
} else {
Variable* var = node->var();
switch (var->location()) {
case VariableLocation::UNALLOCATED:
break;
case VariableLocation::PARAMETER:
SNPrintF(buf + pos, " parameter[%d]", var->index());
break;
case VariableLocation::LOCAL:
SNPrintF(buf + pos, " local[%d]", var->index());
break;
case VariableLocation::CONTEXT:
SNPrintF(buf + pos, " context[%d]", var->index());
break;
case VariableLocation::GLOBAL:
SNPrintF(buf + pos, " global[%d]", var->index());
break;
case VariableLocation::LOOKUP:
SNPrintF(buf + pos, " lookup");
break;
}
PrintLiteralWithModeIndented(buf.start(), var, node->name());
}
PrintLiteralWithModeIndented(buf.start(), var, node->name());
}

View File

@ -104,6 +104,9 @@ class AstPrinter: public PrettyPrinter {
const char* PrintProgram(FunctionLiteral* program);
// Print a node to stdout.
static void PrintOut(Isolate* isolate, AstNode* node);
// Individual nodes
#define DECLARE_VISIT(type) virtual void Visit##type(type* node);
AST_NODE_LIST(DECLARE_VISIT)

View File

@ -535,7 +535,7 @@ Variable* Scope::DeclareLocal(const AstRawString* name, VariableMode mode,
int declaration_group_start) {
DCHECK(!already_resolved());
// This function handles VAR, LET, and CONST modes. DYNAMIC variables are
// introduces during variable allocation, and TEMPORARY variables are
// introduced during variable allocation, and TEMPORARY variables are
// allocated via NewTemporary().
DCHECK(IsDeclaredVariableMode(mode));
++num_var_or_const_;
@ -1646,7 +1646,7 @@ void Scope::AllocateVariablesRecursively(Isolate* isolate) {
}
// If scope is already resolved, we still need to allocate
// variables in inner scopes which might not had been resolved yet.
// variables in inner scopes which might not have been resolved yet.
if (already_resolved()) return;
// The number of slots required for variables.
num_heap_slots_ = Context::MIN_CONTEXT_SLOTS;

View File

@ -2350,6 +2350,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object_observe)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexps)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_unicode_regexps)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_do_expressions)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_iterator_close)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexp_lookbehind)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexp_property)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_function_name)
@ -2975,6 +2976,7 @@ bool Genesis::InstallExperimentalNatives() {
static const char* harmony_regexps_natives[] = {"native harmony-regexp.js",
nullptr};
static const char* harmony_tostring_natives[] = {nullptr};
static const char* harmony_iterator_close_natives[] = {nullptr};
static const char* harmony_sloppy_natives[] = {nullptr};
static const char* harmony_sloppy_function_natives[] = {nullptr};
static const char* harmony_sloppy_let_natives[] = {nullptr};

View File

@ -209,6 +209,7 @@ DEFINE_IMPLICATION(es_staging, move_object_start)
V(harmony_sharedarraybuffer, "harmony sharedarraybuffer") \
V(harmony_simd, "harmony simd") \
V(harmony_do_expressions, "harmony do-expressions") \
V(harmony_iterator_close, "harmony iterator finalization") \
V(harmony_tailcalls, "harmony tail calls") \
V(harmony_object_values_entries, "harmony Object.values / Object.entries") \
V(harmony_object_own_property_descriptors, \

View File

@ -295,6 +295,7 @@ class CallSite {
T(RestrictedFunctionProperties, \
"'caller' and 'arguments' are restricted function properties and cannot " \
"be accessed in this context.") \
T(ReturnMethodNotCallable, "The iterator's 'return' method is not callable") \
T(StaticPrototype, "Classes may not have static property named prototype") \
T(StrictCannotAssign, "Cannot assign to read only '%' in strict mode") \
T(StrictDeleteProperty, "Cannot delete property '%' of %") \

View File

@ -3356,6 +3356,7 @@ void Parser::InitializeForEachStatement(ForEachStatement* stmt,
}
for_of->Initialize(each, subject, body,
iterator,
assign_iterator,
next_result,
result_done,
@ -3633,9 +3634,6 @@ Statement* Parser::DesugarLexicalBindingsInForStatement(
Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
bool* ok) {
// ForStatement ::
// 'for' '(' Expression? ';' Expression? ';' Expression? ')' Statement
int stmt_pos = peek_position();
Statement* init = NULL;
ZoneList<const AstRawString*> lexical_bindings(1, zone());
@ -3773,39 +3771,44 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
}
body_scope->set_end_position(scanner()->location().end_pos);
body_scope = body_scope->FinalizeBlockScope();
body_block->set_scope(body_scope);
body_block->set_scope(body_scope);
// Create a TDZ for any lexically-bound names.
if (IsLexicalVariableMode(parsing_result.descriptor.mode)) {
DCHECK_NULL(init_block);
// Create a TDZ for any lexically-bound names.
if (IsLexicalVariableMode(parsing_result.descriptor.mode)) {
DCHECK_NULL(init_block);
init_block =
factory()->NewBlock(nullptr, 1, false, RelocInfo::kNoPosition);
init_block =
factory()->NewBlock(nullptr, 1, false, RelocInfo::kNoPosition);
for (int i = 0; i < lexical_bindings.length(); ++i) {
// TODO(adamk): This needs to be some sort of special
// INTERNAL variable that's invisible to the debugger
// but visible to everything else.
VariableProxy* tdz_proxy =
NewUnresolved(lexical_bindings[i], LET);
Declaration* tdz_decl = factory()->NewVariableDeclaration(
tdz_proxy, LET, scope_, RelocInfo::kNoPosition);
Variable* tdz_var = Declare(
tdz_decl, DeclarationDescriptor::NORMAL, true, CHECK_OK);
tdz_var->set_initializer_position(position());
}
for (int i = 0; i < lexical_bindings.length(); ++i) {
// TODO(adamk): This needs to be some sort of special
// INTERNAL variable that's invisible to the debugger
// but visible to everything else.
VariableProxy* tdz_proxy =
NewUnresolved(lexical_bindings[i], LET);
Declaration* tdz_decl = factory()->NewVariableDeclaration(
tdz_proxy, LET, scope_, RelocInfo::kNoPosition);
Variable* tdz_var = Declare(
tdz_decl, DeclarationDescriptor::NORMAL, true, CHECK_OK);
tdz_var->set_initializer_position(position());
}
}
Statement* final_loop = loop->IsForOfStatement()
? FinalizeForOfStatement(
loop->AsForOfStatement(), RelocInfo::kNoPosition)
: loop;
for_scope->set_end_position(scanner()->location().end_pos);
for_scope = for_scope->FinalizeBlockScope();
// Parsed for-in loop w/ variable declarations.
if (init_block != nullptr) {
init_block->statements()->Add(loop, zone());
init_block->statements()->Add(final_loop, zone());
init_block->set_scope(for_scope);
return init_block;
} else {
DCHECK_NULL(for_scope);
return loop;
return final_loop;
}
} else {
init = parsing_result.BuildInitializationBlock(
@ -3868,21 +3871,28 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
// expressions in head of the loop should actually have variables
// resolved in the outer scope.
Scope* body_scope = NewScope(for_scope, BLOCK_SCOPE);
BlockState block_state(&scope_, body_scope);
Block* block =
factory()->NewBlock(NULL, 1, false, RelocInfo::kNoPosition);
Statement* body = ParseSubStatement(NULL, CHECK_OK);
block->statements()->Add(body, zone());
InitializeForEachStatement(loop, expression, enumerable, block,
is_destructuring);
body_scope->set_end_position(scanner()->location().end_pos);
body_scope = body_scope->FinalizeBlockScope();
block->set_scope(body_scope);
{
BlockState block_state(&scope_, body_scope);
Block* block =
factory()->NewBlock(NULL, 1, false, RelocInfo::kNoPosition);
Statement* body = ParseSubStatement(NULL, CHECK_OK);
block->statements()->Add(body, zone());
InitializeForEachStatement(loop, expression, enumerable, block,
is_destructuring);
body_scope->set_end_position(scanner()->location().end_pos);
body_scope = body_scope->FinalizeBlockScope();
block->set_scope(body_scope);
}
Statement* final_loop = loop->IsForOfStatement()
? FinalizeForOfStatement(
loop->AsForOfStatement(), RelocInfo::kNoPosition)
: loop;
for_scope->set_end_position(scanner()->location().end_pos);
for_scope = for_scope->FinalizeBlockScope();
DCHECK(for_scope == nullptr);
// Parsed for-in loop.
return loop;
return final_loop;
} else {
init = factory()->NewExpressionStatement(expression, lhs_beg_pos);
@ -5765,7 +5775,7 @@ Expression* Parser::RewriteSpreads(ArrayLiteral* lit) {
ForEachStatement::ITERATE, nullptr, RelocInfo::kNoPosition);
ForOfStatement* for_of = loop->AsForOfStatement();
for_of->Initialize(factory()->NewVariableProxy(each), subject,
append_body, assign_iterator, next_element,
append_body, iterator, assign_iterator, next_element,
element_done, assign_each);
do_block->statements()->Add(for_of, zone());
}
@ -5923,7 +5933,6 @@ Expression* ParserTraits::RewriteYieldStar(
auto scope = parser_->scope_;
auto zone = parser_->zone();
Statement* skip = factory->NewEmptyStatement(nopos);
// Forward definition for break/continue statements.
WhileStatement* loop = factory->NewWhileStatement(nullptr, nopos);
@ -5995,8 +6004,8 @@ Expression* ParserTraits::RewriteYieldStar(
throw_call = factory->NewExpressionStatement(call, nopos);
}
validate_iterator =
factory->NewIfStatement(is_receiver_call, skip, throw_call, nopos);
validate_iterator = factory->NewIfStatement(
is_receiver_call, factory->NewEmptyStatement(nopos), throw_call, nopos);
}
@ -6039,8 +6048,8 @@ Expression* ParserTraits::RewriteYieldStar(
throw_call = factory->NewExpressionStatement(call, nopos);
}
validate_next_output =
factory->NewIfStatement(is_receiver_call, skip, throw_call, nopos);
validate_next_output = factory->NewIfStatement(
is_receiver_call, factory->NewEmptyStatement(nopos), throw_call, nopos);
}
@ -6076,11 +6085,13 @@ Expression* ParserTraits::RewriteYieldStar(
Statement* throw_call = factory->NewExpressionStatement(call, nopos);
Block* then = factory->NewBlock(nullptr, 4+1, false, nopos);
BuildIteratorClose(then->statements(), var_iterator, Nothing<Variable*>(),
Nothing<Variable*>());
Variable* var_tmp = scope->NewTemporary(avfactory->empty_string());
BuildIteratorClose(
then->statements(), var_iterator, factory->NewUndefinedLiteral(nopos),
var_tmp);
then->statements()->Add(throw_call, zone);
check_throw =
factory->NewIfStatement(condition, then, skip, nopos);
check_throw = factory->NewIfStatement(
condition, then, factory->NewEmptyStatement(nopos), nopos);
}
@ -6119,8 +6130,8 @@ Expression* ParserTraits::RewriteYieldStar(
throw_call = factory->NewExpressionStatement(call, nopos);
}
validate_throw_output =
factory->NewIfStatement(is_receiver_call, skip, throw_call, nopos);
validate_throw_output = factory->NewIfStatement(
is_receiver_call, factory->NewEmptyStatement(nopos), throw_call, nopos);
}
@ -6132,7 +6143,8 @@ Expression* ParserTraits::RewriteYieldStar(
factory->NewStringLiteral(avfactory->done_string(), nopos);
Expression* property = factory->NewProperty(output_proxy, literal, nopos);
BreakStatement* break_loop = factory->NewBreakStatement(loop, nopos);
if_done = factory->NewIfStatement(property, break_loop, skip, nopos);
if_done = factory->NewIfStatement(
property, break_loop, factory->NewEmptyStatement(nopos), nopos);
}
@ -6251,8 +6263,8 @@ Expression* ParserTraits::RewriteYieldStar(
case_next->Add(factory->NewBreakStatement(switch_mode, nopos), zone);
auto case_return = new (zone) ZoneList<Statement*>(5, zone);
BuildIteratorClose(
case_return, var_iterator, Just(var_input), Just(var_output));
BuildIteratorClose(case_return, var_iterator,
factory->NewVariableProxy(var_input, nopos), var_output);
case_return->Add(factory->NewBreakStatement(switch_mode, nopos), zone);
auto case_throw = new (zone) ZoneList<Statement*>(5, zone);
@ -6318,17 +6330,25 @@ Expression* ParserTraits::RewriteYieldStar(
void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
Variable* iterator,
Maybe<Variable*> input,
Maybe<Variable*> output) {
Expression* input,
Variable* var_output) {
//
// This function adds four statements to [statements], corresponding to the
// following code:
//
// let iteratorReturn = iterator.return;
// if (IS_NULL_OR_UNDEFINED(iteratorReturn) return input;
// output = %_Call(iteratorReturn, iterator);
// if (!IS_RECEIVER(output)) %ThrowIterResultNotAnObject(output);
//
const int nopos = RelocInfo::kNoPosition;
auto factory = parser_->factory();
auto avfactory = parser_->ast_value_factory();
auto scope = parser_->scope_;
auto zone = parser_->zone();
Statement* skip = factory->NewEmptyStatement(nopos);
// let iteratorReturn = iterator.return;
Variable* var = scope->NewTemporary(avfactory->empty_string());
Variable* var_return = var_output; // Reusing the output variable.
Statement* get_return;
{
Expression* iterator_proxy = factory->NewVariableProxy(iterator);
@ -6336,57 +6356,47 @@ void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
factory->NewStringLiteral(avfactory->return_string(), nopos);
Expression* property =
factory->NewProperty(iterator_proxy, literal, nopos);
Expression* return_proxy = factory->NewVariableProxy(var);
Expression* return_proxy = factory->NewVariableProxy(var_return);
Expression* assignment = factory->NewAssignment(
Token::ASSIGN, return_proxy, property, nopos);
get_return = factory->NewExpressionStatement(assignment, nopos);
}
// if (IS_NULL_OR_UNDEFINED(iteratorReturn) return; OR
// if (IS_NULL_OR_UNDEFINED(iteratorReturn) return input;
Statement* check_return;
{
Expression* condition = factory->NewCompareOperation(
Token::EQ, factory->NewVariableProxy(var),
Token::EQ, factory->NewVariableProxy(var_return),
factory->NewNullLiteral(nopos), nopos);
Expression* value = input.IsJust() ?
static_cast<Expression*>(factory->NewVariableProxy(input.FromJust())) :
factory->NewUndefinedLiteral(nopos);
Statement* return_input = factory->NewReturnStatement(input, nopos);
Statement* return_undefined = factory->NewReturnStatement(value, nopos);
check_return =
factory->NewIfStatement(condition, return_undefined, skip, nopos);
check_return = factory->NewIfStatement(
condition, return_input, factory->NewEmptyStatement(nopos), nopos);
}
// let output = %_Call(iteratorReturn, iterator); OR
// output = %_Call(iteratorReturn, iterator, input);
// output = %_Call(iteratorReturn, iterator);
Statement* call_return;
{
auto args = new (zone) ZoneList<Expression*>(3, zone);
args->Add(factory->NewVariableProxy(var), zone);
args->Add(factory->NewVariableProxy(var_return), zone);
args->Add(factory->NewVariableProxy(iterator), zone);
if (input.IsJust()) {
args->Add(factory->NewVariableProxy(input.FromJust()), zone);
}
Expression* call =
factory->NewCallRuntime(Runtime::kInlineCall, args, nopos);
Expression* output_proxy = factory->NewVariableProxy(
output.IsJust() ? output.FromJust() : var);
Expression* output_proxy = factory->NewVariableProxy(var_output);
Expression* assignment = factory->NewAssignment(
Token::ASSIGN, output_proxy, call, nopos);
call_return = factory->NewExpressionStatement(assignment, nopos);
}
// if (!IS_RECEIVER(output)) %ThrowIterResultNotAnObject(output);
// if (!IS_RECEIVER(output)) %ThrowIteratorResultNotAnObject(output);
Statement* validate_output;
{
Expression* is_receiver_call;
{
auto args = new (zone) ZoneList<Expression*>(1, zone);
args->Add(factory->NewVariableProxy(var), zone);
args->Add(factory->NewVariableProxy(var_output), zone);
is_receiver_call =
factory->NewCallRuntime(Runtime::kInlineIsJSReceiver, args, nopos);
}
@ -6394,14 +6404,14 @@ void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
Statement* throw_call;
{
auto args = new (zone) ZoneList<Expression*>(1, zone);
args->Add(factory->NewVariableProxy(var), zone);
args->Add(factory->NewVariableProxy(var_output), zone);
Expression* call = factory->NewCallRuntime(
Runtime::kThrowIteratorResultNotAnObject, args, nopos);
throw_call = factory->NewExpressionStatement(call, nopos);
}
validate_output =
factory->NewIfStatement(is_receiver_call, skip, throw_call, nopos);
validate_output = factory->NewIfStatement(
is_receiver_call, factory->NewEmptyStatement(nopos), throw_call, nopos);
}
statements->Add(get_return, zone);
@ -6411,5 +6421,355 @@ void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
}
// Runtime encoding of different completion modes.
enum ForOfLoopBodyCompletion { BODY_COMPLETED, BODY_ABORTED, BODY_THREW };
void ParserTraits::BuildIteratorCloseForCompletion(
ZoneList<Statement*>* statements, Variable* iterator,
Variable* completion) {
//
// This function adds two statements to [statements], corresponding to the
// following code:
//
// let iteratorReturn = iterator.return;
// if (!IS_NULL_OR_UNDEFINED(iteratorReturn)) {
// let output;
// if (completion === BODY_THREW) {
// if (!IS_CALLABLE(iteratorReturn)) {
// throw MakeTypeError(kReturnMethodNotCallable);
// }
// try { output = %_Call(iteratorReturn, iterator) } catch (_) { }
// } else {
// output = %_Call(iteratorReturn, iterator);
// }
// if (!IS_RECEIVER(output)) %ThrowIterResultNotAnObject(output);
// }
//
const int nopos = RelocInfo::kNoPosition;
auto factory = parser_->factory();
auto avfactory = parser_->ast_value_factory();
auto scope = parser_->scope_;
auto zone = parser_->zone();
// let output;
Variable* var_output = scope->NewTemporary(avfactory->empty_string());
// let iteratorReturn = iterator.return;
Variable* var_return = var_output; // Reusing the output variable.
Statement* get_return;
{
Expression* iterator_proxy = factory->NewVariableProxy(iterator);
Expression* literal =
factory->NewStringLiteral(avfactory->return_string(), nopos);
Expression* property =
factory->NewProperty(iterator_proxy, literal, nopos);
Expression* return_proxy = factory->NewVariableProxy(var_return);
Expression* assignment = factory->NewAssignment(
Token::ASSIGN, return_proxy, property, nopos);
get_return = factory->NewExpressionStatement(assignment, nopos);
}
// if (!IS_CALLABLE(iteratorReturn)) {
// throw MakeTypeError(kReturnMethodNotCallable);
// }
Statement* check_return_callable;
{
Expression* type_of = factory->NewUnaryOperation(
Token::TYPEOF, factory->NewVariableProxy(var_return), nopos);
Expression* function_literal = factory->NewStringLiteral(
avfactory->function_string(), nopos);
Expression* condition = factory->NewCompareOperation(
Token::EQ_STRICT, type_of, function_literal, nopos);
Expression* call = NewThrowTypeError(
MessageTemplate::kReturnMethodNotCallable,
avfactory->empty_string(), nopos);
Statement* throw_call = factory->NewExpressionStatement(call, nopos);
check_return_callable = factory->NewIfStatement(
condition, factory->NewEmptyStatement(nopos), throw_call, nopos);
}
// output = %_Call(iteratorReturn, iterator);
Statement* call_return;
{
auto args = new (zone) ZoneList<Expression*>(2, zone);
args->Add(factory->NewVariableProxy(var_return), zone);
args->Add(factory->NewVariableProxy(iterator), zone);
Expression* call =
factory->NewCallRuntime(Runtime::kInlineCall, args, nopos);
Expression* output_proxy = factory->NewVariableProxy(var_output);
Expression* assignment = factory->NewAssignment(
Token::ASSIGN, output_proxy, call, nopos);
call_return = factory->NewExpressionStatement(assignment, nopos);
}
// try { output = %_Call(iteratorReturn, iterator) } catch (_) { }
Statement* try_call_return;
{
auto args = new (zone) ZoneList<Expression*>(2, zone);
args->Add(factory->NewVariableProxy(var_return), zone);
args->Add(factory->NewVariableProxy(iterator), zone);
Expression* call =
factory->NewCallRuntime(Runtime::kInlineCall, args, nopos);
Expression* assignment = factory->NewAssignment(
Token::ASSIGN, factory->NewVariableProxy(var_output), call, nopos);
Block* try_block = factory->NewBlock(nullptr, 1, false, nopos);
try_block->statements()->Add(
factory->NewExpressionStatement(assignment, nopos), zone);
Block* catch_block = factory->NewBlock(nullptr, 0, false, nopos);
Scope* catch_scope = NewScope(scope, CATCH_SCOPE);
Variable* catch_variable = catch_scope->DeclareLocal(
avfactory->dot_catch_string(), VAR, kCreatedInitialized,
Variable::NORMAL);
try_call_return = factory->NewTryCatchStatement(
try_block, catch_scope, catch_variable, catch_block, nopos);
}
// if (completion === ABRUPT_THROW) {
// #check_return_callable;
// #try_call_return;
// } else {
// #call_return;
// }
Statement* call_return_carefully;
{
Expression* condition = factory->NewCompareOperation(
Token::EQ_STRICT, factory->NewVariableProxy(completion),
factory->NewSmiLiteral(BODY_THREW, nopos), nopos);
Block* then_block = factory->NewBlock(nullptr, 2, false, nopos);
then_block->statements()->Add(check_return_callable, zone);
then_block->statements()->Add(try_call_return, zone);
call_return_carefully =
factory->NewIfStatement(condition, then_block, call_return, nopos);
}
// if (!IS_RECEIVER(output)) %ThrowIteratorResultNotAnObject(output);
Statement* validate_output;
{
Expression* is_receiver_call;
{
auto args = new (zone) ZoneList<Expression*>(1, zone);
args->Add(factory->NewVariableProxy(var_output), zone);
is_receiver_call =
factory->NewCallRuntime(Runtime::kInlineIsJSReceiver, args, nopos);
}
Statement* throw_call;
{
auto args = new (zone) ZoneList<Expression*>(1, zone);
args->Add(factory->NewVariableProxy(var_output), zone);
Expression* call = factory->NewCallRuntime(
Runtime::kThrowIteratorResultNotAnObject, args, nopos);
throw_call = factory->NewExpressionStatement(call, nopos);
}
validate_output = factory->NewIfStatement(
is_receiver_call, factory->NewEmptyStatement(nopos), throw_call, nopos);
}
// if (!IS_NULL_OR_UNDEFINED(iteratorReturn)) { ... }
Statement* maybe_call_return;
{
Expression* condition = factory->NewCompareOperation(
Token::EQ, factory->NewVariableProxy(var_return),
factory->NewNullLiteral(nopos), nopos);
Block* block = factory->NewBlock(nullptr, 2, false, nopos);
block->statements()->Add(call_return_carefully, zone);
block->statements()->Add(validate_output, zone);
maybe_call_return = factory->NewIfStatement(
condition, factory->NewEmptyStatement(nopos), block, nopos);
}
statements->Add(get_return, zone);
statements->Add(maybe_call_return, zone);
}
Statement* ParserTraits::FinalizeForOfStatement(ForOfStatement* loop, int pos) {
if (!FLAG_harmony_iterator_close) return loop;
//
// This function replaces the loop with the following wrapping:
//
// let completion = BODY_COMPLETED;
// try {
// #loop;
// } catch(e) {
// if (completion === BODY_ABORTED) completion = BODY_THREW;
// throw e;
// } finally {
// if (!(completion === BODY_COMPLETED || IS_UNDEFINED(#iterator))) {
// #BuildIteratorClose(#iterator, completion) // See above.
// }
// }
//
// where the loop's body is wrapped as follows:
//
// {
// {{completion = BODY_ABORTED;}}
// #loop-body
// {{completion = BODY_COMPLETED;}}
// }
const int nopos = RelocInfo::kNoPosition;
auto factory = parser_->factory();
auto avfactory = parser_->ast_value_factory();
auto scope = parser_->scope_;
auto zone = parser_->zone();
// let completion = BODY_COMPLETED;
Variable* var_completion = scope->NewTemporary(avfactory->empty_string());
Statement* initialize_completion;
{
Expression* proxy = factory->NewVariableProxy(var_completion);
Expression* assignment = factory->NewAssignment(
Token::ASSIGN, proxy,
factory->NewSmiLiteral(BODY_COMPLETED, nopos), nopos);
initialize_completion =
factory->NewExpressionStatement(assignment, nopos);
}
// if (completion === BODY_ABORTED) completion = BODY_THREW;
Statement* set_completion_throw;
{
Expression* condition = factory->NewCompareOperation(
Token::EQ_STRICT, factory->NewVariableProxy(var_completion),
factory->NewSmiLiteral(BODY_ABORTED, nopos), nopos);
Expression* proxy = factory->NewVariableProxy(var_completion);
Expression* assignment = factory->NewAssignment(
Token::ASSIGN, proxy, factory->NewSmiLiteral(BODY_THREW, nopos),
nopos);
Statement* statement = factory->NewExpressionStatement(assignment, nopos);
set_completion_throw = factory->NewIfStatement(
condition, statement, factory->NewEmptyStatement(nopos), nopos);
}
// if (!(completion === BODY_COMPLETED || IS_UNDEFINED(#iterator))) {
// #BuildIteratorClose(#iterator, completion)
// }
Block* maybe_close;
{
Expression* condition1 = factory->NewCompareOperation(
Token::EQ_STRICT, factory->NewVariableProxy(var_completion),
factory->NewSmiLiteral(BODY_COMPLETED, nopos), nopos);
Expression* condition2 = factory->NewCompareOperation(
Token::EQ_STRICT, factory->NewVariableProxy(loop->iterator()),
factory->NewUndefinedLiteral(nopos), nopos);
Expression* condition = factory->NewBinaryOperation(
Token::OR, condition1, condition2, nopos);
Block* block = factory->NewBlock(nullptr, 2, false, nopos);
BuildIteratorCloseForCompletion(
block->statements(), loop->iterator(), var_completion);
DCHECK(block->statements()->length() == 2);
maybe_close = factory->NewBlock(nullptr, 1, false, nopos);
maybe_close->statements()->Add(factory->NewIfStatement(
condition, factory->NewEmptyStatement(nopos), block, nopos), zone);
}
// try { #try_block }
// catch(e) {
// #set_completion_throw;
// throw e;
// }
Statement* try_catch;
{
Scope* catch_scope = NewScope(scope, CATCH_SCOPE);
Variable* catch_variable = catch_scope->DeclareLocal(
avfactory->dot_catch_string(), VAR, kCreatedInitialized,
Variable::NORMAL);
Statement* rethrow;
{
Expression* proxy = factory->NewVariableProxy(catch_variable);
rethrow = factory->NewExpressionStatement(
factory->NewThrow(proxy, nopos), nopos);
}
Block* try_block = factory->NewBlock(nullptr, 1, false, nopos);
try_block->statements()->Add(loop, zone);
Block* catch_block = factory->NewBlock(nullptr, 2, false, nopos);
catch_block->statements()->Add(set_completion_throw, zone);
catch_block->statements()->Add(rethrow, zone);
try_catch = factory->NewTryCatchStatement(
try_block, catch_scope, catch_variable, catch_block, nopos);
}
// try { #try_catch } finally { #maybe_close }
Statement* try_finally;
{
Block* try_block = factory->NewBlock(nullptr, 1, false, nopos);
try_block->statements()->Add(try_catch, zone);
try_finally =
factory->NewTryFinallyStatement(try_block, maybe_close, nopos);
}
// #initialize_completion;
// #try_finally;
Statement* final_loop;
{
Block* block = factory->NewBlock(nullptr, 2, false, nopos);
block->statements()->Add(initialize_completion, zone);
block->statements()->Add(try_finally, zone);
final_loop = block;
}
// {{completion = BODY_ABORTED;}}
Statement* set_completion_break;
{
Expression* proxy = factory->NewVariableProxy(var_completion);
Expression* assignment = factory->NewAssignment(
Token::ASSIGN, proxy,
factory->NewSmiLiteral(BODY_ABORTED, nopos), nopos);
Block* block = factory->NewBlock(nullptr, 1, true, nopos);
block->statements()->Add(
factory->NewExpressionStatement(assignment, nopos), zone);
set_completion_break = block;
}
// {{completion = BODY_COMPLETED;}}
Statement* set_completion_normal;
{
Expression* proxy = factory->NewVariableProxy(var_completion);
Expression* assignment = factory->NewAssignment(
Token::ASSIGN, proxy, factory->NewSmiLiteral(BODY_COMPLETED, nopos),
nopos);
Block* block = factory->NewBlock(nullptr, 1, true, nopos);
block->statements()->Add(
factory->NewExpressionStatement(assignment, nopos), zone);
set_completion_normal = block;
}
// { #set_completion_break; #loop-body; #set_completion_normal }
Block* new_body = factory->NewBlock(nullptr, 2, false, nopos);
new_body->statements()->Add(set_completion_break, zone);
new_body->statements()->Add(loop->body(), zone);
new_body->statements()->Add(set_completion_normal, zone);
loop->set_body(new_body);
return final_loop;
}
} // namespace internal
} // namespace v8

View File

@ -461,6 +461,8 @@ class ParserTraits {
MessageTemplate::Template message,
const AstRawString* arg, int pos);
Statement* FinalizeForOfStatement(ForOfStatement* loop, int pos);
// Reporting errors.
void ReportMessageAt(Scanner::Location source_location,
MessageTemplate::Template message,
@ -662,8 +664,12 @@ class ParserTraits {
private:
Parser* parser_;
void BuildIteratorClose(ZoneList<Statement*>* statements, Variable* iterator,
Maybe<Variable*> input, Maybe<Variable*> output);
void BuildIteratorClose(
ZoneList<Statement*>* statements, Variable* iterator,
Expression* input, Variable* output);
void BuildIteratorCloseForCompletion(
ZoneList<Statement*>* statements, Variable* iterator,
Variable* body_threw);
};

View File

@ -31,6 +31,7 @@ class Processor: public AstVisitor {
result_assigned_(false),
replacement_(nullptr),
is_set_(false),
zone_(ast_value_factory->zone()),
scope_(scope),
factory_(ast_value_factory) {
InitializeAstVisitor(parser->stack_limit());

View File

@ -6287,7 +6287,9 @@ TEST(ForIn) {
}
TEST(ForOf) {
// TODO(rmcilroy): Do something about this; new bytecode is too large
// (150+ instructions) to adapt manually.
DISABLED_TEST(ForOf) {
InitializedHandleScope handle_scope;
BytecodeGeneratorHelper helper;
Zone zone;

View File

@ -1061,8 +1061,8 @@
(function TestForInOfTDZ() {
assertThrows("'use strict'; let x = {}; for (let [x, y] of {x});", ReferenceError);
assertThrows("'use strict'; let x = {}; for (let [y, x] of {x});", ReferenceError);
assertThrows("'use strict'; let x = {}; for (let [x, y] of [x]);", ReferenceError);
assertThrows("'use strict'; let x = {}; for (let [y, x] of [x]);", ReferenceError);
assertThrows("'use strict'; let x = {}; for (let [x, y] in {x});", ReferenceError);
assertThrows("'use strict'; let x = {}; for (let [y, x] in {x});", ReferenceError);
}());

View File

@ -238,7 +238,7 @@
assertEquals({value: 1, done: false}, x.next());
assertEquals({value: 42, done: false}, x.next());
assertEquals({value: 43, done: false}, x.return(666));
assertEquals({value: 666, done: false}, x.next());
assertEquals({value: undefined, done: false}, x.next());
assertEquals({value: undefined, done: true}, x.next());
}

View File

@ -0,0 +1,364 @@
// 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-iterator-close
function* g() { yield 42; return 88 };
// Return method is "undefined".
{
g.prototype.return = null;
assertEquals(undefined, (() => {
for (let x of g()) { break; }
})());
assertEquals(undefined, (() => {
for (x of g()) { break; }
})());
assertThrowsEquals(() => {
for (let x of g()) { throw 42; }
}, 42);
assertThrowsEquals(() => {
for (x of g()) { throw 42; }
}, 42);
assertEquals(42, (() => {
for (let x of g()) { return 42; }
})());
assertEquals(42, (() => {
for (x of g()) { return 42; }
})());
assertEquals(42, eval('for (let x of g()) { x; }'));
assertEquals(42, eval('for (let x of g()) { x; }'));
}
// Return method is not callable.
{
g.prototype.return = 666;
assertThrows(() => {
for (let x of g()) { break; }
}, TypeError);
assertThrows(() => {
for (x of g()) { break; }
}, TypeError);
assertThrows(() => {
for (let x of g()) { throw 666; }
}, TypeError);
assertThrows(() => {
for (x of g()) { throw 666; }
}, TypeError);
assertThrows(() => {
for (let x of g()) { return 666; }
}, TypeError);
assertThrows(() => {
for (x of g()) { return 666; }
}, TypeError);
assertEquals(42, eval('for (let x of g()) { x; }'));
assertEquals(42, eval('for (let x of g()) { x; }'));
}
// Return method does not return an object.
{
g.prototype.return = () => 666;
assertThrows(() => {
for (let x of g()) { break; }
}, TypeError);
assertThrows(() => {
for (x of g()) { break; }
}, TypeError);
assertThrows(() => {
for (let x of g()) { throw 666; }
}, TypeError);
assertThrows(() => {
for (x of g()) { throw 666; }
}, TypeError);
assertThrows(() => {
for (let x of g()) { return 666; }
}, TypeError);
assertThrows(() => {
for (x of g()) { return 666; }
}, TypeError);
assertEquals(42, eval('for (let x of g()) { x; }'));
assertEquals(42, eval('for (x of g()) { x; }'));
}
// Return method returns an object.
{
let log = [];
g.prototype.return = (...args) => { log.push(args); return {} };
log = [];
for (let x of g()) { break; }
assertEquals([[]], log);
log = [];
for (x of g()) { break; }
assertEquals([[]], log);
log = [];
assertThrowsEquals(() => {
for (let x of g()) { throw 42; }
}, 42);
assertEquals([[]], log);
log = [];
assertThrowsEquals(() => {
for (x of g()) { throw 42; }
}, 42);
assertEquals([[]], log);
log = [];
assertEquals(42, (() => {
for (let x of g()) { return 42; }
})());
assertEquals([[]], log);
log = [];
assertEquals(42, (() => {
for (x of g()) { return 42; }
})());
assertEquals([[]], log);
log = [];
assertEquals(42, eval('for (let x of g()) { x; }'));
assertEquals([], log);
log = [];
assertEquals(42, eval('for (x of g()) { x; }'));
assertEquals([], log);
}
// Return method throws.
{
let log = [];
g.prototype.return = (...args) => { log.push(args); throw 23 };
log = [];
assertThrowsEquals(() => {
for (let x of g()) { break; }
}, 23);
assertEquals([[]], log);
log = [];
assertThrowsEquals(() => {
for (x of g()) { break; }
}, 23);
assertEquals([[]], log);
log = [];
assertThrowsEquals(() => {
for (let x of g()) { throw 42; }
}, 42);
assertEquals([[]], log);
log = [];
assertThrowsEquals(() => {
for (x of g()) { throw 42; }
}, 42);
assertEquals([[]], log);
log = [];
assertThrowsEquals(() => {
for (let x of g()) { return 42; }
}, 23);
assertEquals([[]], log);
log = [];
assertThrowsEquals(() => {
for (x of g()) { return 42; }
}, 23);
assertEquals([[]], log);
log = [];
assertEquals(42, eval('for (let x of g()) { x; }'));
assertEquals([], log);
log = [];
assertEquals(42, eval('for (x of g()) { x; }'));
assertEquals([], log);
}
// Next method throws.
{
g.prototype.next = () => { throw 666; };
g.prototype.return = () => { assertUnreachable() };
assertThrowsEquals(() => {
for (let x of g()) {}
}, 666);
assertThrowsEquals(() => {
for (x of g()) {}
}, 666);
}
// Nested loops.
{
function* g1() { yield 1; yield 2; throw 3; }
function* g2() { yield -1; yield -2; throw -3; }
assertDoesNotThrow(() => {
for (let x of g1()) {
for (let y of g2()) {
if (y == -2) break;
}
if (x == 2) break;
}
}, -3);
assertThrowsEquals(() => {
for (let x of g1()) {
for (let y of g2()) {
}
}
}, -3);
assertThrowsEquals(() => {
for (let x of g1()) {
for (let y of g2()) {
if (y == -2) break;
}
}
}, 3);
assertDoesNotThrow(() => {
l: for (let x of g1()) {
for (let y of g2()) {
if (y == -2) break l;
}
}
});
assertThrowsEquals(() => {
for (let x of g1()) {
for (let y of g2()) {
throw 4;
}
}
}, 4);
assertThrowsEquals(() => {
for (let x of g1()) {
for (let y of g2()) {
if (y == -2) throw 4;
}
}
}, 4);
let log = [];
g1.prototype.return = () => { log.push(1); throw 5 };
g2.prototype.return = () => { log.push(2); throw -5 };
log = [];
assertThrowsEquals(() => {
for (let x of g1()) {
for (let y of g2()) {
if (y == -2) break;
}
if (x == 2) break;
}
}, -5);
assertEquals([2, 1], log);
log = [];
assertThrowsEquals(() => {
for (let x of g1()) {
for (let y of g2()) {
}
}
}, -3);
assertEquals([1], log);
log = [];
assertThrowsEquals(() => {
for (let x of g1()) {
for (let y of g2()) {
if (y == -2) break;
}
}
}, -5);
assertEquals([2, 1], log);
log = [];
assertThrowsEquals(() => {
l: for (let x of g1()) {
for (let y of g2()) {
if (y == -2) break l;
}
}
}, -5);
assertEquals([2, 1], log);
log = [];
assertThrowsEquals(() => {
for (let x of g1()) {
for (let y of g2()) {
throw 4;
}
}
}, 4);
assertEquals([2, 1], log);
log = [];
assertThrowsEquals(() => {
for (let x of g1()) {
for (let y of g2()) {
if (y == -2) throw 4;
}
}
}, 4);
assertEquals([2, 1], log);
log = [];
assertThrowsEquals(() => {
for (let x of g1()) {
try {
for (let y of g2()) {
}
} catch (_) {}
}
}, 3);
assertEquals([], log);
log = [];
assertThrowsEquals(() => {
for (let x of g1()) {
try {
for (let y of g2()) {
}
} catch (_) {}
if (x == 2) break;
}
}, 5);
assertEquals([1], log);
}

View File

@ -829,6 +829,7 @@
'harmony/regress/regress-4482': [FAIL],
'harmony/reflect': [FAIL],
'harmony/generators': [FAIL],
'harmony/iterator-close': [FAIL],
'regress/regress-572589': [FAIL],
'harmony/reflect-construct': [FAIL],
'es6/promises': [FAIL],

View File

@ -9,9 +9,3 @@
assertThrows("'use strong'; for (let x in []) {}", SyntaxError);
assertThrows("'use strong'; for (const x in []) {}", SyntaxError);
})();
(function ForOfStatement() {
assertTrue(eval("'use strong'; for (x of []) {} true"));
assertTrue(eval("'use strong'; for (let x of []) {} true"));
assertTrue(eval("'use strong'; for (const x of []) {} true"));
})();