Revision: 14353
Author:   [email protected]
Date:     Fri Apr 19 07:11:23 2013
Log:      Generator objects can suspend

* src/ast.h:
* src/parser.cc: Differentiate between the different kinds of yields, in
  anticipation of boxing return values.  Parse `return' into `yield' in
  a generator.

* src/runtime.h:
* src/runtime.cc (Runtime_SuspendJSGeneratorObject): New horrible
  runtime function: saves continuation, context, and operands into the
  generator object.

* src/arm/full-codegen-arm.cc (VisitYield):
* src/ia32/full-codegen-ia32.cc (VisitYield):
* src/x64/full-codegen-x64.cc (VisitYield): Arrange to call
  SuspendJSGeneratorObject.  If the call returns the hole, we suspend.
  Otherwise we resume.

BUG=v8:2355
TEST=These codepaths are tested when the generator is first invoked, and so
are covered by mjsunit/harmony/generators-objects.js.

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

Patch from Andy Wingo <[email protected]>.
http://code.google.com/p/v8/source/detail?r=14353

Modified:
 /branches/bleeding_edge/src/arm/full-codegen-arm.cc
 /branches/bleeding_edge/src/ast.h
 /branches/bleeding_edge/src/full-codegen.cc
 /branches/bleeding_edge/src/ia32/full-codegen-ia32.cc
 /branches/bleeding_edge/src/objects-inl.h
 /branches/bleeding_edge/src/objects.h
 /branches/bleeding_edge/src/parser.cc
 /branches/bleeding_edge/src/runtime.cc
 /branches/bleeding_edge/src/runtime.h
 /branches/bleeding_edge/src/x64/full-codegen-x64.cc
 /branches/bleeding_edge/test/mjsunit/harmony/generators-parsing.js

=======================================
--- /branches/bleeding_edge/src/arm/full-codegen-arm.cc Wed Apr 17 05:47:15 2013 +++ /branches/bleeding_edge/src/arm/full-codegen-arm.cc Fri Apr 19 07:11:23 2013
@@ -1920,6 +1920,60 @@
       break;
   }
 }
+
+
+void FullCodeGenerator::VisitYield(Yield* expr) {
+  Comment cmnt(masm_, "[ Yield");
+ // Evaluate yielded value first; the initial iterator definition depends on
+  // this.  It stays on the stack while we update the iterator.
+  VisitForStackValue(expr->expression());
+
+  switch (expr->yield_kind()) {
+    case Yield::INITIAL:
+    case Yield::SUSPEND: {
+      VisitForStackValue(expr->generator_object());
+      __ CallRuntime(Runtime::kSuspendJSGeneratorObject, 1);
+      __ ldr(context_register(),
+             MemOperand(fp, StandardFrameConstants::kContextOffset));
+
+      Label resume;
+      __ CompareRoot(result_register(), Heap::kTheHoleValueRootIndex);
+      __ b(ne, &resume);
+      __ pop(result_register());
+      if (expr->yield_kind() == Yield::SUSPEND) {
+        // TODO(wingo): Box into { value: VALUE, done: false }.
+      }
+      EmitReturnSequence();
+
+      __ bind(&resume);
+      context()->Plug(result_register());
+      break;
+    }
+
+    case Yield::FINAL: {
+      VisitForAccumulatorValue(expr->generator_object());
+ __ mov(r1, Operand(Smi::FromInt(JSGeneratorObject::kGeneratorClosed)));
+      __ str(r1, FieldMemOperand(result_register(),
+                                 JSGeneratorObject::kContinuationOffset));
+      __ pop(result_register());
+      // TODO(wingo): Box into { value: VALUE, done: true }.
+
+      // Exit all nested statements.
+      NestedStatement* current = nesting_stack_;
+      int stack_depth = 0;
+      int context_length = 0;
+      while (current != NULL) {
+        current = current->Exit(&stack_depth, &context_length);
+      }
+      __ Drop(stack_depth);
+      EmitReturnSequence();
+      break;
+    }
+
+    case Yield::DELEGATING:
+      UNIMPLEMENTED();
+  }
+}


 void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) {
=======================================
--- /branches/bleeding_edge/src/ast.h   Mon Apr 15 05:29:44 2013
+++ /branches/bleeding_edge/src/ast.h   Fri Apr 19 07:11:23 2013
@@ -1964,27 +1964,34 @@
  public:
   DECLARE_NODE_TYPE(Yield)

+  enum Kind {
+ INITIAL, // The initial yield that returns the unboxed generator object.
+    SUSPEND,     // A normal yield: { value: EXPRESSION, done: false }
+    DELEGATING,  // A yield*.
+    FINAL        // A return: { value: EXPRESSION, done: true }
+  };
+
   Expression* generator_object() const { return generator_object_; }
   Expression* expression() const { return expression_; }
-  bool is_delegating_yield() const { return is_delegating_yield_; }
+  Kind yield_kind() const { return yield_kind_; }
   virtual int position() const { return pos_; }

  protected:
   Yield(Isolate* isolate,
         Expression* generator_object,
         Expression* expression,
-        bool is_delegating_yield,
+        Kind yield_kind,
         int pos)
       : Expression(isolate),
         generator_object_(generator_object),
         expression_(expression),
-        is_delegating_yield_(is_delegating_yield),
+        yield_kind_(yield_kind),
         pos_(pos) { }

  private:
   Expression* generator_object_;
   Expression* expression_;
-  bool is_delegating_yield_;
+  Kind yield_kind_;
   int pos_;
 };

@@ -2966,10 +2973,10 @@

   Yield* NewYield(Expression *generator_object,
                   Expression* expression,
-                  bool is_delegating_yield,
+                  Yield::Kind yield_kind,
                   int pos) {
     Yield* yield = new(zone_) Yield(
-        isolate_, generator_object, expression, is_delegating_yield, pos);
+        isolate_, generator_object, expression, yield_kind, pos);
     VISIT_AND_RETURN(Yield, yield)
   }

=======================================
--- /branches/bleeding_edge/src/full-codegen.cc Mon Apr 15 05:29:44 2013
+++ /branches/bleeding_edge/src/full-codegen.cc Fri Apr 19 07:11:23 2013
@@ -1546,30 +1546,6 @@
   Comment cmnt(masm_, "[ SharedFunctionInfoLiteral");
   EmitNewClosure(expr->shared_function_info(), false);
 }
-
-
-void FullCodeGenerator::VisitYield(Yield* expr) {
-  if (expr->is_delegating_yield())
-    UNIMPLEMENTED();
-
-  Comment cmnt(masm_, "[ Yield");
-  // TODO(wingo): Actually update the iterator state.
-  VisitForEffect(expr->generator_object());
-  VisitForAccumulatorValue(expr->expression());
-  // TODO(wingo): Assert that the operand stack depth is 0, at least while
-  // general yield expressions are unimplemented.
-
- // TODO(wingo): What follows is as in VisitReturnStatement. Replace it with a
-  // call to a builtin that will resume the generator.
-  NestedStatement* current = nesting_stack_;
-  int stack_depth = 0;
-  int context_length = 0;
-  while (current != NULL) {
-    current = current->Exit(&stack_depth, &context_length);
-  }
-  __ Drop(stack_depth);
-  EmitReturnSequence();
-}


 void FullCodeGenerator::VisitThrow(Throw* expr) {
=======================================
--- /branches/bleeding_edge/src/ia32/full-codegen-ia32.cc Wed Apr 17 05:47:15 2013 +++ /branches/bleeding_edge/src/ia32/full-codegen-ia32.cc Fri Apr 19 07:11:23 2013
@@ -1881,6 +1881,60 @@
       break;
   }
 }
+
+
+void FullCodeGenerator::VisitYield(Yield* expr) {
+  Comment cmnt(masm_, "[ Yield");
+ // Evaluate yielded value first; the initial iterator definition depends on
+  // this.  It stays on the stack while we update the iterator.
+  VisitForStackValue(expr->expression());
+
+  switch (expr->yield_kind()) {
+    case Yield::INITIAL:
+    case Yield::SUSPEND: {
+      VisitForStackValue(expr->generator_object());
+      __ CallRuntime(Runtime::kSuspendJSGeneratorObject, 1);
+      __ mov(context_register(),
+             Operand(ebp, StandardFrameConstants::kContextOffset));
+
+      Label resume;
+      __ CompareRoot(result_register(), Heap::kTheHoleValueRootIndex);
+      __ j(not_equal, &resume);
+      __ pop(result_register());
+      if (expr->yield_kind() == Yield::SUSPEND) {
+        // TODO(wingo): Box into { value: VALUE, done: false }.
+      }
+      EmitReturnSequence();
+
+      __ bind(&resume);
+      context()->Plug(result_register());
+      break;
+    }
+
+    case Yield::FINAL: {
+      VisitForAccumulatorValue(expr->generator_object());
+      __ mov(FieldOperand(result_register(),
+                          JSGeneratorObject::kContinuationOffset),
+             Immediate(Smi::FromInt(JSGeneratorObject::kGeneratorClosed)));
+      __ pop(result_register());
+      // TODO(wingo): Box into { value: VALUE, done: true }.
+
+      // Exit all nested statements.
+      NestedStatement* current = nesting_stack_;
+      int stack_depth = 0;
+      int context_length = 0;
+      while (current != NULL) {
+        current = current->Exit(&stack_depth, &context_length);
+      }
+      __ Drop(stack_depth);
+      EmitReturnSequence();
+      break;
+    }
+
+    case Yield::DELEGATING:
+      UNIMPLEMENTED();
+  }
+}


 void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) {
=======================================
--- /branches/bleeding_edge/src/objects-inl.h   Fri Apr 19 06:26:47 2013
+++ /branches/bleeding_edge/src/objects-inl.h   Fri Apr 19 07:11:23 2013
@@ -5029,7 +5029,7 @@


 ACCESSORS(JSGeneratorObject, function, JSFunction, kFunctionOffset)
-ACCESSORS(JSGeneratorObject, context, Object, kContextOffset)
+ACCESSORS(JSGeneratorObject, context, Context, kContextOffset)
 SMI_ACCESSORS(JSGeneratorObject, continuation, kContinuationOffset)
ACCESSORS(JSGeneratorObject, operand_stack, FixedArray, kOperandStackOffset)

=======================================
--- /branches/bleeding_edge/src/objects.h       Fri Apr 19 01:30:49 2013
+++ /branches/bleeding_edge/src/objects.h       Fri Apr 19 07:11:23 2013
@@ -6297,10 +6297,14 @@
   // [function]: The function corresponding to this generator object.
   DECL_ACCESSORS(function, JSFunction)

-  // [context]: The context of the suspended computation, or undefined.
-  DECL_ACCESSORS(context, Object)
+  // [context]: The context of the suspended computation.
+  DECL_ACCESSORS(context, Context)

   // [continuation]: Offset into code of continuation.
+  //
+  // A positive offset indicates a suspended generator.  The special
+ // kGeneratorExecuting and kGeneratorClosed values indicate that a generator
+  // cannot be resumed.
   inline int continuation();
   inline void set_continuation(int continuation);

@@ -6314,6 +6318,10 @@
   DECLARE_PRINTER(JSGeneratorObject)
   DECLARE_VERIFIER(JSGeneratorObject)

+  // Magic sentinel values for the continuation.
+  static const int kGeneratorExecuting = -1;
+  static const int kGeneratorClosed = 0;
+
   // Layout description.
   static const int kFunctionOffset = JSObject::kHeaderSize;
   static const int kContextOffset = kFunctionOffset + kPointerSize;
=======================================
--- /branches/bleeding_edge/src/parser.cc       Wed Apr 17 08:01:25 2013
+++ /branches/bleeding_edge/src/parser.cc       Fri Apr 19 07:11:23 2013
@@ -2511,16 +2511,24 @@

   Token::Value tok = peek();
   Statement* result;
+  Expression* return_value;
   if (scanner().HasAnyLineTerminatorBeforeNext() ||
       tok == Token::SEMICOLON ||
       tok == Token::RBRACE ||
       tok == Token::EOS) {
-    ExpectSemicolon(CHECK_OK);
-    result = factory()->NewReturnStatement(GetLiteralUndefined());
+    return_value = GetLiteralUndefined();
   } else {
-    Expression* expr = ParseExpression(true, CHECK_OK);
-    ExpectSemicolon(CHECK_OK);
-    result = factory()->NewReturnStatement(expr);
+    return_value = ParseExpression(true, CHECK_OK);
+  }
+  ExpectSemicolon(CHECK_OK);
+  if (is_generator()) {
+    Expression* generator = factory()->NewVariableProxy(
+        current_function_state_->generator_object_variable());
+    Expression* yield = factory()->NewYield(
+        generator, return_value, Yield::FINAL, RelocInfo::kNoPosition);
+    result = factory()->NewExpressionStatement(yield);
+  } else {
+    result = factory()->NewReturnStatement(return_value);
   }

   // An ECMAScript program is considered syntactically incorrect if it
@@ -3100,12 +3108,12 @@
   //   'yield' '*'? AssignmentExpression
   int position = scanner().peek_location().beg_pos;
   Expect(Token::YIELD, CHECK_OK);
-  bool is_yield_star = Check(Token::MUL);
+  Yield::Kind kind =
+      Check(Token::MUL) ? Yield::DELEGATING : Yield::SUSPEND;
   Expression* generator_object = factory()->NewVariableProxy(
       current_function_state_->generator_object_variable());
   Expression* expression = ParseAssignmentExpression(false, CHECK_OK);
-  return factory()->NewYield(generator_object, expression, is_yield_star,
-                             position);
+  return factory()->NewYield(generator_object, expression, kind, position);
 }


@@ -4591,12 +4599,22 @@
         VariableProxy* get_proxy = factory()->NewVariableProxy(
             current_function_state_->generator_object_variable());
         Yield* yield = factory()->NewYield(
-            get_proxy, assignment, false, RelocInfo::kNoPosition);
+            get_proxy, assignment, Yield::INITIAL, RelocInfo::kNoPosition);
         body->Add(factory()->NewExpressionStatement(yield), zone());
       }

       ParseSourceElements(body, Token::RBRACE, false, false, CHECK_OK);

+      if (is_generator) {
+        VariableProxy* get_proxy = factory()->NewVariableProxy(
+            current_function_state_->generator_object_variable());
+        Expression *undefined = factory()->NewLiteral(
+            isolate()->factory()->undefined_value());
+        Yield* yield = factory()->NewYield(
+            get_proxy, undefined, Yield::FINAL, RelocInfo::kNoPosition);
+        body->Add(factory()->NewExpressionStatement(yield), zone());
+      }
+
materialized_literal_count = function_state.materialized_literal_count();
       expected_property_count = function_state.expected_property_count();
       handler_count = function_state.handler_count();
=======================================
--- /branches/bleeding_edge/src/runtime.cc      Fri Apr 19 06:26:47 2013
+++ /branches/bleeding_edge/src/runtime.cc      Fri Apr 19 07:11:23 2013
@@ -2397,6 +2397,7 @@
 RUNTIME_FUNCTION(MaybeObject*, Runtime_CreateJSGeneratorObject) {
   NoHandleAllocation ha(isolate);
   ASSERT(args.length() == 0);
+
   JavaScriptFrameIterator it(isolate);
   JavaScriptFrame* frame = it.frame();
   JSFunction* function = JSFunction::cast(frame->function());
@@ -2411,12 +2412,67 @@
     if (!maybe_generator->To(&generator)) return maybe_generator;
   }
   generator->set_function(function);
-  generator->set_context(isolate->heap()->undefined_value());
+  generator->set_context(Context::cast(frame->context()));
   generator->set_continuation(0);
   generator->set_operand_stack(isolate->heap()->empty_fixed_array());

   return generator;
 }
+
+
+RUNTIME_FUNCTION(MaybeObject*, Runtime_SuspendJSGeneratorObject) {
+  HandleScope scope(isolate);
+  ASSERT(args.length() == 1);
+  CONVERT_ARG_HANDLE_CHECKED(JSGeneratorObject, generator_object, 0);
+
+  JavaScriptFrameIterator stack_iterator(isolate);
+  JavaScriptFrame *frame = stack_iterator.frame();
+  Handle<JSFunction> function(JSFunction::cast(frame->function()));
+  RUNTIME_ASSERT(function->shared()->is_generator());
+
+  intptr_t offset = frame->pc() - function->code()->instruction_start();
+  ASSERT(*function == generator_object->function());
+  ASSERT(offset > 0 && Smi::IsValid(offset));
+  generator_object->set_continuation(offset);
+
+ // Generator functions force context allocation for locals, so Local0 points
+  // to the bottom of the operand stack.  Assume the stack grows down.
+  //
+  // TODO(wingo): Move these magical calculations to frames.h when the
+  // generators implementation has stabilized.
+  intptr_t stack_size_in_bytes =
+      (frame->fp() + JavaScriptFrameConstants::kLocal0Offset) -
+      (frame->sp() - kPointerSize);
+  ASSERT(IsAddressAligned(frame->fp(), kPointerSize));
+  ASSERT(IsAligned(stack_size_in_bytes, kPointerSize));
+  ASSERT(stack_size_in_bytes >= 0);
+  ASSERT(Smi::IsValid(stack_size_in_bytes));
+  unsigned stack_size = stack_size_in_bytes >> kPointerSizeLog2;
+
+ // We expect there to be at least two values on the stack: the return value of + // the yield expression, and the argument to this runtime call. Neither of
+  // those should be saved.
+  ASSERT(stack_size >= 2);
+  stack_size -= 2;
+
+  if (stack_size == 0) {
+    ASSERT_EQ(generator_object->operand_stack(),
+              isolate->heap()->empty_fixed_array());
+    // If there are no operands on the stack, there shouldn't be a handler
+ // active either. Also, the active context will be the same as the function
+    // itself, so there is no need to save the context.
+    ASSERT_EQ(frame->context(), generator_object->context());
+    ASSERT(!frame->HasHandler());
+  } else {
+    generator_object->set_context(Context::cast(frame->context()));
+    // TODO(wingo): Save the operand stack and/or the stack handlers.
+    UNIMPLEMENTED();
+  }
+
+ // The return value is the hole for a suspend return, and anything else for a
+  // resume return.
+  return isolate->heap()->the_hole_value();
+}


 MUST_USE_RESULT static MaybeObject* CharFromCode(Isolate* isolate,
=======================================
--- /branches/bleeding_edge/src/runtime.h       Fri Apr 19 01:30:49 2013
+++ /branches/bleeding_edge/src/runtime.h       Fri Apr 19 07:11:23 2013
@@ -298,6 +298,7 @@
   \
   /* Harmony generators */ \
   F(CreateJSGeneratorObject, 0, 1) \
+  F(SuspendJSGeneratorObject, 1, 1) \
   \
   /* Harmony modules */ \
   F(IsJSModule, 1, 1) \
=======================================
--- /branches/bleeding_edge/src/x64/full-codegen-x64.cc Wed Apr 17 05:47:15 2013 +++ /branches/bleeding_edge/src/x64/full-codegen-x64.cc Fri Apr 19 07:11:23 2013
@@ -1905,6 +1905,60 @@
       break;
   }
 }
+
+
+void FullCodeGenerator::VisitYield(Yield* expr) {
+  Comment cmnt(masm_, "[ Yield");
+ // Evaluate yielded value first; the initial iterator definition depends on
+  // this.  It stays on the stack while we update the iterator.
+  VisitForStackValue(expr->expression());
+
+  switch (expr->yield_kind()) {
+    case Yield::INITIAL:
+    case Yield::SUSPEND: {
+      VisitForStackValue(expr->generator_object());
+      __ CallRuntime(Runtime::kSuspendJSGeneratorObject, 1);
+      __ movq(context_register(),
+              Operand(rbp, StandardFrameConstants::kContextOffset));
+
+      Label resume;
+      __ CompareRoot(result_register(), Heap::kTheHoleValueRootIndex);
+      __ j(not_equal, &resume);
+      __ pop(result_register());
+      if (expr->yield_kind() == Yield::SUSPEND) {
+        // TODO(wingo): Box into { value: VALUE, done: false }.
+      }
+      EmitReturnSequence();
+
+      __ bind(&resume);
+      context()->Plug(result_register());
+      break;
+    }
+
+    case Yield::FINAL: {
+      VisitForAccumulatorValue(expr->generator_object());
+      __ Move(FieldOperand(result_register(),
+                           JSGeneratorObject::kContinuationOffset),
+              Smi::FromInt(JSGeneratorObject::kGeneratorClosed));
+      __ pop(result_register());
+      // TODO(wingo): Box into { value: VALUE, done: true }.
+
+      // Exit all nested statements.
+      NestedStatement* current = nesting_stack_;
+      int stack_depth = 0;
+      int context_length = 0;
+      while (current != NULL) {
+        current = current->Exit(&stack_depth, &context_length);
+      }
+      __ Drop(stack_depth);
+      EmitReturnSequence();
+      break;
+    }
+
+    case Yield::DELEGATING:
+      UNIMPLEMENTED();
+  }
+}


 void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) {
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/generators-parsing.js Fri Apr 5 01:38:40 2013 +++ /branches/bleeding_edge/test/mjsunit/harmony/generators-parsing.js Fri Apr 19 07:11:23 2013
@@ -37,6 +37,12 @@

 // You can have a generator in strict mode.
 function* g() { "use strict"; yield 3; yield 4; }
+
+// Generators can have return statements also, which internally parse to a kind
+// of yield expression.
+function* g() { yield 1; return; }
+function* g() { yield 1; return 2; }
+function* g() { yield 1; return 2; yield "dead"; }

 // Generator expression.
 (function* () { yield 3; });

--
--
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/groups/opt_out.


Reply via email to