Revision: 21480
Author: [email protected]
Date: Mon May 26 08:07:02 2014 UTC
Log: Make let variables fresh in each iteration of a for-loop.
BUG=v8:2198
LOG=N
TEST=mjsunit/harmony/block-for
[email protected]
Review URL: https://codereview.chromium.org/292743009
http://code.google.com/p/v8/source/detail?r=21480
Modified:
/branches/bleeding_edge/src/parser.cc
/branches/bleeding_edge/src/parser.h
/branches/bleeding_edge/test/mjsunit/harmony/block-for.js
/branches/bleeding_edge/test/mjsunit/harmony/debug-blockscopes.js
=======================================
--- /branches/bleeding_edge/src/parser.cc Thu May 22 15:27:57 2014 UTC
+++ /branches/bleeding_edge/src/parser.cc Mon May 26 08:07:02 2014 UTC
@@ -2835,6 +2835,168 @@
stmt->Initialize(each, subject, body);
}
}
+
+
+Statement* Parser::DesugarLetBindingsInForStatement(
+ Scope* inner_scope, ZoneStringList* names, ForStatement* loop,
+ Statement* init, Expression* cond, Statement* next, Statement* body,
+ bool* ok) {
+ // ES6 13.6.3.4 specifies that on each loop iteration the let variables
are
+ // copied into a new environment. After copying, the "next" statement of
the
+ // loop is executed to update the loop variables. The loop condition is
+ // checked and the loop body is executed.
+ //
+ // We rewrite a for statement of the form
+ //
+ // for (let x = i; cond; next) body
+ //
+ // into
+ //
+ // {
+ // let x = i;
+ // temp_x = x;
+ // flag = 1;
+ // for (;;) {
+ // let x = temp_x;
+ // if (flag == 1) {
+ // flag = 0;
+ // } else {
+ // next;
+ // }
+ // if (cond) {
+ // <empty>
+ // } else {
+ // break;
+ // }
+ // b
+ // temp_x = x;
+ // }
+ // }
+
+ ASSERT(names->length() > 0);
+ Scope* for_scope = scope_;
+ ZoneList<Variable*> temps(names->length(), zone());
+
+ Block* outer_block = factory()->NewBlock(NULL, names->length() + 3,
false,
+ RelocInfo::kNoPosition);
+ outer_block->AddStatement(init, zone());
+
+ Handle<String> temp_name = isolate()->factory()->dot_for_string();
+ Handle<Smi> smi0 = handle(Smi::FromInt(0), isolate());
+ Handle<Smi> smi1 = handle(Smi::FromInt(1), isolate());
+
+
+ // For each let variable x:
+ // make statement: temp_x = x.
+ for (int i = 0; i < names->length(); i++) {
+ VariableProxy* proxy =
+ NewUnresolved(names->at(i), LET, Interface::NewValue());
+ Variable* temp = scope_->DeclarationScope()->NewTemporary(temp_name);
+ VariableProxy* temp_proxy = factory()->NewVariableProxy(temp);
+ Assignment* assignment = factory()->NewAssignment(
+ Token::ASSIGN, temp_proxy, proxy, RelocInfo::kNoPosition);
+ Statement* assignment_statement = factory()->NewExpressionStatement(
+ assignment, RelocInfo::kNoPosition);
+ outer_block->AddStatement(assignment_statement, zone());
+ temps.Add(temp, zone());
+ }
+
+ Variable* flag = scope_->DeclarationScope()->NewTemporary(temp_name);
+ // Make statement: flag = 1.
+ {
+ VariableProxy* flag_proxy = factory()->NewVariableProxy(flag);
+ Expression* const1 = factory()->NewLiteral(smi1,
RelocInfo::kNoPosition);
+ Assignment* assignment = factory()->NewAssignment(
+ Token::ASSIGN, flag_proxy, const1, RelocInfo::kNoPosition);
+ Statement* assignment_statement = factory()->NewExpressionStatement(
+ assignment, RelocInfo::kNoPosition);
+ outer_block->AddStatement(assignment_statement, zone());
+ }
+
+ outer_block->AddStatement(loop, zone());
+ outer_block->set_scope(for_scope);
+ scope_ = inner_scope;
+
+ Block* inner_block = factory()->NewBlock(NULL, 2 * names->length() + 3,
+ false, RelocInfo::kNoPosition);
+ int pos = scanner()->location().beg_pos;
+ ZoneList<Variable*> inner_vars(names->length(), zone());
+
+ // For each let variable x:
+ // make statement: let x = temp_x.
+ for (int i = 0; i < names->length(); i++) {
+ VariableProxy* proxy =
+ NewUnresolved(names->at(i), LET, Interface::NewValue());
+ Declaration* declaration =
+ factory()->NewVariableDeclaration(proxy, LET, scope_, pos);
+ Declare(declaration, true, CHECK_OK);
+ inner_vars.Add(declaration->proxy()->var(), zone());
+ VariableProxy* temp_proxy = factory()->NewVariableProxy(temps.at(i));
+ Assignment* assignment = factory()->NewAssignment(
+ Token::INIT_LET, proxy, temp_proxy, pos);
+ Statement* assignment_statement = factory()->NewExpressionStatement(
+ assignment, pos);
+ proxy->var()->set_initializer_position(pos);
+ inner_block->AddStatement(assignment_statement, zone());
+ }
+
+ // Make statement: if (flag == 1) { flag = 0; } else { next; }.
+ {
+ Expression* compare = NULL;
+ // Make compare expresion: flag == 1.
+ {
+ Expression* const1 = factory()->NewLiteral(smi1,
RelocInfo::kNoPosition);
+ VariableProxy* flag_proxy = factory()->NewVariableProxy(flag);
+ compare = factory()->NewCompareOperation(
+ Token::EQ, flag_proxy, const1, RelocInfo::kNoPosition);
+ }
+ Statement* clear_flag = NULL;
+ // Make statement: flag = 0.
+ {
+ VariableProxy* flag_proxy = factory()->NewVariableProxy(flag);
+ Expression* const0 = factory()->NewLiteral(smi0,
RelocInfo::kNoPosition);
+ Assignment* assignment = factory()->NewAssignment(
+ Token::ASSIGN, flag_proxy, const0, RelocInfo::kNoPosition);
+ clear_flag = factory()->NewExpressionStatement(assignment, pos);
+ }
+ Statement* clear_flag_or_next = factory()->NewIfStatement(
+ compare, clear_flag, next, RelocInfo::kNoPosition);
+ inner_block->AddStatement(clear_flag_or_next, zone());
+ }
+
+
+ // Make statement: if (cond) { } else { break; }.
+ {
+ Statement* empty =
factory()->NewEmptyStatement(RelocInfo::kNoPosition);
+ BreakableStatement* t = LookupBreakTarget(Handle<String>(), CHECK_OK);
+ Statement* stop = factory()->NewBreakStatement(t,
RelocInfo::kNoPosition);
+ Statement* if_not_cond_break = factory()->NewIfStatement(
+ cond, empty, stop, RelocInfo::kNoPosition);
+ inner_block->AddStatement(if_not_cond_break, zone());
+ }
+
+ inner_block->AddStatement(body, zone());
+
+ // For each let variable x:
+ // make statement: temp_x = x;
+ for (int i = 0; i < names->length(); i++) {
+ VariableProxy* temp_proxy = factory()->NewVariableProxy(temps.at(i));
+ int pos = scanner()->location().end_pos;
+ VariableProxy* proxy = factory()->NewVariableProxy(inner_vars.at(i),
pos);
+ Assignment* assignment = factory()->NewAssignment(
+ Token::ASSIGN, temp_proxy, proxy, RelocInfo::kNoPosition);
+ Statement* assignment_statement = factory()->NewExpressionStatement(
+ assignment, RelocInfo::kNoPosition);
+ inner_block->AddStatement(assignment_statement, zone());
+ }
+
+ inner_scope->set_end_position(scanner()->location().end_pos);
+ inner_block->set_scope(inner_scope);
+ scope_ = for_scope;
+
+ loop->Initialize(NULL, NULL, NULL, inner_block);
+ return outer_block;
+}
Statement* Parser::ParseForStatement(ZoneStringList* labels, bool* ok) {
@@ -2843,6 +3005,7 @@
int pos = peek_position();
Statement* init = NULL;
+ ZoneStringList let_bindings(1, zone());
// Create an in-between scope for let-bound iteration variables.
Scope* saved_scope = scope_;
@@ -2894,8 +3057,8 @@
Handle<String> name;
VariableDeclarationProperties decl_props = kHasNoInitializers;
Block* variable_statement =
- ParseVariableDeclarations(kForStatement, &decl_props, NULL, &name,
- CHECK_OK);
+ ParseVariableDeclarations(kForStatement, &decl_props,
&let_bindings,
+ &name, CHECK_OK);
bool accept_IN = !name.is_null() && decl_props != kHasInitializers;
bool accept_OF = decl_props == kHasNoInitializers;
ForEachStatement::VisitMode mode;
@@ -2997,6 +3160,15 @@
// Parsed initializer at this point.
Expect(Token::SEMICOLON, CHECK_OK);
+
+ // If there are let bindings, then condition and the next statement of
the
+ // for loop must be parsed in a new scope.
+ Scope* inner_scope = NULL;
+ if (let_bindings.length() > 0) {
+ inner_scope = NewScope(for_scope, BLOCK_SCOPE);
+ inner_scope->set_start_position(scanner()->location().beg_pos);
+ scope_ = inner_scope;
+ }
Expression* cond = NULL;
if (peek() != Token::SEMICOLON) {
@@ -3012,31 +3184,22 @@
Expect(Token::RPAREN, CHECK_OK);
Statement* body = ParseStatement(NULL, CHECK_OK);
- scope_ = saved_scope;
- for_scope->set_end_position(scanner()->location().end_pos);
- for_scope = for_scope->FinalizeBlockScope();
- if (for_scope != NULL) {
- // Rewrite a for statement of the form
- //
- // for (let x = i; c; n) b
- //
- // into
- //
- // {
- // let x = i;
- // for (; c; n) b
- // }
- ASSERT(init != NULL);
- Block* result = factory()->NewBlock(NULL, 2, false,
RelocInfo::kNoPosition);
- result->AddStatement(init, zone());
- result->AddStatement(loop, zone());
- result->set_scope(for_scope);
- loop->Initialize(NULL, cond, next, body);
- return result;
+
+ Statement* result = NULL;
+ if (let_bindings.length() > 0) {
+ scope_ = for_scope;
+ result = DesugarLetBindingsInForStatement(inner_scope, &let_bindings,
loop,
+ init, cond, next, body,
CHECK_OK);
+ scope_ = saved_scope;
+ for_scope->set_end_position(scanner()->location().end_pos);
} else {
loop->Initialize(init, cond, next, body);
- return loop;
+ result = loop;
+ scope_ = saved_scope;
+ for_scope->set_end_position(scanner()->location().end_pos);
+ for_scope->FinalizeBlockScope();
}
+ return result;
}
=======================================
--- /branches/bleeding_edge/src/parser.h Tue May 20 12:22:04 2014 UTC
+++ /branches/bleeding_edge/src/parser.h Mon May 26 08:07:02 2014 UTC
@@ -718,6 +718,10 @@
Expression* each,
Expression* subject,
Statement* body);
+ Statement* DesugarLetBindingsInForStatement(
+ Scope* inner_scope, ZoneStringList* names, ForStatement* loop,
+ Statement* init, Expression* cond, Statement* next, Statement* body,
+ bool* ok);
FunctionLiteral* ParseFunctionLiteral(
Handle<String> name,
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/block-for.js Thu Nov 24
15:17:04 2011 UTC
+++ /branches/bleeding_edge/test/mjsunit/harmony/block-for.js Mon May 26
08:07:02 2014 UTC
@@ -102,7 +102,7 @@
assertThrows("function foo() { 'use strict'; for (let x = 3, y = 4 in {})
{ } }", SyntaxError);
-// In a normal for statement the iteration variable is not
+// In a normal for statement the iteration variable is
// freshly allocated for each iteration.
function closures1() {
let a = [];
@@ -110,7 +110,7 @@
a.push(function () { return i; });
}
for (let j = 0; j < 5; ++j) {
- assertEquals(5, a[j]());
+ assertEquals(j, a[j]());
}
}
closures1();
@@ -123,13 +123,45 @@
b.push(function () { return j; });
}
for (let k = 0; k < 5; ++k) {
- assertEquals(5, a[k]());
- assertEquals(15, b[k]());
+ assertEquals(k, a[k]());
+ assertEquals(k + 10, b[k]());
}
}
closures2();
+function closure_in_for_init() {
+ let a = [];
+ for (let i = 0, f = function() { return i }; i < 5; ++i) {
+ a.push(f);
+ }
+ for (let k = 0; k < 5; ++k) {
+ assertEquals(0, a[k]());
+ }
+}
+closure_in_for_init();
+
+
+function closure_in_for_cond() {
+ let a = [];
+ for (let i = 0; a.push(function () { return i; }), i < 5; ++i) { }
+ for (let k = 0; k < 5; ++k) {
+ assertEquals(k, a[k]());
+ }
+}
+closure_in_for_next();
+
+
+function closure_in_for_next() {
+ let a = [];
+ for (let i = 0; i < 5; a.push(function () { return i; }), ++i) { }
+ for (let k = 0; k < 5; ++k) {
+ assertEquals(k + 1, a[k]());
+ }
+}
+closure_in_for_next();
+
+
// In a for-in statement the iteration variable is fresh
// for earch iteration.
function closures3(x) {
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/debug-blockscopes.js Wed
Apr 23 08:58:41 2014 UTC
+++ /branches/bleeding_edge/test/mjsunit/harmony/debug-blockscopes.js Mon
May 26 08:07:02 2014 UTC
@@ -412,10 +412,12 @@
listener_delegate = function(exec_state) {
CheckScopeChain([debug.ScopeType.Block,
+ debug.ScopeType.Block,
debug.ScopeType.Local,
debug.ScopeType.Global], exec_state);
CheckScopeContent({x:3}, 0, exec_state);
- CheckScopeContent({}, 1, exec_state);
+ CheckScopeContent({x:3}, 1, exec_state);
+ CheckScopeContent({}, 2, exec_state);
};
for_loop_3();
EndTest();
@@ -433,12 +435,14 @@
listener_delegate = function(exec_state) {
CheckScopeChain([debug.ScopeType.Block,
+ debug.ScopeType.Block,
debug.ScopeType.Block,
debug.ScopeType.Local,
debug.ScopeType.Global], exec_state);
CheckScopeContent({x:5}, 0, exec_state);
CheckScopeContent({x:3}, 1, exec_state);
- CheckScopeContent({}, 2, exec_state);
+ CheckScopeContent({x:3}, 2, exec_state);
+ CheckScopeContent({}, 3, exec_state);
};
for_loop_4();
EndTest();
@@ -455,10 +459,12 @@
listener_delegate = function(exec_state) {
CheckScopeChain([debug.ScopeType.Block,
+ debug.ScopeType.Block,
debug.ScopeType.Local,
debug.ScopeType.Global], exec_state);
CheckScopeContent({x:3,y:5}, 0, exec_state);
- CheckScopeContent({}, 1, exec_state);
+ CheckScopeContent({x:3,y:5}, 1, exec_state);
+ CheckScopeContent({}, 2, exec_state);
};
for_loop_5();
EndTest();
--
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
---
You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.