Home | History | Annotate | Download | only in compiler
      1 // Copyright 2015 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "src/compiler/js-inlining-heuristic.h"
      6 
      7 #include "src/compiler.h"
      8 #include "src/compiler/node-matchers.h"
      9 #include "src/objects-inl.h"
     10 
     11 namespace v8 {
     12 namespace internal {
     13 namespace compiler {
     14 
     15 Reduction JSInliningHeuristic::Reduce(Node* node) {
     16   if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange();
     17 
     18   // Check if we already saw that {node} before, and if so, just skip it.
     19   if (seen_.find(node->id()) != seen_.end()) return NoChange();
     20   seen_.insert(node->id());
     21 
     22   Node* callee = node->InputAt(0);
     23   HeapObjectMatcher match(callee);
     24   if (!match.HasValue() || !match.Value()->IsJSFunction()) return NoChange();
     25   Handle<JSFunction> function = Handle<JSFunction>::cast(match.Value());
     26 
     27   // Functions marked with %SetForceInlineFlag are immediately inlined.
     28   if (function->shared()->force_inline()) {
     29     return inliner_.ReduceJSCall(node, function);
     30   }
     31 
     32   // Handling of special inlining modes right away:
     33   //  - For restricted inlining: stop all handling at this point.
     34   //  - For stressing inlining: immediately handle all functions.
     35   switch (mode_) {
     36     case kRestrictedInlining:
     37       return NoChange();
     38     case kStressInlining:
     39       return inliner_.ReduceJSCall(node, function);
     40     case kGeneralInlining:
     41       break;
     42   }
     43 
     44   // ---------------------------------------------------------------------------
     45   // Everything below this line is part of the inlining heuristic.
     46   // ---------------------------------------------------------------------------
     47 
     48   // Built-in functions are handled by the JSBuiltinReducer.
     49   if (function->shared()->HasBuiltinFunctionId()) return NoChange();
     50 
     51   // Don't inline builtins.
     52   if (function->shared()->IsBuiltin()) return NoChange();
     53 
     54   // Quick check on source code length to avoid parsing large candidate.
     55   if (function->shared()->SourceSize() > FLAG_max_inlined_source_size) {
     56     return NoChange();
     57   }
     58 
     59   // Quick check on the size of the AST to avoid parsing large candidate.
     60   if (function->shared()->ast_node_count() > FLAG_max_inlined_nodes) {
     61     return NoChange();
     62   }
     63 
     64   // Avoid inlining within or across the boundary of asm.js code.
     65   if (info_->shared_info()->asm_function()) return NoChange();
     66   if (function->shared()->asm_function()) return NoChange();
     67 
     68   // Stop inlinining once the maximum allowed level is reached.
     69   int level = 0;
     70   for (Node* frame_state = NodeProperties::GetFrameStateInput(node, 0);
     71        frame_state->opcode() == IrOpcode::kFrameState;
     72        frame_state = NodeProperties::GetFrameStateInput(frame_state, 0)) {
     73     if (++level > FLAG_max_inlining_levels) return NoChange();
     74   }
     75 
     76   // Gather feedback on how often this call site has been hit before.
     77   int calls = -1;  // Same default as CallICNexus::ExtractCallCount.
     78   if (node->opcode() == IrOpcode::kJSCallFunction) {
     79     CallFunctionParameters p = CallFunctionParametersOf(node->op());
     80     if (p.feedback().IsValid()) {
     81       CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
     82       calls = nexus.ExtractCallCount();
     83     }
     84   } else {
     85     DCHECK_EQ(IrOpcode::kJSCallConstruct, node->opcode());
     86     CallConstructParameters p = CallConstructParametersOf(node->op());
     87     if (p.feedback().IsValid()) {
     88       int const extra_index =
     89           p.feedback().vector()->GetIndex(p.feedback().slot()) + 1;
     90       Handle<Object> feedback_extra(p.feedback().vector()->get(extra_index),
     91                                     function->GetIsolate());
     92       if (feedback_extra->IsSmi()) {
     93         calls = Handle<Smi>::cast(feedback_extra)->value();
     94       }
     95     }
     96   }
     97 
     98   // ---------------------------------------------------------------------------
     99   // Everything above this line is part of the inlining heuristic.
    100   // ---------------------------------------------------------------------------
    101 
    102   // In the general case we remember the candidate for later.
    103   candidates_.insert({function, node, calls});
    104   return NoChange();
    105 }
    106 
    107 
    108 void JSInliningHeuristic::Finalize() {
    109   if (candidates_.empty()) return;  // Nothing to do without candidates.
    110   if (FLAG_trace_turbo_inlining) PrintCandidates();
    111 
    112   // We inline at most one candidate in every iteration of the fixpoint.
    113   // This is to ensure that we don't consume the full inlining budget
    114   // on things that aren't called very often.
    115   // TODO(bmeurer): Use std::priority_queue instead of std::set here.
    116   while (!candidates_.empty()) {
    117     if (cumulative_count_ > FLAG_max_inlined_nodes_cumulative) return;
    118     auto i = candidates_.begin();
    119     Candidate candidate = *i;
    120     candidates_.erase(i);
    121     // Make sure we don't try to inline dead candidate nodes.
    122     if (!candidate.node->IsDead()) {
    123       Reduction r = inliner_.ReduceJSCall(candidate.node, candidate.function);
    124       if (r.Changed()) {
    125         cumulative_count_ += candidate.function->shared()->ast_node_count();
    126         return;
    127       }
    128     }
    129   }
    130 }
    131 
    132 
    133 bool JSInliningHeuristic::CandidateCompare::operator()(
    134     const Candidate& left, const Candidate& right) const {
    135   if (left.calls != right.calls) {
    136     return left.calls > right.calls;
    137   }
    138   return left.node < right.node;
    139 }
    140 
    141 
    142 void JSInliningHeuristic::PrintCandidates() {
    143   PrintF("Candidates for inlining (size=%zu):\n", candidates_.size());
    144   for (const Candidate& candidate : candidates_) {
    145     PrintF("  id:%d, calls:%d, size[source]:%d, size[ast]:%d / %s\n",
    146            candidate.node->id(), candidate.calls,
    147            candidate.function->shared()->SourceSize(),
    148            candidate.function->shared()->ast_node_count(),
    149            candidate.function->shared()->DebugName()->ToCString().get());
    150   }
    151 }
    152 
    153 }  // namespace compiler
    154 }  // namespace internal
    155 }  // namespace v8
    156