/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "jit/LICM.h"

#include "jit/IonAnalysis.h"
#include "jit/JitSpewer.h"
#include "jit/MIRGenerator.h"
#include "jit/MIRGraph.h"

using namespace js;
using namespace js::jit;

// There are two constants which control whether a loop is LICM'd or is left
// unchanged.  For rationale see comment in jit::LICM() below.
//
// A bit of quick profiling with the wasm Embenchen suite on x64 shows that
// the threshold pair (100,25) has either no effect or gives a small net
// reduction in memory traffic, compared to unconstrained LICMing.  Halving
// them to (50,12) gives a small overall increase in memory traffic,
// suggesting it excludes too many loops from LICM.  Doubling them to (200,50)
// gives a win that is even smaller than (100,25), hence (100,25) seems the
// best choice.
//
// If a loop has more than this number of basic blocks in its body, it won't
// be LICM'd.
static constexpr size_t LargestAllowedLoop = 100;

// If a loop contains an MTableSwitch instruction that has more than this many
// successors, it won't be LICM'd.
static constexpr size_t LargestAllowedTableSwitch = 25;

// Test whether any instruction in the loop possiblyCalls().
static bool LoopContainsPossibleCall(MIRGraph& graph, MBasicBlock* header,
                                     MBasicBlock* backedge) {
  for (auto i(graph.rpoBegin(header));; ++i) {
    MOZ_ASSERT(i != graph.rpoEnd(),
               "Reached end of graph searching for blocks in loop");
    MBasicBlock* block = *i;
    if (!block->isMarked()) {
      continue;
    }

    for (auto ins : *block) {
      if (ins->possiblyCalls()) {
#ifdef JS_JITSPEW
        JitSpew(JitSpew_LICM, "    Possible call found at %s%u", ins->opName(),
                ins->id());
#endif
        return true;
      }
    }

    if (block == backedge) {
      break;
    }
  }
  return false;
}

// Tests whether any instruction in the loop is a table-switch with more than
// `LargestAllowedTableSwitch` successors.  If it returns true, it also
// returns the actual number of successors of the instruction in question,
// although that is used only for statistics/debug printing.
static bool LoopContainsBigTableSwitch(MIRGraph& graph, MBasicBlock* header,
                                       /*OUT*/ size_t* numSuccessors) {
  MBasicBlock* backedge = header->backedge();

  for (auto i(graph.rpoBegin(header));; ++i) {
    MOZ_ASSERT(i != graph.rpoEnd(),
               "Reached end of graph searching for blocks in loop");
    MBasicBlock* block = *i;
    if (!block->isMarked()) {
      continue;
    }

    for (auto ins : *block) {
      if (ins->isTableSwitch() &&
          ins->toTableSwitch()->numSuccessors() > LargestAllowedTableSwitch) {
        *numSuccessors = ins->toTableSwitch()->numSuccessors();
        return true;
      }
    }

    if (block == backedge) {
      break;
    }
  }
  return false;
}

// When a nested loop has no exits back into what would be its parent loop,
// MarkLoopBlocks on the parent loop doesn't mark the blocks of the nested
// loop, since they technically aren't part of the loop. However, AliasAnalysis
// currently does consider such nested loops to be part of their parent
// loops. Consequently, we can't use IsInLoop on dependency() values; we must
// test whether a dependency() is *before* the loop, even if it is not
// technically in the loop.
static bool IsBeforeLoop(MDefinition* ins, MBasicBlock* header) {
  return ins->block()->id() < header->id();
}

// Test whether the given instruction is inside the loop (and thus not
// loop-invariant).
static bool IsInLoop(MDefinition* ins) { return ins->block()->isMarked(); }

// Test whether the given instruction is cheap and not worth hoisting unless
// one of its users will be hoisted as well.
static bool RequiresHoistedUse(const MDefinition* ins, bool hasCalls) {
  if (ins->isBox()) {
    MOZ_ASSERT(!ins->toBox()->input()->isBox(),
               "Box of a box could lead to unbounded recursion");
    return true;
  }

  // Integer constants are usually cheap and aren't worth hoisting on their
  // own, in general. Floating-point constants typically are worth hoisting,
  // unless they'll end up being spilled (eg. due to a call).
  if (ins->isConstant() && (!IsFloatingPointType(ins->type()) || hasCalls)) {
    return true;
  }

  return false;
}

// Test whether the given instruction has any operands defined within the loop.
static bool HasOperandInLoop(MInstruction* ins, bool hasCalls) {
  // An instruction is only loop invariant if it and all of its operands can
  // be safely hoisted into the loop preheader.
  for (size_t i = 0, e = ins->numOperands(); i != e; ++i) {
    MDefinition* op = ins->getOperand(i);

    if (!IsInLoop(op)) {
      continue;
    }

    if (RequiresHoistedUse(op, hasCalls)) {
      // Recursively test for loop invariance. Note that the recursion is
      // bounded because we require RequiresHoistedUse to be set at each
      // level.
      if (!HasOperandInLoop(op->toInstruction(), hasCalls)) {
        continue;
      }
    }

    return true;
  }
  return false;
}

// Test whether the given instruction is hoistable, ignoring memory
// dependencies.
static bool IsHoistableIgnoringDependency(MInstruction* ins, bool hasCalls) {
  return ins->isMovable() && !ins->isEffectful() &&
         !HasOperandInLoop(ins, hasCalls);
}

// Test whether the given instruction has a memory dependency inside the loop.
static bool HasDependencyInLoop(MInstruction* ins, MBasicBlock* header) {
  // Don't hoist if this instruction depends on a store inside the loop.
  if (MDefinition* dep = ins->dependency()) {
    return !IsBeforeLoop(dep, header);
  }
  return false;
}

// Test whether the given instruction is hoistable.
static bool IsHoistable(MInstruction* ins, MBasicBlock* header, bool hasCalls) {
  return IsHoistableIgnoringDependency(ins, hasCalls) &&
         !HasDependencyInLoop(ins, header);
}

// In preparation for hoisting an instruction, hoist any of its operands which
// were too cheap to hoist on their own.
static void MoveDeferredOperands(MInstruction* ins, MInstruction* hoistPoint,
                                 bool hasCalls) {
  // If any of our operands were waiting for a user to be hoisted, make a note
  // to hoist them.
  for (size_t i = 0, e = ins->numOperands(); i != e; ++i) {
    MDefinition* op = ins->getOperand(i);
    if (!IsInLoop(op)) {
      continue;
    }
    MOZ_ASSERT(RequiresHoistedUse(op, hasCalls),
               "Deferred loop-invariant operand is not cheap");
    MInstruction* opIns = op->toInstruction();

    // Recursively move the operands. Note that the recursion is bounded
    // because we require RequiresHoistedUse to be set at each level.
    MoveDeferredOperands(opIns, hoistPoint, hasCalls);

#ifdef JS_JITSPEW
    JitSpew(JitSpew_LICM,
            "      Hoisting %s%u (now that a user will be hoisted)",
            opIns->opName(), opIns->id());
#endif

    opIns->block()->moveBefore(hoistPoint, opIns);
    opIns->setBailoutKind(BailoutKind::LICM);
  }
}

static void VisitLoopBlock(MBasicBlock* block, MBasicBlock* header,
                           MInstruction* hoistPoint, bool hasCalls) {
  for (auto insIter(block->begin()), insEnd(block->end()); insIter != insEnd;) {
    MInstruction* ins = *insIter++;

    if (!IsHoistable(ins, header, hasCalls)) {
#ifdef JS_JITSPEW
      if (IsHoistableIgnoringDependency(ins, hasCalls)) {
        JitSpew(JitSpew_LICM,
                "      %s%u isn't hoistable due to dependency on %s%u",
                ins->opName(), ins->id(), ins->dependency()->opName(),
                ins->dependency()->id());
      }
#endif
      continue;
    }

    // Don't hoist a cheap constant if it doesn't enable us to hoist one of
    // its uses. We want those instructions as close as possible to their
    // use, to minimize register pressure.
    if (RequiresHoistedUse(ins, hasCalls)) {
#ifdef JS_JITSPEW
      JitSpew(JitSpew_LICM, "      %s%u will be hoisted only if its users are",
              ins->opName(), ins->id());
#endif
      continue;
    }

    // Hoist operands which were too cheap to hoist on their own.
    MoveDeferredOperands(ins, hoistPoint, hasCalls);

#ifdef JS_JITSPEW
    JitSpew(JitSpew_LICM, "      Hoisting %s%u", ins->opName(), ins->id());
#endif

    // Move the instruction to the hoistPoint.
    block->moveBefore(hoistPoint, ins);
    ins->setBailoutKind(BailoutKind::LICM);
  }
}

static void VisitLoop(MIRGraph& graph, MBasicBlock* header) {
  MInstruction* hoistPoint = header->loopPredecessor()->lastIns();

#ifdef JS_JITSPEW
  JitSpew(JitSpew_LICM, "  Visiting loop with header block%u, hoisting to %s%u",
          header->id(), hoistPoint->opName(), hoistPoint->id());
#endif

  MBasicBlock* backedge = header->backedge();

  // This indicates whether the loop contains calls or other things which
  // clobber most or all floating-point registers. In such loops,
  // floating-point constants should not be hoisted unless it enables further
  // hoisting.
  bool hasCalls = LoopContainsPossibleCall(graph, header, backedge);

  for (auto i(graph.rpoBegin(header));; ++i) {
    MOZ_ASSERT(i != graph.rpoEnd(),
               "Reached end of graph searching for blocks in loop");
    MBasicBlock* block = *i;
    if (!block->isMarked()) {
      continue;
    }

#ifdef JS_JITSPEW
    JitSpew(JitSpew_LICM, "    Visiting block%u", block->id());
#endif

    VisitLoopBlock(block, header, hoistPoint, hasCalls);

    if (block == backedge) {
      break;
    }
  }
}

bool jit::LICM(const MIRGenerator* mir, MIRGraph& graph) {
  JitSpew(JitSpew_LICM, "Beginning LICM pass");

  // Iterate in RPO to visit outer loops before inner loops. We'd hoist the
  // same things either way, but outer first means we do a little less work.
  for (auto i(graph.rpoBegin()), e(graph.rpoEnd()); i != e; ++i) {
    MBasicBlock* header = *i;
    if (!header->isLoopHeader()) {
      continue;
    }

    bool canOsr;
    size_t numBlocks = MarkLoopBlocks(graph, header, &canOsr);

    if (numBlocks == 0) {
      JitSpew(JitSpew_LICM,
              "  Skipping loop with header block%u -- contains zero blocks",
              header->id());
      continue;
    }

    // There are various reasons why we might choose not to LICM a given loop:
    //
    // (a) Hoisting out of a loop that has an entry from the OSR block in
    //     addition to its normal entry is tricky.  In theory we could clone
    //     the instruction and insert phis.  In practice we don't bother.
    //
    // (b) If the loop contains a large number of blocks, we play safe and
    //     punt, in order to reduce the risk of creating excessive register
    //     pressure by hoisting lots of values out of the loop.  In a larger
    //     loop there's more likely to be duplication of invariant expressions
    //     within the loop body, and that duplication will be GVN'd but only
    //     within the scope of the loop body, so there's less loss from not
    //     lifting them out of the loop entirely.
    //
    // (c) If the loop contains a multiway switch with many successors, there
    //     could be paths with low probabilities, from which LICMing will be a
    //     net loss, especially if a large number of values are hoisted out.
    //     See bug 1708381 for a spectacular example and bug 1712078 for
    //     further discussion.
    //
    // It's preferable to perform test (c) only if (a) and (b) pass since (c)
    // is more expensive to determine -- requiring a visit to all the MIR
    // nodes -- than (a) or (b), which only involve visiting all blocks.

    bool doVisit = true;
    if (canOsr) {
      JitSpew(JitSpew_LICM, "  Skipping loop with header block%u due to OSR",
              header->id());
      doVisit = false;
    } else if (numBlocks > LargestAllowedLoop) {
      JitSpew(JitSpew_LICM,
              "  Skipping loop with header block%u "
              "due to too many blocks (%u > thresh %u)",
              header->id(), (uint32_t)numBlocks, (uint32_t)LargestAllowedLoop);
      doVisit = false;
    } else {
      size_t switchSize = 0;
      if (LoopContainsBigTableSwitch(graph, header, &switchSize)) {
        JitSpew(JitSpew_LICM,
                "  Skipping loop with header block%u "
                "due to oversize tableswitch (%u > thresh %u)",
                header->id(), (uint32_t)switchSize,
                (uint32_t)LargestAllowedTableSwitch);
        doVisit = false;
      }
    }

    if (doVisit) {
      VisitLoop(graph, header);
    }

    UnmarkLoopBlocks(graph, header);

    if (mir->shouldCancel("LICM (main loop)")) {
      return false;
    }
  }

  return true;
}
