[generators] Implement Generator.prototype.return.
Note: This is currently only used by yield*, we still need to support it in other places (such as for-of loops). It can be used manually of course. (This CL does not touch the full-codegen implementation of yield* because that code is already dead. The yield* desugaring already supports return and doesn't need to be touched.) BUG=v8:3566 LOG=y Review URL: https://codereview.chromium.org/1639343005 Cr-Commit-Position: refs/heads/master@{#33744}
This commit is contained in:
parent
813f48ff7e
commit
dbd8640813
@ -2564,6 +2564,7 @@ void AstGraphBuilder::VisitCallRuntime(CallRuntime* expr) {
|
||||
|
||||
// TODO(mstarzinger): This bailout is a gigantic hack, the owner is ashamed.
|
||||
if (function->function_id == Runtime::kInlineGeneratorNext ||
|
||||
function->function_id == Runtime::kInlineGeneratorReturn ||
|
||||
function->function_id == Runtime::kInlineGeneratorThrow) {
|
||||
ast_context()->ProduceValue(jsgraph()->TheHoleConstant());
|
||||
return SetStackOverflow();
|
||||
|
@ -1937,8 +1937,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
|
||||
|
||||
__ jmp(&suspend);
|
||||
__ bind(&continuation);
|
||||
// When we arrive here, the stack top is the resume mode and
|
||||
// result_register() holds the input value (the argument given to the
|
||||
// respective resume operation).
|
||||
__ RecordGeneratorContinuation();
|
||||
__ jmp(&resume);
|
||||
__ pop(r1);
|
||||
__ cmp(r1, Operand(Smi::FromInt(JSGeneratorObject::RETURN)));
|
||||
__ b(ne, &resume);
|
||||
__ push(result_register());
|
||||
EmitCreateIteratorResult(true);
|
||||
EmitUnwindBeforeReturn();
|
||||
EmitReturnSequence();
|
||||
|
||||
__ bind(&suspend);
|
||||
VisitForAccumulatorValue(expr->generator_object());
|
||||
@ -2160,6 +2169,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ add(r3, r3, r2);
|
||||
__ mov(r2, Operand(Smi::FromInt(JSGeneratorObject::kGeneratorExecuting)));
|
||||
__ str(r2, FieldMemOperand(r1, JSGeneratorObject::kContinuationOffset));
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
__ Jump(r3);
|
||||
}
|
||||
__ bind(&slow_resume);
|
||||
@ -2174,6 +2184,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ push(r2);
|
||||
__ b(&push_operand_holes);
|
||||
__ bind(&call_resume);
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
DCHECK(!result_register().is(r1));
|
||||
__ Push(r1, result_register());
|
||||
__ Push(Smi::FromInt(resume_mode));
|
||||
|
@ -4343,8 +4343,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
|
||||
// looks at its pos(). Is it possible to do something more efficient here,
|
||||
// perhaps using Adr?
|
||||
__ Bind(&continuation);
|
||||
// When we arrive here, the stack top is the resume mode and
|
||||
// result_register() holds the input value (the argument given to the
|
||||
// respective resume operation).
|
||||
__ RecordGeneratorContinuation();
|
||||
__ B(&resume);
|
||||
__ Pop(x1);
|
||||
__ Cmp(x1, Smi::FromInt(JSGeneratorObject::RETURN));
|
||||
__ B(ne, &resume);
|
||||
__ Push(result_register());
|
||||
EmitCreateIteratorResult(true);
|
||||
EmitUnwindBeforeReturn();
|
||||
EmitReturnSequence();
|
||||
|
||||
__ Bind(&suspend);
|
||||
VisitForAccumulatorValue(expr->generator_object());
|
||||
@ -4571,6 +4580,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ Mov(x12, Smi::FromInt(JSGeneratorObject::kGeneratorExecuting));
|
||||
__ Str(x12, FieldMemOperand(generator_object,
|
||||
JSGeneratorObject::kContinuationOffset));
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
__ Br(x10);
|
||||
|
||||
__ Bind(&slow_resume);
|
||||
@ -4581,6 +4591,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ PushMultipleTimes(the_hole, operand_stack_size);
|
||||
|
||||
__ Mov(x10, Smi::FromInt(resume_mode));
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
__ Push(generator_object, result_register(), x10);
|
||||
__ CallRuntime(Runtime::kResumeJSGeneratorObject);
|
||||
// Not reached: the runtime call returns elsewhere.
|
||||
|
@ -633,6 +633,13 @@ void FullCodeGenerator::EmitGeneratorNext(CallRuntime* expr) {
|
||||
}
|
||||
|
||||
|
||||
void FullCodeGenerator::EmitGeneratorReturn(CallRuntime* expr) {
|
||||
ZoneList<Expression*>* args = expr->arguments();
|
||||
DCHECK(args->length() == 2);
|
||||
EmitGeneratorResume(args->at(0), args->at(1), JSGeneratorObject::RETURN);
|
||||
}
|
||||
|
||||
|
||||
void FullCodeGenerator::EmitGeneratorThrow(CallRuntime* expr) {
|
||||
ZoneList<Expression*>* args = expr->arguments();
|
||||
DCHECK(args->length() == 2);
|
||||
|
@ -496,6 +496,7 @@ class FullCodeGenerator: public AstVisitor {
|
||||
F(GetSuperConstructor) \
|
||||
F(FastOneByteArrayJoin) \
|
||||
F(GeneratorNext) \
|
||||
F(GeneratorReturn) \
|
||||
F(GeneratorThrow) \
|
||||
F(DebugBreakInOptimizedCode) \
|
||||
F(ClassOf) \
|
||||
|
@ -1846,8 +1846,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
|
||||
|
||||
__ jmp(&suspend);
|
||||
__ bind(&continuation);
|
||||
// When we arrive here, the stack top is the resume mode and
|
||||
// result_register() holds the input value (the argument given to the
|
||||
// respective resume operation).
|
||||
__ RecordGeneratorContinuation();
|
||||
__ jmp(&resume);
|
||||
__ pop(ebx);
|
||||
__ cmp(ebx, Immediate(Smi::FromInt(JSGeneratorObject::RETURN)));
|
||||
__ j(not_equal, &resume);
|
||||
__ push(result_register());
|
||||
EmitCreateIteratorResult(true);
|
||||
EmitUnwindBeforeReturn();
|
||||
EmitReturnSequence();
|
||||
|
||||
__ bind(&suspend);
|
||||
VisitForAccumulatorValue(expr->generator_object());
|
||||
@ -2060,6 +2069,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ add(edx, ecx);
|
||||
__ mov(FieldOperand(ebx, JSGeneratorObject::kContinuationOffset),
|
||||
Immediate(Smi::FromInt(JSGeneratorObject::kGeneratorExecuting)));
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
__ jmp(edx);
|
||||
__ bind(&slow_resume);
|
||||
}
|
||||
@ -2073,6 +2083,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ push(ecx);
|
||||
__ jmp(&push_operand_holes);
|
||||
__ bind(&call_resume);
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
__ push(ebx);
|
||||
__ push(result_register());
|
||||
__ Push(Smi::FromInt(resume_mode));
|
||||
|
@ -1935,8 +1935,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
|
||||
|
||||
__ jmp(&suspend);
|
||||
__ bind(&continuation);
|
||||
// When we arrive here, the stack top is the resume mode and
|
||||
// result_register() holds the input value (the argument given to the
|
||||
// respective resume operation).
|
||||
__ RecordGeneratorContinuation();
|
||||
__ jmp(&resume);
|
||||
__ pop(a1);
|
||||
__ Branch(&resume, ne, a1,
|
||||
Operand(Smi::FromInt(JSGeneratorObject::RETURN)));
|
||||
__ push(result_register());
|
||||
EmitCreateIteratorResult(true);
|
||||
EmitUnwindBeforeReturn();
|
||||
EmitReturnSequence();
|
||||
|
||||
__ bind(&suspend);
|
||||
VisitForAccumulatorValue(expr->generator_object());
|
||||
@ -2152,6 +2161,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ Addu(a3, a3, Operand(a2));
|
||||
__ li(a2, Operand(Smi::FromInt(JSGeneratorObject::kGeneratorExecuting)));
|
||||
__ sw(a2, FieldMemOperand(a1, JSGeneratorObject::kContinuationOffset));
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
__ Jump(a3);
|
||||
__ bind(&slow_resume);
|
||||
}
|
||||
@ -2165,6 +2175,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ push(a2);
|
||||
__ Branch(&push_operand_holes);
|
||||
__ bind(&call_resume);
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
DCHECK(!result_register().is(a1));
|
||||
__ Push(a1, result_register());
|
||||
__ Push(Smi::FromInt(resume_mode));
|
||||
|
@ -1936,8 +1936,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
|
||||
|
||||
__ jmp(&suspend);
|
||||
__ bind(&continuation);
|
||||
// When we arrive here, the stack top is the resume mode and
|
||||
// result_register() holds the input value (the argument given to the
|
||||
// respective resume operation).
|
||||
__ RecordGeneratorContinuation();
|
||||
__ jmp(&resume);
|
||||
__ pop(a1);
|
||||
__ Branch(&resume, ne, a1,
|
||||
Operand(Smi::FromInt(JSGeneratorObject::RETURN)));
|
||||
__ push(result_register());
|
||||
EmitCreateIteratorResult(true);
|
||||
EmitUnwindBeforeReturn();
|
||||
EmitReturnSequence();
|
||||
|
||||
__ bind(&suspend);
|
||||
VisitForAccumulatorValue(expr->generator_object());
|
||||
@ -2154,6 +2163,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ Daddu(a3, a3, Operand(a2));
|
||||
__ li(a2, Operand(Smi::FromInt(JSGeneratorObject::kGeneratorExecuting)));
|
||||
__ sd(a2, FieldMemOperand(a1, JSGeneratorObject::kContinuationOffset));
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
__ Jump(a3);
|
||||
__ bind(&slow_resume);
|
||||
}
|
||||
@ -2167,6 +2177,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ push(a2);
|
||||
__ Branch(&push_operand_holes);
|
||||
__ bind(&call_resume);
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
DCHECK(!result_register().is(a1));
|
||||
__ Push(a1, result_register());
|
||||
__ Push(Smi::FromInt(resume_mode));
|
||||
|
@ -1868,8 +1868,17 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
|
||||
|
||||
__ jmp(&suspend);
|
||||
__ bind(&continuation);
|
||||
// When we arrive here, the stack top is the resume mode and
|
||||
// result_register() holds the input value (the argument given to the
|
||||
// respective resume operation).
|
||||
__ RecordGeneratorContinuation();
|
||||
__ jmp(&resume);
|
||||
__ Pop(rbx);
|
||||
__ SmiCompare(rbx, Smi::FromInt(JSGeneratorObject::RETURN));
|
||||
__ j(not_equal, &resume);
|
||||
__ Push(result_register());
|
||||
EmitCreateIteratorResult(true);
|
||||
EmitUnwindBeforeReturn();
|
||||
EmitReturnSequence();
|
||||
|
||||
__ bind(&suspend);
|
||||
VisitForAccumulatorValue(expr->generator_object());
|
||||
@ -2017,8 +2026,8 @@ void FullCodeGenerator::VisitYield(Yield* expr) {
|
||||
}
|
||||
|
||||
|
||||
void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
Expression *value,
|
||||
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 CallRuntime(Runtime::kSuspendJSGeneratorObject) returned it. Or it
|
||||
@ -2083,6 +2092,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ addp(rdx, rcx);
|
||||
__ Move(FieldOperand(rbx, JSGeneratorObject::kContinuationOffset),
|
||||
Smi::FromInt(JSGeneratorObject::kGeneratorExecuting));
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
__ jmp(rdx);
|
||||
__ bind(&slow_resume);
|
||||
}
|
||||
@ -2096,6 +2106,7 @@ void FullCodeGenerator::EmitGeneratorResume(Expression *generator,
|
||||
__ Push(rcx);
|
||||
__ jmp(&push_operand_holes);
|
||||
__ bind(&call_resume);
|
||||
__ Push(Smi::FromInt(resume_mode)); // Consumed in continuation.
|
||||
__ Push(rbx);
|
||||
__ Push(result_register());
|
||||
__ Push(Smi::FromInt(resume_mode));
|
||||
|
@ -39,7 +39,28 @@ function GeneratorObjectNext(value) {
|
||||
return %_GeneratorNext(this, value);
|
||||
} else if (continuation == 0) {
|
||||
// Generator is already closed.
|
||||
return { value: void 0, done: true };
|
||||
return %_CreateIterResultObject(UNDEFINED, true);
|
||||
} else {
|
||||
// Generator is running.
|
||||
throw MakeTypeError(kGeneratorRunning);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function GeneratorObjectReturn(value) {
|
||||
if (!IS_GENERATOR(this)) {
|
||||
throw MakeTypeError(kIncompatibleMethodReceiver,
|
||||
'[Generator].prototype.return', this);
|
||||
}
|
||||
|
||||
var continuation = %GeneratorGetContinuation(this);
|
||||
if (continuation > 0) {
|
||||
// Generator is suspended.
|
||||
DEBUG_PREPARE_STEP_IN_IF_STEPPING(this);
|
||||
return %_GeneratorReturn(this, value);
|
||||
} else if (continuation == 0) {
|
||||
// Generator is already closed.
|
||||
return %_CreateIterResultObject(value, true);
|
||||
} else {
|
||||
// Generator is running.
|
||||
throw MakeTypeError(kGeneratorRunning);
|
||||
@ -56,6 +77,7 @@ function GeneratorObjectThrow(exn) {
|
||||
var continuation = %GeneratorGetContinuation(this);
|
||||
if (continuation > 0) {
|
||||
// Generator is suspended.
|
||||
DEBUG_PREPARE_STEP_IN_IF_STEPPING(this);
|
||||
return %_GeneratorThrow(this, exn);
|
||||
} else if (continuation == 0) {
|
||||
// Generator is already closed.
|
||||
@ -68,9 +90,11 @@ function GeneratorObjectThrow(exn) {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Both Runtime_GeneratorNext and Runtime_GeneratorThrow are supported by
|
||||
// neither Crankshaft nor TurboFan, disable optimization of wrappers here.
|
||||
// None of the three resume operations (Runtime_GeneratorNext,
|
||||
// Runtime_GeneratorReturn, Runtime_GeneratorThrow) is supported by
|
||||
// Crankshaft or TurboFan. Disable optimization of wrappers here.
|
||||
%NeverOptimizeFunction(GeneratorObjectNext);
|
||||
%NeverOptimizeFunction(GeneratorObjectReturn);
|
||||
%NeverOptimizeFunction(GeneratorObjectThrow);
|
||||
|
||||
// Set up non-enumerable functions on the generator prototype object.
|
||||
@ -78,6 +102,7 @@ var GeneratorObjectPrototype = GeneratorFunctionPrototype.prototype;
|
||||
utils.InstallFunctions(GeneratorObjectPrototype,
|
||||
DONT_ENUM,
|
||||
["next", GeneratorObjectNext,
|
||||
"return", GeneratorObjectReturn,
|
||||
"throw", GeneratorObjectThrow]);
|
||||
|
||||
%AddNamedProperty(GeneratorObjectPrototype, "constructor",
|
||||
|
@ -7271,7 +7271,7 @@ class JSGeneratorObject: public JSObject {
|
||||
static const int kSize = kOperandStackOffset + kPointerSize;
|
||||
|
||||
// Resume mode, for use by runtime functions.
|
||||
enum ResumeMode { NEXT, THROW };
|
||||
enum ResumeMode { NEXT, RETURN, THROW };
|
||||
|
||||
private:
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(JSGeneratorObject);
|
||||
|
@ -5869,9 +5869,6 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
|
||||
Statement* skip = factory->NewEmptyStatement(nopos);
|
||||
|
||||
enum { kNext, kReturn, kThrow };
|
||||
// TODO(neis): Use JSGenerator::ResumeMode once extended with RETURN.
|
||||
|
||||
// Forward definition for break/continue statements.
|
||||
WhileStatement* loop = factory->NewWhileStatement(nullptr, nopos);
|
||||
|
||||
@ -5892,7 +5889,7 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
Statement* initialize_mode;
|
||||
{
|
||||
Expression* mode_proxy = factory->NewVariableProxy(var_mode);
|
||||
Expression* knext = factory->NewSmiLiteral(kNext, nopos);
|
||||
Expression* knext = factory->NewSmiLiteral(JSGeneratorObject::NEXT, nopos);
|
||||
Expression* assignment =
|
||||
factory->NewAssignment(Token::ASSIGN, mode_proxy, knext, nopos);
|
||||
initialize_mode = factory->NewExpressionStatement(assignment, nopos);
|
||||
@ -6087,7 +6084,8 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
Statement* set_mode_return;
|
||||
{
|
||||
Expression* mode_proxy = factory->NewVariableProxy(var_mode);
|
||||
Expression* kreturn = factory->NewSmiLiteral(kReturn, nopos);
|
||||
Expression* kreturn =
|
||||
factory->NewSmiLiteral(JSGeneratorObject::RETURN, nopos);
|
||||
Expression* assignment =
|
||||
factory->NewAssignment(Token::ASSIGN, mode_proxy, kreturn, nopos);
|
||||
set_mode_return = factory->NewExpressionStatement(assignment, nopos);
|
||||
@ -6108,7 +6106,7 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
Statement* set_mode_next;
|
||||
{
|
||||
Expression* mode_proxy = factory->NewVariableProxy(var_mode);
|
||||
Expression* knext = factory->NewSmiLiteral(kNext, nopos);
|
||||
Expression* knext = factory->NewSmiLiteral(JSGeneratorObject::NEXT, nopos);
|
||||
Expression* assignment =
|
||||
factory->NewAssignment(Token::ASSIGN, mode_proxy, knext, nopos);
|
||||
set_mode_next = factory->NewExpressionStatement(assignment, nopos);
|
||||
@ -6119,7 +6117,8 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
Statement* set_mode_throw;
|
||||
{
|
||||
Expression* mode_proxy = factory->NewVariableProxy(var_mode);
|
||||
Expression* kthrow = factory->NewSmiLiteral(kThrow, nopos);
|
||||
Expression* kthrow =
|
||||
factory->NewSmiLiteral(JSGeneratorObject::THROW, nopos);
|
||||
Expression* assignment =
|
||||
factory->NewAssignment(Token::ASSIGN, mode_proxy, kthrow, nopos);
|
||||
set_mode_throw = factory->NewExpressionStatement(assignment, nopos);
|
||||
@ -6208,9 +6207,11 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
case_throw->Add(factory->NewBreakStatement(switch_mode, nopos), zone);
|
||||
|
||||
auto cases = new (zone) ZoneList<CaseClause*>(3, zone);
|
||||
Expression* knext = factory->NewSmiLiteral(kNext, nopos);
|
||||
Expression* kreturn = factory->NewSmiLiteral(kReturn, nopos);
|
||||
Expression* kthrow = factory->NewSmiLiteral(kThrow, nopos);
|
||||
Expression* knext = factory->NewSmiLiteral(JSGeneratorObject::NEXT, nopos);
|
||||
Expression* kreturn =
|
||||
factory->NewSmiLiteral(JSGeneratorObject::RETURN, nopos);
|
||||
Expression* kthrow =
|
||||
factory->NewSmiLiteral(JSGeneratorObject::THROW, nopos);
|
||||
cases->Add(factory->NewCaseClause(knext, case_next, nopos), zone);
|
||||
cases->Add(factory->NewCaseClause(kreturn, case_return, nopos), zone);
|
||||
cases->Add(factory->NewCaseClause(kthrow, case_throw, nopos), zone);
|
||||
|
@ -87,9 +87,9 @@ RUNTIME_FUNCTION(Runtime_SuspendJSGeneratorObject) {
|
||||
// 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 and GeneratorThrow. EmitGeneratorResumeResume is
|
||||
// called in any case, as it needs to reconstruct the stack frame and make space
|
||||
// for arguments and operands.
|
||||
// inlined into GeneratorNext, GeneratorReturn, and GeneratorThrow.
|
||||
// EmitGeneratorResume is called in any case, as it needs to reconstruct the
|
||||
// stack frame and make space for arguments and operands.
|
||||
RUNTIME_FUNCTION(Runtime_ResumeJSGeneratorObject) {
|
||||
SealHandleScope shs(isolate);
|
||||
DCHECK(args.length() == 3);
|
||||
@ -125,7 +125,10 @@ RUNTIME_FUNCTION(Runtime_ResumeJSGeneratorObject) {
|
||||
JSGeneratorObject::ResumeMode resume_mode =
|
||||
static_cast<JSGeneratorObject::ResumeMode>(resume_mode_int);
|
||||
switch (resume_mode) {
|
||||
// Note: this looks like NEXT and RETURN are the same but RETURN receives
|
||||
// special treatment in the generator code (to which we return here).
|
||||
case JSGeneratorObject::NEXT:
|
||||
case JSGeneratorObject::RETURN:
|
||||
return value;
|
||||
case JSGeneratorObject::THROW:
|
||||
return isolate->Throw(value);
|
||||
@ -213,15 +216,25 @@ RUNTIME_FUNCTION(Runtime_GeneratorGetSourcePosition) {
|
||||
}
|
||||
|
||||
|
||||
// Optimization for the following three functions is disabled in
|
||||
// js/generator.js and compiler/ast-graph-builder.cc.
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_GeneratorNext) {
|
||||
UNREACHABLE(); // Optimization disabled in SetUpGenerators().
|
||||
return NULL;
|
||||
UNREACHABLE();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_GeneratorReturn) {
|
||||
UNREACHABLE();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_GeneratorThrow) {
|
||||
UNREACHABLE(); // Optimization disabled in SetUpGenerators().
|
||||
return NULL;
|
||||
UNREACHABLE();
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -262,6 +262,7 @@ namespace internal {
|
||||
F(GeneratorGetContinuation, 1, 1) \
|
||||
F(GeneratorGetSourcePosition, 1, 1) \
|
||||
F(GeneratorNext, 2, 1) \
|
||||
F(GeneratorReturn, 2, 1) \
|
||||
F(GeneratorThrow, 2, 1)
|
||||
|
||||
|
||||
|
@ -99,7 +99,7 @@ function TestGeneratorObjectPrototype() {
|
||||
assertSame(GeneratorObjectPrototype,
|
||||
Object.getPrototypeOf((function*(){yield 1}).prototype));
|
||||
|
||||
var expected_property_names = ["next", "throw", "constructor"];
|
||||
var expected_property_names = ["next", "return", "throw", "constructor"];
|
||||
var found_property_names =
|
||||
Object.getOwnPropertyNames(GeneratorObjectPrototype);
|
||||
|
||||
|
@ -22,18 +22,25 @@
|
||||
}
|
||||
|
||||
{
|
||||
x = g();
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next(1));
|
||||
assertEquals({value: 2, done: false}, x.next(2));
|
||||
assertEquals({value: 3, done: true}, x.next(3));
|
||||
}
|
||||
|
||||
{
|
||||
x = g();
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next(1));
|
||||
assertEquals({value: 2, done: false}, x.throw(2));
|
||||
assertEquals({value: 3, done: true}, x.next(3));
|
||||
}
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next(1));
|
||||
assertEquals({value: 2, done: false}, x.return(2));
|
||||
assertEquals({value: 3, done: true}, x.next(3));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -53,18 +60,25 @@
|
||||
}
|
||||
|
||||
{
|
||||
x = g();
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next(1));
|
||||
assertEquals({value: undefined, done: false}, x.next(2));
|
||||
assertEquals({value: 3, done: true}, x.next(3));
|
||||
}
|
||||
|
||||
{
|
||||
x = g();
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next(1));
|
||||
assertEquals({value: undefined, done: false}, x.next(2));
|
||||
assertEquals({value: 42, done: true}, x.throw(42));
|
||||
}
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next(1));
|
||||
assertEquals({value: undefined, done: false}, x.next(2));
|
||||
assertEquals({value: 42, done: true}, x.return(42));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
252
test/mjsunit/harmony/generators.js
Normal file
252
test/mjsunit/harmony/generators.js
Normal file
@ -0,0 +1,252 @@
|
||||
// Copyright 2016 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
|
||||
{ // yield in try-catch
|
||||
|
||||
let g = function*() {
|
||||
try {yield 1} catch (error) {assertEquals("caught", error)}
|
||||
};
|
||||
|
||||
assertThrowsEquals(() => g().throw("not caught"), "not caught");
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next());
|
||||
assertEquals({value: undefined, done: true}, x.throw("caught"));
|
||||
}
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next());
|
||||
assertEquals({value: undefined, done: true}, x.next());
|
||||
assertThrowsEquals(() => x.throw("not caught"), "not caught");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{ // return that doesn't close
|
||||
let g = function*() { try {return 42} finally {yield 43} };
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 43, done: false}, x.next());
|
||||
assertEquals({value: 42, done: true}, x.next());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{ // return that doesn't close
|
||||
let x;
|
||||
let g = function*() { try {return 42} finally {x.throw(666)} };
|
||||
|
||||
{
|
||||
x = g();
|
||||
assertThrows(() => x.next(), TypeError); // still executing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{ // yield in try-finally, finally clause performs return
|
||||
|
||||
let g = function*() { try {yield 42} finally {return 13} };
|
||||
|
||||
{ // "return" closes at suspendedStart
|
||||
let x = g();
|
||||
assertEquals({value: 666, done: true}, x.return(666));
|
||||
assertEquals({value: undefined, done: true}, x.next(42));
|
||||
assertThrowsEquals(() => x.throw(43), 43);
|
||||
assertEquals({value: 42, done: true}, x.return(42));
|
||||
}
|
||||
|
||||
{ // "throw" closes at suspendedStart
|
||||
let x = g();
|
||||
assertThrowsEquals(() => x.throw(666), 666);
|
||||
assertEquals({value: undefined, done: true}, x.next(42));
|
||||
assertEquals({value: 43, done: true}, x.return(43));
|
||||
assertThrowsEquals(() => x.throw(44), 44);
|
||||
}
|
||||
|
||||
{ // "next" closes at suspendedYield
|
||||
let x = g();
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 13, done: true}, x.next(666));
|
||||
assertEquals({value: undefined, done: true}, x.next(666));
|
||||
assertThrowsEquals(() => x.throw(666), 666);
|
||||
}
|
||||
|
||||
{ // "return" closes at suspendedYield
|
||||
let x = g();
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 13, done: true}, x.return(666));
|
||||
assertEquals({value: undefined, done: true}, x.next(666));
|
||||
assertEquals({value: 666, done: true}, x.return(666));
|
||||
}
|
||||
|
||||
{ // "throw" closes at suspendedYield
|
||||
let x = g();
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 13, done: true}, x.throw(666));
|
||||
assertThrowsEquals(() => x.throw(666), 666);
|
||||
assertEquals({value: undefined, done: true}, x.next(666));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{ // yield in try-finally, finally clause doesn't perform return
|
||||
|
||||
let g = function*() { try {yield 42} finally {13} };
|
||||
|
||||
{ // "return" closes at suspendedStart
|
||||
let x = g();
|
||||
assertEquals({value: 666, done: true}, x.return(666));
|
||||
assertEquals({value: undefined, done: true}, x.next(42));
|
||||
assertThrowsEquals(() => x.throw(43), 43);
|
||||
assertEquals({value: 42, done: true}, x.return(42));
|
||||
}
|
||||
|
||||
{ // "throw" closes at suspendedStart
|
||||
let x = g();
|
||||
assertThrowsEquals(() => x.throw(666), 666);
|
||||
assertEquals({value: undefined, done: true}, x.next(42));
|
||||
assertEquals({value: 43, done: true}, x.return(43));
|
||||
assertThrowsEquals(() => x.throw(44), 44);
|
||||
}
|
||||
|
||||
{ // "next" closes at suspendedYield
|
||||
let x = g();
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: undefined, done: true}, x.next(666));
|
||||
assertEquals({value: undefined, done: true}, x.next(666));
|
||||
assertThrowsEquals(() => x.throw(666), 666);
|
||||
assertEquals({value: 42, done: true}, x.return(42));
|
||||
}
|
||||
|
||||
{ // "return" closes at suspendedYield
|
||||
let x = g();
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 666, done: true}, x.return(666));
|
||||
assertEquals({value: undefined, done: true}, x.next(666));
|
||||
assertThrowsEquals(() => x.throw(44), 44);
|
||||
assertEquals({value: 42, done: true}, x.return(42));
|
||||
}
|
||||
|
||||
{ // "throw" closes at suspendedYield
|
||||
let x = g();
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertThrowsEquals(() => x.throw(666), 666);
|
||||
assertEquals({value: undefined, done: true}, x.next(666));
|
||||
assertThrowsEquals(() => x.throw(666), 666);
|
||||
assertEquals({value: 42, done: true}, x.return(42));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{ // yield in try-finally, finally clause yields and performs return
|
||||
|
||||
let g = function*() { try {yield 42} finally {yield 43; return 13} };
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 43, done: false}, x.return(666));
|
||||
assertEquals({value: 13, done: true}, x.next());
|
||||
assertEquals({value: 666, done: true}, x.return(666));
|
||||
}
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 666, done: true}, x.return(666));
|
||||
assertEquals({value: undefined, done: true}, x.next());
|
||||
assertEquals({value: 666, done: true}, x.return(666));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{ // yield in try-finally, finally clause yields and doesn't perform return
|
||||
|
||||
let g = function*() { try {yield 42} finally {yield 43; 13} };
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 43, done: false}, x.return(666));
|
||||
assertEquals({value: 666, done: true}, x.next());
|
||||
assertEquals({value: 5, done: true}, x.return(5));
|
||||
}
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 666, done: true}, x.return(666));
|
||||
assertEquals({value: undefined, done: true}, x.next());
|
||||
assertEquals({value: 666, done: true}, x.return(666));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{ // yield*, finally clause performs return
|
||||
|
||||
let h = function*() { try {yield 42} finally {yield 43; return 13} };
|
||||
let g = function*() { yield 1; yield yield* h(); };
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next());
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 43, done: false}, x.next(666));
|
||||
assertEquals({value: 13, done: false}, x.next());
|
||||
assertEquals({value: undefined, done: true}, x.next());
|
||||
}
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next());
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 43, done: false}, x.return(666));
|
||||
assertEquals({value: 13, done: false}, x.next());
|
||||
assertEquals({value: undefined, done: true}, x.next());
|
||||
}
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next());
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 43, done: false}, x.throw(666));
|
||||
assertEquals({value: 13, done: false}, x.next());
|
||||
assertEquals({value: undefined, done: true}, x.next());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{ // yield*, finally clause does not perform return
|
||||
|
||||
let h = function*() { try {yield 42} finally {yield 43; 13} };
|
||||
let g = function*() { yield 1; yield yield* h(); };
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next());
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 43, done: false}, x.next(666));
|
||||
assertEquals({value: undefined, done: false}, x.next());
|
||||
assertEquals({value: undefined, done: true}, x.next());
|
||||
}
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next());
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 43, done: false}, x.return(666));
|
||||
assertEquals({value: 666, done: false}, x.next());
|
||||
assertEquals({value: undefined, done: true}, x.next());
|
||||
}
|
||||
|
||||
{
|
||||
let x = g();
|
||||
assertEquals({value: 1, done: false}, x.next());
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 43, done: false}, x.throw(666));
|
||||
assertThrowsEquals(() => x.next(), 666);
|
||||
}
|
||||
}
|
@ -149,9 +149,6 @@
|
||||
'built-ins/Array/prototype/values/iteration-mutable': [FAIL],
|
||||
'built-ins/Array/prototype/Symbol.unscopables/value': [FAIL],
|
||||
|
||||
# https://code.google.com/p/v8/issues/detail?id=3566
|
||||
'built-ins/GeneratorPrototype/return/*': [SKIP],
|
||||
|
||||
# https://code.google.com/p/v8/issues/detail?id=4248
|
||||
'language/expressions/compound-assignment/S11.13.2_A5.*': [FAIL],
|
||||
'language/expressions/compound-assignment/S11.13.2_A6.*': [FAIL],
|
||||
|
Loading…
Reference in New Issue
Block a user