Commit d0304f91 authored by oth's avatar oth Committed by Commit bot

[Interpreter] Add support for break statements in labelled blocks.

BUG=V8:4280
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#32912}
parent d3168202
......@@ -104,65 +104,65 @@ class BytecodeGenerator::ControlScope BASE_EMBEDDED {
};
// Scoped class for enabling 'break' and 'continue' in iteration
// constructs, e.g. do...while, while..., for...
class BytecodeGenerator::ControlScopeForIteration
// Scoped class for enabling break inside blocks and switch blocks.
class BytecodeGenerator::ControlScopeForBreakable final
: public BytecodeGenerator::ControlScope {
public:
ControlScopeForIteration(BytecodeGenerator* generator,
IterationStatement* statement,
LoopBuilder* loop_builder)
ControlScopeForBreakable(BytecodeGenerator* generator,
BreakableStatement* statement,
BreakableControlFlowBuilder* control_builder)
: ControlScope(generator),
statement_(statement),
loop_builder_(loop_builder) {}
control_builder_(control_builder) {}
protected:
virtual bool Execute(Command command, Statement* statement) {
if (statement != statement_) return false;
switch (command) {
case CMD_BREAK:
loop_builder_->Break();
control_builder_->Break();
return true;
case CMD_CONTINUE:
loop_builder_->Continue();
return true;
break;
}
return false;
}
private:
Statement* statement_;
LoopBuilder* loop_builder_;
BreakableControlFlowBuilder* control_builder_;
};
// Scoped class for enabling 'break' in switch statements.
class BytecodeGenerator::ControlScopeForSwitch
// Scoped class for enabling 'break' and 'continue' in iteration
// constructs, e.g. do...while, while..., for...
class BytecodeGenerator::ControlScopeForIteration final
: public BytecodeGenerator::ControlScope {
public:
ControlScopeForSwitch(BytecodeGenerator* generator,
SwitchStatement* statement,
SwitchBuilder* switch_builder)
ControlScopeForIteration(BytecodeGenerator* generator,
IterationStatement* statement,
LoopBuilder* loop_builder)
: ControlScope(generator),
statement_(statement),
switch_builder_(switch_builder) {}
loop_builder_(loop_builder) {}
protected:
virtual bool Execute(Command command, Statement* statement) {
if (statement != statement_) return false;
switch (command) {
case CMD_BREAK:
switch_builder_->Break();
loop_builder_->Break();
return true;
case CMD_CONTINUE:
break;
loop_builder_->Continue();
return true;
}
return false;
}
private:
Statement* statement_;
SwitchBuilder* switch_builder_;
LoopBuilder* loop_builder_;
};
......@@ -484,6 +484,9 @@ void BytecodeGenerator::MakeBytecodeBody() {
void BytecodeGenerator::VisitBlock(Block* stmt) {
BlockBuilder block_builder(this->builder());
ControlScopeForBreakable execution_control(this, stmt, &block_builder);
if (stmt->scope() == NULL) {
// Visit statements in the same scope, no declarations.
VisitStatements(stmt->statements());
......@@ -499,6 +502,7 @@ void BytecodeGenerator::VisitBlock(Block* stmt) {
VisitStatements(stmt->statements());
}
}
if (stmt->labels() != nullptr) block_builder.EndBlock();
}
......@@ -685,7 +689,7 @@ void BytecodeGenerator::VisitWithStatement(WithStatement* stmt) {
void BytecodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
ZoneList<CaseClause*>* clauses = stmt->cases();
SwitchBuilder switch_builder(builder(), clauses->length());
ControlScopeForSwitch scope(this, stmt, &switch_builder);
ControlScopeForBreakable scope(this, stmt, &switch_builder);
int default_index = -1;
// Keep the switch value in a register until a case matches.
......@@ -753,7 +757,7 @@ void BytecodeGenerator::VisitDoWhileStatement(DoWhileStatement* stmt) {
VisitForAccumulatorValue(stmt->cond());
loop_builder.JumpToHeaderIfTrue();
}
loop_builder.LoopEnd();
loop_builder.EndLoop();
}
......@@ -773,7 +777,7 @@ void BytecodeGenerator::VisitWhileStatement(WhileStatement* stmt) {
}
Visit(stmt->body());
loop_builder.JumpToHeader();
loop_builder.LoopEnd();
loop_builder.EndLoop();
}
......@@ -802,7 +806,7 @@ void BytecodeGenerator::VisitForStatement(ForStatement* stmt) {
Visit(stmt->next());
}
loop_builder.JumpToHeader();
loop_builder.LoopEnd();
loop_builder.EndLoop();
}
......@@ -894,7 +898,7 @@ void BytecodeGenerator::VisitForInStatement(ForInStatement* stmt) {
builder()->LoadAccumulatorWithRegister(index).CountOperation(
Token::Value::ADD, language_mode_strength());
loop_builder.JumpToHeader();
loop_builder.LoopEnd();
loop_builder.EndLoop();
}
......
......@@ -29,8 +29,8 @@ class BytecodeGenerator final : public AstVisitor {
private:
class ContextScope;
class ControlScope;
class ControlScopeForBreakable;
class ControlScopeForIteration;
class ControlScopeForSwitch;
class ExpressionResultScope;
class EffectResultScope;
class AccumulatorResultScope;
......
......@@ -81,10 +81,16 @@ void BreakableControlFlowBuilder::BindLabels(const BytecodeLabel& target,
}
void BlockBuilder::EndBlock() {
builder()->Bind(&block_end_);
SetBreakTarget(block_end_);
}
LoopBuilder::~LoopBuilder() { DCHECK(continue_sites_.empty()); }
void LoopBuilder::LoopEnd() {
void LoopBuilder::EndLoop() {
// Loop must have closed form, i.e. all loop elements are within the loop,
// the loop header precedes the body and next elements in the loop.
DCHECK(loop_header_.is_bound());
......
......@@ -65,6 +65,20 @@ class BreakableControlFlowBuilder : public ControlFlowBuilder {
ZoneVector<BytecodeLabel> break_sites_;
};
// Class to track control flow for block statements (which can break in JS).
class BlockBuilder final : public BreakableControlFlowBuilder {
public:
explicit BlockBuilder(BytecodeArrayBuilder* builder)
: BreakableControlFlowBuilder(builder) {}
void EndBlock();
private:
BytecodeLabel block_end_;
};
// A class to help with co-ordinating break and continue statements with
// their loop.
class LoopBuilder final : public BreakableControlFlowBuilder {
......@@ -79,7 +93,7 @@ class LoopBuilder final : public BreakableControlFlowBuilder {
void Next() { builder()->Bind(&next_); }
void JumpToHeader() { builder()->Jump(&loop_header_); }
void JumpToHeaderIfTrue() { builder()->JumpIfTrue(&loop_header_); }
void LoopEnd();
void EndLoop();
// This method is called when visiting continue statements in the AST.
// Inserts a jump to a unbound label that is patched when the corresponding
......@@ -101,8 +115,8 @@ class LoopBuilder final : public BreakableControlFlowBuilder {
ZoneVector<BytecodeLabel> continue_sites_;
};
// A class to help with co-ordinating break statements with their switch.
// TODO(oth): add support for TF branch/merge info.
class SwitchBuilder final : public BreakableControlFlowBuilder {
public:
explicit SwitchBuilder(BytecodeArrayBuilder* builder, int number_of_cases)
......
......@@ -1672,6 +1672,47 @@ TEST(BytecodeGraphBuilderNestedSwitch) {
}
TEST(BytecodeGraphBuilderBreakableBlocks) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone* zone = scope.main_zone();
Factory* factory = isolate->factory();
ExpectedSnippet<0> snippets[] = {
{"var x = 0;\n"
"my_heart: {\n"
" x = x + 1;\n"
" break my_heart;\n"
" x = x + 2;\n"
"}\n"
"return x;\n",
{factory->NewNumberFromInt(1)}},
{"var sum = 0;\n"
"outta_here: {\n"
" for (var x = 0; x < 10; ++x) {\n"
" for (var y = 0; y < 3; ++y) {\n"
" ++sum;\n"
" if (x + y == 12) { break outta_here; }\n"
" }\n"
" }\n"
"}\n"
"return sum;",
{factory->NewNumber(30)}},
};
for (size_t i = 0; i < arraysize(snippets); i++) {
ScopedVector<char> script(1024);
SNPrintF(script, "function %s() { %s }\n%s();", kFunctionName,
snippets[i].code_snippet, kFunctionName);
BytecodeGraphTester tester(isolate, zone, script.start());
auto callable = tester.GetCallable<>();
Handle<Object> return_value = callable().ToHandleChecked();
CHECK(return_value->SameValue(*snippets[i].return_value()));
}
}
TEST(BytecodeGraphBuilderWhile) {
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
......
......@@ -1968,6 +1968,91 @@ TEST(DeclareGlobals) {
}
TEST(BreakableBlocks) {
InitializedHandleScope handle_scope;
BytecodeGeneratorHelper helper;
ExpectedSnippet<int> snippets[] = {
{"var x = 0;\n"
"label: {\n"
" x = x + 1;\n"
" break label;\n"
" x = x + 1;\n"
"}\n"
"return x;",
1 * kPointerSize,
1,
14,
{
B(LdaZero), //
B(Star), R(0), //
B(LdaSmi8), U8(1), //
B(Add), R(0), //
B(Star), R(0), //
B(Jump), U8(2), //
B(Ldar), R(0), //
B(Return) //
}},
{"var sum = 0;\n"
"outer: {\n"
" for (var x = 0; x < 10; ++x) {\n"
" for (var y = 0; y < 3; ++y) {\n"
" ++sum;\n"
" if (x + y == 12) { break outer; }\n"
" }\n"
" }\n"
"}\n"
"return sum;",
4 * kPointerSize,
1,
60,
{
B(LdaZero), //
B(Star), R(0), //
B(LdaZero), //
B(Star), R(1), //
B(LdaSmi8), U8(10), //
B(TestLessThan), R(1), //
B(JumpIfFalse), U8(47), //
B(LdaZero), //
B(Star), R(2), //
B(LdaSmi8), U8(3), //
B(TestLessThan), R(2), //
B(JumpIfFalse), U8(30), //
B(Ldar), R(0), //
B(ToNumber), //
B(Inc), //
B(Star), R(0), //
B(Ldar), R(2), //
B(Add), R(1), //
B(Star), R(3), //
B(LdaSmi8), U8(12), //
B(TestEqual), R(3), //
B(JumpIfFalse), U8(4), //
B(Jump), U8(18), //
B(Ldar), R(2), //
B(ToNumber), //
B(Inc), //
B(Star), R(2), //
B(Jump), U8(-32), //
B(Ldar), R(1), //
B(ToNumber), //
B(Inc), //
B(Star), R(1), //
B(Jump), U8(-49), //
B(Ldar), R(0), //
B(Return), //
}},
};
for (size_t i = 0; i < arraysize(snippets); i++) {
Handle<BytecodeArray> bytecode_array =
helper.MakeBytecodeForFunctionBody(snippets[i].code_snippet);
CheckBytecodeArrayEqual(snippets[i], bytecode_array);
}
}
TEST(BasicLoops) {
InitializedHandleScope handle_scope;
BytecodeGeneratorHelper helper;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment