class AstGraphBuilder::ControlScopeForCatch : public ControlScope {
public:
ControlScopeForCatch(AstGraphBuilder* owner, TryCatchBuilder* control)
- : ControlScope(owner, 0), control_(control) {}
+ : ControlScope(owner, 0), control_(control) {
+ builder()->try_nesting_level_++; // Increment nesting.
+ }
+ ~ControlScopeForCatch() {
+ builder()->try_nesting_level_--; // Decrement nesting.
+ }
protected:
virtual bool Execute(Command cmd, Statement* target, Node* value) OVERRIDE {
public:
ControlScopeForFinally(AstGraphBuilder* owner, DeferredCommands* commands,
TryFinallyBuilder* control)
- : ControlScope(owner, 0), commands_(commands), control_(control) {}
+ : ControlScope(owner, 0), commands_(commands), control_(control) {
+ builder()->try_nesting_level_++; // Increment nesting.
+ }
+ ~ControlScopeForFinally() {
+ builder()->try_nesting_level_--; // Decrement nesting.
+ }
protected:
virtual bool Execute(Command cmd, Statement* target, Node* value) OVERRIDE {
globals_(0, local_zone),
execution_control_(nullptr),
execution_context_(nullptr),
+ try_nesting_level_(0),
input_buffer_size_(0),
input_buffer_(nullptr),
exit_control_(nullptr),
if (!has_context && !has_framestate && !has_control && !has_effect) {
result = graph()->NewNode(op, value_input_count, value_inputs, incomplete);
} else {
+ bool inside_try_scope = try_nesting_level_ > 0;
int input_count_with_deps = value_input_count;
if (has_context) ++input_count_with_deps;
if (has_framestate) ++input_count_with_deps;
if (has_effect) {
environment_->UpdateEffectDependency(result);
}
- if (result->op()->ControlOutputCount() > 0 &&
- !environment()->IsMarkedAsUnreachable()) {
- environment_->UpdateControlDependency(result);
+ if (!environment()->IsMarkedAsUnreachable()) {
+ // Update the current control dependency for control-producing nodes.
+ if (NodeProperties::IsControl(result)) {
+ environment_->UpdateControlDependency(result);
+ }
+ // Add implicit exception continuation for throwing nodes.
+ if (!result->op()->HasProperty(Operator::kNoThrow) && inside_try_scope) {
+ Node* on_exception = graph()->NewNode(common()->IfException(), result);
+ environment_->UpdateControlDependency(on_exception);
+ if (FLAG_turbo_exceptions) {
+ execution_control()->ThrowValue(jsgraph()->UndefinedConstant());
+ }
+ }
+ // Add implicit success continuation for throwing nodes.
+ if (!result->op()->HasProperty(Operator::kNoThrow)) {
+ Node* on_success = graph()->NewNode(common()->IfSuccess(), result);
+ environment_->UpdateControlDependency(on_success);
+ }
}
}
SetOncePointer<Node> function_closure_;
SetOncePointer<Node> function_context_;
+ // Tracks how many try-blocks are currently entered.
+ int try_nesting_level_;
+
// Temporary storage for building node input lists.
int input_buffer_size_;
Node** input_buffer_;
if (value->opcode() != opcode) return false;
bool first = true;
for (Edge const edge : value->use_edges()) {
+ if (NodeProperties::IsControlEdge(edge)) continue;
if (NodeProperties::IsEffectEdge(edge)) continue;
DCHECK(NodeProperties::IsValueEdge(edge));
if (!first) return false;
V(End, Operator::kKontrol, 0, 0, 1, 0, 0, 0) \
V(IfTrue, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
V(IfFalse, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
+ V(IfSuccess, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
+ V(IfException, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
V(IfDefault, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
V(Throw, Operator::kFoldable, 1, 1, 1, 0, 0, 1) \
V(Return, Operator::kNoThrow, 1, 1, 1, 0, 0, 1) \
Operator::ZeroIfPure(descriptor->properties()),
Operator::ZeroIfPure(descriptor->properties()),
descriptor->ReturnCount(),
- Operator::ZeroIfPure(descriptor->properties()), 0, descriptor) {}
+ Operator::ZeroIfPure(descriptor->properties()),
+ Operator::ZeroIfNoThrow(descriptor->properties()), descriptor) {}
void PrintParameter(std::ostream& os) const OVERRIDE {
os << "[" << *parameter() << "]";
const Operator* Branch(BranchHint = BranchHint::kNone);
const Operator* IfTrue();
const Operator* IfFalse();
+ const Operator* IfSuccess();
+ const Operator* IfException();
const Operator* Switch(size_t control_output_count);
const Operator* IfValue(int32_t value);
const Operator* IfDefault();
pop = false; // restart traversing successors of this node.
break;
}
- if (NodeProperties::IsControl(succ) &&
+ if (succ->op()->ControlOutputCount() > 0 &&
!marked.IsReachableFromStart(succ)) {
// {succ} is a control node and not yet reached from start.
marked.Push(succ);
// Any control nodes not reachable from start are dead, even loops.
for (size_t i = 0; i < nodes.size(); i++) {
Node* node = nodes[i];
- if (NodeProperties::IsControl(node) &&
+ if (node->op()->ControlOutputCount() > 0 &&
!marked.IsReachableFromStart(node)) {
ReplaceNode(node, dead()); // uses will be added to revisit queue.
}
DCHECK(NodeProperties::IsControlEdge(edge));
if (edge.from() == branch) continue;
switch (edge.from()->opcode()) {
-#define CASE(Opcode) case IrOpcode::k##Opcode:
- CONTROL_OP_LIST(CASE)
-#undef CASE
- // Update all control nodes (except {branch}) pointing to the {loop}.
- edge.UpdateTo(if_true);
+ case IrOpcode::kPhi:
break;
case IrOpcode::kEffectPhi:
effects.push_back(edge.from());
break;
default:
+ // Update all control edges (except {branch}) pointing to the {loop}.
+ edge.UpdateTo(if_true);
break;
}
}
}
-namespace {
-
-V8_INLINE void CheckNoPhis(const BasicBlock* block) {
+void InstructionSelector::VisitControl(BasicBlock* block) {
#ifdef DEBUG
- // Branch targets should not have phis.
- for (BasicBlock::const_iterator i = block->begin(); i != block->end(); ++i) {
- const Node* node = *i;
- CHECK_NE(IrOpcode::kPhi, node->opcode());
+ // SSA deconstruction requires targets of branches not to have phis.
+ // Edge split form guarantees this property, but is more strict.
+ if (block->SuccessorCount() > 1) {
+ for (BasicBlock* const successor : block->successors()) {
+ for (Node* const node : *successor) {
+ CHECK(!IrOpcode::IsPhiOpcode(node->opcode()));
+ }
+ }
}
#endif
-}
-} // namespace
-
-
-void InstructionSelector::VisitControl(BasicBlock* block) {
Node* input = block->control_input();
switch (block->control()) {
case BasicBlock::kGoto:
return VisitGoto(block->SuccessorAt(0));
+ case BasicBlock::kCall: {
+ DCHECK_EQ(IrOpcode::kCall, input->opcode());
+ BasicBlock* success = block->SuccessorAt(0);
+ // TODO(mstarzinger): Record location of {exception} in {handler_table}.
+ // BasicBlock* exception = block->SuccessorAt(1);
+ return VisitCall(input), VisitGoto(success);
+ }
case BasicBlock::kBranch: {
DCHECK_EQ(IrOpcode::kBranch, input->opcode());
BasicBlock* tbranch = block->SuccessorAt(0);
BasicBlock* fbranch = block->SuccessorAt(1);
- // SSA deconstruction requires targets of branches not to have phis.
- // Edge split form guarantees this property, but is more strict.
- CheckNoPhis(tbranch);
- CheckNoPhis(fbranch);
if (tbranch == fbranch) return VisitGoto(tbranch);
// Treat special Branch(Always, IfTrue, IfFalse) as Goto(IfTrue).
Node* const condition = input->InputAt(0);
// Last successor must be Default.
BasicBlock* default_branch = block->successors().back();
DCHECK_EQ(IrOpcode::kIfDefault, default_branch->front()->opcode());
- // SSA deconstruction requires targets of branches not to have phis.
- // Edge split form guarantees this property, but is more strict.
- CheckNoPhis(default_branch);
// All other successors must be cases.
size_t case_count = block->SuccessorCount() - 1;
DCHECK_LE(1u, case_count);
case_values[index] = value;
if (min_value > value) min_value = value;
if (max_value < value) max_value = value;
- // SSA deconstruction requires targets of branches not to have phis.
- // Edge split form guarantees this property, but is more strict.
- CheckNoPhis(branch);
}
DCHECK_LE(min_value, max_value);
return VisitSwitch(input, default_branch, case_branches, case_values,
case IrOpcode::kBranch:
case IrOpcode::kIfTrue:
case IrOpcode::kIfFalse:
+ case IrOpcode::kIfSuccess:
+ case IrOpcode::kIfException:
case IrOpcode::kSwitch:
case IrOpcode::kIfValue:
case IrOpcode::kIfDefault:
Node* merge0 = graph()->NewNode(merge_op, if_true0, if_false0);
-
// Replace all effect uses of {node} with the {ephi0}.
Node* ephi0 = graph()->NewNode(ephi_op, etrue0, efalse0, merge0);
NodeProperties::ReplaceWithValue(node, node, ephi0);
Reduction JSIntrinsicLowering::Change(Node* node, const Operator* op) {
- // Remove the effects from the node and update its effect usages.
+ // Replace all effect uses of {node} with the effect dependency.
NodeProperties::ReplaceWithValue(node, node);
// Remove the inputs corresponding to context, effect and control.
NodeProperties::RemoveNonValueInputs(node);
: Operator(IrOpcode::kJS##Name, properties, "JS" #Name, \
value_input_count, Operator::ZeroIfPure(properties), \
Operator::ZeroIfPure(properties), value_output_count, \
- Operator::ZeroIfPure(properties), 0) {} \
+ Operator::ZeroIfPure(properties), \
+ Operator::ZeroIfNoThrow(properties)) {} \
}; \
Name##Operator k##Name##Operator;
CACHED_OP_LIST(CACHED)
StorePropertyOperator()
: Operator1<LanguageMode>(IrOpcode::kJSStoreProperty,
Operator::kNoProperties, "JSStoreProperty", 3,
- 1, 1, 0, 1, 0, kLanguageMode) {}
+ 1, 1, 0, 1, 2, kLanguageMode) {}
};
StorePropertyOperator<SLOPPY> kStorePropertySloppyOperator;
StorePropertyOperator<STRICT> kStorePropertyStrictOperator;
return new (zone()) Operator1<CallFunctionParameters>( // --
IrOpcode::kJSCallFunction, Operator::kNoProperties, // opcode
"JSCallFunction", // name
- parameters.arity(), 1, 1, 1, 1, 0, // inputs/outputs
+ parameters.arity(), 1, 1, 1, 1, 2, // inputs/outputs
parameters); // parameter
}
return new (zone()) Operator1<CallRuntimeParameters>( // --
IrOpcode::kJSCallRuntime, Operator::kNoProperties, // opcode
"JSCallRuntime", // name
- parameters.arity(), 1, 1, f->result_size, 1, 0, // inputs/outputs
+ parameters.arity(), 1, 1, f->result_size, 1, 2, // inputs/outputs
parameters); // parameter
}
return new (zone()) Operator1<int>( // --
IrOpcode::kJSCallConstruct, Operator::kNoProperties, // opcode
"JSCallConstruct", // name
- arguments, 1, 1, 1, 1, 0, // counts
+ arguments, 1, 1, 1, 1, 2, // counts
arguments); // parameter
}
return new (zone()) Operator1<LoadNamedParameters>( // --
IrOpcode::kJSLoadNamed, Operator::kNoProperties, // opcode
"JSLoadNamed", // name
- 1, 1, 1, 1, 1, 0, // counts
+ 1, 1, 1, 1, 1, 2, // counts
parameters); // parameter
}
return new (zone()) Operator1<LoadPropertyParameters>( // --
IrOpcode::kJSLoadProperty, Operator::kNoProperties, // opcode
"JSLoadProperty", // name
- 2, 1, 1, 1, 1, 0, // counts
+ 2, 1, 1, 1, 1, 2, // counts
parameters); // parameter
}
return new (zone()) Operator1<StoreNamedParameters>( // --
IrOpcode::kJSStoreNamed, Operator::kNoProperties, // opcode
"JSStoreNamed", // name
- 2, 1, 1, 0, 1, 0, // counts
+ 2, 1, 1, 0, 1, 2, // counts
parameters); // parameter
}
return new (zone()) Operator1<LanguageMode>( // --
IrOpcode::kJSDeleteProperty, Operator::kNoProperties, // opcode
"JSDeleteProperty", // name
- 2, 1, 1, 1, 1, 0, // counts
+ 2, 1, 1, 1, 1, 2, // counts
language_mode); // parameter
}
const Operator* JSOperatorBuilder::LoadContext(size_t depth, size_t index,
bool immutable) {
ContextAccess access(depth, index, immutable);
- return new (zone()) Operator1<ContextAccess>( // --
- IrOpcode::kJSLoadContext, Operator::kNoWrite, // opcode
- "JSLoadContext", // name
- 1, 1, 0, 1, 1, 0, // counts
- access); // parameter
+ return new (zone()) Operator1<ContextAccess>( // --
+ IrOpcode::kJSLoadContext, // opcode
+ Operator::kNoWrite | Operator::kNoThrow, // flags
+ "JSLoadContext", // name
+ 1, 1, 0, 1, 1, 0, // counts
+ access); // parameter
}
const Operator* JSOperatorBuilder::StoreContext(size_t depth, size_t index) {
ContextAccess access(depth, index, false);
- return new (zone()) Operator1<ContextAccess>( // --
- IrOpcode::kJSStoreContext, Operator::kNoRead, // opcode
- "JSStoreContext", // name
- 2, 1, 1, 0, 1, 0, // counts
- access); // parameter
+ return new (zone()) Operator1<ContextAccess>( // --
+ IrOpcode::kJSStoreContext, // opcode
+ Operator::kNoRead | Operator::kNoThrow, // flags
+ "JSStoreContext", // name
+ 2, 1, 1, 0, 1, 0, // counts
+ access); // parameter
}
return new (zone()) Operator1<Unique<String>>( // --
IrOpcode::kJSCreateCatchContext, Operator::kNoProperties, // opcode
"JSCreateCatchContext", // name
- 2, 1, 1, 1, 1, 0, // counts
+ 2, 1, 1, 1, 1, 2, // counts
name); // parameter
}
// - relax effects from generic but not-side-effecting operations
-// Relax the effects of {node} by immediately replacing effect uses of {node}
-// with the effect input to {node}.
+// Relax the effects of {node} by immediately replacing effect and control uses
+// of {node} with the effect and control input to {node}.
// TODO(turbofan): replace the effect input to {node} with {graph->start()}.
// TODO(titzer): move into a GraphEditor?
-static void RelaxEffects(Node* node) {
+static void RelaxEffectsAndControls(Node* node) {
NodeProperties::ReplaceWithValue(node, node, NULL);
}
+// Relax the control uses of {node} by immediately replacing them with the
+// control input to {node}.
+// TODO(titzer): move into a GraphEditor?
+static void RelaxControls(Node* node) {
+ NodeProperties::ReplaceWithValue(node, node, node);
+}
+
+
JSTypedLowering::JSTypedLowering(JSGraph* jsgraph, Zone* zone)
: jsgraph_(jsgraph), simplified_(graph()->zone()), conversions_(zone) {
zero_range_ = Type::Range(0.0, 1.0, graph()->zone());
DCHECK_EQ(0, op->ControlInputCount());
DCHECK_EQ(2, op->ValueInputCount());
- // Remove the effects from the node, if any, and update its effect usages.
+ // Remove the effects from the node, and update its effect/control usages.
if (node_->op()->EffectInputCount() > 0) {
- RelaxEffects(node_);
+ RelaxEffectsAndControls(node_);
}
// Remove the inputs corresponding to context, effect, and control.
NodeProperties::RemoveNonValueInputs(node_);
DCHECK(NodeProperties::IsControl(control));
DCHECK(NodeProperties::GetBounds(node).upper->Is(Type::Number()));
DCHECK(!NodeProperties::GetBounds(input).upper->Is(Type::Number()));
- RelaxEffects(node);
+ RelaxEffectsAndControls(node);
node->set_op(common()->Phi(kMachAnyTagged, input_count));
for (int i = 0; i < input_count; ++i) {
// We must be very careful not to introduce cycles when pushing
DCHECK_EQ(3, input_count);
DCHECK(NodeProperties::GetBounds(node).upper->Is(Type::Number()));
DCHECK(!NodeProperties::GetBounds(input).upper->Is(Type::Number()));
- RelaxEffects(node);
+ RelaxEffectsAndControls(node);
node->set_op(common()->Select(kMachAnyTagged, input_hint));
node->ReplaceInput(0, input->InputAt(0));
for (int i = 1; i < input_count; ++i) {
NodeProperties::GetControlInput(node) != graph()->start()) {
// JSToNumber(x:plain-primitive,context,effect,control)
// => JSToNumber(x,no-context,start,start)
- RelaxEffects(node);
+ RelaxEffectsAndControls(node);
NodeProperties::ReplaceContextInput(node, jsgraph()->NoContextConstant());
NodeProperties::ReplaceControlInput(node, graph()->start());
NodeProperties::ReplaceEffectInput(node, graph()->start());
node->ReplaceInput(3, effect);
node->ReplaceInput(4, control);
node->TrimInputCount(5);
+ RelaxControls(node);
return Changed(node);
}
// Compute byte offset.
node->ReplaceInput(4, effect);
node->ReplaceInput(5, control);
node->TrimInputCount(6);
+ RelaxControls(node);
return Changed(node);
}
}
// static
void NodeProperties::ReplaceWithValue(Node* node, Node* value, Node* effect) {
- DCHECK(node->op()->ControlOutputCount() == 0);
if (!effect && node->op()->EffectInputCount() > 0) {
effect = NodeProperties::GetEffectInput(node);
}
- // Requires distinguishing between value and effect edges.
+ // Requires distinguishing between value, effect and control edges.
for (Edge edge : node->use_edges()) {
- if (IsEffectEdge(edge)) {
+ if (IsControlEdge(edge)) {
+ DCHECK_EQ(IrOpcode::kIfSuccess, edge.from()->opcode());
+ Node* control = NodeProperties::GetControlInput(node);
+ edge.from()->ReplaceUses(control);
+ edge.UpdateTo(NULL);
+ } else if (IsEffectEdge(edge)) {
DCHECK_NOT_NULL(effect);
edge.UpdateTo(effect);
} else {
void NodeProperties::CollectControlProjections(Node* node, Node** projections,
size_t projection_count) {
#ifdef DEBUG
- DCHECK_EQ(static_cast<int>(projection_count), node->UseCount());
+ DCHECK_LE(static_cast<int>(projection_count), node->UseCount());
std::memset(projections, 0, sizeof(*projections) * projection_count);
#endif
size_t if_value_index = 0;
for (Node* const use : node->uses()) {
size_t index;
switch (use->opcode()) {
- default:
- UNREACHABLE();
- // Fall through.
case IrOpcode::kIfTrue:
DCHECK_EQ(IrOpcode::kBranch, node->opcode());
index = 0;
DCHECK_EQ(IrOpcode::kBranch, node->opcode());
index = 1;
break;
+ case IrOpcode::kIfSuccess:
+ DCHECK_EQ(IrOpcode::kCall, node->opcode());
+ index = 0;
+ break;
+ case IrOpcode::kIfException:
+ DCHECK_EQ(IrOpcode::kCall, node->opcode());
+ index = 1;
+ break;
case IrOpcode::kIfValue:
DCHECK_EQ(IrOpcode::kSwitch, node->opcode());
index = if_value_index++;
DCHECK_EQ(IrOpcode::kSwitch, node->opcode());
index = projection_count - 1;
break;
+ default:
+ continue;
}
DCHECK_LT(if_value_index, projection_count);
DCHECK_LT(index, projection_count);
static void RemoveNonValueInputs(Node* node);
// Replace value uses of {node} with {value} and effect uses of {node} with
- // {effect}. If {effect == NULL}, then use the effect input to {node}.
+ // {effect}. If {effect == NULL}, then use the effect input to {node}. All
+ // control uses will be relaxed assuming {node} cannot throw.
static void ReplaceWithValue(Node* node, Node* value, Node* effect = nullptr);
static Node* FindProjection(Node* node, size_t projection_index);
// Collect the branch-related projections from a node, such as IfTrue,
- // IfFalse, IfValue and IfDefault.
+ // IfFalse, IfSuccess, IfException, IfValue and IfDefault.
// - Branch: [ IfTrue, IfFalse ]
+ // - Call : [ IfSuccess, IfException ]
// - Switch: [ IfValue, ..., IfDefault ]
static void CollectControlProjections(Node* node, Node** proj, size_t count);
V(Branch) \
V(IfTrue) \
V(IfFalse) \
+ V(IfSuccess) \
+ V(IfException) \
V(Switch) \
V(IfValue) \
V(IfDefault) \
return kInt32Constant <= value && value <= kHeapConstant;
}
- static bool IsPhiOpcode(Value val) {
- return val == kPhi || val == kEffectPhi;
+ static bool IsPhiOpcode(Value value) {
+ return value == kPhi || value == kEffectPhi;
+ }
+
+ static bool IsMergeOpcode(Value value) {
+ return value == kMerge || value == kLoop;
}
};
return opcode == IrOpcode::kStart || opcode == IrOpcode::kEnd ||
opcode == IrOpcode::kDead || opcode == IrOpcode::kLoop ||
opcode == IrOpcode::kMerge || opcode == IrOpcode::kIfTrue ||
- opcode == IrOpcode::kIfFalse || opcode == IrOpcode::kIfValue ||
+ opcode == IrOpcode::kIfFalse || opcode == IrOpcode::kIfSuccess ||
+ opcode == IrOpcode::kIfException || opcode == IrOpcode::kIfValue ||
opcode == IrOpcode::kIfDefault;
}
int EffectOutputCount() const { return effect_out_; }
int ControlOutputCount() const { return control_out_; }
+ static size_t ZeroIfNoThrow(Properties properties) {
+ return (properties & kNoThrow) == kNoThrow ? 0 : 2;
+ }
+
static size_t ZeroIfPure(Properties properties) {
return (properties & kPure) == kPure ? 0 : 1;
}
return os << "none";
case BasicBlock::kGoto:
return os << "goto";
+ case BasicBlock::kCall:
+ return os << "call";
case BasicBlock::kBranch:
return os << "branch";
case BasicBlock::kSwitch:
void Schedule::AddGoto(BasicBlock* block, BasicBlock* succ) {
- DCHECK(block->control() == BasicBlock::kNone);
+ DCHECK_EQ(BasicBlock::kNone, block->control());
block->set_control(BasicBlock::kGoto);
AddSuccessor(block, succ);
}
+void Schedule::AddCall(BasicBlock* block, Node* call, BasicBlock* success_block,
+ BasicBlock* exception_block) {
+ DCHECK_EQ(BasicBlock::kNone, block->control());
+ DCHECK_EQ(IrOpcode::kCall, call->opcode());
+ block->set_control(BasicBlock::kCall);
+ AddSuccessor(block, success_block);
+ AddSuccessor(block, exception_block);
+ SetControlInput(block, call);
+}
+
+
void Schedule::AddBranch(BasicBlock* block, Node* branch, BasicBlock* tblock,
BasicBlock* fblock) {
- DCHECK(block->control() == BasicBlock::kNone);
- DCHECK(branch->opcode() == IrOpcode::kBranch);
+ DCHECK_EQ(BasicBlock::kNone, block->control());
+ DCHECK_EQ(IrOpcode::kBranch, branch->opcode());
block->set_control(BasicBlock::kBranch);
AddSuccessor(block, tblock);
AddSuccessor(block, fblock);
void Schedule::AddReturn(BasicBlock* block, Node* input) {
- DCHECK(block->control() == BasicBlock::kNone);
+ DCHECK_EQ(BasicBlock::kNone, block->control());
block->set_control(BasicBlock::kReturn);
SetControlInput(block, input);
if (block != end()) AddSuccessor(block, end());
void Schedule::AddThrow(BasicBlock* block, Node* input) {
- DCHECK(block->control() == BasicBlock::kNone);
+ DCHECK_EQ(BasicBlock::kNone, block->control());
block->set_control(BasicBlock::kThrow);
SetControlInput(block, input);
if (block != end()) AddSuccessor(block, end());
void Schedule::InsertBranch(BasicBlock* block, BasicBlock* end, Node* branch,
BasicBlock* tblock, BasicBlock* fblock) {
- DCHECK(block->control() != BasicBlock::kNone);
- DCHECK(end->control() == BasicBlock::kNone);
+ DCHECK_NE(BasicBlock::kNone, block->control());
+ DCHECK_EQ(BasicBlock::kNone, end->control());
end->set_control(block->control());
block->set_control(BasicBlock::kBranch);
MoveSuccessors(block, end);
enum Control {
kNone, // Control not initialized yet.
kGoto, // Goto a single successor block.
+ kCall, // Call with continuation as first successor, exception second.
kBranch, // Branch if true to first successor, otherwise second.
kSwitch, // Table dispatch to one of the successor blocks.
kReturn, // Return a value from this method.
// BasicBlock building: add a goto to the end of {block}.
void AddGoto(BasicBlock* block, BasicBlock* succ);
+ // BasicBlock building: add a call at the end of {block}.
+ void AddCall(BasicBlock* block, Node* call, BasicBlock* success_block,
+ BasicBlock* exception_block);
+
// BasicBlock building: add a branch at the end of {block}.
void AddBranch(BasicBlock* block, Node* branch, BasicBlock* tblock,
BasicBlock* fblock);
}
private:
- // TODO(mstarzinger): Only for Scheduler::FuseFloatingControl.
+ friend class ScheduleLateNodeVisitor;
friend class Scheduler;
void FixNode(BasicBlock* block, Node* node) {
case IrOpcode::kSwitch:
BuildBlocksForSuccessors(node);
break;
+ case IrOpcode::kCall:
+ if (IsExceptionalCall(node)) {
+ BuildBlocksForSuccessors(node);
+ }
+ break;
default:
break;
}
scheduler_->UpdatePlacement(node, Scheduler::kFixed);
ConnectThrow(node);
break;
+ case IrOpcode::kCall:
+ if (IsExceptionalCall(node)) {
+ scheduler_->UpdatePlacement(node, Scheduler::kFixed);
+ ConnectCall(node);
+ }
+ break;
default:
break;
}
}
}
+ BasicBlock* FindPredecessorBlock(Node* node) {
+ BasicBlock* predecessor_block = nullptr;
+ while (true) {
+ predecessor_block = schedule_->block(node);
+ if (predecessor_block != nullptr) break;
+ node = NodeProperties::GetControlInput(node);
+ }
+ return predecessor_block;
+ }
+
+ void ConnectCall(Node* call) {
+ BasicBlock* successor_blocks[2];
+ CollectSuccessorBlocks(call, successor_blocks, arraysize(successor_blocks));
+
+ // Consider the exception continuation to be deferred.
+ successor_blocks[1]->set_deferred(true);
+
+ Node* call_control = NodeProperties::GetControlInput(call);
+ BasicBlock* call_block = FindPredecessorBlock(call_control);
+ TraceConnect(call, call_block, successor_blocks[0]);
+ TraceConnect(call, call_block, successor_blocks[1]);
+ schedule_->AddCall(call_block, call, successor_blocks[0],
+ successor_blocks[1]);
+ }
+
void ConnectBranch(Node* branch) {
BasicBlock* successor_blocks[2];
CollectSuccessorBlocks(branch, successor_blocks,
schedule_->InsertBranch(component_start_, component_end_, branch,
successor_blocks[0], successor_blocks[1]);
} else {
- Node* branch_block_node = NodeProperties::GetControlInput(branch);
- BasicBlock* branch_block = schedule_->block(branch_block_node);
- DCHECK_NOT_NULL(branch_block);
-
+ Node* branch_control = NodeProperties::GetControlInput(branch);
+ BasicBlock* branch_block = FindPredecessorBlock(branch_control);
TraceConnect(branch, branch_block, successor_blocks[0]);
TraceConnect(branch, branch_block, successor_blocks[1]);
schedule_->AddBranch(branch_block, branch, successor_blocks[0],
schedule_->InsertSwitch(component_start_, component_end_, sw,
successor_blocks, successor_count);
} else {
- Node* sw_block_node = NodeProperties::GetControlInput(sw);
- BasicBlock* sw_block = schedule_->block(sw_block_node);
- DCHECK_NOT_NULL(sw_block);
-
+ Node* switch_control = NodeProperties::GetControlInput(sw);
+ BasicBlock* switch_block = FindPredecessorBlock(switch_control);
for (size_t index = 0; index < successor_count; ++index) {
- TraceConnect(sw, sw_block, successor_blocks[index]);
+ TraceConnect(sw, switch_block, successor_blocks[index]);
}
- schedule_->AddSwitch(sw_block, sw, successor_blocks, successor_count);
+ schedule_->AddSwitch(switch_block, sw, successor_blocks, successor_count);
}
}
// For all of the merge's control inputs, add a goto at the end to the
// merge's basic block.
for (Node* const input : merge->inputs()) {
- BasicBlock* predecessor_block = schedule_->block(input);
+ BasicBlock* predecessor_block = FindPredecessorBlock(input);
TraceConnect(merge, predecessor_block, block);
schedule_->AddGoto(predecessor_block, block);
}
}
void ConnectReturn(Node* ret) {
- Node* return_block_node = NodeProperties::GetControlInput(ret);
- BasicBlock* return_block = schedule_->block(return_block_node);
+ Node* return_control = NodeProperties::GetControlInput(ret);
+ BasicBlock* return_block = FindPredecessorBlock(return_control);
TraceConnect(ret, return_block, NULL);
schedule_->AddReturn(return_block, ret);
}
void ConnectThrow(Node* thr) {
- Node* throw_block_node = NodeProperties::GetControlInput(thr);
- BasicBlock* throw_block = schedule_->block(throw_block_node);
+ Node* throw_control = NodeProperties::GetControlInput(thr);
+ BasicBlock* throw_block = FindPredecessorBlock(throw_control);
TraceConnect(thr, throw_block, NULL);
schedule_->AddThrow(throw_block, thr);
}
}
}
+ bool IsExceptionalCall(Node* node) {
+ for (Node* const use : node->uses()) {
+ if (use->opcode() == IrOpcode::kIfException) return true;
+ }
+ return false;
+ }
+
bool IsFinalMerge(Node* node) {
return (node->opcode() == IrOpcode::kMerge &&
node == scheduler_->graph_->end()->InputAt(0));
}
// Schedule the node or a floating control structure.
- if (NodeProperties::IsControl(node)) {
+ if (IrOpcode::IsMergeOpcode(node->opcode())) {
ScheduleFloatingControl(block, node);
} else {
ScheduleNode(block, node);
return block;
}
+ BasicBlock* FindPredecessorBlock(Node* node) {
+ return scheduler_->control_flow_builder_->FindPredecessorBlock(node);
+ }
+
BasicBlock* GetBlockForUse(Edge edge) {
Node* use = edge.from();
- IrOpcode::Value opcode = use->opcode();
- if (IrOpcode::IsPhiOpcode(opcode)) {
+ if (IrOpcode::IsPhiOpcode(use->opcode())) {
// If the use is from a coupled (i.e. floating) phi, compute the common
// dominator of its uses. This will not recurse more than one level.
if (scheduler_->GetPlacement(use) == Scheduler::kCoupled) {
DCHECK_EQ(edge.to(), NodeProperties::GetControlInput(use));
return GetCommonDominatorOfUses(use);
}
- // If the use is from a fixed (i.e. non-floating) phi, use the block
- // of the corresponding control input to the merge.
+ // If the use is from a fixed (i.e. non-floating) phi, we use the
+ // predecessor block of the corresponding control input to the merge.
if (scheduler_->GetPlacement(use) == Scheduler::kFixed) {
Trace(" input@%d into a fixed phi #%d:%s\n", edge.index(), use->id(),
use->op()->mnemonic());
Node* merge = NodeProperties::GetControlInput(use, 0);
- opcode = merge->opcode();
- DCHECK(opcode == IrOpcode::kMerge || opcode == IrOpcode::kLoop);
- use = NodeProperties::GetControlInput(merge, edge.index());
+ DCHECK(IrOpcode::IsMergeOpcode(merge->opcode()));
+ Node* input = NodeProperties::GetControlInput(merge, edge.index());
+ return FindPredecessorBlock(input);
+ }
+ } else if (IrOpcode::IsMergeOpcode(use->opcode())) {
+ // If the use is from a fixed (i.e. non-floating) merge, we use the
+ // predecessor block of the current input to the merge.
+ if (scheduler_->GetPlacement(use) == Scheduler::kFixed) {
+ Trace(" input@%d into a fixed merge #%d:%s\n", edge.index(), use->id(),
+ use->op()->mnemonic());
+ return FindPredecessorBlock(edge.to());
}
}
BasicBlock* result = schedule_->block(use);
// Type is empty.
CheckNotTyped(node);
break;
+ case IrOpcode::kIfSuccess:
+ case IrOpcode::kIfException: {
+ // IfSuccess and IfException continuation only on throwing nodes.
+ Node* input = NodeProperties::GetControlInput(node, 0);
+ CHECK(!input->op()->HasProperty(Operator::kNoThrow));
+ // Type is empty.
+ CheckNotTyped(node);
+ break;
+ }
case IrOpcode::kSwitch: {
// Switch uses are Case and Default.
int count_case = 0, count_default = 0;
}
+TEST(CatchCall) {
+ i::FLAG_turbo_exceptions = true;
+ const char* src =
+ "(function(fun) {"
+ " var r = '-';"
+ " try {"
+ " r += 'A-';"
+ " fun();"
+ " } catch (e) {"
+ " r += e;"
+ " }"
+ " return r;"
+ "})";
+ FunctionTester T(src);
+
+ CompileRun("function thrower() { throw 'T-'; }");
+#if 0 // TODO(mstarzinger): Enable once we have exception handlers.
+ T.CheckCall(T.Val("-A-T-"), T.NewFunction("thrower"));
+#endif
+}
+
+
TEST(Finally) {
i::FLAG_turbo_exceptions = true;
const char* src =
SHARED(End, Operator::kKontrol, 0, 0, 1, 0, 0, 0),
SHARED(IfTrue, Operator::kKontrol, 0, 0, 1, 0, 0, 1),
SHARED(IfFalse, Operator::kKontrol, 0, 0, 1, 0, 0, 1),
+ SHARED(IfSuccess, Operator::kKontrol, 0, 0, 1, 0, 0, 1),
+ SHARED(IfException, Operator::kKontrol, 0, 0, 1, 0, 0, 1),
SHARED(Throw, Operator::kFoldable, 1, 1, 1, 0, 0, 1),
SHARED(Return, Operator::kNoThrow, 1, 1, 1, 0, 0, 1)
#undef SHARED
int control_input_count;
int value_output_count;
int effect_output_count;
+ int control_output_count;
};
const SharedOperator kSharedOperators[] = {
#define SHARED(Name, properties, value_input_count, frame_state_input_count, \
effect_input_count, control_input_count, value_output_count, \
- effect_output_count) \
+ effect_output_count, control_output_count) \
{ \
&JSOperatorBuilder::Name, IrOpcode::kJS##Name, properties, \
value_input_count, frame_state_input_count, effect_input_count, \
- control_input_count, value_output_count, effect_output_count \
+ control_input_count, value_output_count, effect_output_count, \
+ control_output_count \
}
- SHARED(Equal, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(NotEqual, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(StrictEqual, Operator::kPure, 2, 0, 0, 0, 1, 0),
- SHARED(StrictNotEqual, Operator::kPure, 2, 0, 0, 0, 1, 0),
- SHARED(LessThan, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(GreaterThan, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(LessThanOrEqual, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(GreaterThanOrEqual, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(BitwiseOr, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(BitwiseXor, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(BitwiseAnd, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(ShiftLeft, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(ShiftRight, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(ShiftRightLogical, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(Add, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(Subtract, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(Multiply, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(Divide, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(Modulus, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(UnaryNot, Operator::kPure, 1, 0, 0, 0, 1, 0),
- SHARED(ToBoolean, Operator::kPure, 1, 0, 0, 0, 1, 0),
- SHARED(ToNumber, Operator::kNoProperties, 1, 1, 1, 1, 1, 1),
- SHARED(ToString, Operator::kNoProperties, 1, 0, 1, 1, 1, 1),
- SHARED(ToName, Operator::kNoProperties, 1, 1, 1, 1, 1, 1),
- SHARED(ToObject, Operator::kNoProperties, 1, 1, 1, 1, 1, 1),
- SHARED(Yield, Operator::kNoProperties, 1, 0, 1, 1, 1, 1),
- SHARED(Create, Operator::kEliminatable, 0, 0, 1, 1, 1, 1),
- SHARED(HasProperty, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(TypeOf, Operator::kPure, 1, 0, 0, 0, 1, 0),
- SHARED(InstanceOf, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(Debugger, Operator::kNoProperties, 0, 0, 1, 1, 0, 1),
- SHARED(CreateFunctionContext, Operator::kNoProperties, 1, 0, 1, 1, 1, 1),
- SHARED(CreateWithContext, Operator::kNoProperties, 2, 1, 1, 1, 1, 1),
- SHARED(CreateBlockContext, Operator::kNoProperties, 2, 0, 1, 1, 1, 1),
- SHARED(CreateModuleContext, Operator::kNoProperties, 2, 0, 1, 1, 1, 1),
- SHARED(CreateScriptContext, Operator::kNoProperties, 2, 0, 1, 1, 1, 1)
+ SHARED(Equal, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(NotEqual, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(StrictEqual, Operator::kPure, 2, 0, 0, 0, 1, 0, 0),
+ SHARED(StrictNotEqual, Operator::kPure, 2, 0, 0, 0, 1, 0, 0),
+ SHARED(LessThan, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(GreaterThan, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(LessThanOrEqual, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(GreaterThanOrEqual, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(BitwiseOr, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(BitwiseXor, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(BitwiseAnd, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(ShiftLeft, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(ShiftRight, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(ShiftRightLogical, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(Add, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(Subtract, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(Multiply, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(Divide, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(Modulus, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(UnaryNot, Operator::kPure, 1, 0, 0, 0, 1, 0, 0),
+ SHARED(ToBoolean, Operator::kPure, 1, 0, 0, 0, 1, 0, 0),
+ SHARED(ToNumber, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2),
+ SHARED(ToString, Operator::kNoProperties, 1, 0, 1, 1, 1, 1, 2),
+ SHARED(ToName, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2),
+ SHARED(ToObject, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2),
+ SHARED(Yield, Operator::kNoProperties, 1, 0, 1, 1, 1, 1, 2),
+ SHARED(Create, Operator::kEliminatable, 0, 0, 1, 1, 1, 1, 0),
+ SHARED(HasProperty, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(TypeOf, Operator::kPure, 1, 0, 0, 0, 1, 0, 0),
+ SHARED(InstanceOf, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(Debugger, Operator::kNoProperties, 0, 0, 1, 1, 0, 1, 2),
+ SHARED(CreateFunctionContext, Operator::kNoProperties, 1, 0, 1, 1, 1, 1, 2),
+ SHARED(CreateWithContext, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
+ SHARED(CreateBlockContext, Operator::kNoProperties, 2, 0, 1, 1, 1, 1, 2),
+ SHARED(CreateModuleContext, Operator::kNoProperties, 2, 0, 1, 1, 1, 1, 2),
+ SHARED(CreateScriptContext, Operator::kNoProperties, 2, 0, 1, 1, 1, 1, 2)
#undef SHARED
};
EXPECT_EQ(sop.value_output_count, op->ValueOutputCount());
EXPECT_EQ(sop.effect_output_count, op->EffectOutputCount());
- EXPECT_EQ(0, op->ControlOutputCount());
+ EXPECT_EQ(sop.control_output_count, op->ControlOutputCount());
}
EXPECT_EQ(0, op->ValueOutputCount());
EXPECT_EQ(1, op->EffectOutputCount());
- EXPECT_EQ(0, op->ControlOutputCount());
+ EXPECT_EQ(2, op->ControlOutputCount());
}
#include "testing/gmock/include/gmock/gmock.h"
using testing::AnyOf;
+using testing::ElementsAre;
using testing::IsNull;
namespace v8 {
typedef TestWithZone NodePropertiesTest;
+namespace {
+
+const Operator kMockOperator(IrOpcode::kDead, Operator::kNoProperties,
+ "MockOperator", 0, 0, 0, 1, 0, 0);
+const Operator kMockOpEffect(IrOpcode::kDead, Operator::kNoProperties,
+ "MockOpEffect", 0, 1, 0, 1, 1, 0);
+const Operator kMockOpControl(IrOpcode::kDead, Operator::kNoProperties,
+ "MockOpControl", 0, 0, 1, 1, 0, 1);
+
+} // namespace
+
+
+TEST_F(NodePropertiesTest, ReplaceWithValue_ValueUse) {
+ CommonOperatorBuilder common(zone());
+ Node* node = Node::New(zone(), 0, &kMockOperator, 0, nullptr, false);
+ Node* use_value = Node::New(zone(), 0, common.Return(), 1, &node, false);
+ Node* replacement = Node::New(zone(), 0, &kMockOperator, 0, nullptr, false);
+ NodeProperties::ReplaceWithValue(node, replacement);
+ EXPECT_EQ(replacement, use_value->InputAt(0));
+ EXPECT_EQ(0, node->UseCount());
+ EXPECT_EQ(1, replacement->UseCount());
+ EXPECT_THAT(replacement->uses(), ElementsAre(use_value));
+}
+
+
+TEST_F(NodePropertiesTest, ReplaceWithValue_EffectUse) {
+ CommonOperatorBuilder common(zone());
+ Node* start = Node::New(zone(), 0, common.Start(1), 0, nullptr, false);
+ Node* node = Node::New(zone(), 0, &kMockOpEffect, 1, &start, false);
+ Node* use_effect = Node::New(zone(), 0, common.EffectPhi(1), 1, &node, false);
+ Node* replacement = Node::New(zone(), 0, &kMockOperator, 0, nullptr, false);
+ NodeProperties::ReplaceWithValue(node, replacement);
+ EXPECT_EQ(start, use_effect->InputAt(0));
+ EXPECT_EQ(0, node->UseCount());
+ EXPECT_EQ(2, start->UseCount());
+ EXPECT_EQ(0, replacement->UseCount());
+ EXPECT_THAT(start->uses(), ElementsAre(node, use_effect));
+}
+
+
+TEST_F(NodePropertiesTest, ReplaceWithValue_ControlUse) {
+ CommonOperatorBuilder common(zone());
+ Node* start = Node::New(zone(), 0, common.Start(1), 0, nullptr, false);
+ Node* node = Node::New(zone(), 0, &kMockOpControl, 1, &start, false);
+ Node* success = Node::New(zone(), 0, common.IfSuccess(), 1, &node, false);
+ Node* use_control = Node::New(zone(), 0, common.Merge(1), 1, &success, false);
+ Node* replacement = Node::New(zone(), 0, &kMockOperator, 0, nullptr, false);
+ NodeProperties::ReplaceWithValue(node, replacement);
+ EXPECT_EQ(start, use_control->InputAt(0));
+ EXPECT_EQ(0, node->UseCount());
+ EXPECT_EQ(2, start->UseCount());
+ EXPECT_EQ(0, replacement->UseCount());
+ EXPECT_THAT(start->uses(), ElementsAre(node, use_control));
+}
+
+
TEST_F(NodePropertiesTest, FindProjection) {
CommonOperatorBuilder common(zone());
Node* start = Node::New(zone(), 0, common.Start(1), 0, nullptr, false);
namespace {
+const Operator kCallOperator(IrOpcode::kCall, Operator::kNoProperties,
+ "MockCall", 0, 0, 0, 0, 0, 0);
const Operator kBranchOperator(IrOpcode::kBranch, Operator::kNoProperties,
- "Branch", 0, 0, 0, 0, 0, 0);
+ "MockBranch", 0, 0, 0, 0, 0, 0);
const Operator kDummyOperator(IrOpcode::kParameter, Operator::kNoProperties,
"Dummy", 0, 0, 0, 0, 0, 0);
}
+TEST_F(ScheduleTest, AddCall) {
+ Schedule schedule(zone());
+ BasicBlock* start = schedule.start();
+
+ Node* call = Node::New(zone(), 0, &kCallOperator, 0, nullptr, false);
+ BasicBlock* sblock = schedule.NewBasicBlock();
+ BasicBlock* eblock = schedule.NewBasicBlock();
+ schedule.AddCall(start, call, sblock, eblock);
+
+ EXPECT_EQ(start, schedule.block(call));
+
+ EXPECT_EQ(0u, start->PredecessorCount());
+ EXPECT_EQ(2u, start->SuccessorCount());
+ EXPECT_EQ(sblock, start->SuccessorAt(0));
+ EXPECT_EQ(eblock, start->SuccessorAt(1));
+ EXPECT_THAT(start->successors(), ElementsAre(sblock, eblock));
+
+ EXPECT_EQ(1u, sblock->PredecessorCount());
+ EXPECT_EQ(0u, sblock->SuccessorCount());
+ EXPECT_EQ(start, sblock->PredecessorAt(0));
+ EXPECT_THAT(sblock->predecessors(), ElementsAre(start));
+
+ EXPECT_EQ(1u, eblock->PredecessorCount());
+ EXPECT_EQ(0u, eblock->SuccessorCount());
+ EXPECT_EQ(start, eblock->PredecessorAt(0));
+ EXPECT_THAT(eblock->predecessors(), ElementsAre(start));
+}
+
+
TEST_F(ScheduleTest, AddBranch) {
Schedule schedule(zone());
BasicBlock* start = schedule.start();