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
This commit is contained in:
parent
b08fcc54a0
commit
23f39546b9
@ -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 = {
|
||||
|
261
test/mjsunit/harmony/generators-iteration.js
Normal file
261
test/mjsunit/harmony/generators-iteration.js
Normal file
@ -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();
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user