Beispiel #1
0
void OSRPass::_run(IRManager & irm) {
    splitCriticalEdges(irm);
    computeDominatorsAndLoops(irm);
    LoopTree* loopTree = irm.getLoopTree();
    DominatorTree* dominatorTree = irm.getDominatorTree();
    OSR osr(irm, irm.getMemoryManager(), loopTree, dominatorTree);
    osr.runPass();
}
Beispiel #2
0
/*
 * For all guard instructions in unit, check to see if we can relax the
 * destination type to something less specific. The GuardConstraints map
 * contains information about what properties of the guarded type matter for
 * each instruction. If simple is true, guards will not be relaxed past
 * DataTypeSpecific except guards which are relaxed all the way to
 * DataTypeGeneric. Returns true iff any changes were made to the trace.
 */
bool relaxGuards(IRUnit& unit, const GuardConstraints& guards, bool simple) {
  Timer _t("optimize_relaxGuards");

  splitCriticalEdges(unit);
  auto blocks = rpoSortCfg(unit);
  auto changed = false;

  for (auto* block : blocks) {
    for (auto& inst : *block) {
      if (!isGuardOp(inst.op())) continue;

      auto it = guards.find(&inst);
      auto constraint = it == guards.end() ? TypeConstraint() : it->second;
      FTRACE(2, "relaxGuards processing {} with constraint {}\n",
             inst, constraint);

      if (simple && constraint.category > DataTypeGeneric &&
          constraint.category < DataTypeSpecific) {
        constraint.category = DataTypeSpecific;
      }

      auto const oldType = inst.typeParam();
      auto newType = relaxType(oldType, constraint);

      // Sometimes we (legitimately) end up with a guard like this:
      //
      // t4:StkPtr = GuardStk<BoxedArr,0,<DataTypeGeneric,
      //                                  inner:DataTypeSpecific,
      //                                  Type::BoxedCell>> t2:StkPtr
      //
      // The outer category is DataTypeGeneric because we know from eval stack
      // flavors that the top of the stack here is always boxed. The inner
      // category is DataTypeSpecific, indicating we care what the inner type
      // is, even though it's just a hint. If we treated this like any other
      // guard, we would relax the typeParam to Type::Gen and insert an assert
      // to Type::BoxedCell right after it. Unfortunately, this loses the hint
      // that the inner type is Arr. Eventually we should have some side
      // channel for passing around hints for inner ref types, but for now the
      // best we can do is forcibly keep the guard around, preserving the inner
      // type hint.
      if (constraint.assertedType.isBoxed() &&
          oldType < constraint.assertedType) {
        auto relaxedInner = relaxInner(oldType, constraint);

        if (relaxedInner < Type::BoxedCell && newType >= Type::BoxedCell) {
          FTRACE(1, "relaxGuards changing newType to {}\n", newType);
          newType = relaxedInner;
        }
      }

      if (constraint.assertedType < newType) {
        // If the asserted type is more specific than the new guarded type, set
        // the guard to the relaxed type but insert an assert operation between
        // the instruction and its dst. We go from something like this:
        //
        // t5:FramePtr = GuardLoc<Int, 4, <DataTypeGeneric,Int>> t4:FramePtr
        //
        // to this:
        //
        // t6:FramePtr = GuardLoc<Gen, 4> t4:FramePtr
        // t5:FramePtr = AssertLoc<Int, 4> t6:FramePtr

        auto* oldDst = inst.dst();
        auto* newDst = unit.genDst(&inst);
        auto* newAssert = [&] {
          switch (inst.op()) {
            case GuardLoc:
            case CheckLoc:
              return unit.genWithDst(oldDst,
                                     guardToAssert(inst.op()),
                                     inst.marker(),
                                     *inst.extra<LocalId>(),
                                     constraint.assertedType,
                                     newDst);

            case GuardStk:
            case CheckStk:
              return unit.genWithDst(oldDst,
                                     guardToAssert(inst.op()),
                                     inst.marker(),
                                     *inst.extra<StackOffset>(),
                                     constraint.assertedType,
                                     newDst);

            case CheckType:
              return unit.genWithDst(oldDst,
                                     guardToAssert(inst.op()),
                                     inst.marker(),
                                     constraint.assertedType,
                                     newDst);

            default: always_assert(false);
          }
        }();

        FTRACE(1, "relaxGuards inserting {} between {} and its dst, "
               "changing typeParam to {}\n",
               *newAssert, inst, newType);
        inst.setTypeParam(newType);

        // Now, insert the assert after the guard. For control flow guards,
        // this means inserting it on the next edge.
        if (inst.isControlFlow()) {
          auto* block = inst.next();
          block->insert(block->skipHeader(), newAssert);
        } else {
          auto* block = inst.block();
          auto it = block->iteratorTo(&inst);
          ++it;
          block->insert(it, newAssert);
        }

        changed = true;
      } else if (oldType != newType) {
        FTRACE(1, "relaxGuards changing {}'s type to {}\n", inst, newType);
        inst.setTypeParam(newType);
        changed = true;
      }
    }
  }

  if (!changed) return false;

  // Make a second pass to reflow types, with some special logic for loads.
  FrameState state(unit);
  for (auto* block : blocks) {
    state.startBlock(block);

    for (auto& inst : *block) {
      state.setMarker(inst.marker());
      copyProp(&inst);
      visitLoad(&inst, state);
      if (!removeGuard(unit, &inst, state)) {
        retypeDests(&inst);
        state.update(&inst);
      }
    }

    state.finishBlock(block);
  }

  return true;
}
Beispiel #3
0
/*
 * For all guard instructions in unit, check to see if we can relax the
 * destination type to something less specific. The GuardConstraints map
 * contains information about what properties of the guarded type matter for
 * each instruction. If simple is true, guards will not be relaxed past
 * DataTypeSpecific except guards which are relaxed all the way to
 * DataTypeGeneric. Returns true iff any changes were made to the trace.
 */
bool relaxGuards(IRUnit& unit, const GuardConstraints& constraints,
                 RelaxGuardsFlags flags) {
  Timer _t(Timer::optimize_relaxGuards);
  ITRACE(2, "entering relaxGuards\n");
  Indent _i;
  bool simple = flags & RelaxSimple;
  bool reflow = flags & RelaxReflow;
  splitCriticalEdges(unit);
  auto& guards = constraints.guards;
  auto blocks = rpoSortCfg(unit);
  auto changed = false;

  for (auto* block : blocks) {
    for (auto& inst : *block) {
      if (!isGuardOp(inst.op())) continue;

      auto it = guards.find(&inst);
      auto constraint = it == guards.end() ? TypeConstraint() : it->second;
      ITRACE(2, "relaxGuards processing {} with constraint {}\n",
             inst, constraint);

      auto simplifyCategory = [simple](DataTypeCategory& cat) {
        if (simple && cat > DataTypeGeneric && cat < DataTypeSpecific) {
          cat = DataTypeSpecific;
        }
      };
      simplifyCategory(constraint.category);
      simplifyCategory(constraint.innerCat);

      auto const oldType = inst.typeParam();
      auto newType = relaxType(oldType, constraint);

      if (oldType != newType) {
        ITRACE(1, "relaxGuards changing {}'s type to {}\n", inst, newType);
        inst.setTypeParam(newType);
        changed = true;
      }
    }
  }

  if (!changed) return false;
  if (!reflow) return true;

  // Make a second pass to reflow types, with some special logic for loads.
  FrameState state{unit, unit.entry()->front().marker()};
  for (auto* block : blocks) {
    ITRACE(2, "relaxGuards reflow entering B{}\n", block->id());
    Indent _i;
    state.startBlock(block, block->front().marker());

    for (auto& inst : *block) {
      state.setMarker(inst.marker());
      copyProp(&inst);
      visitLoad(&inst, state);
      retypeDests(&inst, &unit);
      state.update(&inst);
    }

    state.finishBlock(block);
  }

  return true;
}
Beispiel #4
0
void optimizeStores(IRUnit& unit) {
  PassTracer tracer{&unit, Trace::hhir_store, "optimizeStores"};

  // This isn't required for correctness, but it may allow removing stores that
  // otherwise we would leave alone.
  splitCriticalEdges(unit);

  auto incompleteQ = dataflow_worklist<PostOrderId>(unit.numBlocks());

  /*
   * Global state for this pass, visible while processing any block.
   */
  auto genv = Global { unit };
  auto const& poBlockList = genv.poBlockList;
  if (genv.ainfo.locations.size() == 0) {
    FTRACE(1, "no memory accesses to possibly optimize\n");
    return;
  }
  FTRACE(1, "\nLocations:\n{}\n", show(genv.ainfo));

  /*
   * Initialize the block state structures.
   *
   * Important note: every block starts with an empty liveOut set, including
   * blocks that are exiting the region.  When an HHIR region is exited,
   * there's always some instruction we can use to indicate via memory_effects
   * what may be read (e.g. EndCatch, RetCtrl, ReqBindJmp, etc).  When we start
   * iterating, we'll appropriately add things to GEN based on these.
   */
  for (auto poId = uint32_t{0}; poId < poBlockList.size(); ++poId) {
    genv.blockStates[poBlockList[poId]->id()].id = poId;
    incompleteQ.push(poId);
  }

  /*
   * Analyze each block to compute its transfer function.
   *
   * The blockAnalysis vector is indexed by post order id.
   */
  auto const blockAnalysis = [&] () -> jit::vector<BlockAnalysis> {
    auto ret = jit::vector<BlockAnalysis>{};
    ret.reserve(unit.numBlocks());
    for (auto id = uint32_t{0}; id < poBlockList.size(); ++id) {
      ret.push_back(analyze_block(genv, poBlockList[id]));
    }
    return ret;
  }();

  FTRACE(2, "Transfer functions:\n{}\n",
    [&]() -> std::string {
      auto ret = std::string{};
      for (auto poId = uint32_t{0}; poId < poBlockList.size(); ++poId) {
        auto& analysis = blockAnalysis[poId];
        folly::format(
          &ret,
          " B{}\n"
          "   gen:  {}\n"
          "   kill: {}\n",
          poBlockList[poId]->id(),
          show(analysis.gen),
          show(analysis.kill)
        );
      }
      return ret;
    }()
  );

  /*
   * Iterate on the liveOut states until we reach a fixed point.
   */
  FTRACE(4, "Iterating\n");
  while (!incompleteQ.empty()) {
    auto const poId = incompleteQ.pop();
    auto const blk  = poBlockList[poId];

    auto& state         = genv.blockStates[blk->id()];
    auto const transfer = blockAnalysis[poId];
    assertx(state.id == poId);

    state.liveIn = transfer.gen | (state.liveOut & ~transfer.kill);
    FTRACE(4, "  block B{}\n"
              "    live out: {}\n"
              "        gen : {}\n"
              "       kill : {}\n"
              "    live in : {}\n",
              blk->id(),
              show(state.liveOut),
              show(transfer.gen),
              show(transfer.kill),
              show(state.liveIn));

    /*
     * Update predecessors by merging the live in state into the live out state
     * of each predecessor.
     *
     * If anything changes, reschedule the predecessor.
     */
    blk->forEachPred([&] (Block* pred) {
      FTRACE(4, "   -> {}\n", pred->id());
      auto& predState = genv.blockStates[pred->id()];

      auto const oldLiveOut = predState.liveOut;
      predState.liveOut |= state.liveIn;

      if (predState.liveOut != oldLiveOut) {
        incompleteQ.push(predState.id);
      }
    });
  }

  /*
   * We've reached a fixed point.  Now we can act on this information to remove
   * dead stores.
   */
  FTRACE(2, "\nFixed point:\n{}\n",
    [&]() -> std::string {
      auto ret = std::string{};
      for (auto& blk : poBlockList) {
        folly::format(
          &ret,
          " B{: <3}: {}\n",
          blk->id(),
          show(genv.blockStates[blk->id()].liveOut)
        );
      }
      return ret;
    }()
  );
  for (auto& block : poBlockList) {
    optimize_block(genv, block);
  }
}