Refactor construction of switch statements to avoid subgraphs.

Refactor construction of switch statements so it doesn't use class
HSubgraph.

There are also a few improvements.  We do not use an auxiliary list of
comparisons because they're embedded as a linked list in the graph
under construction.  We share a common break block for all breaks from
the same switch.  We do not insert empty blocks unless necessary to
maintain edge-split form.

There is also a bug fix.  The entry to a clause body is a potential
join and must have a join ID set, otherwise deoptimization within the
body can go to an unpredictable place in the unoptimized code.

Review URL: http://codereview.chromium.org/6650021

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@7099 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 23777e1b
......@@ -878,6 +878,7 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
Comment cmnt(masm_, "[ Case body");
CaseClause* clause = clauses->at(i);
__ bind(clause->body_target()->entry_label());
PrepareForBailoutForId(clause->EntryId(), NO_REGISTERS);
VisitStatements(clause->statements());
}
......
......@@ -1062,6 +1062,8 @@ CaseClause::CaseClause(Expression* label,
: label_(label),
statements_(statements),
position_(pos),
compare_type_(NONE) {}
compare_type_(NONE),
entry_id_(AstNode::GetNextId()) {
}
} } // namespace v8::internal
......@@ -175,6 +175,8 @@ class AstNode: public ZoneObject {
static unsigned current_id_;
static unsigned count_;
unsigned id_;
friend class CaseClause; // Generates AST IDs.
};
......@@ -694,6 +696,8 @@ class CaseClause: public ZoneObject {
int position() { return position_; }
void set_position(int pos) { position_ = pos; }
int EntryId() { return entry_id_; }
// Type feedback information.
void RecordTypeFeedback(TypeFeedbackOracle* oracle);
bool IsSmiCompare() { return compare_type_ == SMI_ONLY; }
......@@ -706,6 +710,7 @@ class CaseClause: public ZoneObject {
int position_;
enum CompareTypeFeedback { NONE, SMI_ONLY, OBJECT_ONLY };
CompareTypeFeedback compare_type_;
int entry_id_;
};
......
......@@ -2360,13 +2360,6 @@ HBasicBlock* HGraphBuilder::CreateBasicBlock(HEnvironment* env) {
}
HSubgraph* HGraphBuilder::CreateEmptySubgraph() {
HSubgraph* subgraph = new HSubgraph(graph());
subgraph->Initialize(graph()->CreateBasicBlock());
return subgraph;
}
HBasicBlock* HGraphBuilder::CreateLoopHeaderBlock() {
HBasicBlock* header = graph()->CreateBasicBlock();
HEnvironment* entry_env = environment()->CopyAsLoopHeader(header);
......@@ -2514,173 +2507,129 @@ void HGraphBuilder::VisitWithExitStatement(WithExitStatement* stmt) {
}
HCompare* HGraphBuilder::BuildSwitchCompare(HSubgraph* subgraph,
HValue* switch_value,
CaseClause* clause) {
AddToSubgraph(subgraph, clause->label());
if (HasStackOverflow()) return NULL;
HValue* clause_value = subgraph->exit_block()->last_environment()->Pop();
HCompare* compare = new HCompare(switch_value,
clause_value,
Token::EQ_STRICT);
compare->SetInputRepresentation(Representation::Integer32());
subgraph->exit_block()->AddInstruction(compare);
return compare;
}
void HGraphBuilder::VisitSwitchStatement(SwitchStatement* stmt) {
VISIT_FOR_VALUE(stmt->tag());
// TODO(3168478): simulate added for tag should be enough.
AddSimulate(stmt->EntryId());
HValue* switch_value = Pop();
// We only optimize switch statements with smi-literal smi comparisons,
// with a bounded number of clauses.
const int kCaseClauseLimit = 128;
ZoneList<CaseClause*>* clauses = stmt->cases();
int num_clauses = clauses->length();
if (num_clauses == 0) return;
if (num_clauses > 128) BAILOUT("SwitchStatement: too many clauses");
int num_smi_clauses = num_clauses;
for (int i = 0; i < num_clauses; i++) {
CaseClause* clause = clauses->at(i);
if (clause->is_default()) continue;
clause->RecordTypeFeedback(oracle());
if (!clause->IsSmiCompare()) {
if (i == 0) BAILOUT("SwitchStatement: no smi compares");
// We will deoptimize if the first non-smi compare is reached.
num_smi_clauses = i;
break;
}
if (!clause->label()->IsSmiLiteral()) {
BAILOUT("SwitchStatement: non-literal switch label");
int clause_count = clauses->length();
if (clause_count > kCaseClauseLimit) {
BAILOUT("SwitchStatement: too many clauses");
}
}
// The single exit block of the whole switch statement.
HBasicBlock* single_exit_block = graph_->CreateBasicBlock();
// Build a series of empty subgraphs for the comparisons.
// The default clause does not have a comparison subgraph.
ZoneList<HSubgraph*> compare_graphs(num_smi_clauses);
for (int i = 0; i < num_smi_clauses; i++) {
if (clauses->at(i)->is_default()) {
compare_graphs.Add(NULL);
} else {
compare_graphs.Add(CreateEmptySubgraph());
}
}
VISIT_FOR_VALUE(stmt->tag());
HValue* tag_value = Pop();
HBasicBlock* first_test_block = current_block();
HSubgraph* prev_graph = current_subgraph_;
HCompare* prev_compare_inst = NULL;
for (int i = 0; i < num_smi_clauses; i++) {
// 1. Build all the tests, with dangling true branches. Unconditionally
// deoptimize if we encounter a non-smi comparison.
for (int i = 0; i < clause_count; ++i) {
CaseClause* clause = clauses->at(i);
if (clause->is_default()) continue;
// Finish the previous graph by connecting it to the current.
HSubgraph* subgraph = compare_graphs.at(i);
if (prev_compare_inst == NULL) {
ASSERT(prev_graph == current_subgraph_);
prev_graph->exit_block()->Finish(new HGoto(subgraph->entry_block()));
} else {
HBasicBlock* empty = graph()->CreateBasicBlock();
prev_graph->exit_block()->Finish(new HTest(prev_compare_inst,
empty,
subgraph->entry_block()));
}
// Build instructions for current subgraph.
ASSERT(clause->IsSmiCompare());
prev_compare_inst = BuildSwitchCompare(subgraph, switch_value, clause);
if (HasStackOverflow()) return;
prev_graph = subgraph;
if (!clause->label()->IsSmiLiteral()) {
BAILOUT("SwitchStatement: non-literal switch label");
}
// Finish last comparison if there was at least one comparison.
// last_false_block is the (empty) false-block of the last comparison. If
// there are no comparisons at all (a single default clause), it is just
// the last block of the current subgraph.
HBasicBlock* last_false_block = current_block();
if (prev_graph != current_subgraph_) {
last_false_block = graph()->CreateBasicBlock();
HBasicBlock* empty = graph()->CreateBasicBlock();
prev_graph->exit_block()->Finish(new HTest(prev_compare_inst,
empty,
last_false_block));
// Unconditionally deoptimize on the first non-smi compare.
clause->RecordTypeFeedback(oracle());
if (!clause->IsSmiCompare()) {
if (current_block() == first_test_block) {
// If the first test is the one that deopts and if the tag value is
// a phi, we need to have some use of that phi to prevent phi
// elimination from removing it. This HSimulate is such a use.
Push(tag_value);
AddSimulate(stmt->EntryId());
Drop(1);
}
// If we have a non-smi compare clause, we deoptimize after trying
// all the previous compares.
if (num_smi_clauses < num_clauses) {
last_false_block->Finish(new HDeoptimize);
current_block()->Finish(new HDeoptimize());
set_current_block(NULL);
break;
}
// Build statement blocks, connect them to their comparison block and
// to the previous statement block, if there is a fall-through.
HSubgraph* previous_subgraph = NULL;
for (int i = 0; i < num_clauses; i++) {
// Otherwise generate a compare and branch.
VISIT_FOR_VALUE(clause->label());
HValue* label_value = Pop();
HCompare* compare = new HCompare(tag_value, label_value, Token::EQ_STRICT);
compare->SetInputRepresentation(Representation::Integer32());
ASSERT(!compare->HasSideEffects());
AddInstruction(compare);
HBasicBlock* body_block = graph()->CreateBasicBlock();
HBasicBlock* next_test_block = graph()->CreateBasicBlock();
HTest* branch = new HTest(compare, body_block, next_test_block);
current_block()->Finish(branch);
set_current_block(next_test_block);
}
// Save the current block to use for the default or to join with the
// exit. This block is NULL if we deoptimized.
HBasicBlock* last_block = current_block();
// 2. Loop over the clauses and the linked list of tests in lockstep,
// translating the clause bodies.
HBasicBlock* curr_test_block = first_test_block;
HBasicBlock* fall_through_block = NULL;
BreakAndContinueInfo break_info(stmt);
{ BreakAndContinueScope push(&break_info, this);
for (int i = 0; i < clause_count; ++i) {
CaseClause* clause = clauses->at(i);
// Subgraph for the statements of the clause is only created when
// it's reachable either from the corresponding compare or as a
// fall-through from previous statements.
HSubgraph* subgraph = NULL;
if (i < num_smi_clauses) {
// Identify the block where normal (non-fall-through) control flow
// goes to.
HBasicBlock* normal_block = NULL;
if (clause->is_default() && last_block != NULL) {
normal_block = last_block;
last_block = NULL; // Cleared to indicate we've handled it.
} else if (!curr_test_block->end()->IsDeoptimize()) {
normal_block = curr_test_block->end()->FirstSuccessor();
curr_test_block = curr_test_block->end()->SecondSuccessor();
}
// Identify a block to emit the body into.
if (normal_block == NULL) {
if (fall_through_block == NULL) {
// (a) Unreachable.
if (clause->is_default()) {
if (!last_false_block->IsFinished()) {
// Default clause: Connect it to the last false block.
subgraph = CreateEmptySubgraph();
last_false_block->Finish(new HGoto(subgraph->entry_block()));
}
continue; // Might still be reachable clause bodies.
} else {
ASSERT(clause->IsSmiCompare());
// Connect with the corresponding comparison.
subgraph = CreateEmptySubgraph();
HBasicBlock* empty =
compare_graphs.at(i)->exit_block()->end()->FirstSuccessor();
empty->Finish(new HGoto(subgraph->entry_block()));
}
}
// Check for fall-through from previous statement block.
if (previous_subgraph != NULL && previous_subgraph->exit_block() != NULL) {
if (subgraph == NULL) subgraph = CreateEmptySubgraph();
previous_subgraph->exit_block()->
Finish(new HGoto(subgraph->entry_block()));
}
if (subgraph != NULL) {
BreakAndContinueInfo break_info(stmt);
{ BreakAndContinueScope push(&break_info, this);
ADD_TO_SUBGRAPH(subgraph, clause->statements());
}
if (break_info.break_block() != NULL) {
break_info.break_block()->SetJoinId(stmt->ExitId());
break_info.break_block()->Finish(new HGoto(single_exit_block));
break;
}
} else {
// (b) Reachable only as fall through.
set_current_block(fall_through_block);
}
previous_subgraph = subgraph;
} else if (fall_through_block == NULL) {
// (c) Reachable only normally.
set_current_block(normal_block);
} else {
// (d) Reachable both ways.
HBasicBlock* join = CreateJoin(fall_through_block,
normal_block,
clause->EntryId());
set_current_block(join);
}
// If the last statement block has a fall-through, connect it to the
// single exit block.
if (previous_subgraph != NULL && previous_subgraph->exit_block() != NULL) {
previous_subgraph->exit_block()->Finish(new HGoto(single_exit_block));
VisitStatements(clause->statements());
CHECK_BAILOUT;
fall_through_block = current_block();
}
// If there is no default clause finish the last comparison's false target.
if (!last_false_block->IsFinished()) {
last_false_block->Finish(new HGoto(single_exit_block));
}
if (single_exit_block->HasPredecessor()) {
set_current_block(single_exit_block);
// Create an up-to-3-way join. Use the break block if it exists since
// it's already a join block.
HBasicBlock* break_block = break_info.break_block();
if (break_block == NULL) {
set_current_block(CreateJoin(fall_through_block,
last_block,
stmt->ExitId()));
} else {
set_current_block(NULL);
if (fall_through_block != NULL) fall_through_block->Goto(break_block);
if (last_block != NULL) last_block->Goto(break_block);
break_block->SetJoinId(stmt->ExitId());
set_current_block(break_block);
}
}
bool HGraphBuilder::HasOsrEntryAt(IterationStatement* statement) {
return statement->OsrEntryId() == info()->osr_ast_id();
}
......
......@@ -794,7 +794,6 @@ class HGraphBuilder: public AstVisitor {
#undef DECLARE_VISIT
HBasicBlock* CreateBasicBlock(HEnvironment* env);
HSubgraph* CreateEmptySubgraph();
HBasicBlock* CreateLoopHeaderBlock();
// Helpers for flow graph construction.
......@@ -887,10 +886,6 @@ class HGraphBuilder: public AstVisitor {
HValue* val,
Expression* expr);
HCompare* BuildSwitchCompare(HSubgraph* subgraph,
HValue* switch_value,
CaseClause* clause);
HValue* BuildContextChainWalk(Variable* var);
void AddCheckConstantFunction(Call* expr,
......
......@@ -825,6 +825,7 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
Comment cmnt(masm_, "[ Case body");
CaseClause* clause = clauses->at(i);
__ bind(clause->body_target()->entry_label());
PrepareForBailoutForId(clause->EntryId(), NO_REGISTERS);
VisitStatements(clause->statements());
}
......
......@@ -837,6 +837,7 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
Comment cmnt(masm_, "[ Case body");
CaseClause* clause = clauses->at(i);
__ bind(clause->body_target()->entry_label());
PrepareForBailoutForId(clause->EntryId(), NO_REGISTERS);
VisitStatements(clause->statements());
}
......
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