Generators can resume
authormstarzinger@chromium.org <mstarzinger@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 24 Apr 2013 13:00:16 +0000 (13:00 +0000)
committermstarzinger@chromium.org <mstarzinger@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 24 Apr 2013 13:00:16 +0000 (13:00 +0000)
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

15 files changed:
src/arm/full-codegen-arm.cc
src/full-codegen.cc
src/full-codegen.h
src/generator.js
src/hydrogen.cc
src/ia32/full-codegen-ia32.cc
src/messages.js
src/objects.h
src/runtime.cc
src/runtime.h
src/x64/full-codegen-x64.cc
test/mjsunit/fuzz-natives-part4.js
test/mjsunit/harmony/generators-iteration.js [new file with mode: 0644]
test/mjsunit/harmony/generators-runtime.js
test/mjsunit/mjsunit.status

index 285490b..0647f5e 100644 (file)
@@ -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();
index b73ceed..81f4c1d 100644 (file)
@@ -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:
index b9647c2..3734ae5 100644 (file)
@@ -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,
index 481d4d3..5e61091 100644 (file)
@@ -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);
index e34c48a..9ca78cc 100644 (file)
@@ -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
 
index c4f8a4c..6b10c89 100644 (file)
@@ -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();
index 67fe3cc..15a39b7 100644 (file)
@@ -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"]
 };
 
 
index 08d2af0..322841c 100644 (file)
@@ -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);
 };
index cd1af05..bc18251 100644 (file)
@@ -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()) {
index 83e1641..a70d8c4 100644 (file)
@@ -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)
 
 
 // ----------------------------------------------------------------------------
index 947a57c..2f461a8 100644 (file)
@@ -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();
index e627065..542dcf3 100644 (file)
@@ -200,6 +200,10 @@ var knownProblems = {
   "_GetCachedArrayIndex": true,
   "_OneByteSeqStringSetChar": true,
   "_TwoByteSeqStringSetChar": true,
+
+  // Only applicable to generators.
+  "_GeneratorSend": true,
+  "_GeneratorThrow": true
 };
 
 var currentlyUncallable = {
diff --git a/test/mjsunit/harmony/generators-iteration.js b/test/mjsunit/harmony/generators-iteration.js
new file mode 100644 (file)
index 0000000..be795ea
--- /dev/null
@@ -0,0 +1,261 @@
+// 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();
index d28140c..0182fc3 100644 (file)
@@ -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);
 
index ad26fe6..eb3ed91 100644 (file)
@@ -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: