Generators can resume

The generator object methods "next", "send", and "throw" now
include some inline assembly to set up a resumed stack frame.  In some
common cases, we can just jump back into the frame to resume it.
Otherwise the resume code calls out to a runtime to fill in the operand
stack, rewind the handlers, and possibly to throw an exception.

BUG=v8:2355
TESTS=mjsunit/harmony/generators-iteration

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

Patch from Andy Wingo <wingo@igalia.com>.

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@14415 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent b08fcc54
......@@ -1976,6 +1976,98 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
}
void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
Expression *value,
JSGeneratorObject::ResumeMode resume_mode) {
// The value stays in r0, and is ultimately read by the resumed generator, as
// if the CallRuntime(Runtime::kSuspendJSGeneratorObject) returned it. r1
// will hold the generator object until the activation has been resumed.
VisitForStackValue(generator);
VisitForAccumulatorValue(value);
__ pop(r1);
// Check generator state.
Label wrong_state, done;
__ ldr(r3, FieldMemOperand(r1, JSGeneratorObject::kContinuationOffset));
STATIC_ASSERT(JSGeneratorObject::kGeneratorExecuting <= 0);
STATIC_ASSERT(JSGeneratorObject::kGeneratorClosed <= 0);
__ cmp(r3, Operand(Smi::FromInt(0)));
__ b(le, &wrong_state);
// Load suspended function and context.
__ ldr(cp, FieldMemOperand(r1, JSGeneratorObject::kContextOffset));
__ ldr(r4, FieldMemOperand(r1, JSGeneratorObject::kFunctionOffset));
// Push holes for arguments to generator function.
__ ldr(r3, FieldMemOperand(r4, JSFunction::kSharedFunctionInfoOffset));
__ ldr(r3,
FieldMemOperand(r3, SharedFunctionInfo::kFormalParameterCountOffset));
__ Move(r2, isolate()->factory()->the_hole_value());
Label push_argument_holes;
__ bind(&push_argument_holes);
__ push(r2);
__ sub(r3, r3, Operand(1));
__ b(vc, &push_argument_holes);
// Enter a new JavaScript frame, and initialize its slots as they were when
// the generator was suspended.
Label push_frame, resume_frame;
__ bind(&push_frame);
__ bl(&resume_frame);
__ jmp(&done);
__ bind(&resume_frame);
__ push(fp); // Caller's frame pointer.
__ mov(fp, sp);
__ push(cp); // Callee's context.
__ push(r4); // Callee's JS Function.
// Load the operand stack size.
__ ldr(r3, FieldMemOperand(r1, JSGeneratorObject::kOperandStackOffset));
__ ldr(r3, FieldMemOperand(r3, FixedArray::kLengthOffset));
__ SmiUntag(r3);
// If we are sending a value and there is no operand stack, we can jump back
// in directly.
if (resume_mode == JSGeneratorObject::SEND) {
Label slow_resume;
__ cmp(r3, Operand(0));
__ b(ne, &slow_resume);
__ ldr(r3, FieldMemOperand(r4, JSFunction::kCodeEntryOffset));
__ ldr(r2, FieldMemOperand(r1, JSGeneratorObject::kContinuationOffset));
__ SmiUntag(r2);
__ add(r3, r3, r2);
__ mov(r2, Operand(Smi::FromInt(JSGeneratorObject::kGeneratorExecuting)));
__ str(r2, FieldMemOperand(r1, JSGeneratorObject::kContinuationOffset));
__ Jump(r3);
__ bind(&slow_resume);
}
// Otherwise, we push holes for the operand stack and call the runtime to fix
// up the stack and the handlers.
Label push_operand_holes, call_resume;
__ bind(&push_operand_holes);
__ sub(r3, r3, Operand(1));
__ b(vs, &call_resume);
__ push(r2);
__ b(&push_operand_holes);
__ bind(&call_resume);
__ push(r1);
__ push(result_register());
__ Push(Smi::FromInt(resume_mode));
__ CallRuntime(Runtime::kResumeJSGeneratorObject, 3);
// Not reached: the runtime call returns elsewhere.
__ stop("not-reached");
// Throw error if we attempt to operate on a running generator.
__ bind(&wrong_state);
__ push(r1);
__ CallRuntime(Runtime::kThrowGeneratorStateError, 1);
__ bind(&done);
context()->Plug(result_register());
}
void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) {
SetSourcePosition(prop->position());
Literal* key = prop->key()->AsLiteral();
......
......@@ -923,6 +923,20 @@ void FullCodeGenerator::EmitInlineRuntimeCall(CallRuntime* expr) {
}
void FullCodeGenerator::EmitGeneratorSend(CallRuntime* expr) {
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 2);
EmitGeneratorResume(args->at(0), args->at(1), JSGeneratorObject::SEND);
}
void FullCodeGenerator::EmitGeneratorThrow(CallRuntime* expr) {
ZoneList<Expression*>* args = expr->arguments();
ASSERT(args->length() == 2);
EmitGeneratorResume(args->at(0), args->at(1), JSGeneratorObject::THROW);
}
void FullCodeGenerator::VisitBinaryOperation(BinaryOperation* expr) {
switch (expr->op()) {
case Token::COMMA:
......
......@@ -486,6 +486,11 @@ class FullCodeGenerator: public AstVisitor {
INLINE_RUNTIME_FUNCTION_LIST(EMIT_INLINE_RUNTIME_CALL)
#undef EMIT_INLINE_RUNTIME_CALL
// Platform-specific code for resuming generators.
void EmitGeneratorResume(Expression *generator,
Expression *value,
JSGeneratorObject::ResumeMode resume_mode);
// Platform-specific code for loading variables.
void EmitLoadGlobalCheckExtensions(Variable* var,
TypeofState typeof_state,
......
......@@ -44,7 +44,7 @@ function GeneratorObjectNext() {
['[Generator].prototype.next', this]);
}
// TODO(wingo): Implement.
return %_GeneratorSend(this, void 0);
}
function GeneratorObjectSend(value) {
......@@ -53,7 +53,7 @@ function GeneratorObjectSend(value) {
['[Generator].prototype.send', this]);
}
// TODO(wingo): Implement.
return %_GeneratorSend(this, value);
}
function GeneratorObjectThrow(exn) {
......@@ -62,16 +62,7 @@ function GeneratorObjectThrow(exn) {
['[Generator].prototype.throw', this]);
}
// TODO(wingo): Implement.
}
function GeneratorObjectClose() {
if (!IS_GENERATOR(this)) {
throw MakeTypeError('incompatible_method_receiver',
['[Generator].prototype.close', this]);
}
// TODO(wingo): Implement.
return %_GeneratorThrow(this, exn);
}
function SetUpGenerators() {
......@@ -81,8 +72,7 @@ function SetUpGenerators() {
DONT_ENUM | DONT_DELETE | READ_ONLY,
["next", GeneratorObjectNext,
"send", GeneratorObjectSend,
"throw", GeneratorObjectThrow,
"close", GeneratorObjectClose]);
"throw", GeneratorObjectThrow]);
%SetProperty(GeneratorObjectPrototype, "constructor",
GeneratorFunctionPrototype, DONT_ENUM | DONT_DELETE | READ_ONLY);
%SetPrototype(GeneratorFunctionPrototype, $Function.prototype);
......
......@@ -11237,6 +11237,17 @@ void HOptimizedGraphBuilder::GenerateFastAsciiArrayJoin(CallRuntime* call) {
}
// Support for generators.
void HOptimizedGraphBuilder::GenerateGeneratorSend(CallRuntime* call) {
return Bailout("inlined runtime function: GeneratorSend");
}
void HOptimizedGraphBuilder::GenerateGeneratorThrow(CallRuntime* call) {
return Bailout("inlined runtime function: GeneratorThrow");
}
#undef CHECK_BAILOUT
#undef CHECK_ALIVE
......
......@@ -1937,6 +1937,98 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
}
void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
Expression *value,
JSGeneratorObject::ResumeMode resume_mode) {
// The value stays in eax, and is ultimately read by the resumed generator, as
// if the CallRuntime(Runtime::kSuspendJSGeneratorObject) returned it. ebx
// will hold the generator object until the activation has been resumed.
VisitForStackValue(generator);
VisitForAccumulatorValue(value);
__ pop(ebx);
// Check generator state.
Label wrong_state, done;
STATIC_ASSERT(JSGeneratorObject::kGeneratorExecuting <= 0);
STATIC_ASSERT(JSGeneratorObject::kGeneratorClosed <= 0);
__ cmp(FieldOperand(ebx, JSGeneratorObject::kContinuationOffset),
Immediate(Smi::FromInt(0)));
__ j(less_equal, &wrong_state);
// Load suspended function and context.
__ mov(esi, FieldOperand(ebx, JSGeneratorObject::kContextOffset));
__ mov(edi, FieldOperand(ebx, JSGeneratorObject::kFunctionOffset));
// Push holes for arguments to generator function.
__ mov(edx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
__ mov(edx,
FieldOperand(edx, SharedFunctionInfo::kFormalParameterCountOffset));
__ mov(ecx, isolate()->factory()->the_hole_value());
Label push_argument_holes;
__ bind(&push_argument_holes);
__ push(ecx);
__ sub(edx, Immediate(1));
__ j(not_carry, &push_argument_holes);
// Enter a new JavaScript frame, and initialize its slots as they were when
// the generator was suspended.
Label push_frame, resume_frame;
__ bind(&push_frame);
__ call(&resume_frame);
__ jmp(&done);
__ bind(&resume_frame);
__ push(ebp); // Caller's frame pointer.
__ mov(ebp, esp);
__ push(esi); // Callee's context.
__ push(edi); // Callee's JS Function.
// Load the operand stack size.
__ mov(edx, FieldOperand(ebx, JSGeneratorObject::kOperandStackOffset));
__ mov(edx, FieldOperand(edx, FixedArray::kLengthOffset));
__ SmiUntag(edx);
// If we are sending a value and there is no operand stack, we can jump back
// in directly.
if (resume_mode == JSGeneratorObject::SEND) {
Label slow_resume;
__ cmp(edx, Immediate(0));
__ j(not_zero, &slow_resume);
__ mov(edx, FieldOperand(edi, JSFunction::kCodeEntryOffset));
__ mov(ecx, FieldOperand(ebx, JSGeneratorObject::kContinuationOffset));
__ SmiUntag(ecx);
__ add(edx, ecx);
__ mov(FieldOperand(ebx, JSGeneratorObject::kContinuationOffset),
Immediate(Smi::FromInt(JSGeneratorObject::kGeneratorExecuting)));
__ jmp(edx);
__ bind(&slow_resume);
}
// Otherwise, we push holes for the operand stack and call the runtime to fix
// up the stack and the handlers.
Label push_operand_holes, call_resume;
__ bind(&push_operand_holes);
__ sub(edx, Immediate(1));
__ j(carry, &call_resume);
__ push(ecx);
__ jmp(&push_operand_holes);
__ bind(&call_resume);
__ push(ebx);
__ push(result_register());
__ Push(Smi::FromInt(resume_mode));
__ CallRuntime(Runtime::kResumeJSGeneratorObject, 3);
// Not reached: the runtime call returns elsewhere.
__ Abort("Generator failed to resume.");
// Throw error if we attempt to operate on a running generator.
__ bind(&wrong_state);
__ push(ebx);
__ CallRuntime(Runtime::kThrowGeneratorStateError, 1);
__ bind(&done);
context()->Plug(result_register());
}
void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) {
SetSourcePosition(prop->position());
Literal* key = prop->key()->AsLiteral();
......
......@@ -31,6 +31,8 @@ var kMessages = {
// Error
cyclic_proto: ["Cyclic __proto__ value"],
code_gen_from_strings: ["%0"],
generator_running: ["Generator is already running"],
generator_finished: ["Generator has already finished"],
// TypeError
unexpected_token: ["Unexpected token ", "%0"],
unexpected_token_number: ["Unexpected number"],
......@@ -158,7 +160,7 @@ var kMessages = {
symbol_to_string: ["Conversion from symbol to string"],
invalid_module_path: ["Module does not export '", "%0", "', or export is not itself a module"],
module_type_error: ["Module '", "%0", "' used improperly"],
module_export_undefined: ["Export '", "%0", "' is not defined in module"],
module_export_undefined: ["Export '", "%0", "' is not defined in module"]
};
......
......@@ -6349,6 +6349,9 @@ class JSGeneratorObject: public JSObject {
static const int kOperandStackOffset = kContinuationOffset + kPointerSize;
static const int kSize = kOperandStackOffset + kPointerSize;
// Resume mode, for use by runtime functions.
enum ResumeMode { SEND, THROW };
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(JSGeneratorObject);
};
......
......@@ -2475,6 +2475,65 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_SuspendJSGeneratorObject) {
}
// Note that this function is the slow path for resuming generators. It is only
// called if the suspended activation had operands on the stack, stack handlers
// needing rewinding, or if the resume should throw an exception. The fast path
// is handled directly in FullCodeGenerator::EmitGeneratorResume(), which is
// inlined into GeneratorNext, GeneratorSend, and GeneratorThrow.
// EmitGeneratorResumeResume is called in any case, as it needs to reconstruct
// the stack frame and make space for arguments and operands.
RUNTIME_FUNCTION(MaybeObject*, Runtime_ResumeJSGeneratorObject) {
HandleScope scope(isolate);
ASSERT(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, generator_object, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
CONVERT_SMI_ARG_CHECKED(resume_mode_int, 2);
JavaScriptFrameIterator stack_iterator(isolate);
JavaScriptFrame *frame = stack_iterator.frame();
ASSERT_EQ(frame->function(), generator_object->function());
STATIC_ASSERT(JSGeneratorObject::kGeneratorExecuting <= 0);
STATIC_ASSERT(JSGeneratorObject::kGeneratorClosed <= 0);
Address pc = generator_object->function()->code()->instruction_start();
int offset = generator_object->continuation();
ASSERT(offset > 0);
frame->set_pc(pc + offset);
generator_object->set_continuation(JSGeneratorObject::kGeneratorExecuting);
if (generator_object->operand_stack()->length() != 0) {
// TODO(wingo): Copy operand stack. Rewind handlers.
UNIMPLEMENTED();
}
JSGeneratorObject::ResumeMode resume_mode =
static_cast<JSGeneratorObject::ResumeMode>(resume_mode_int);
switch (resume_mode) {
case JSGeneratorObject::SEND:
return *value;
case JSGeneratorObject::THROW:
return isolate->Throw(*value);
}
UNREACHABLE();
return isolate->ThrowIllegalOperation();
}
RUNTIME_FUNCTION(MaybeObject*, Runtime_ThrowGeneratorStateError) {
HandleScope scope(isolate);
ASSERT(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, generator, 0);
int continuation = generator->continuation();
const char *message = continuation == JSGeneratorObject::kGeneratorClosed ?
"generator_finished" : "generator_running";
Vector< Handle<Object> > argv = HandleVector<Object>(NULL, 0);
Handle<Object> error = isolate->factory()->NewError(message, argv);
return isolate->Throw(*error);
}
MUST_USE_RESULT static MaybeObject* CharFromCode(Isolate* isolate,
Object* char_code) {
if (char_code->IsNumber()) {
......
......@@ -299,6 +299,8 @@ namespace internal {
/* Harmony generators */ \
F(CreateJSGeneratorObject, 0, 1) \
F(SuspendJSGeneratorObject, 1, 1) \
F(ResumeJSGeneratorObject, 3, 1) \
F(ThrowGeneratorStateError, 1, 1) \
\
/* Harmony modules */ \
F(IsJSModule, 1, 1) \
......@@ -560,7 +562,9 @@ namespace internal {
F(IsRegExpEquivalent, 2, 1) \
F(HasCachedArrayIndex, 1, 1) \
F(GetCachedArrayIndex, 1, 1) \
F(FastAsciiArrayJoin, 2, 1)
F(FastAsciiArrayJoin, 2, 1) \
F(GeneratorSend, 2, 1) \
F(GeneratorThrow, 2, 1)
// ----------------------------------------------------------------------------
......
......@@ -1961,6 +1961,99 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
}
void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
Expression *value,
JSGeneratorObject::ResumeMode resume_mode) {
// The value stays in rax, and is ultimately read by the resumed generator, as
// if the CallRuntime(Runtime::kSuspendJSGeneratorObject) returned it. rbx
// will hold the generator object until the activation has been resumed.
VisitForStackValue(generator);
VisitForAccumulatorValue(value);
__ pop(rbx);
// Check generator state.
Label wrong_state, done;
STATIC_ASSERT(JSGeneratorObject::kGeneratorExecuting <= 0);
STATIC_ASSERT(JSGeneratorObject::kGeneratorClosed <= 0);
__ SmiCompare(FieldOperand(rbx, JSGeneratorObject::kContinuationOffset),
Smi::FromInt(0));
__ j(less_equal, &wrong_state);
// Load suspended function and context.
__ movq(rsi, FieldOperand(rbx, JSGeneratorObject::kContextOffset));
__ movq(rdi, FieldOperand(rbx, JSGeneratorObject::kFunctionOffset));
// Push holes for arguments to generator function.
__ movq(rdx, FieldOperand(rdi, JSFunction::kSharedFunctionInfoOffset));
__ movsxlq(rdx,
FieldOperand(rdx,
SharedFunctionInfo::kFormalParameterCountOffset));
__ LoadRoot(rcx, Heap::kTheHoleValueRootIndex);
Label push_argument_holes;
__ bind(&push_argument_holes);
__ push(rcx);
__ subq(rdx, Immediate(1));
__ j(not_carry, &push_argument_holes);
// Enter a new JavaScript frame, and initialize its slots as they were when
// the generator was suspended.
Label push_frame, resume_frame;
__ bind(&push_frame);
__ call(&resume_frame);
__ jmp(&done);
__ bind(&resume_frame);
__ push(rbp); // Caller's frame pointer.
__ movq(rbp, rsp);
__ push(rsi); // Callee's context.
__ push(rdi); // Callee's JS Function.
// Load the operand stack size.
__ movq(rdx, FieldOperand(rbx, JSGeneratorObject::kOperandStackOffset));
__ movq(rdx, FieldOperand(rdx, FixedArray::kLengthOffset));
__ SmiToInteger32(rdx, rdx);
// If we are sending a value and there is no operand stack, we can jump back
// in directly.
if (resume_mode == JSGeneratorObject::SEND) {
Label slow_resume;
__ cmpq(rdx, Immediate(0));
__ j(not_zero, &slow_resume);
__ movq(rdx, FieldOperand(rdi, JSFunction::kCodeEntryOffset));
__ SmiToInteger64(rcx,
FieldOperand(rbx, JSGeneratorObject::kContinuationOffset));
__ addq(rdx, rcx);
__ Move(FieldOperand(rbx, JSGeneratorObject::kContinuationOffset),
Smi::FromInt(JSGeneratorObject::kGeneratorExecuting));
__ jmp(rdx);
__ bind(&slow_resume);
}
// Otherwise, we push holes for the operand stack and call the runtime to fix
// up the stack and the handlers.
Label push_operand_holes, call_resume;
__ bind(&push_operand_holes);
__ subq(rdx, Immediate(1));
__ j(carry, &call_resume);
__ push(rcx);
__ jmp(&push_operand_holes);
__ bind(&call_resume);
__ push(rbx);
__ push(result_register());
__ Push(Smi::FromInt(resume_mode));
__ CallRuntime(Runtime::kResumeJSGeneratorObject, 3);
// Not reached: the runtime call returns elsewhere.
__ Abort("Generator failed to resume.");
// Throw error if we attempt to operate on a running generator.
__ bind(&wrong_state);
__ push(rbx);
__ CallRuntime(Runtime::kThrowGeneratorStateError, 1);
__ bind(&done);
context()->Plug(result_register());
}
void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) {
SetSourcePosition(prop->position());
Literal* key = prop->key()->AsLiteral();
......
......@@ -200,6 +200,10 @@ var knownProblems = {
"_GetCachedArrayIndex": true,
"_OneByteSeqStringSetChar": true,
"_TwoByteSeqStringSetChar": true,
// Only applicable to generators.
"_GeneratorSend": true,
"_GeneratorThrow": true
};
var currentlyUncallable = {
......
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --harmony-generators --expose-gc
// Test generator iteration.
var GeneratorFunction = (function*(){yield 1;}).__proto__.constructor;
function TestGenerator(g, expected_values_for_next,
send_val, expected_values_for_send) {
function testNext(thunk) {
var iter = thunk();
for (var i = 0; i < expected_values_for_next.length; i++) {
assertEquals(expected_values_for_next[i], iter.next());
}
assertThrows(function() { iter.next(); }, Error);
}
function testSend(thunk) {
var iter = thunk();
for (var i = 0; i < expected_values_for_send.length; i++) {
assertEquals(expected_values_for_send[i], iter.send(send_val));
}
assertThrows(function() { iter.send(send_val); }, Error);
}
function testThrow(thunk) {
for (var i = 0; i < expected_values_for_next.length; i++) {
var iter = thunk();
for (var j = 0; j < i; j++) {
assertEquals(expected_values_for_next[j], iter.next());
}
function Sentinel() {}
assertThrows(function () { iter.throw(new Sentinel); }, Sentinel);
assertThrows(function () { iter.next(); }, Error);
}
}
testNext(g);
testSend(g);
testThrow(g);
if (g instanceof GeneratorFunction) {
testNext(function() { return new g(); });
testSend(function() { return new g(); });
testThrow(function() { return new g(); });
}
}
TestGenerator(function* g1() { },
[undefined],
"foo",
[undefined]);
TestGenerator(function* g2() { yield 1; },
[1, undefined],
"foo",
[1, undefined]);
TestGenerator(function* g3() { yield 1; yield 2; },
[1, 2, undefined],
"foo",
[1, 2, undefined]);
TestGenerator(function* g4() { yield 1; yield 2; return 3; },
[1, 2, 3],
"foo",
[1, 2, 3]);
TestGenerator(function* g5() { return 1; },
[1],
"foo",
[1]);
TestGenerator(function* g6() { var x = yield 1; return x; },
[1, undefined],
"foo",
[1, "foo"]);
TestGenerator(function* g7() { var x = yield 1; yield 2; return x; },
[1, 2, undefined],
"foo",
[1, 2, "foo"]);
TestGenerator(function* g8() { for (var x = 0; x < 4; x++) { yield x; } },
[0, 1, 2, 3, undefined],
"foo",
[0, 1, 2, 3, undefined]);
// Generator with arguments.
TestGenerator(
function g9() {
return (function*(a, b, c, d) {
yield a; yield b; yield c; yield d;
})("fee", "fi", "fo", "fum");
},
["fee", "fi", "fo", "fum", undefined],
"foo",
["fee", "fi", "fo", "fum", undefined]);
// Too few arguments.
TestGenerator(
function g10() {
return (function*(a, b, c, d) {
yield a; yield b; yield c; yield d;
})("fee", "fi");
},
["fee", "fi", undefined, undefined, undefined],
"foo",
["fee", "fi", undefined, undefined, undefined]);
// Too many arguments.
TestGenerator(
function g11() {
return (function*(a, b, c, d) {
yield a; yield b; yield c; yield d;
})("fee", "fi", "fo", "fum", "I smell the blood of an Englishman");
},
["fee", "fi", "fo", "fum", undefined],
"foo",
["fee", "fi", "fo", "fum", undefined]);
// The arguments object.
TestGenerator(
function g12() {
return (function*(a, b, c, d) {
for (var i = 0; i < arguments.length; i++) {
yield arguments[i];
}
})("fee", "fi", "fo", "fum", "I smell the blood of an Englishman");
},
["fee", "fi", "fo", "fum", "I smell the blood of an Englishman",
undefined],
"foo",
["fee", "fi", "fo", "fum", "I smell the blood of an Englishman",
undefined]);
// Access to captured free variables.
TestGenerator(
function g13() {
return (function(a, b, c, d) {
return (function*() {
yield a; yield b; yield c; yield d;
})();
})("fee", "fi", "fo", "fum");
},
["fee", "fi", "fo", "fum", undefined],
"foo",
["fee", "fi", "fo", "fum", undefined]);
// Abusing the arguments object.
TestGenerator(
function g14() {
return (function*(a, b, c, d) {
arguments[0] = "Be he live";
arguments[1] = "or be he dead";
arguments[2] = "I'll grind his bones";
arguments[3] = "to make my bread";
yield a; yield b; yield c; yield d;
})("fee", "fi", "fo", "fum");
},
["Be he live", "or be he dead", "I'll grind his bones", "to make my bread",
undefined],
"foo",
["Be he live", "or be he dead", "I'll grind his bones", "to make my bread",
undefined]);
// Abusing the arguments object: strict mode.
TestGenerator(
function g15() {
return (function*(a, b, c, d) {
"use strict";
arguments[0] = "Be he live";
arguments[1] = "or be he dead";
arguments[2] = "I'll grind his bones";
arguments[3] = "to make my bread";
yield a; yield b; yield c; yield d;
})("fee", "fi", "fo", "fum");
},
["fee", "fi", "fo", "fum", undefined],
"foo",
["fee", "fi", "fo", "fum", undefined]);
// GC.
TestGenerator(function* g16() { yield "baz"; gc(); yield "qux"; },
["baz", "qux", undefined],
"foo",
["baz", "qux", undefined]);
// Receivers.
function TestReceivers() {
TestGenerator(
function g17() {
function* g() { yield this.x; yield this.y; }
var o = { start: g, x: 1, y: 2 };
return o.start();
},
[1, 2, undefined],
"foo",
[1, 2, undefined]);
TestGenerator(
function g18() {
function* g() { yield this.x; yield this.y; }
var iter = new g;
iter.x = 1;
iter.y = 2;
return iter;
},
[1, 2, undefined],
"foo",
[1, 2, undefined]);
}
// TODO(wingo): Enable this test. Currently accessing "this" doesn't work as
// prior to generators, nothing needed to heap-allocate the receiver.
// TestReceivers();
function TestRecursion() {
function TestNextRecursion() {
function* g() { yield iter.next(); }
var iter = g();
return iter.next();
}
function TestSendRecursion() {
function* g() { yield iter.send(42); }
var iter = g();
return iter.next();
}
function TestThrowRecursion() {
function* g() { yield iter.throw(1); }
var iter = g();
return iter.next();
}
assertThrows(TestNextRecursion, Error);
assertThrows(TestSendRecursion, Error);
assertThrows(TestThrowRecursion, Error);
}
TestRecursion();
......@@ -84,8 +84,7 @@ function TestGeneratorObjectPrototype() {
assertSame(GeneratorObjectPrototype,
Object.getPrototypeOf((function*(){yield 1}).prototype));
var expected_property_names = ["next", "send", "throw", "close",
"constructor"];
var expected_property_names = ["next", "send", "throw", "constructor"];
var found_property_names =
Object.getOwnPropertyNames(GeneratorObjectPrototype);
......
......@@ -37,6 +37,9 @@ regress/regress-1119: FAIL
# TODO(wingo): Currently fails in no-snapshot mode, hence disabled for now.
harmony/generators-objects: SKIP
# TODO(wingo): Resuming of iterators currently crashes in ARM.
harmony/generators-iteration: SKIP if ($arch == arm || $arch == android_arm)
# Issue 1719: Slow to collect arrays over several contexts.
regress/regress-524: SKIP
# When that bug is fixed, revert the expectation to:
......
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