From 566cdc3bcd221768f1150b129dcd68815b094128 Mon Sep 17 00:00:00 2001 From: "sigurds@chromium.org" Date: Wed, 20 Aug 2014 13:05:03 +0000 Subject: [PATCH] Reland "Add initial support for inlining." Reland Fixes: * Remove usage of C++11 vector members. * Guard tests by V8_TURBO_TARGET. Changes: * Make context specialization clean up after itself. * Add UpdateToAndIncrement to Inputs::iterator. Uses:iterator already provides this member function. * Allow next node id in graph to be set. R=titzer@chromium.org, mstarzinger@chromium.org Review URL: https://codereview.chromium.org/484083003 git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@23231 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- BUILD.gn | 2 + src/compiler/generic-graph.h | 1 + src/compiler/generic-node.h | 6 + src/compiler/js-context-specialization.cc | 29 +-- src/compiler/js-inlining.cc | 329 ++++++++++++++++++++++++++++++ src/compiler/js-inlining.h | 34 +++ src/compiler/js-typed-lowering.cc | 35 +--- src/compiler/node-properties-inl.h | 22 ++ src/compiler/node-properties.h | 2 + src/compiler/pipeline.cc | 13 +- src/flag-definitions.h | 2 + test/cctest/cctest.gyp | 1 + test/cctest/compiler/test-run-inlining.cc | 184 +++++++++++++++++ tools/gyp/v8.gyp | 2 + 14 files changed, 609 insertions(+), 53 deletions(-) create mode 100644 src/compiler/js-inlining.cc create mode 100644 src/compiler/js-inlining.h create mode 100644 test/cctest/compiler/test-run-inlining.cc diff --git a/BUILD.gn b/BUILD.gn index 087f373..6d9a7af 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -499,6 +499,8 @@ source_set("v8_base") { "src/compiler/js-generic-lowering.h", "src/compiler/js-graph.cc", "src/compiler/js-graph.h", + "src/compiler/js-inlining.cc", + "src/compiler/js-inlining.h", "src/compiler/js-operator.h", "src/compiler/js-typed-lowering.cc", "src/compiler/js-typed-lowering.h", diff --git a/src/compiler/generic-graph.h b/src/compiler/generic-graph.h index a555456..e8de9eb 100644 --- a/src/compiler/generic-graph.h +++ b/src/compiler/generic-graph.h @@ -22,6 +22,7 @@ class GenericGraphBase : public ZoneObject { NodeId NextNodeID() { return next_node_id_++; } NodeId NodeCount() const { return next_node_id_; } + void SetNextNodeId(NodeId next) { next_node_id_ = next; } private: Zone* zone_; diff --git a/src/compiler/generic-node.h b/src/compiler/generic-node.h index 287d852..aadee76 100644 --- a/src/compiler/generic-node.h +++ b/src/compiler/generic-node.h @@ -204,6 +204,12 @@ class GenericNode::Inputs::iterator { ++index_; return *this; } + iterator& UpdateToAndIncrement(GenericNode* new_to) { + typename GenericNode::Input* input = GetInput(); + input->Update(new_to); + index_++; + return *this; + } int index() { return index_; } private: diff --git a/src/compiler/js-context-specialization.cc b/src/compiler/js-context-specialization.cc index bdf1427..b54d5d9 100644 --- a/src/compiler/js-context-specialization.cc +++ b/src/compiler/js-context-specialization.cc @@ -15,26 +15,6 @@ namespace v8 { namespace internal { namespace compiler { -// TODO(titzer): factor this out to a common routine with js-typed-lowering. -static void ReplaceEffectfulWithValue(Node* node, Node* value) { - Node* effect = NULL; - if (OperatorProperties::HasEffectInput(node->op())) { - effect = NodeProperties::GetEffectInput(node); - } - - // Requires distinguishing between value and effect edges. - UseIter iter = node->uses().begin(); - while (iter != node->uses().end()) { - if (NodeProperties::IsEffectEdge(iter.edge())) { - DCHECK_NE(NULL, effect); - iter = iter.UpdateToAndIncrement(effect); - } else { - iter = iter.UpdateToAndIncrement(value); - } - } -} - - class ContextSpecializationVisitor : public NullNodeVisitor { public: explicit ContextSpecializationVisitor(JSContextSpecializer* spec) @@ -45,14 +25,16 @@ class ContextSpecializationVisitor : public NullNodeVisitor { case IrOpcode::kJSLoadContext: { Reduction r = spec_->ReduceJSLoadContext(node); if (r.Changed() && r.replacement() != node) { - ReplaceEffectfulWithValue(node, r.replacement()); + NodeProperties::ReplaceWithValue(node, r.replacement()); + node->RemoveAllInputs(); } break; } case IrOpcode::kJSStoreContext: { Reduction r = spec_->ReduceJSStoreContext(node); if (r.Changed() && r.replacement() != node) { - ReplaceEffectfulWithValue(node, r.replacement()); + NodeProperties::ReplaceWithValue(node, r.replacement()); + node->RemoveAllInputs(); } break; } @@ -68,7 +50,8 @@ class ContextSpecializationVisitor : public NullNodeVisitor { void JSContextSpecializer::SpecializeToContext() { - ReplaceEffectfulWithValue(context_, jsgraph_->Constant(info_->context())); + NodeProperties::ReplaceWithValue(context_, + jsgraph_->Constant(info_->context())); ContextSpecializationVisitor visitor(this); jsgraph_->graph()->VisitNodeInputsFromEnd(&visitor); diff --git a/src/compiler/js-inlining.cc b/src/compiler/js-inlining.cc new file mode 100644 index 0000000..542b7e5 --- /dev/null +++ b/src/compiler/js-inlining.cc @@ -0,0 +1,329 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/compiler/ast-graph-builder.h" +#include "src/compiler/common-operator.h" +#include "src/compiler/generic-node-inl.h" +#include "src/compiler/graph-inl.h" +#include "src/compiler/graph-visualizer.h" +#include "src/compiler/js-inlining.h" +#include "src/compiler/js-operator.h" +#include "src/compiler/node-aux-data-inl.h" +#include "src/compiler/node-matchers.h" +#include "src/compiler/node-properties-inl.h" +#include "src/compiler/simplified-operator.h" +#include "src/compiler/typer.h" +#include "src/parser.h" +#include "src/rewriter.h" +#include "src/scopes.h" + + +namespace v8 { +namespace internal { +namespace compiler { + +class InlinerVisitor : public NullNodeVisitor { + public: + explicit InlinerVisitor(JSInliner* inliner) : inliner_(inliner) {} + + GenericGraphVisit::Control Post(Node* node) { + switch (node->opcode()) { + case IrOpcode::kJSCallFunction: + inliner_->TryInlineCall(node); + break; + default: + break; + } + return GenericGraphVisit::CONTINUE; + } + + private: + JSInliner* inliner_; +}; + + +void JSInliner::Inline() { + InlinerVisitor visitor(this); + jsgraph_->graph()->VisitNodeInputsFromEnd(&visitor); +} + + +static void MoveWithDependencies(Graph* graph, Node* node, Node* old_block, + Node* new_block) { + if (OperatorProperties::HasControlInput(node->op())) { + // Check if we have left the old_block. + if (NodeProperties::GetControlInput(node) != old_block) return; + // If not, move this node to the new_block. + NodeProperties::ReplaceControlInput(node, new_block); + } + // Independent of whether a node has a control input or not, + // it might have a dependency that is pinned to old_block. + for (InputIter iter = node->inputs().begin(); iter != node->inputs().end(); + ++iter) { + if (NodeProperties::IsControlEdge(iter.edge())) continue; + MoveWithDependencies(graph, *iter, old_block, new_block); + } +} + + +static void MoveAllControlNodes(Node* from, Node* to) { + for (UseIter iter = from->uses().begin(); iter != from->uses().end();) { + if (NodeProperties::IsControlEdge(iter.edge())) { + iter.UpdateToAndIncrement(to); + } else { + ++iter; + } + } +} + + +// TODO(sigurds) Find a home for this function and reuse it everywhere (esp. in +// test cases, where similar code is currently duplicated). +static void Parse(Handle function, CompilationInfoWithZone* info) { + CHECK(Parser::Parse(info)); + StrictMode strict_mode = info->function()->strict_mode(); + info->SetStrictMode(strict_mode); + info->SetOptimizing(BailoutId::None(), Handle(function->code())); + CHECK(Rewriter::Rewrite(info)); + CHECK(Scope::Analyze(info)); + CHECK_NE(NULL, info->scope()); + Handle scope_info = ScopeInfo::Create(info->scope(), info->zone()); + info->shared_info()->set_scope_info(*scope_info); +} + + +// A facade on a JSFunction's graph to facilitate inlining. It assumes the +// that the function graph has only one return statement, and provides +// {UnifyReturn} to convert a function graph to that end. +// InlineAtCall will create some new nodes using {graph}'s builders (and hence +// those nodes will live in {graph}'s zone. +class Inlinee { + public: + explicit Inlinee(JSGraph* graph) : jsgraph_(graph) {} + + Graph* graph() { return jsgraph_->graph(); } + JSGraph* jsgraph() { return jsgraph_; } + + // Returns the last regular control node, that is + // the last control node before the end node. + Node* end_block() { return NodeProperties::GetControlInput(unique_return()); } + + // Return the effect output of the graph, + // that is the effect input of the return statement of the inlinee. + Node* effect_output() { + return NodeProperties::GetEffectInput(unique_return()); + } + // Return the value output of the graph, + // that is the value input of the return statement of the inlinee. + Node* value_output() { + return NodeProperties::GetValueInput(unique_return(), 0); + } + // Return the unique return statement of the graph. + Node* unique_return() { + Node* unique_return = + NodeProperties::GetControlInput(jsgraph_->graph()->end()); + DCHECK_EQ(IrOpcode::kReturn, unique_return->opcode()); + return unique_return; + } + // Inline this graph at {call}, use {jsgraph} and its zone to create + // any new nodes. + void InlineAtCall(JSGraph* jsgraph, Node* call); + // Ensure that only a single return reaches the end node. + void UnifyReturn(); + + private: + JSGraph* jsgraph_; +}; + + +void Inlinee::UnifyReturn() { + Graph* graph = jsgraph_->graph(); + + Node* final_merge = NodeProperties::GetControlInput(graph->end(), 0); + if (final_merge->opcode() == IrOpcode::kReturn) { + // nothing to do + return; + } + DCHECK_EQ(IrOpcode::kMerge, final_merge->opcode()); + + int predecessors = + OperatorProperties::GetControlInputCount(final_merge->op()); + Operator* op_phi = jsgraph_->common()->Phi(predecessors); + Operator* op_ephi = jsgraph_->common()->EffectPhi(predecessors); + + NodeVector values(NodeVector::allocator_type(jsgraph_->zone())); + NodeVector effects(NodeVector::allocator_type(jsgraph_->zone())); + // Iterate over all control flow predecessors, + // which must be return statements. + InputIter iter = final_merge->inputs().begin(); + while (iter != final_merge->inputs().end()) { + Node* input = *iter; + switch (input->opcode()) { + case IrOpcode::kReturn: + values.push_back(NodeProperties::GetValueInput(input, 0)); + effects.push_back(NodeProperties::GetEffectInput(input)); + iter.UpdateToAndIncrement(NodeProperties::GetControlInput(input)); + input->RemoveAllInputs(); + break; + default: + UNREACHABLE(); + ++iter; + break; + } + } + values.push_back(final_merge); + effects.push_back(final_merge); + Node* phi = + graph->NewNode(op_phi, static_cast(values.size()), &values.front()); + Node* ephi = graph->NewNode(op_ephi, static_cast(effects.size()), + &effects.front()); + Node* new_return = + graph->NewNode(jsgraph_->common()->Return(), phi, ephi, final_merge); + graph->end()->ReplaceInput(0, new_return); +} + + +void Inlinee::InlineAtCall(JSGraph* jsgraph, Node* call) { + MachineOperatorBuilder machine(jsgraph->zone()); + + Node* control = NodeProperties::GetControlInput(call); + // Move all the nodes to the end block. + MoveAllControlNodes(control, end_block()); + // Now move the ones the call depends on back up. + // We have to do this back-and-forth to treat the case where the call is + // pinned to the start block. + MoveWithDependencies(graph(), call, end_block(), control); + + // The inlinee uses the context from the JSFunction object. This will + // also be the effect dependency for the inlinee as it produces an effect. + // TODO(sigurds) Use simplified load once it is ready. + Node* context = jsgraph->graph()->NewNode( + machine.Load(kMachAnyTagged), NodeProperties::GetValueInput(call, 0), + jsgraph->Int32Constant(JSFunction::kContextOffset - kHeapObjectTag), + NodeProperties::GetEffectInput(call)); + + // {inlinee_inputs} counts JSFunction, Receiver, arguments, context, + // but not effect, control. + int inlinee_inputs = graph()->start()->op()->OutputCount(); + // Context is last argument. + int inlinee_context_index = inlinee_inputs - 1; + // {inliner_inputs} counts JSFunction, Receiver, arguments, but not + // context, effect, control. + int inliner_inputs = OperatorProperties::GetValueInputCount(call->op()); + // Iterate over all uses of the start node. + UseIter iter = graph()->start()->uses().begin(); + while (iter != graph()->start()->uses().end()) { + Node* use = *iter; + switch (use->opcode()) { + case IrOpcode::kParameter: { + int index = 1 + static_cast*>(use->op())->parameter(); + if (index < inliner_inputs && index < inlinee_context_index) { + // There is an input from the call, and the index is a value + // projection but not the context, so rewire the input. + NodeProperties::ReplaceWithValue(*iter, call->InputAt(index)); + } else if (index == inlinee_context_index) { + // This is the context projection, rewire it to the context from the + // JSFunction object. + NodeProperties::ReplaceWithValue(*iter, context); + } else if (index < inlinee_context_index) { + // Call has fewer arguments than required, fill with undefined. + NodeProperties::ReplaceWithValue(*iter, jsgraph->UndefinedConstant()); + } else { + // We got too many arguments, discard for now. + // TODO(sigurds): Fix to treat arguments array correctly. + } + ++iter; + break; + } + default: + if (NodeProperties::IsEffectEdge(iter.edge())) { + iter.UpdateToAndIncrement(context); + } else if (NodeProperties::IsControlEdge(iter.edge())) { + iter.UpdateToAndIncrement(control); + } else { + UNREACHABLE(); + } + break; + } + } + + // Iterate over all uses of the call node. + iter = call->uses().begin(); + while (iter != call->uses().end()) { + if (NodeProperties::IsEffectEdge(iter.edge())) { + iter.UpdateToAndIncrement(effect_output()); + } else if (NodeProperties::IsControlEdge(iter.edge())) { + UNREACHABLE(); + } else { + DCHECK(NodeProperties::IsValueEdge(iter.edge())); + iter.UpdateToAndIncrement(value_output()); + } + } + call->RemoveAllInputs(); + DCHECK_EQ(0, call->UseCount()); + // TODO(sigurds) Remove this once we copy. + unique_return()->RemoveAllInputs(); +} + + +void JSInliner::TryInlineCall(Node* node) { + DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode()); + + ValueMatcher > match(node->InputAt(0)); + if (!match.HasValue()) { + return; + } + + Handle function = match.Value(); + + if (function->shared()->native()) { + if (FLAG_trace_turbo_inlining) { + SmartArrayPointer name = + function->shared()->DebugName()->ToCString(); + PrintF("Not Inlining %s into %s because inlinee is native\n", name.get(), + info_->shared_info()->DebugName()->ToCString().get()); + } + return; + } + + CompilationInfoWithZone info(function); + Parse(function, &info); + + if (info.scope()->arguments() != NULL) { + // For now do not inline functions that use their arguments array. + SmartArrayPointer name = function->shared()->DebugName()->ToCString(); + if (FLAG_trace_turbo_inlining) { + PrintF( + "Not Inlining %s into %s because inlinee uses arguments " + "array\n", + name.get(), info_->shared_info()->DebugName()->ToCString().get()); + } + return; + } + + if (FLAG_trace_turbo_inlining) { + SmartArrayPointer name = function->shared()->DebugName()->ToCString(); + PrintF("Inlining %s into %s\n", name.get(), + info_->shared_info()->DebugName()->ToCString().get()); + } + + Graph graph(info_->zone()); + graph.SetNextNodeId(jsgraph_->graph()->NextNodeID()); + + Typer typer(info_->zone()); + CommonOperatorBuilder common(info_->zone()); + JSGraph jsgraph(&graph, &common, &typer); + + AstGraphBuilder graph_builder(&info, &jsgraph); + graph_builder.CreateGraph(); + + Inlinee inlinee(&jsgraph); + inlinee.UnifyReturn(); + inlinee.InlineAtCall(jsgraph_, node); + + jsgraph_->graph()->SetNextNodeId(inlinee.graph()->NextNodeID()); +} +} +} +} // namespace v8::internal::compiler diff --git a/src/compiler/js-inlining.h b/src/compiler/js-inlining.h new file mode 100644 index 0000000..a434571 --- /dev/null +++ b/src/compiler/js-inlining.h @@ -0,0 +1,34 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_COMPILER_JS_INLINING_H_ +#define V8_COMPILER_JS_INLINING_H_ + +#include "src/compiler/js-graph.h" +#include "src/v8.h" + +namespace v8 { +namespace internal { +namespace compiler { + +class JSInliner { + public: + JSInliner(CompilationInfo* info, JSGraph* jsgraph) + : info_(info), jsgraph_(jsgraph) {} + + void Inline(); + void TryInlineCall(Node* node); + + private: + friend class InlinerVisitor; + CompilationInfo* info_; + JSGraph* jsgraph_; + + static void UnifyReturn(Graph* graph); +}; +} +} +} // namespace v8::internal::compiler + +#endif // V8_COMPILER_JS_INLINING_H_ diff --git a/src/compiler/js-typed-lowering.cc b/src/compiler/js-typed-lowering.cc index 361cb94..4c166e0 100644 --- a/src/compiler/js-typed-lowering.cc +++ b/src/compiler/js-typed-lowering.cc @@ -17,39 +17,18 @@ namespace compiler { // - relax effects from generic but not-side-effecting operations // - relax effects for ToNumber(mixed) -// Replace value uses of {node} with {value} and effect uses of {node} with -// {effect}. If {effect == NULL}, then use the effect input to {node}. -// TODO(titzer): move into a GraphEditor? -static void ReplaceUses(Node* node, Node* value, Node* effect) { - if (value == effect) { - // Effect and value updates are the same; no special iteration needed. - if (value != node) node->ReplaceUses(value); - return; - } - - if (effect == NULL) effect = NodeProperties::GetEffectInput(node); - - // The iteration requires distinguishing between value and effect edges. - UseIter iter = node->uses().begin(); - while (iter != node->uses().end()) { - if (NodeProperties::IsEffectEdge(iter.edge())) { - iter = iter.UpdateToAndIncrement(effect); - } else { - iter = iter.UpdateToAndIncrement(value); - } - } -} - // Relax the effects of {node} by immediately replacing effect uses of {node} // with the effect 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) { ReplaceUses(node, node, NULL); } +static void RelaxEffects(Node* node) { + NodeProperties::ReplaceWithValue(node, node, NULL); +} Reduction JSTypedLowering::ReplaceEagerly(Node* old, Node* node) { - ReplaceUses(old, node, node); + NodeProperties::ReplaceWithValue(old, node, node); return Reducer::Changed(node); } @@ -522,7 +501,7 @@ Reduction JSTypedLowering::ReduceJSToBooleanInput(Node* input) { static Reduction ReplaceWithReduction(Node* node, Reduction reduction) { if (reduction.Changed()) { - ReplaceUses(node, reduction.replacement(), NULL); + NodeProperties::ReplaceWithValue(node, reduction.replacement()); return reduction; } return Reducer::NoChange(); @@ -573,13 +552,13 @@ Reduction JSTypedLowering::Reduce(Node* node) { // !x => BooleanNot(x) value = graph()->NewNode(simplified()->BooleanNot(), result.replacement()); - ReplaceUses(node, value, NULL); + NodeProperties::ReplaceWithValue(node, value); return Changed(value); } else { // !x => BooleanNot(JSToBoolean(x)) value = graph()->NewNode(simplified()->BooleanNot(), node); node->set_op(javascript()->ToBoolean()); - ReplaceUses(node, value, node); + NodeProperties::ReplaceWithValue(node, value, node); // Note: ReplaceUses() smashes all uses, so smash it back here. value->ReplaceInput(0, node); return ReplaceWith(value); diff --git a/src/compiler/node-properties-inl.h b/src/compiler/node-properties-inl.h index 2d63b0c..4a0f157 100644 --- a/src/compiler/node-properties-inl.h +++ b/src/compiler/node-properties-inl.h @@ -148,6 +148,28 @@ inline void NodeProperties::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}. +inline void NodeProperties::ReplaceWithValue(Node* node, Node* value, + Node* effect) { + DCHECK(!OperatorProperties::HasControlOutput(node->op())); + if (effect == NULL && OperatorProperties::HasEffectInput(node->op())) { + effect = NodeProperties::GetEffectInput(node); + } + + // Requires distinguishing between value and effect edges. + UseIter iter = node->uses().begin(); + while (iter != node->uses().end()) { + if (NodeProperties::IsEffectEdge(iter.edge())) { + DCHECK_NE(NULL, effect); + iter = iter.UpdateToAndIncrement(effect); + } else { + iter = iter.UpdateToAndIncrement(value); + } + } +} + + // ----------------------------------------------------------------------------- // Type Bounds. diff --git a/src/compiler/node-properties.h b/src/compiler/node-properties.h index 6088a0a..5843444 100644 --- a/src/compiler/node-properties.h +++ b/src/compiler/node-properties.h @@ -33,6 +33,8 @@ class NodeProperties { static inline void ReplaceEffectInput(Node* node, Node* effect, int index = 0); static inline void RemoveNonValueInputs(Node* node); + static inline void ReplaceWithValue(Node* node, Node* value, + Node* effect = NULL); static inline Bounds GetBounds(Node* node); static inline void SetBounds(Node* node, Bounds bounds); diff --git a/src/compiler/pipeline.cc b/src/compiler/pipeline.cc index 2e9ef98..bc1513a 100644 --- a/src/compiler/pipeline.cc +++ b/src/compiler/pipeline.cc @@ -13,6 +13,7 @@ #include "src/compiler/instruction-selector.h" #include "src/compiler/js-context-specialization.h" #include "src/compiler/js-generic-lowering.h" +#include "src/compiler/js-inlining.h" #include "src/compiler/js-typed-lowering.h" #include "src/compiler/phi-reducer.h" #include "src/compiler/register-allocator.h" @@ -186,14 +187,22 @@ Handle Pipeline::GenerateCode() { VerifyAndPrintGraph(&graph, "Initial untyped"); if (FLAG_context_specialization) { - SourcePositionTable::Scope pos_(&source_positions, - SourcePosition::Unknown()); + SourcePositionTable::Scope pos(&source_positions, + SourcePosition::Unknown()); // Specialize the code to the context as aggressively as possible. JSContextSpecializer spec(info(), &jsgraph, context_node); spec.SpecializeToContext(); VerifyAndPrintGraph(&graph, "Context specialized"); } + if (FLAG_turbo_inlining) { + SourcePositionTable::Scope pos(&source_positions, + SourcePosition::Unknown()); + JSInliner inliner(info(), &jsgraph); + inliner.Inline(); + VerifyAndPrintGraph(&graph, "Inlined"); + } + // Print a replay of the initial graph. if (FLAG_print_turbo_replay) { GraphReplayPrinter::PrintReplay(&graph); diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 16fe48a..868802c 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -338,6 +338,8 @@ DEFINE_BOOL(turbo_source_positions, false, DEFINE_BOOL(context_specialization, true, "enable context specialization in TurboFan") DEFINE_BOOL(turbo_deoptimization, false, "enable deoptimization in TurboFan") +DEFINE_BOOL(turbo_inlining, false, "enable inlining in TurboFan") +DEFINE_BOOL(trace_turbo_inlining, false, "trace TurboFan inlining") DEFINE_INT(typed_array_max_size_in_heap, 64, "threshold for in-heap typed array") diff --git a/test/cctest/cctest.gyp b/test/cctest/cctest.gyp index 559dd6b..e31fa86 100644 --- a/test/cctest/cctest.gyp +++ b/test/cctest/cctest.gyp @@ -71,6 +71,7 @@ 'compiler/test-pipeline.cc', 'compiler/test-representation-change.cc', 'compiler/test-run-deopt.cc', + 'compiler/test-run-inlining.cc', 'compiler/test-run-intrinsics.cc', 'compiler/test-run-jsbranches.cc', 'compiler/test-run-jscalls.cc', diff --git a/test/cctest/compiler/test-run-inlining.cc b/test/cctest/compiler/test-run-inlining.cc new file mode 100644 index 0000000..e676fb2 --- /dev/null +++ b/test/cctest/compiler/test-run-inlining.cc @@ -0,0 +1,184 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#include "test/cctest/compiler/function-tester.h" + +#if V8_TURBOFAN_TARGET + +using namespace v8::internal; +using namespace v8::internal::compiler; + +// TODO(sigurds) At the moment we do not write optimization frames when +// inlining, thus the reported stack depth changes depending on inlining. +// AssertStackDepth checks the stack depth actually changes as a simple way +// to ensure that inlining actually occurs. +// Once inlining creates optimization frames, all these unit tests need to +// check that the optimization frame is there. + + +static void AssertStackDepth(const v8::FunctionCallbackInfo& args) { + v8::HandleScope scope(args.GetIsolate()); + v8::Handle stackTrace = v8::StackTrace::CurrentStackTrace( + args.GetIsolate(), 10, v8::StackTrace::kDetailed); + CHECK_EQ(args[0]->ToInt32()->Value(), stackTrace->GetFrameCount()); +} + + +static void InstallAssertStackDepthHelper(v8::Isolate* isolate) { + v8::Local context = isolate->GetCurrentContext(); + v8::Local t = + v8::FunctionTemplate::New(isolate, AssertStackDepth); + context->Global()->Set(v8_str("AssertStackDepth"), t->GetFunction()); +} + + +TEST(SimpleInlining) { + FLAG_turbo_inlining = true; + FunctionTester T( + "(function(){" + "function foo(s) { AssertStackDepth(1); return s; };" + "function bar(s, t) { return foo(s); };" + "return bar;})();"); + + InstallAssertStackDepthHelper(CcTest::isolate()); + T.CheckCall(T.Val(1), T.Val(1), T.Val(2)); +} + + +TEST(SimpleInliningContext) { + FLAG_turbo_inlining = true; + FunctionTester T( + "(function () {" + "function foo(s) { AssertStackDepth(1); var x = 12; return s + x; };" + "function bar(s, t) { return foo(s); };" + "return bar;" + "})();"); + + InstallAssertStackDepthHelper(CcTest::isolate()); + T.CheckCall(T.Val(13), T.Val(1), T.Val(2)); +} + + +TEST(CaptureContext) { + FLAG_turbo_inlining = true; + FunctionTester T( + "var f = (function () {" + "var x = 42;" + "function bar(s) { return x + s; };" + "return (function (s) { return bar(s); });" + "})();" + "(function (s) { return f(s)})"); + + InstallAssertStackDepthHelper(CcTest::isolate()); + T.CheckCall(T.Val(42 + 12), T.Val(12), T.undefined()); +} + + +// TODO(sigurds) For now we do not inline any native functions. If we do at +// some point, change this test. +TEST(DontInlineEval) { + FLAG_turbo_inlining = true; + FunctionTester T( + "var x = 42;" + "(function () {" + "function bar(s, t) { return eval(\"AssertStackDepth(2); x\") };" + "return bar;" + "})();"); + + InstallAssertStackDepthHelper(CcTest::isolate()); + T.CheckCall(T.Val(42), T.Val("x"), T.undefined()); +} + + +TEST(InlineOmitArguments) { + FLAG_turbo_inlining = true; + FunctionTester T( + "(function () {" + "var x = 42;" + "function bar(s, t, u, v) { AssertStackDepth(1); return x + s; };" + "return (function (s,t) { return bar(s); });" + "})();"); + + InstallAssertStackDepthHelper(CcTest::isolate()); + T.CheckCall(T.Val(42 + 12), T.Val(12), T.undefined()); +} + + +TEST(InlineSurplusArguments) { + FLAG_turbo_inlining = true; + FunctionTester T( + "(function () {" + "var x = 42;" + "function foo(s) { AssertStackDepth(1); return x + s; };" + "function bar(s,t) { return foo(s,t,13); };" + "return bar;" + "})();"); + + InstallAssertStackDepthHelper(CcTest::isolate()); + T.CheckCall(T.Val(42 + 12), T.Val(12), T.undefined()); +} + + +TEST(InlineTwice) { + FLAG_turbo_inlining = true; + FunctionTester T( + "(function () {" + "var x = 42;" + "function bar(s) { AssertStackDepth(1); return x + s; };" + "return (function (s,t) { return bar(s) + bar(t); });" + "})();"); + + InstallAssertStackDepthHelper(CcTest::isolate()); + T.CheckCall(T.Val(2 * 42 + 12 + 4), T.Val(12), T.Val(4)); +} + + +TEST(InlineTwiceDependent) { + FLAG_turbo_inlining = true; + FunctionTester T( + "(function () {" + "var x = 42;" + "function foo(s) { AssertStackDepth(1); return x + s; };" + "function bar(s,t) { return foo(foo(s)); };" + "return bar;" + "})();"); + + InstallAssertStackDepthHelper(CcTest::isolate()); + T.CheckCall(T.Val(42 + 42 + 12), T.Val(12), T.Val(4)); +} + + +TEST(InlineTwiceDependentDiamond) { + FLAG_turbo_inlining = true; + FunctionTester T( + "(function () {" + "function foo(s) { if (true) {" + " return 12 } else { return 13; } };" + "function bar(s,t) { return foo(foo(1)); };" + "return bar;" + "})();"); + + InstallAssertStackDepthHelper(CcTest::isolate()); + T.CheckCall(T.Val(12), T.undefined(), T.undefined()); +} + + +TEST(InlineTwiceDependentDiamondReal) { + FLAG_turbo_inlining = true; + FunctionTester T( + "(function () {" + "var x = 41;" + "function foo(s) { AssertStackDepth(1); if (s % 2 == 0) {" + " return x - s } else { return x + s; } };" + "function bar(s,t) { return foo(foo(s)); };" + "return bar;" + "})();"); + + InstallAssertStackDepthHelper(CcTest::isolate()); + T.CheckCall(T.Val(-11), T.Val(11), T.Val(4)); +} + +#endif // V8_TURBOFAN_TARGET diff --git a/tools/gyp/v8.gyp b/tools/gyp/v8.gyp index 01ca799..061de57 100644 --- a/tools/gyp/v8.gyp +++ b/tools/gyp/v8.gyp @@ -384,6 +384,8 @@ '../../src/compiler/js-generic-lowering.h', '../../src/compiler/js-graph.cc', '../../src/compiler/js-graph.h', + '../../src/compiler/js-inlining.cc', + '../../src/compiler/js-inlining.h', '../../src/compiler/js-operator.h', '../../src/compiler/js-typed-lowering.cc', '../../src/compiler/js-typed-lowering.h', -- 2.7.4