Index: src/interpreter/bytecode-generator.cc |
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc |
index 0227f13b146454a347e623159b6606ce2325bc14..a488fc04b1272032a21a20d7aa7e57e338e8570e 100644 |
--- a/src/interpreter/bytecode-generator.cc |
+++ b/src/interpreter/bytecode-generator.cc |
@@ -565,8 +565,10 @@ BytecodeGenerator::BytecodeGenerator(Isolate* isolate, Zone* zone) |
execution_context_(nullptr), |
execution_result_(nullptr), |
register_allocator_(nullptr), |
+ resume_points_(0, zone), |
try_catch_nesting_level_(0), |
- try_finally_nesting_level_(0) { |
+ try_finally_nesting_level_(0), |
+ yield_count_(0) { |
InitializeAstVisitor(isolate); |
} |
@@ -586,6 +588,10 @@ Handle<BytecodeArray> BytecodeGenerator::MakeBytecode(CompilationInfo* info) { |
// Initialize control scope. |
ControlScopeForTopLevel control(this); |
+ if (IsGeneratorFunction(info->literal()->kind())) { |
+ BuildGeneratorPrologue(); |
+ } |
+ |
// Build function context only if there are context allocated variables. |
if (scope()->NeedsContext()) { |
// Push a new inner context scope for the function. |
@@ -634,6 +640,40 @@ void BytecodeGenerator::MakeBytecodeBody() { |
VisitStatements(info()->literal()->body()); |
} |
+void BytecodeGenerator::BuildGeneratorPrologue() { |
+ resume_points_.clear(); |
rmcilroy
2016/04/15 13:17:46
Do we need the clear? it should be empty when we r
neis
2016/04/18 08:04:17
MakeBytecode could be called several times on the
rmcilroy
2016/04/18 10:01:57
No it's only ever used for a single compile info.
neis
2016/04/18 14:07:52
Ok. After your change I can probably initialize it
|
+ resume_points_.resize(info()->literal()->yield_count()); |
+ |
+ BytecodeLabel regular_call; |
+ builder() |
+ ->LoadAccumulatorWithRegister(Register::new_target()) |
+ .JumpIfUndefined(®ular_call); |
+ |
+ // This is a resume call. Restore registers and perform state dispatch. |
+ // (The current context has already been restored by the trampoline.) |
+ { |
+ RegisterAllocationScope register_scope(this); |
+ Register state = register_allocator()->NewRegister(); |
+ builder() |
+ ->CallRuntime(Runtime::kResumeIgnitionGenerator, Register::new_target(), |
+ 1) |
+ .StoreAccumulatorInRegister(state); |
+ |
+ // TODO(neis): Optimize this by using a proper jump table. |
+ for (size_t i = 0; i < resume_points_.size(); ++i) { |
+ builder() |
+ ->LoadLiteral(Smi::FromInt(static_cast<int>(i))) |
+ .CompareOperation(Token::Value::EQ_STRICT, state) |
+ .JumpIfTrue(&(resume_points_[i])); |
+ } |
+ builder()->Illegal(); // Should never get here. |
+ } |
+ |
+ builder()->Bind(®ular_call); |
+ // This is a regular call. Fall through to the ordinary function prologue, |
+ // after which we will run into the generator object creation and the initial |
+ // yield (both inserted by the parser). |
+} |
void BytecodeGenerator::VisitBlock(Block* stmt) { |
// Visit declarations and statements. |
@@ -2229,16 +2269,83 @@ void BytecodeGenerator::VisitAssignment(Assignment* expr) { |
execution_result()->SetResultInAccumulator(); |
} |
+void BytecodeGenerator::VisitYield(Yield* expr) { |
+ int id = yield_count_++; |
rmcilroy
2016/04/15 13:17:46
Rather than doing this id naming implicitly, would
neis
2016/04/18 08:04:17
I don't think that would be safer and I don't see
rmcilroy
2016/04/18 10:01:57
OK. I'm fine with this for this CL, but maybe it's
neis
2016/04/18 14:07:52
OK. Also, I probably need to move them into array
|
-void BytecodeGenerator::VisitYield(Yield* expr) { UNIMPLEMENTED(); } |
+ builder()->SetExpressionPosition(expr); |
+ Register value = VisitForRegisterValue(expr->expression()); |
rmcilroy
2016/04/15 13:17:46
nit - maybe scope the pre-yield, and post-resume p
neis
2016/04/18 08:04:17
Thanks, I didn't think of that. I added a scope f
|
+ |
+ register_allocator()->PrepareForConsecutiveAllocations(2); |
+ Register generator = register_allocator()->NextConsecutiveRegister(); |
+ Register state = register_allocator()->NextConsecutiveRegister(); |
+ |
+ // Save context, registers, and state. Then return. |
+ VisitForRegisterValue(expr->generator_object(), generator); |
+ builder() |
+ ->LoadLiteral(Smi::FromInt(id)) |
+ .StoreAccumulatorInRegister(state) |
+ .CallRuntime(Runtime::kSuspendIgnitionGenerator, generator, 2) |
+ .LoadAccumulatorWithRegister(value) |
+ .Return(); // Hard return (ignore any finally blocks). |
+ builder()->Bind(&(resume_points_[id])); |
+ // Upon resume, we continue here. |
+ |
+ Register input = register_allocator()->NewRegister(); |
+ builder() |
+ ->CallRuntime(Runtime::kGeneratorGetInput, generator, 1) |
+ .StoreAccumulatorInRegister(input); |
+ |
+ Register resume_mode = register_allocator()->NewRegister(); |
+ builder() |
+ ->CallRuntime(Runtime::kGeneratorGetResumeMode, generator, 1) |
rmcilroy
2016/04/15 13:17:46
Maybe you could have a runtime function which retu
neis
2016/04/18 08:04:17
Let's keep it as is for now. I think we want to g
rmcilroy
2016/04/18 10:01:57
Acknowledged.
|
+ .StoreAccumulatorInRegister(resume_mode); |
+ |
+ // Dispatch on resume mode. |
+ { |
+ BytecodeLabel resume_with_next; |
+ BytecodeLabel resume_with_return; |
+ BytecodeLabel resume_with_throw; |
+ |
+ builder() |
+ ->LoadLiteral(Smi::FromInt(JSGeneratorObject::kReturn)) |
+ .CompareOperation(Token::EQ_STRICT, resume_mode) |
+ .JumpIfTrue(&resume_with_return) |
+ .LoadLiteral(Smi::FromInt(JSGeneratorObject::kThrow)) |
+ .CompareOperation(Token::EQ_STRICT, resume_mode) |
+ .JumpIfTrue(&resume_with_throw) |
+ .Jump(&resume_with_next); |
rmcilroy
2016/04/15 13:17:46
(micro optimization) - presumably JSGeneratorObjec
neis
2016/04/18 08:04:17
Done.
|
+ |
+ builder()->Bind(&resume_with_return); |
+ { |
+ register_allocator()->PrepareForConsecutiveAllocations(2); |
+ Register value = register_allocator()->NextConsecutiveRegister(); |
+ Register done = register_allocator()->NextConsecutiveRegister(); |
+ builder() |
+ ->MoveRegister(input, value) |
+ .LoadTrue() |
+ .StoreAccumulatorInRegister(done) |
+ .CallRuntime(Runtime::kCreateIterResultObject, value, 2); |
+ execution_control()->ReturnAccumulator(); |
+ } |
+ |
+ builder()->Bind(&resume_with_throw); |
+ builder() |
+ ->LoadAccumulatorWithRegister(input) |
+ .Throw(); |
+ |
+ builder()->Bind(&resume_with_next); |
+ builder()->LoadAccumulatorWithRegister(input); |
+ execution_result()->SetResultInAccumulator(); |
+ } |
+} |
void BytecodeGenerator::VisitThrow(Throw* expr) { |
VisitForAccumulatorValue(expr->exception()); |
builder()->SetExpressionPosition(expr); |
builder()->Throw(); |
- // Throw statments are modeled as expression instead of statments. These are |
- // converted from assignment statements in Rewriter::ReWrite pass. An |
+ // Throw statements are modeled as expressions instead of statements. These |
+ // are converted from assignment statements in Rewriter::ReWrite pass. An |
rmcilroy
2016/04/15 13:17:46
Thanks!
|
// assignment statement expects a value in the accumulator. This is a hack to |
// avoid DCHECK fails assert accumulator has been set. |
execution_result()->SetResultInAccumulator(); |