tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

LICM.cpp (13210B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "jit/LICM.h"
      8 
      9 #include "jit/IonAnalysis.h"
     10 #include "jit/JitSpewer.h"
     11 #include "jit/MIRGenerator.h"
     12 #include "jit/MIRGraph.h"
     13 
     14 using namespace js;
     15 using namespace js::jit;
     16 
     17 // There are two constants which control whether a loop is LICM'd or is left
     18 // unchanged.  For rationale see comment in jit::LICM() below.
     19 //
     20 // A bit of quick profiling with the wasm Embenchen suite on x64 shows that
     21 // the threshold pair (100,25) has either no effect or gives a small net
     22 // reduction in memory traffic, compared to unconstrained LICMing.  Halving
     23 // them to (50,12) gives a small overall increase in memory traffic,
     24 // suggesting it excludes too many loops from LICM.  Doubling them to (200,50)
     25 // gives a win that is even smaller than (100,25), hence (100,25) seems the
     26 // best choice.
     27 //
     28 // If a loop has more than this number of basic blocks in its body, it won't
     29 // be LICM'd.
     30 static constexpr size_t LargestAllowedLoop = 100;
     31 
     32 // If a loop contains an MTableSwitch instruction that has more than this many
     33 // successors, it won't be LICM'd.
     34 static constexpr size_t LargestAllowedTableSwitch = 25;
     35 
     36 // Test whether any instruction in the loop possiblyCalls().
     37 static bool LoopContainsPossibleCall(MIRGraph& graph, MBasicBlock* header,
     38                                     MBasicBlock* backedge) {
     39  for (auto i(graph.rpoBegin(header));; ++i) {
     40    MOZ_ASSERT(i != graph.rpoEnd(),
     41               "Reached end of graph searching for blocks in loop");
     42    MBasicBlock* block = *i;
     43    if (!block->isMarked()) {
     44      continue;
     45    }
     46 
     47    for (auto insIter(block->begin()), insEnd(block->end()); insIter != insEnd;
     48         ++insIter) {
     49      MInstruction* ins = *insIter;
     50      if (ins->possiblyCalls()) {
     51 #ifdef JS_JITSPEW
     52        JitSpew(JitSpew_LICM, "    Possible call found at %s%u", ins->opName(),
     53                ins->id());
     54 #endif
     55        return true;
     56      }
     57    }
     58 
     59    if (block == backedge) {
     60      break;
     61    }
     62  }
     63  return false;
     64 }
     65 
     66 // Tests whether any instruction in the loop is a table-switch with more than
     67 // `LargestAllowedTableSwitch` successors.  If it returns true, it also
     68 // returns the actual number of successors of the instruction in question,
     69 // although that is used only for statistics/debug printing.
     70 static bool LoopContainsBigTableSwitch(MIRGraph& graph, MBasicBlock* header,
     71                                       /*OUT*/ size_t* numSuccessors) {
     72  MBasicBlock* backedge = header->backedge();
     73 
     74  for (auto i(graph.rpoBegin(header));; ++i) {
     75    MOZ_ASSERT(i != graph.rpoEnd(),
     76               "Reached end of graph searching for blocks in loop");
     77    MBasicBlock* block = *i;
     78    if (!block->isMarked()) {
     79      continue;
     80    }
     81 
     82    for (auto insIter(block->begin()), insEnd(block->end()); insIter != insEnd;
     83         ++insIter) {
     84      MInstruction* ins = *insIter;
     85      if (ins->isTableSwitch() &&
     86          ins->toTableSwitch()->numSuccessors() > LargestAllowedTableSwitch) {
     87        *numSuccessors = ins->toTableSwitch()->numSuccessors();
     88        return true;
     89      }
     90    }
     91 
     92    if (block == backedge) {
     93      break;
     94    }
     95  }
     96  return false;
     97 }
     98 
     99 // When a nested loop has no exits back into what would be its parent loop,
    100 // MarkLoopBlocks on the parent loop doesn't mark the blocks of the nested
    101 // loop, since they technically aren't part of the loop. However, AliasAnalysis
    102 // currently does consider such nested loops to be part of their parent
    103 // loops. Consequently, we can't use IsInLoop on dependency() values; we must
    104 // test whether a dependency() is *before* the loop, even if it is not
    105 // technically in the loop.
    106 static bool IsBeforeLoop(MDefinition* ins, MBasicBlock* header) {
    107  return ins->block()->id() < header->id();
    108 }
    109 
    110 // Test whether the given instruction is inside the loop (and thus not
    111 // loop-invariant).
    112 static bool IsInLoop(MDefinition* ins) { return ins->block()->isMarked(); }
    113 
    114 // Test whether the given instruction is cheap and not worth hoisting unless
    115 // one of its users will be hoisted as well.
    116 static bool RequiresHoistedUse(const MDefinition* ins, bool hasCalls) {
    117  if (ins->isBox()) {
    118    MOZ_ASSERT(!ins->toBox()->input()->isBox(),
    119               "Box of a box could lead to unbounded recursion");
    120    return true;
    121  }
    122 
    123  // Integer constants are usually cheap and aren't worth hoisting on their
    124  // own, in general. Floating-point constants typically are worth hoisting,
    125  // unless they'll end up being spilled (eg. due to a call).
    126  if (ins->isConstant() && (!IsFloatingPointType(ins->type()) || hasCalls)) {
    127    return true;
    128  }
    129 
    130  return false;
    131 }
    132 
    133 // Test whether the given instruction has any operands defined within the loop.
    134 static bool HasOperandInLoop(MInstruction* ins, bool hasCalls) {
    135  // An instruction is only loop invariant if it and all of its operands can
    136  // be safely hoisted into the loop preheader.
    137  for (size_t i = 0, e = ins->numOperands(); i != e; ++i) {
    138    MDefinition* op = ins->getOperand(i);
    139 
    140    if (!IsInLoop(op)) {
    141      continue;
    142    }
    143 
    144    if (RequiresHoistedUse(op, hasCalls)) {
    145      // Recursively test for loop invariance. Note that the recursion is
    146      // bounded because we require RequiresHoistedUse to be set at each
    147      // level.
    148      if (!HasOperandInLoop(op->toInstruction(), hasCalls)) {
    149        continue;
    150      }
    151    }
    152 
    153    return true;
    154  }
    155  return false;
    156 }
    157 
    158 // Test whether the given instruction is hoistable, ignoring memory
    159 // dependencies.
    160 static bool IsHoistableIgnoringDependency(MInstruction* ins, bool hasCalls) {
    161  return ins->isMovable() && !ins->isEffectful() &&
    162         !HasOperandInLoop(ins, hasCalls);
    163 }
    164 
    165 // Test whether the given instruction has a memory dependency inside the loop.
    166 static bool HasDependencyInLoop(MInstruction* ins, MBasicBlock* header) {
    167  // Don't hoist if this instruction depends on a store inside the loop.
    168  if (MDefinition* dep = ins->dependency()) {
    169    return !IsBeforeLoop(dep, header);
    170  }
    171  return false;
    172 }
    173 
    174 // Test whether the given instruction is hoistable.
    175 static bool IsHoistable(MInstruction* ins, MBasicBlock* header, bool hasCalls) {
    176  return IsHoistableIgnoringDependency(ins, hasCalls) &&
    177         !HasDependencyInLoop(ins, header);
    178 }
    179 
    180 // In preparation for hoisting an instruction, hoist any of its operands which
    181 // were too cheap to hoist on their own.
    182 static void MoveDeferredOperands(MInstruction* ins, MInstruction* hoistPoint,
    183                                 bool hasCalls) {
    184  // If any of our operands were waiting for a user to be hoisted, make a note
    185  // to hoist them.
    186  for (size_t i = 0, e = ins->numOperands(); i != e; ++i) {
    187    MDefinition* op = ins->getOperand(i);
    188    if (!IsInLoop(op)) {
    189      continue;
    190    }
    191    MOZ_ASSERT(RequiresHoistedUse(op, hasCalls),
    192               "Deferred loop-invariant operand is not cheap");
    193    MInstruction* opIns = op->toInstruction();
    194 
    195    // Recursively move the operands. Note that the recursion is bounded
    196    // because we require RequiresHoistedUse to be set at each level.
    197    MoveDeferredOperands(opIns, hoistPoint, hasCalls);
    198 
    199 #ifdef JS_JITSPEW
    200    JitSpew(JitSpew_LICM,
    201            "      Hoisting %s%u (now that a user will be hoisted)",
    202            opIns->opName(), opIns->id());
    203 #endif
    204 
    205    opIns->block()->moveBefore(hoistPoint, opIns);
    206    opIns->setBailoutKind(BailoutKind::LICM);
    207  }
    208 }
    209 
    210 static void VisitLoopBlock(MBasicBlock* block, MBasicBlock* header,
    211                           MInstruction* hoistPoint, bool hasCalls) {
    212  for (auto insIter(block->begin()), insEnd(block->end()); insIter != insEnd;) {
    213    MInstruction* ins = *insIter++;
    214 
    215    if (!IsHoistable(ins, header, hasCalls)) {
    216 #ifdef JS_JITSPEW
    217      if (IsHoistableIgnoringDependency(ins, hasCalls)) {
    218        JitSpew(JitSpew_LICM,
    219                "      %s%u isn't hoistable due to dependency on %s%u",
    220                ins->opName(), ins->id(), ins->dependency()->opName(),
    221                ins->dependency()->id());
    222      }
    223 #endif
    224      continue;
    225    }
    226 
    227    // Don't hoist a cheap constant if it doesn't enable us to hoist one of
    228    // its uses. We want those instructions as close as possible to their
    229    // use, to minimize register pressure.
    230    if (RequiresHoistedUse(ins, hasCalls)) {
    231 #ifdef JS_JITSPEW
    232      JitSpew(JitSpew_LICM, "      %s%u will be hoisted only if its users are",
    233              ins->opName(), ins->id());
    234 #endif
    235      continue;
    236    }
    237 
    238    // Hoist operands which were too cheap to hoist on their own.
    239    MoveDeferredOperands(ins, hoistPoint, hasCalls);
    240 
    241 #ifdef JS_JITSPEW
    242    JitSpew(JitSpew_LICM, "      Hoisting %s%u", ins->opName(), ins->id());
    243 #endif
    244 
    245    // Move the instruction to the hoistPoint.
    246    block->moveBefore(hoistPoint, ins);
    247    ins->setBailoutKind(BailoutKind::LICM);
    248  }
    249 }
    250 
    251 static void VisitLoop(MIRGraph& graph, MBasicBlock* header) {
    252  MInstruction* hoistPoint = header->loopPredecessor()->lastIns();
    253 
    254 #ifdef JS_JITSPEW
    255  JitSpew(JitSpew_LICM, "  Visiting loop with header block%u, hoisting to %s%u",
    256          header->id(), hoistPoint->opName(), hoistPoint->id());
    257 #endif
    258 
    259  MBasicBlock* backedge = header->backedge();
    260 
    261  // This indicates whether the loop contains calls or other things which
    262  // clobber most or all floating-point registers. In such loops,
    263  // floating-point constants should not be hoisted unless it enables further
    264  // hoisting.
    265  bool hasCalls = LoopContainsPossibleCall(graph, header, backedge);
    266 
    267  for (auto i(graph.rpoBegin(header));; ++i) {
    268    MOZ_ASSERT(i != graph.rpoEnd(),
    269               "Reached end of graph searching for blocks in loop");
    270    MBasicBlock* block = *i;
    271    if (!block->isMarked()) {
    272      continue;
    273    }
    274 
    275 #ifdef JS_JITSPEW
    276    JitSpew(JitSpew_LICM, "    Visiting block%u", block->id());
    277 #endif
    278 
    279    VisitLoopBlock(block, header, hoistPoint, hasCalls);
    280 
    281    if (block == backedge) {
    282      break;
    283    }
    284  }
    285 }
    286 
    287 bool jit::LICM(const MIRGenerator* mir, MIRGraph& graph) {
    288  JitSpew(JitSpew_LICM, "Beginning LICM pass");
    289 
    290  // Iterate in RPO to visit outer loops before inner loops. We'd hoist the
    291  // same things either way, but outer first means we do a little less work.
    292  for (auto i(graph.rpoBegin()), e(graph.rpoEnd()); i != e; ++i) {
    293    MBasicBlock* header = *i;
    294    if (!header->isLoopHeader()) {
    295      continue;
    296    }
    297 
    298    bool canOsr;
    299    size_t numBlocks = MarkLoopBlocks(graph, header, &canOsr);
    300 
    301    if (numBlocks == 0) {
    302      JitSpew(JitSpew_LICM,
    303              "  Skipping loop with header block%u -- contains zero blocks",
    304              header->id());
    305      continue;
    306    }
    307 
    308    // There are various reasons why we might choose not to LICM a given loop:
    309    //
    310    // (a) Hoisting out of a loop that has an entry from the OSR block in
    311    //     addition to its normal entry is tricky.  In theory we could clone
    312    //     the instruction and insert phis.  In practice we don't bother.
    313    //
    314    // (b) If the loop contains a large number of blocks, we play safe and
    315    //     punt, in order to reduce the risk of creating excessive register
    316    //     pressure by hoisting lots of values out of the loop.  In a larger
    317    //     loop there's more likely to be duplication of invariant expressions
    318    //     within the loop body, and that duplication will be GVN'd but only
    319    //     within the scope of the loop body, so there's less loss from not
    320    //     lifting them out of the loop entirely.
    321    //
    322    // (c) If the loop contains a multiway switch with many successors, there
    323    //     could be paths with low probabilities, from which LICMing will be a
    324    //     net loss, especially if a large number of values are hoisted out.
    325    //     See bug 1708381 for a spectacular example and bug 1712078 for
    326    //     further discussion.
    327    //
    328    // It's preferable to perform test (c) only if (a) and (b) pass since (c)
    329    // is more expensive to determine -- requiring a visit to all the MIR
    330    // nodes -- than (a) or (b), which only involve visiting all blocks.
    331 
    332    bool doVisit = true;
    333    if (canOsr) {
    334      JitSpew(JitSpew_LICM, "  Skipping loop with header block%u due to OSR",
    335              header->id());
    336      doVisit = false;
    337    } else if (numBlocks > LargestAllowedLoop) {
    338      JitSpew(JitSpew_LICM,
    339              "  Skipping loop with header block%u "
    340              "due to too many blocks (%u > thresh %u)",
    341              header->id(), (uint32_t)numBlocks, (uint32_t)LargestAllowedLoop);
    342      doVisit = false;
    343    } else {
    344      size_t switchSize = 0;
    345      if (LoopContainsBigTableSwitch(graph, header, &switchSize)) {
    346        JitSpew(JitSpew_LICM,
    347                "  Skipping loop with header block%u "
    348                "due to oversize tableswitch (%u > thresh %u)",
    349                header->id(), (uint32_t)switchSize,
    350                (uint32_t)LargestAllowedTableSwitch);
    351        doVisit = false;
    352      }
    353    }
    354 
    355    if (doVisit) {
    356      VisitLoop(graph, header);
    357    }
    358 
    359    UnmarkLoopBlocks(graph, header);
    360 
    361    if (mir->shouldCancel("LICM (main loop)")) {
    362      return false;
    363    }
    364  }
    365 
    366  return true;
    367 }