[turbofan] Introduce node regions for protection from scheduling.

This CL re-purposes ValueEffect and Finish as delimiters for regions
that are scheduled atomically (renamed to BeginRegion, FinishRegion).

The BeginRegion node takes and produces an effect. For the uses that do
not care about the placement in the effect chain, it is ok to feed
graph->start() as an effect input.

The FinishRegion takes a value and an effect and produces a value and
an effect. It is important that any value or effect produced inside the
region is not used outside the region. The FinishRegion node is the only
way to smuggle an effect and a value out.

At the moment, this does not support control flow inside the region. Control flow would be hard.

During scheduling we do some sanity check, but the checks are not exhaustive. Here is what we check:
- the effect chain between begin and finish is linear (no splitting,
  single effect input and output).
- any value produced is consumed by the FinishRegion node.
- no control flow outputs.

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

Cr-Commit-Position: refs/heads/master@{#31265}
This commit is contained in:
jarin 2015-10-14 07:53:04 -07:00 committed by Commit bot
parent 1919fa38c2
commit 59c616ccd7
16 changed files with 157 additions and 126 deletions

View File

@ -66,7 +66,7 @@ Node* ChangeLowering::AllocateHeapNumberWithValue(Node* value, Node* control) {
Callable callable = CodeFactory::AllocateHeapNumber(isolate());
Node* target = jsgraph()->HeapConstant(callable.code());
Node* context = jsgraph()->NoContextConstant();
Node* effect = graph()->NewNode(common()->ValueEffect(1), value);
Node* effect = graph()->NewNode(common()->BeginRegion(), graph()->start());
if (!allocate_heap_number_operator_.is_set()) {
CallDescriptor* descriptor = Linkage::GetStubCallDescriptor(
isolate(), jsgraph()->zone(), callable.descriptor(), 0,
@ -78,7 +78,7 @@ Node* ChangeLowering::AllocateHeapNumberWithValue(Node* value, Node* control) {
Node* store = graph()->NewNode(
machine()->Store(StoreRepresentation(kMachFloat64, kNoWriteBarrier)),
heap_number, HeapNumberValueIndexConstant(), value, heap_number, control);
return graph()->NewNode(common()->Finish(1), heap_number, store);
return graph()->NewNode(common()->FinishRegion(), heap_number, store);
}

View File

@ -125,7 +125,9 @@ std::ostream& operator<<(std::ostream& os, ParameterInfo const& i) {
V(Deoptimize, Operator::kNoThrow, 1, 1, 1, 0, 0, 1) \
V(Terminate, Operator::kKontrol, 0, 1, 1, 0, 0, 1) \
V(OsrNormalEntry, Operator::kFoldable, 0, 1, 1, 0, 1, 1) \
V(OsrLoopEntry, Operator::kFoldable, 0, 1, 1, 0, 1, 1)
V(OsrLoopEntry, Operator::kFoldable, 0, 1, 1, 0, 1, 1) \
V(BeginRegion, Operator::kNoThrow, 0, 1, 0, 0, 1, 0) \
V(FinishRegion, Operator::kNoThrow, 1, 1, 0, 1, 1, 0)
#define CACHED_RETURN_LIST(V) \
@ -678,24 +680,6 @@ const Operator* CommonOperatorBuilder::EffectSet(int arguments) {
}
const Operator* CommonOperatorBuilder::ValueEffect(int arguments) {
DCHECK(arguments > 0); // Disallow empty value effects.
return new (zone()) Operator( // --
IrOpcode::kValueEffect, Operator::kPure, // opcode
"ValueEffect", // name
arguments, 0, 0, 0, 1, 0); // counts
}
const Operator* CommonOperatorBuilder::Finish(int arguments) {
DCHECK(arguments > 0); // Disallow empty finishes.
return new (zone()) Operator( // --
IrOpcode::kFinish, Operator::kPure, // opcode
"Finish", // name
1, arguments, 0, 1, 0, 0); // counts
}
const Operator* CommonOperatorBuilder::StateValues(int arguments) {
switch (arguments) {
#define CACHED_STATE_VALUES(arguments) \

View File

@ -145,8 +145,8 @@ class CommonOperatorBuilder final : public ZoneObject {
const Operator* Phi(MachineType type, int value_input_count);
const Operator* EffectPhi(int effect_input_count);
const Operator* EffectSet(int arguments);
const Operator* ValueEffect(int arguments);
const Operator* Finish(int arguments);
const Operator* BeginRegion();
const Operator* FinishRegion();
const Operator* StateValues(int arguments);
const Operator* TypedStateValues(const ZoneVector<MachineType>* types);
const Operator* FrameState(BailoutId bailout_id,

View File

@ -545,12 +545,13 @@ void InstructionSelector::VisitNode(Node* node) {
case IrOpcode::kEffectPhi:
case IrOpcode::kMerge:
case IrOpcode::kTerminate:
case IrOpcode::kBeginRegion:
// No code needed for these graph artifacts.
return;
case IrOpcode::kIfException:
return MarkAsReference(node), VisitIfException(node);
case IrOpcode::kFinish:
return MarkAsReference(node), VisitFinish(node);
case IrOpcode::kFinishRegion:
return MarkAsReference(node), VisitFinishRegion(node);
case IrOpcode::kParameter: {
MachineType type =
linkage()->GetParameterType(ParameterIndexOf(node->op()));
@ -925,7 +926,7 @@ void InstructionSelector::VisitBitcastInt64ToFloat64(Node* node) {
#endif // V8_TARGET_ARCH_32_BIT
void InstructionSelector::VisitFinish(Node* node) {
void InstructionSelector::VisitFinishRegion(Node* node) {
OperandGenerator g(this);
Node* value = node->InputAt(0);
Emit(kArchNop, g.DefineSameAsFirst(node), g.Use(value));

View File

@ -200,7 +200,7 @@ class InstructionSelector final {
MACHINE_OP_LIST(DECLARE_GENERATOR)
#undef DECLARE_GENERATOR
void VisitFinish(Node* node);
void VisitFinishRegion(Node* node);
void VisitParameter(Node* node);
void VisitIfException(Node* node);
void VisitOsrValue(Node* node);

View File

@ -46,6 +46,7 @@ class AllocationBuilder final {
// Primitive allocation of static size.
void Allocate(int size) {
effect_ = graph()->NewNode(jsgraph()->common()->BeginRegion(), effect_);
allocation_ = graph()->NewNode(
simplified()->Allocate(), jsgraph()->Constant(size), effect_, control_);
effect_ = allocation_;
@ -70,8 +71,13 @@ class AllocationBuilder final {
Store(access, jsgraph()->Constant(value));
}
Node* allocation() const { return allocation_; }
Node* effect() const { return effect_; }
void Finish(Node* node) {
NodeProperties::SetType(allocation_, NodeProperties::GetType(node));
node->ReplaceInput(0, allocation_);
node->ReplaceInput(1, effect_);
node->TrimInputCount(2);
NodeProperties::ChangeOp(node, jsgraph()->common()->FinishRegion());
}
protected:
JSGraph* jsgraph() { return jsgraph_; }
@ -1274,13 +1280,8 @@ Reduction JSTypedLowering::ReduceJSCreateFunctionContext(Node* node) {
for (int i = Context::MIN_CONTEXT_SLOTS; i < context_length; ++i) {
a.Store(AccessBuilder::ForContextSlot(i), jsgraph()->TheHoleConstant());
}
// TODO(mstarzinger): We could mutate {node} into the allocation instead.
NodeProperties::SetType(a.allocation(), NodeProperties::GetType(node));
ReplaceWithValue(node, node, a.effect());
node->ReplaceInput(0, a.allocation());
node->ReplaceInput(1, a.effect());
node->TrimInputCount(2);
NodeProperties::ChangeOp(node, common()->Finish(1));
RelaxControls(node);
a.Finish(node);
return Changed(node);
}
@ -1325,13 +1326,8 @@ Reduction JSTypedLowering::ReduceJSCreateWithContext(Node* node) {
a.Store(AccessBuilder::ForContextSlot(Context::PREVIOUS_INDEX), context);
a.Store(AccessBuilder::ForContextSlot(Context::EXTENSION_INDEX), input);
a.Store(AccessBuilder::ForContextSlot(Context::GLOBAL_OBJECT_INDEX), load);
// TODO(mstarzinger): We could mutate {node} into the allocation instead.
NodeProperties::SetType(a.allocation(), NodeProperties::GetType(node));
ReplaceWithValue(node, node, a.effect());
node->ReplaceInput(0, a.allocation());
node->ReplaceInput(1, a.effect());
node->TrimInputCount(2);
NodeProperties::ChangeOp(node, common()->Finish(1));
RelaxControls(node);
a.Finish(node);
return Changed(node);
}
@ -1366,13 +1362,8 @@ Reduction JSTypedLowering::ReduceJSCreateBlockContext(Node* node) {
for (int i = Context::MIN_CONTEXT_SLOTS; i < context_length; ++i) {
a.Store(AccessBuilder::ForContextSlot(i), jsgraph()->TheHoleConstant());
}
// TODO(mstarzinger): We could mutate {node} into the allocation instead.
NodeProperties::SetType(a.allocation(), NodeProperties::GetType(node));
ReplaceWithValue(node, node, a.effect());
node->ReplaceInput(0, a.allocation());
node->ReplaceInput(1, a.effect());
node->TrimInputCount(2);
NodeProperties::ChangeOp(node, common()->Finish(1));
RelaxControls(node);
a.Finish(node);
return Changed(node);
}

View File

@ -44,8 +44,8 @@
V(Phi) \
V(EffectSet) \
V(EffectPhi) \
V(ValueEffect) \
V(Finish) \
V(BeginRegion) \
V(FinishRegion) \
V(FrameState) \
V(StateValues) \
V(TypedStateValues) \

View File

@ -1394,6 +1394,8 @@ class ScheduleLateNodeVisitor {
// Schedule the node or a floating control structure.
if (IrOpcode::IsMergeOpcode(node->opcode())) {
ScheduleFloatingControl(block, node);
} else if (node->opcode() == IrOpcode::kFinishRegion) {
ScheduleRegion(block, node);
} else {
ScheduleNode(block, node);
}
@ -1572,6 +1574,34 @@ class ScheduleLateNodeVisitor {
scheduler_->FuseFloatingControl(block, node);
}
void ScheduleRegion(BasicBlock* block, Node* region_end) {
// We only allow regions of instructions connected into a linear
// effect chain. The only value allowed to be produced by a node
// in the chain must be the value consumed by the FinishRegion node.
// We schedule back to front; we first schedule FinishRegion.
CHECK_EQ(IrOpcode::kFinishRegion, region_end->opcode());
ScheduleNode(block, region_end);
// Schedule the chain.
Node* node = NodeProperties::GetEffectInput(region_end);
while (node->opcode() != IrOpcode::kBeginRegion) {
DCHECK_EQ(0, scheduler_->GetData(node)->unscheduled_count_);
DCHECK_EQ(1, node->op()->EffectInputCount());
DCHECK_EQ(1, node->op()->EffectOutputCount());
DCHECK_EQ(0, node->op()->ControlOutputCount());
// The value output (if there is any) must be consumed
// by the EndRegion node.
DCHECK(node->op()->ValueOutputCount() == 0 ||
node == region_end->InputAt(0));
ScheduleNode(block, node);
node = NodeProperties::GetEffectInput(node);
}
// Schedule the BeginRegion node.
DCHECK_EQ(0, scheduler_->GetData(node)->unscheduled_count_);
ScheduleNode(block, node);
}
void ScheduleNode(BasicBlock* block, Node* node) {
schedule_->PlanNode(block, node);
scheduler_->scheduled_nodes_[block->id().ToSize()].push_back(node);

View File

@ -554,13 +554,13 @@ Type* Typer::Visitor::TypeEffectSet(Node* node) {
}
Type* Typer::Visitor::TypeValueEffect(Node* node) {
Type* Typer::Visitor::TypeBeginRegion(Node* node) {
UNREACHABLE();
return nullptr;
}
Type* Typer::Visitor::TypeFinish(Node* node) { return Operand(node, 0); }
Type* Typer::Visitor::TypeFinishRegion(Node* node) { return Operand(node, 0); }
Type* Typer::Visitor::TypeFrameState(Node* node) {

View File

@ -413,10 +413,10 @@ void Verifier::Visitor::Check(Node* node) {
CHECK_LT(1, effect_count);
break;
}
case IrOpcode::kValueEffect:
case IrOpcode::kBeginRegion:
// TODO(rossberg): what are the constraints on these?
break;
case IrOpcode::kFinish: {
case IrOpcode::kFinishRegion: {
// TODO(rossberg): what are the constraints on these?
// Type must be subsumed by input type.
if (typing == TYPED) {

View File

@ -128,9 +128,10 @@ TARGET_TEST_P(ChangeLoweringCommonTest, ChangeFloat64ToTagged) {
Capture<Node*> heap_number;
EXPECT_THAT(
r.replacement(),
IsFinish(
IsFinishRegion(
AllOf(CaptureEq(&heap_number),
IsAllocateHeapNumber(IsValueEffect(value), graph()->start())),
IsAllocateHeapNumber(IsBeginRegion(graph()->start()),
graph()->start())),
IsStore(StoreRepresentation(kMachFloat64, kNoWriteBarrier),
CaptureEq(&heap_number),
IsIntPtrConstant(HeapNumber::kValueOffset - kHeapObjectTag),
@ -218,14 +219,15 @@ TARGET_TEST_F(ChangeLowering32Test, ChangeInt32ToTagged) {
EXPECT_THAT(
r.replacement(),
IsPhi(kMachAnyTagged,
IsFinish(AllOf(CaptureEq(&heap_number),
IsAllocateHeapNumber(_, CaptureEq(&if_true))),
IsStore(StoreRepresentation(kMachFloat64, kNoWriteBarrier),
CaptureEq(&heap_number),
IsIntPtrConstant(HeapNumber::kValueOffset -
kHeapObjectTag),
IsChangeInt32ToFloat64(value),
CaptureEq(&heap_number), CaptureEq(&if_true))),
IsFinishRegion(
AllOf(CaptureEq(&heap_number),
IsAllocateHeapNumber(_, CaptureEq(&if_true))),
IsStore(
StoreRepresentation(kMachFloat64, kNoWriteBarrier),
CaptureEq(&heap_number),
IsIntPtrConstant(HeapNumber::kValueOffset - kHeapObjectTag),
IsChangeInt32ToFloat64(value), CaptureEq(&heap_number),
CaptureEq(&if_true))),
IsProjection(0, AllOf(CaptureEq(&add),
IsInt32AddWithOverflow(value, value))),
IsMerge(AllOf(CaptureEq(&if_true), IsIfTrue(CaptureEq(&branch))),
@ -319,14 +321,15 @@ TARGET_TEST_F(ChangeLowering32Test, ChangeUint32ToTagged) {
IsPhi(
kMachAnyTagged,
IsWord32Shl(value, IsInt32Constant(kSmiTagSize + kSmiShiftSize)),
IsFinish(AllOf(CaptureEq(&heap_number),
IsAllocateHeapNumber(_, CaptureEq(&if_false))),
IsStore(StoreRepresentation(kMachFloat64, kNoWriteBarrier),
CaptureEq(&heap_number),
IsInt32Constant(HeapNumber::kValueOffset -
kHeapObjectTag),
IsChangeUint32ToFloat64(value),
CaptureEq(&heap_number), CaptureEq(&if_false))),
IsFinishRegion(
AllOf(CaptureEq(&heap_number),
IsAllocateHeapNumber(_, CaptureEq(&if_false))),
IsStore(
StoreRepresentation(kMachFloat64, kNoWriteBarrier),
CaptureEq(&heap_number),
IsInt32Constant(HeapNumber::kValueOffset - kHeapObjectTag),
IsChangeUint32ToFloat64(value), CaptureEq(&heap_number),
CaptureEq(&if_false))),
IsMerge(IsIfTrue(AllOf(
CaptureEq(&branch),
IsBranch(IsUint32LessThanOrEqual(
@ -443,14 +446,15 @@ TARGET_TEST_F(ChangeLowering64Test, ChangeUint32ToTagged) {
kMachAnyTagged,
IsWord64Shl(IsChangeUint32ToUint64(value),
IsInt64Constant(kSmiTagSize + kSmiShiftSize)),
IsFinish(AllOf(CaptureEq(&heap_number),
IsAllocateHeapNumber(_, CaptureEq(&if_false))),
IsStore(StoreRepresentation(kMachFloat64, kNoWriteBarrier),
CaptureEq(&heap_number),
IsInt64Constant(HeapNumber::kValueOffset -
kHeapObjectTag),
IsChangeUint32ToFloat64(value),
CaptureEq(&heap_number), CaptureEq(&if_false))),
IsFinishRegion(
AllOf(CaptureEq(&heap_number),
IsAllocateHeapNumber(_, CaptureEq(&if_false))),
IsStore(
StoreRepresentation(kMachFloat64, kNoWriteBarrier),
CaptureEq(&heap_number),
IsInt64Constant(HeapNumber::kValueOffset - kHeapObjectTag),
IsChangeUint32ToFloat64(value), CaptureEq(&heap_number),
CaptureEq(&if_false))),
IsMerge(IsIfTrue(AllOf(
CaptureEq(&branch),
IsBranch(IsUint32LessThanOrEqual(

View File

@ -358,28 +358,24 @@ TEST_F(CommonOperatorTest, NumberConstant) {
}
TEST_F(CommonOperatorTest, ValueEffect) {
TRACED_FOREACH(int, arguments, kArguments) {
const Operator* op = common()->ValueEffect(arguments);
EXPECT_EQ(arguments, op->ValueInputCount());
EXPECT_EQ(arguments, OperatorProperties::GetTotalInputCount(op));
EXPECT_EQ(0, op->ControlOutputCount());
EXPECT_EQ(1, op->EffectOutputCount());
EXPECT_EQ(0, op->ValueOutputCount());
}
TEST_F(CommonOperatorTest, BeginRegion) {
const Operator* op = common()->BeginRegion();
EXPECT_EQ(1, op->EffectInputCount());
EXPECT_EQ(1, OperatorProperties::GetTotalInputCount(op));
EXPECT_EQ(0, op->ControlOutputCount());
EXPECT_EQ(1, op->EffectOutputCount());
EXPECT_EQ(0, op->ValueOutputCount());
}
TEST_F(CommonOperatorTest, Finish) {
TRACED_FOREACH(int, arguments, kArguments) {
const Operator* op = common()->Finish(arguments);
EXPECT_EQ(1, op->ValueInputCount());
EXPECT_EQ(arguments, op->EffectInputCount());
EXPECT_EQ(arguments + 1, OperatorProperties::GetTotalInputCount(op));
EXPECT_EQ(0, op->ControlOutputCount());
EXPECT_EQ(0, op->EffectOutputCount());
EXPECT_EQ(1, op->ValueOutputCount());
}
TEST_F(CommonOperatorTest, FinishRegion) {
const Operator* op = common()->FinishRegion();
EXPECT_EQ(1, op->ValueInputCount());
EXPECT_EQ(1, op->EffectInputCount());
EXPECT_EQ(2, OperatorProperties::GetTotalInputCount(op));
EXPECT_EQ(0, op->ControlOutputCount());
EXPECT_EQ(1, op->EffectOutputCount());
EXPECT_EQ(1, op->ValueOutputCount());
}
} // namespace compiler

View File

@ -241,13 +241,14 @@ TARGET_TEST_F(InstructionSelectorTest, ReferenceParameter) {
// -----------------------------------------------------------------------------
// Finish.
// FinishRegion.
TARGET_TEST_F(InstructionSelectorTest, Finish) {
TARGET_TEST_F(InstructionSelectorTest, FinishRegion) {
StreamBuilder m(this, kMachAnyTagged, kMachAnyTagged);
Node* param = m.Parameter(0);
Node* finish = m.AddNode(m.common()->Finish(1), param, m.graph()->start());
Node* finish =
m.AddNode(m.common()->FinishRegion(), param, m.graph()->start());
m.Return(finish);
Stream s = m.Build(kAllInstructions);
ASSERT_EQ(4U, s.size());
@ -333,8 +334,9 @@ TARGET_TEST_F(InstructionSelectorTest, ValueEffect) {
Stream s1 = m1.Build(kAllInstructions);
StreamBuilder m2(this, kMachInt32, kMachPtr);
Node* p2 = m2.Parameter(0);
m2.Return(m2.AddNode(m2.machine()->Load(kMachInt32), p2, m2.Int32Constant(0),
m2.AddNode(m2.common()->ValueEffect(1), p2)));
m2.Return(
m2.AddNode(m2.machine()->Load(kMachInt32), p2, m2.Int32Constant(0),
m2.AddNode(m2.common()->BeginRegion(), m2.graph()->start())));
Stream s2 = m2.Build(kAllInstructions);
EXPECT_LE(3U, s1.size());
ASSERT_EQ(s1.size(), s2.size());

View File

@ -1066,10 +1066,10 @@ TEST_F(JSTypedLoweringTest, JSCreateFunctionContextViaInlinedAllocation) {
context, effect, control));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(),
IsFinish(IsAllocate(IsNumberConstant(Context::SizeFor(
8 + Context::MIN_CONTEXT_SLOTS)),
effect, control),
_));
IsFinishRegion(IsAllocate(IsNumberConstant(Context::SizeFor(
8 + Context::MIN_CONTEXT_SLOTS)),
IsBeginRegion(effect), control),
_));
}
@ -1106,10 +1106,10 @@ TEST_F(JSTypedLoweringTest, JSCreateWithContext) {
closure, context, frame_state, effect, control));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(),
IsFinish(IsAllocate(IsNumberConstant(Context::SizeFor(
Context::MIN_CONTEXT_SLOTS)),
effect, control),
_));
IsFinishRegion(IsAllocate(IsNumberConstant(Context::SizeFor(
Context::MIN_CONTEXT_SLOTS)),
IsBeginRegion(effect), control),
_));
}
} // namespace compiler

View File

@ -261,11 +261,34 @@ class IsControl3Matcher final : public NodeMatcher {
};
class IsFinishMatcher final : public NodeMatcher {
class IsBeginRegionMatcher final : public NodeMatcher {
public:
IsFinishMatcher(const Matcher<Node*>& value_matcher,
const Matcher<Node*>& effect_matcher)
: NodeMatcher(IrOpcode::kFinish),
explicit IsBeginRegionMatcher(const Matcher<Node*>& effect_matcher)
: NodeMatcher(IrOpcode::kBeginRegion), effect_matcher_(effect_matcher) {}
void DescribeTo(std::ostream* os) const final {
NodeMatcher::DescribeTo(os);
*os << " whose effect (";
effect_matcher_.DescribeTo(os);
*os << ")";
}
bool MatchAndExplain(Node* node, MatchResultListener* listener) const final {
return (NodeMatcher::MatchAndExplain(node, listener) &&
PrintMatchAndExplain(NodeProperties::GetEffectInput(node), "effect",
effect_matcher_, listener));
}
private:
const Matcher<Node*> effect_matcher_;
};
class IsFinishRegionMatcher final : public NodeMatcher {
public:
IsFinishRegionMatcher(const Matcher<Node*>& value_matcher,
const Matcher<Node*>& effect_matcher)
: NodeMatcher(IrOpcode::kFinishRegion),
value_matcher_(value_matcher),
effect_matcher_(effect_matcher) {}
@ -1501,14 +1524,14 @@ Matcher<Node*> IsIfDefault(const Matcher<Node*>& control_matcher) {
}
Matcher<Node*> IsValueEffect(const Matcher<Node*>& value_matcher) {
return MakeMatcher(new IsUnopMatcher(IrOpcode::kValueEffect, value_matcher));
Matcher<Node*> IsBeginRegion(const Matcher<Node*>& effect_matcher) {
return MakeMatcher(new IsBeginRegionMatcher(effect_matcher));
}
Matcher<Node*> IsFinish(const Matcher<Node*>& value_matcher,
const Matcher<Node*>& effect_matcher) {
return MakeMatcher(new IsFinishMatcher(value_matcher, effect_matcher));
Matcher<Node*> IsFinishRegion(const Matcher<Node*>& value_matcher,
const Matcher<Node*>& effect_matcher) {
return MakeMatcher(new IsFinishRegionMatcher(value_matcher, effect_matcher));
}

View File

@ -63,9 +63,9 @@ Matcher<Node*> IsSwitch(const Matcher<Node*>& value_matcher,
Matcher<Node*> IsIfValue(const Matcher<int32_t>& value_matcher,
const Matcher<Node*>& control_matcher);
Matcher<Node*> IsIfDefault(const Matcher<Node*>& control_matcher);
Matcher<Node*> IsValueEffect(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsFinish(const Matcher<Node*>& value_matcher,
const Matcher<Node*>& effect_matcher);
Matcher<Node*> IsBeginRegion(const Matcher<Node*>& effect_matcher);
Matcher<Node*> IsFinishRegion(const Matcher<Node*>& value_matcher,
const Matcher<Node*>& effect_matcher);
Matcher<Node*> IsReturn(const Matcher<Node*>& value_matcher,
const Matcher<Node*>& effect_matcher,
const Matcher<Node*>& control_matcher);