Commit cb1bf4af authored by rossberg's avatar rossberg Committed by Commit bot

[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}
parent 504796e9
......@@ -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") \
......
......@@ -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);
}
......
......@@ -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_;
......
......@@ -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,11 +1554,15 @@ 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());
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;
......@@ -1567,6 +1583,7 @@ void AstPrinter::VisitVariableProxy(VariableProxy* node) {
break;
}
PrintLiteralWithModeIndented(buf.start(), var, node->name());
}
}
......
......@@ -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)
......
......@@ -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;
......
......@@ -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};
......
......@@ -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, \
......
......@@ -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 %") \
......
This diff is collapsed.
......@@ -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);
};
......
......@@ -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());
......
......@@ -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;
......
......@@ -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);
}());
......
......@@ -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());
}
......
// 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);
}
......@@ -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],
......
......@@ -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"));
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment