From 23f39546b90095aa4499b9700eac1ca13d551e81 Mon Sep 17 00:00:00 2001 From: "mstarzinger@chromium.org" Date: Wed, 24 Apr 2013 13:00:16 +0000 Subject: [PATCH] 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 . git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@14415 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/arm/full-codegen-arm.cc | 92 ++++++++++ src/full-codegen.cc | 14 ++ src/full-codegen.h | 5 + src/generator.js | 18 +- src/hydrogen.cc | 11 ++ src/ia32/full-codegen-ia32.cc | 92 ++++++++++ src/messages.js | 4 +- src/objects.h | 3 + src/runtime.cc | 59 ++++++ src/runtime.h | 6 +- src/x64/full-codegen-x64.cc | 93 ++++++++++ test/mjsunit/fuzz-natives-part4.js | 4 + test/mjsunit/harmony/generators-iteration.js | 261 +++++++++++++++++++++++++++ test/mjsunit/harmony/generators-runtime.js | 3 +- test/mjsunit/mjsunit.status | 3 + 15 files changed, 650 insertions(+), 18 deletions(-) create mode 100644 test/mjsunit/harmony/generators-iteration.js diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index 285490b..0647f5e 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -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(); diff --git a/src/full-codegen.cc b/src/full-codegen.cc index b73ceed..81f4c1d 100644 --- a/src/full-codegen.cc +++ b/src/full-codegen.cc @@ -923,6 +923,20 @@ void FullCodeGenerator::EmitInlineRuntimeCall(CallRuntime* expr) { } +void FullCodeGenerator::EmitGeneratorSend(CallRuntime* expr) { + ZoneList* args = expr->arguments(); + ASSERT(args->length() == 2); + EmitGeneratorResume(args->at(0), args->at(1), JSGeneratorObject::SEND); +} + + +void FullCodeGenerator::EmitGeneratorThrow(CallRuntime* expr) { + ZoneList* 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: diff --git a/src/full-codegen.h b/src/full-codegen.h index b9647c2..3734ae5 100644 --- a/src/full-codegen.h +++ b/src/full-codegen.h @@ -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, diff --git a/src/generator.js b/src/generator.js index 481d4d3..5e61091 100644 --- a/src/generator.js +++ b/src/generator.js @@ -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); diff --git a/src/hydrogen.cc b/src/hydrogen.cc index e34c48a..9ca78cc 100644 --- a/src/hydrogen.cc +++ b/src/hydrogen.cc @@ -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 diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index c4f8a4c..6b10c89 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -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(); diff --git a/src/messages.js b/src/messages.js index 67fe3cc..15a39b7 100644 --- a/src/messages.js +++ b/src/messages.js @@ -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"] }; diff --git a/src/objects.h b/src/objects.h index 08d2af0..322841c 100644 --- a/src/objects.h +++ b/src/objects.h @@ -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); }; diff --git a/src/runtime.cc b/src/runtime.cc index cd1af05..bc18251 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -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(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 > argv = HandleVector(NULL, 0); + Handle 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()) { diff --git a/src/runtime.h b/src/runtime.h index 83e1641..a70d8c4 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -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) // ---------------------------------------------------------------------------- diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index 947a57c..2f461a8 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -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(); diff --git a/test/mjsunit/fuzz-natives-part4.js b/test/mjsunit/fuzz-natives-part4.js index e627065..542dcf3 100644 --- a/test/mjsunit/fuzz-natives-part4.js +++ b/test/mjsunit/fuzz-natives-part4.js @@ -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 index 0000000..be795ea --- /dev/null +++ b/test/mjsunit/harmony/generators-iteration.js @@ -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(); diff --git a/test/mjsunit/harmony/generators-runtime.js b/test/mjsunit/harmony/generators-runtime.js index d28140c..0182fc3 100644 --- a/test/mjsunit/harmony/generators-runtime.js +++ b/test/mjsunit/harmony/generators-runtime.js @@ -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); diff --git a/test/mjsunit/mjsunit.status b/test/mjsunit/mjsunit.status index ad26fe6..eb3ed91 100644 --- a/test/mjsunit/mjsunit.status +++ b/test/mjsunit/mjsunit.status @@ -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: -- 2.7.4