Add initial support for inlining.
authorsigurds@chromium.org <sigurds@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 19 Aug 2014 12:23:19 +0000 (12:23 +0000)
committersigurds@chromium.org <sigurds@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 19 Aug 2014 12:23:19 +0000 (12:23 +0000)
* Add stack depth checking to function tester.
* 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=mstarzinger@chromium.org, titzer@chromium.org

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

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@23197 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

14 files changed:
BUILD.gn
src/compiler/generic-graph.h
src/compiler/generic-node.h
src/compiler/js-context-specialization.cc
src/compiler/js-inlining.cc [new file with mode: 0644]
src/compiler/js-inlining.h [new file with mode: 0644]
src/compiler/js-typed-lowering.cc
src/compiler/node-properties-inl.h
src/compiler/node-properties.h
src/compiler/pipeline.cc
src/flag-definitions.h
test/cctest/cctest.gyp
test/cctest/compiler/test-run-inlining.cc [new file with mode: 0644]
tools/gyp/v8.gyp

index ea182420c9921f926b9b6f07807d2912230058eb..f5501fb5a32f79a2bd1b28ee23c444cb807b385a 100644 (file)
--- 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",
index a55545654535869d54e1c4c43bbb6a8a97c802c1..e8de9ebd931c3712395f5eafd360b8f4bc99e183 100644 (file)
@@ -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_;
index 287d852f5ed7f053a0add2ba494753744907f89a..aadee7611a7e0a3cd8192c2aff3a5aa6a29a1f12 100644 (file)
@@ -204,6 +204,12 @@ class GenericNode<B, S>::Inputs::iterator {
     ++index_;
     return *this;
   }
+  iterator& UpdateToAndIncrement(GenericNode<B, S>* new_to) {
+    typename GenericNode<B, S>::Input* input = GetInput();
+    input->Update(new_to);
+    index_++;
+    return *this;
+  }
   int index() { return index_; }
 
  private:
index bdf142763a8887ca3db22cdda719b94eae24f86a..b54d5d99c2058cf17d1674b5f9cc7e3e01953741 100644 (file)
@@ -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 (file)
index 0000000..5dcc0e1
--- /dev/null
@@ -0,0 +1,327 @@
+// 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<JSFunction> function, CompilationInfoWithZone* info) {
+  CHECK(Parser::Parse(info));
+  StrictMode strict_mode = info->function()->strict_mode();
+  info->SetStrictMode(strict_mode);
+  info->SetOptimizing(BailoutId::None(), Handle<Code>(function->code()));
+  CHECK(Rewriter::Rewrite(info));
+  CHECK(Scope::Analyze(info));
+  CHECK_NE(NULL, info->scope());
+  Handle<ScopeInfo> 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);
+
+  std::vector<Node*> values;
+  std::vector<Node*> effects;
+  // 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, values.size(), values.data());
+  Node* ephi = graph->NewNode(op_ephi, effects.size(), effects.data());
+  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<Operator1<int>*>(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<Handle<JSFunction> > match(node->InputAt(0));
+  if (!match.HasValue()) {
+    return;
+  }
+
+  Handle<JSFunction> function = match.Value();
+
+  if (function->shared()->native()) {
+    if (FLAG_trace_turbo_inlining) {
+      SmartArrayPointer<char> 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<char> 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<char> 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 (file)
index 0000000..a434571
--- /dev/null
@@ -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_
index 361cb94f058c3e39ce117c69ba3b44c32a91039c..4c166e0dc53d3b2e6dd2319873de41bd230417cc 100644 (file)
@@ -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);
index 2d63b0cc1b75061fde3d2a573f4d67f0e07d8f1c..4a0f1573b591f8f656cab01b9489611dc2c268d2 100644 (file)
@@ -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.
 
index 6088a0a3a063ccaca31d1edd2fd8bd0ee29c5a95..584344485ed3468528277d273a1757e663224392 100644 (file)
@@ -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);
index 2e9ef98b657cf4d4b91bf710660c6e1324371052..bc1513ad5c9638bf597630bb408fbaf5f9c993c9 100644 (file)
@@ -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<Code> 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);
index 16fe48adeb9d1f930ab90729cfb28a66fd94a8df..868802cf63778c9fbc58616d8c7301a55b1737c7 100644 (file)
@@ -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")
index 559dd6bf271d994cd66850a489ee43f93b03b7d1..e31fa869afe74fba51db2b2b8e655da0416cc382 100644 (file)
@@ -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 (file)
index 0000000..565bff6
--- /dev/null
@@ -0,0 +1,180 @@
+// 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"
+
+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 at 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<v8::Value>& args) {
+  v8::HandleScope scope(args.GetIsolate());
+  v8::Handle<v8::StackTrace> 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<v8::Context> context = isolate->GetCurrentContext();
+  v8::Local<v8::FunctionTemplate> t =
+      v8::FunctionTemplate::New(isolate, AssertStackDepth);
+  context->Global()->Set(v8_str("AssertStackDepth"), t->GetFunction());
+}
+
+
+TEST(SimpleInlining) {
+  i::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) {
+  i::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) {
+  i::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) {
+  i::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) {
+  i::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) {
+  i::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) {
+  i::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) {
+  i::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) {
+  i::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) {
+  i::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));
+}
index 3b832852e3b40f2a6f2dbdd2af6f5519d50d2293..a715acc8f9097aa4200ed3344619098b338b1c04 100644 (file)
         '../../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',