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.

Reply via email to