From 6881d7609a22eb05b450f626a62259e2e062c543 Mon Sep 17 00:00:00 2001 From: mstarzinger Date: Fri, 20 Feb 2015 01:54:49 -0800 Subject: [PATCH] Model exceptional edges from call nodes in TurboFan. R=titzer@chromium.org,bmeurer@chromium.org Review URL: https://codereview.chromium.org/928213003 Cr-Commit-Position: refs/heads/master@{#26766} --- src/compiler/ast-graph-builder.cc | 37 ++++++-- src/compiler/ast-graph-builder.h | 3 + src/compiler/change-lowering.cc | 1 + src/compiler/common-operator.cc | 5 +- src/compiler/common-operator.h | 2 + src/compiler/control-reducer.cc | 12 ++- src/compiler/instruction-selector.cc | 40 ++++----- src/compiler/js-intrinsic-lowering.cc | 3 +- src/compiler/js-operator.cc | 43 +++++----- src/compiler/js-typed-lowering.cc | 26 ++++-- src/compiler/node-properties.cc | 25 ++++-- src/compiler/node-properties.h | 6 +- src/compiler/opcodes.h | 10 ++- src/compiler/operator-properties.cc | 3 +- src/compiler/operator.h | 4 + src/compiler/schedule.cc | 27 ++++-- src/compiler/schedule.h | 5 ++ src/compiler/scheduler.cc | 98 ++++++++++++++++------ src/compiler/verifier.cc | 9 ++ test/cctest/compiler/test-run-jsexceptions.cc | 22 +++++ .../unittests/compiler/common-operator-unittest.cc | 2 + test/unittests/compiler/js-operator-unittest.cc | 82 +++++++++--------- .../unittests/compiler/node-properties-unittest.cc | 57 +++++++++++++ test/unittests/compiler/schedule-unittest.cc | 33 +++++++- 24 files changed, 406 insertions(+), 149 deletions(-) diff --git a/src/compiler/ast-graph-builder.cc b/src/compiler/ast-graph-builder.cc index 4b1fa1c..47532a5 100644 --- a/src/compiler/ast-graph-builder.cc +++ b/src/compiler/ast-graph-builder.cc @@ -328,7 +328,12 @@ class AstGraphBuilder::ControlScopeForIteration : public ControlScope { 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 { @@ -354,7 +359,12 @@ class AstGraphBuilder::ControlScopeForFinally : public ControlScope { 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 { @@ -379,6 +389,7 @@ AstGraphBuilder::AstGraphBuilder(Zone* local_zone, CompilationInfo* info, globals_(0, local_zone), execution_control_(nullptr), execution_context_(nullptr), + try_nesting_level_(0), input_buffer_size_(0), input_buffer_(nullptr), exit_control_(nullptr), @@ -2974,6 +2985,7 @@ Node* AstGraphBuilder::MakeNode(const Operator* op, int value_input_count, 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; @@ -3001,9 +3013,24 @@ Node* AstGraphBuilder::MakeNode(const Operator* op, int value_input_count, 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); + } } } diff --git a/src/compiler/ast-graph-builder.h b/src/compiler/ast-graph-builder.h index ebeb6c6..c79d9c3 100644 --- a/src/compiler/ast-graph-builder.h +++ b/src/compiler/ast-graph-builder.h @@ -84,6 +84,9 @@ class AstGraphBuilder : public AstVisitor { SetOncePointer function_closure_; SetOncePointer 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_; diff --git a/src/compiler/change-lowering.cc b/src/compiler/change-lowering.cc index 0057b10..6b39e92 100644 --- a/src/compiler/change-lowering.cc +++ b/src/compiler/change-lowering.cc @@ -202,6 +202,7 @@ bool CanCover(Node* value, IrOpcode::Value opcode) { 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; diff --git a/src/compiler/common-operator.cc b/src/compiler/common-operator.cc index 8aea3df..5641db1 100644 --- a/src/compiler/common-operator.cc +++ b/src/compiler/common-operator.cc @@ -115,6 +115,8 @@ size_t ProjectionIndexOf(const Operator* const op) { 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) \ @@ -495,7 +497,8 @@ const Operator* CommonOperatorBuilder::Call(const CallDescriptor* descriptor) { 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() << "]"; diff --git a/src/compiler/common-operator.h b/src/compiler/common-operator.h index 9f2c575..e74c629 100644 --- a/src/compiler/common-operator.h +++ b/src/compiler/common-operator.h @@ -172,6 +172,8 @@ class CommonOperatorBuilder FINAL : public ZoneObject { 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(); diff --git a/src/compiler/control-reducer.cc b/src/compiler/control-reducer.cc index d20c8dd..136064f 100644 --- a/src/compiler/control-reducer.cc +++ b/src/compiler/control-reducer.cc @@ -131,7 +131,7 @@ class ControlReducerImpl { 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); @@ -155,7 +155,7 @@ class ControlReducerImpl { // 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. } @@ -190,16 +190,14 @@ class ControlReducerImpl { 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; } } diff --git a/src/compiler/instruction-selector.cc b/src/compiler/instruction-selector.cc index c170a01..2d0b2bd 100644 --- a/src/compiler/instruction-selector.cc +++ b/src/compiler/instruction-selector.cc @@ -485,34 +485,34 @@ void InstructionSelector::VisitBlock(BasicBlock* block) { } -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); @@ -524,9 +524,6 @@ void InstructionSelector::VisitControl(BasicBlock* block) { // 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); @@ -541,9 +538,6 @@ void InstructionSelector::VisitControl(BasicBlock* block) { 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, @@ -721,6 +715,8 @@ void InstructionSelector::VisitNode(Node* node) { case IrOpcode::kBranch: case IrOpcode::kIfTrue: case IrOpcode::kIfFalse: + case IrOpcode::kIfSuccess: + case IrOpcode::kIfException: case IrOpcode::kSwitch: case IrOpcode::kIfValue: case IrOpcode::kIfDefault: diff --git a/src/compiler/js-intrinsic-lowering.cc b/src/compiler/js-intrinsic-lowering.cc index a1e6935..bbddc99 100644 --- a/src/compiler/js-intrinsic-lowering.cc +++ b/src/compiler/js-intrinsic-lowering.cc @@ -145,7 +145,6 @@ Reduction JSIntrinsicLowering::ReduceInlineValueOf(Node* node) { 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); @@ -156,7 +155,7 @@ Reduction JSIntrinsicLowering::ReduceInlineValueOf(Node* node) { 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); diff --git a/src/compiler/js-operator.cc b/src/compiler/js-operator.cc index 72c3969..2e0de6b 100644 --- a/src/compiler/js-operator.cc +++ b/src/compiler/js-operator.cc @@ -254,7 +254,8 @@ struct JSOperatorGlobalCache FINAL { : 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) @@ -265,7 +266,7 @@ struct JSOperatorGlobalCache FINAL { StorePropertyOperator() : Operator1(IrOpcode::kJSStoreProperty, Operator::kNoProperties, "JSStoreProperty", 3, - 1, 1, 0, 1, 0, kLanguageMode) {} + 1, 1, 0, 1, 2, kLanguageMode) {} }; StorePropertyOperator kStorePropertySloppyOperator; StorePropertyOperator kStorePropertyStrictOperator; @@ -294,7 +295,7 @@ const Operator* JSOperatorBuilder::CallFunction(size_t arity, return new (zone()) Operator1( // -- 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 } @@ -307,7 +308,7 @@ const Operator* JSOperatorBuilder::CallRuntime(Runtime::FunctionId id, return new (zone()) Operator1( // -- 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 } @@ -316,7 +317,7 @@ const Operator* JSOperatorBuilder::CallConstruct(int arguments) { return new (zone()) Operator1( // -- IrOpcode::kJSCallConstruct, Operator::kNoProperties, // opcode "JSCallConstruct", // name - arguments, 1, 1, 1, 1, 0, // counts + arguments, 1, 1, 1, 1, 2, // counts arguments); // parameter } @@ -328,7 +329,7 @@ const Operator* JSOperatorBuilder::LoadNamed(const Unique& name, return new (zone()) Operator1( // -- IrOpcode::kJSLoadNamed, Operator::kNoProperties, // opcode "JSLoadNamed", // name - 1, 1, 1, 1, 1, 0, // counts + 1, 1, 1, 1, 1, 2, // counts parameters); // parameter } @@ -339,7 +340,7 @@ const Operator* JSOperatorBuilder::LoadProperty( return new (zone()) Operator1( // -- IrOpcode::kJSLoadProperty, Operator::kNoProperties, // opcode "JSLoadProperty", // name - 2, 1, 1, 1, 1, 0, // counts + 2, 1, 1, 1, 1, 2, // counts parameters); // parameter } @@ -361,7 +362,7 @@ const Operator* JSOperatorBuilder::StoreNamed(LanguageMode language_mode, return new (zone()) Operator1( // -- IrOpcode::kJSStoreNamed, Operator::kNoProperties, // opcode "JSStoreNamed", // name - 2, 1, 1, 0, 1, 0, // counts + 2, 1, 1, 0, 1, 2, // counts parameters); // parameter } @@ -370,7 +371,7 @@ const Operator* JSOperatorBuilder::DeleteProperty(LanguageMode language_mode) { return new (zone()) Operator1( // -- IrOpcode::kJSDeleteProperty, Operator::kNoProperties, // opcode "JSDeleteProperty", // name - 2, 1, 1, 1, 1, 0, // counts + 2, 1, 1, 1, 1, 2, // counts language_mode); // parameter } @@ -378,21 +379,23 @@ const Operator* JSOperatorBuilder::DeleteProperty(LanguageMode language_mode) { const Operator* JSOperatorBuilder::LoadContext(size_t depth, size_t index, bool immutable) { ContextAccess access(depth, index, immutable); - return new (zone()) Operator1( // -- - IrOpcode::kJSLoadContext, Operator::kNoWrite, // opcode - "JSLoadContext", // name - 1, 1, 0, 1, 1, 0, // counts - access); // parameter + return new (zone()) Operator1( // -- + 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( // -- - IrOpcode::kJSStoreContext, Operator::kNoRead, // opcode - "JSStoreContext", // name - 2, 1, 1, 0, 1, 0, // counts - access); // parameter + return new (zone()) Operator1( // -- + IrOpcode::kJSStoreContext, // opcode + Operator::kNoRead | Operator::kNoThrow, // flags + "JSStoreContext", // name + 2, 1, 1, 0, 1, 0, // counts + access); // parameter } @@ -401,7 +404,7 @@ const Operator* JSOperatorBuilder::CreateCatchContext( return new (zone()) Operator1>( // -- IrOpcode::kJSCreateCatchContext, Operator::kNoProperties, // opcode "JSCreateCatchContext", // name - 2, 1, 1, 1, 1, 0, // counts + 2, 1, 1, 1, 1, 2, // counts name); // parameter } diff --git a/src/compiler/js-typed-lowering.cc b/src/compiler/js-typed-lowering.cc index bbe46fb..5766936 100644 --- a/src/compiler/js-typed-lowering.cc +++ b/src/compiler/js-typed-lowering.cc @@ -19,15 +19,23 @@ namespace compiler { // - 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()); @@ -116,9 +124,9 @@ class JSBinopReduction FINAL { 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_); @@ -584,7 +592,7 @@ Reduction JSTypedLowering::ReduceJSToNumber(Node* 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 @@ -616,7 +624,7 @@ Reduction JSTypedLowering::ReduceJSToNumber(Node* node) { 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) { @@ -640,7 +648,7 @@ Reduction JSTypedLowering::ReduceJSToNumber(Node* node) { 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()); @@ -796,6 +804,7 @@ Reduction JSTypedLowering::ReduceJSStoreProperty(Node* node) { node->ReplaceInput(3, effect); node->ReplaceInput(4, control); node->TrimInputCount(5); + RelaxControls(node); return Changed(node); } // Compute byte offset. @@ -809,6 +818,7 @@ Reduction JSTypedLowering::ReduceJSStoreProperty(Node* node) { node->ReplaceInput(4, effect); node->ReplaceInput(5, control); node->TrimInputCount(6); + RelaxControls(node); return Changed(node); } } diff --git a/src/compiler/node-properties.cc b/src/compiler/node-properties.cc index 47de74e..44e1448 100644 --- a/src/compiler/node-properties.cc +++ b/src/compiler/node-properties.cc @@ -152,14 +152,18 @@ void NodeProperties::RemoveNonValueInputs(Node* 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 { @@ -185,16 +189,13 @@ Node* NodeProperties::FindProjection(Node* node, size_t projection_index) { void NodeProperties::CollectControlProjections(Node* node, Node** projections, size_t projection_count) { #ifdef DEBUG - DCHECK_EQ(static_cast(projection_count), node->UseCount()); + DCHECK_LE(static_cast(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; @@ -203,6 +204,14 @@ void NodeProperties::CollectControlProjections(Node* node, Node** projections, 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++; @@ -211,6 +220,8 @@ void NodeProperties::CollectControlProjections(Node* node, Node** projections, 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); diff --git a/src/compiler/node-properties.h b/src/compiler/node-properties.h index a13eea3..47396fb 100644 --- a/src/compiler/node-properties.h +++ b/src/compiler/node-properties.h @@ -81,7 +81,8 @@ class NodeProperties FINAL { 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); @@ -91,8 +92,9 @@ class NodeProperties FINAL { 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); diff --git a/src/compiler/opcodes.h b/src/compiler/opcodes.h index a4f8d3e..31ef304 100644 --- a/src/compiler/opcodes.h +++ b/src/compiler/opcodes.h @@ -12,6 +12,8 @@ V(Branch) \ V(IfTrue) \ V(IfFalse) \ + V(IfSuccess) \ + V(IfException) \ V(Switch) \ V(IfValue) \ V(IfDefault) \ @@ -299,8 +301,12 @@ class IrOpcode { 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; } }; diff --git a/src/compiler/operator-properties.cc b/src/compiler/operator-properties.cc index 53bd16c..64f384b 100644 --- a/src/compiler/operator-properties.cc +++ b/src/compiler/operator-properties.cc @@ -100,7 +100,8 @@ bool OperatorProperties::IsBasicBlockBegin(const Operator* op) { 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; } diff --git a/src/compiler/operator.h b/src/compiler/operator.h index 6407499..114cf6a 100644 --- a/src/compiler/operator.h +++ b/src/compiler/operator.h @@ -97,6 +97,10 @@ class Operator : public ZoneObject { 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; } diff --git a/src/compiler/schedule.cc b/src/compiler/schedule.cc index 8924ae5..ec0caf0 100644 --- a/src/compiler/schedule.cc +++ b/src/compiler/schedule.cc @@ -100,6 +100,8 @@ std::ostream& operator<<(std::ostream& os, const BasicBlock::Control& c) { return os << "none"; case BasicBlock::kGoto: return os << "goto"; + case BasicBlock::kCall: + return os << "call"; case BasicBlock::kBranch: return os << "branch"; case BasicBlock::kSwitch: @@ -194,16 +196,27 @@ void Schedule::AddNode(BasicBlock* block, Node* node) { 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); @@ -224,7 +237,7 @@ void Schedule::AddSwitch(BasicBlock* block, Node* sw, BasicBlock** succ_blocks, 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()); @@ -232,7 +245,7 @@ void Schedule::AddReturn(BasicBlock* block, Node* input) { 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()); @@ -241,8 +254,8 @@ void Schedule::AddThrow(BasicBlock* block, Node* input) { 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); diff --git a/src/compiler/schedule.h b/src/compiler/schedule.h index d4d6453..d46825e 100644 --- a/src/compiler/schedule.h +++ b/src/compiler/schedule.h @@ -32,6 +32,7 @@ class BasicBlock FINAL : public ZoneObject { 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. @@ -233,6 +234,10 @@ class Schedule FINAL : public ZoneObject { // 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); diff --git a/src/compiler/scheduler.cc b/src/compiler/scheduler.cc index 6e105e3..8cbc05e 100644 --- a/src/compiler/scheduler.cc +++ b/src/compiler/scheduler.cc @@ -286,7 +286,7 @@ class CFGBuilder : public ZoneObject { } private: - // TODO(mstarzinger): Only for Scheduler::FuseFloatingControl. + friend class ScheduleLateNodeVisitor; friend class Scheduler; void FixNode(BasicBlock* block, Node* node) { @@ -320,6 +320,11 @@ class CFGBuilder : public ZoneObject { case IrOpcode::kSwitch: BuildBlocksForSuccessors(node); break; + case IrOpcode::kCall: + if (IsExceptionalCall(node)) { + BuildBlocksForSuccessors(node); + } + break; default: break; } @@ -347,6 +352,12 @@ class CFGBuilder : public ZoneObject { scheduler_->UpdatePlacement(node, Scheduler::kFixed); ConnectThrow(node); break; + case IrOpcode::kCall: + if (IsExceptionalCall(node)) { + scheduler_->UpdatePlacement(node, Scheduler::kFixed); + ConnectCall(node); + } + break; default: break; } @@ -381,6 +392,31 @@ class CFGBuilder : public ZoneObject { } } + 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, @@ -404,10 +440,8 @@ class CFGBuilder : public ZoneObject { 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], @@ -428,14 +462,12 @@ class CFGBuilder : public ZoneObject { 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); } } @@ -448,22 +480,22 @@ class CFGBuilder : public ZoneObject { // 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); } @@ -479,6 +511,13 @@ class CFGBuilder : public ZoneObject { } } + 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)); @@ -1324,7 +1363,7 @@ class ScheduleLateNodeVisitor { } // Schedule the node or a floating control structure. - if (NodeProperties::IsControl(node)) { + if (IrOpcode::IsMergeOpcode(node->opcode())) { ScheduleFloatingControl(block, node); } else { ScheduleNode(block, node); @@ -1448,10 +1487,13 @@ class ScheduleLateNodeVisitor { 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) { @@ -1460,15 +1502,23 @@ class ScheduleLateNodeVisitor { 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); diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc index 9480afb..fe19eaa 100644 --- a/src/compiler/verifier.cc +++ b/src/compiler/verifier.cc @@ -228,6 +228,15 @@ void Verifier::Visitor::Check(Node* node) { // 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; diff --git a/test/cctest/compiler/test-run-jsexceptions.cc b/test/cctest/compiler/test-run-jsexceptions.cc index 74990da..7610962 100644 --- a/test/cctest/compiler/test-run-jsexceptions.cc +++ b/test/cctest/compiler/test-run-jsexceptions.cc @@ -118,6 +118,28 @@ TEST(CatchBreak) { } +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 = diff --git a/test/unittests/compiler/common-operator-unittest.cc b/test/unittests/compiler/common-operator-unittest.cc index 6e60cfd..c0d25ea 100644 --- a/test/unittests/compiler/common-operator-unittest.cc +++ b/test/unittests/compiler/common-operator-unittest.cc @@ -53,6 +53,8 @@ const SharedOperator kSharedOperators[] = { 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 diff --git a/test/unittests/compiler/js-operator-unittest.cc b/test/unittests/compiler/js-operator-unittest.cc index 8f4622a..ef0a4df 100644 --- a/test/unittests/compiler/js-operator-unittest.cc +++ b/test/unittests/compiler/js-operator-unittest.cc @@ -28,6 +28,7 @@ struct SharedOperator { int control_input_count; int value_output_count; int effect_output_count; + int control_output_count; }; @@ -39,48 +40,49 @@ std::ostream& operator<<(std::ostream& os, const SharedOperator& sop) { 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 }; @@ -122,7 +124,7 @@ TEST_P(JSSharedOperatorTest, NumberOfInputsAndOutputs) { 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()); } @@ -181,7 +183,7 @@ TEST_P(JSStorePropertyOperatorTest, NumberOfInputsAndOutputs) { EXPECT_EQ(0, op->ValueOutputCount()); EXPECT_EQ(1, op->EffectOutputCount()); - EXPECT_EQ(0, op->ControlOutputCount()); + EXPECT_EQ(2, op->ControlOutputCount()); } diff --git a/test/unittests/compiler/node-properties-unittest.cc b/test/unittests/compiler/node-properties-unittest.cc index bb471bd..f4ce0d3 100644 --- a/test/unittests/compiler/node-properties-unittest.cc +++ b/test/unittests/compiler/node-properties-unittest.cc @@ -8,6 +8,7 @@ #include "testing/gmock/include/gmock/gmock.h" using testing::AnyOf; +using testing::ElementsAre; using testing::IsNull; namespace v8 { @@ -17,6 +18,62 @@ namespace compiler { 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); diff --git a/test/unittests/compiler/schedule-unittest.cc b/test/unittests/compiler/schedule-unittest.cc index 70fd4d5..bc82535 100644 --- a/test/unittests/compiler/schedule-unittest.cc +++ b/test/unittests/compiler/schedule-unittest.cc @@ -73,8 +73,10 @@ typedef TestWithZone ScheduleTest; 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); @@ -135,6 +137,35 @@ TEST_F(ScheduleTest, AddGoto) { } +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(); -- 2.7.4