void FrameStateMgr::update()

in hphp/runtime/vm/jit/frame-state.cpp [260:642]


void FrameStateMgr::update(const IRInstruction* inst) {
  ITRACE(3, "FrameStateMgr::update processing {}\n", *inst);
  Indent _i;

  if (auto const taken = inst->taken()) {
    // If CheckMROProp fails, we know the bit is false, so propagate
    // that to the taken edge.
    auto const oldMROProp = cur().mROProp;
    if (inst->is(CheckMROProp)) cur().mROProp = TriBool::No;

    /*
     * TODO(#4323657): we should make this assertion for all non-empty blocks
     * (exits in addition to catches).  It would fail right now for exit
     * traces.
     *
     * If you hit this assertion: you've created a catch block and then
     * modified tracked state, then generated a potentially exception-throwing
     * instruction using that catch block as a target.  This is not allowed.
     */
    if (debug && taken->isCatch()) {
      auto const tmp = save(taken);
      always_assert_flog(
        !tmp,
        "catch block B{} had non-matching in state",
        taken->id()
      );
    }

    // When we're building the IR, we append a conditional jump after
    // generating its target block: see emitJmpCondHelper, where we
    // call makeExit() before gen(JmpZero).  It doesn't make sense to
    // update the target block state at this point, so don't.  The
    // state doesn't have this problem during optimization passes,
    // because we'll always process the jump before the target block.
    if (taken->empty()) save(taken);

    // Restore the original value since we're going to process the
    // next edge now.
    cur().mROProp = oldMROProp;
  }

  auto const killIterLocals = [&](const std::initializer_list<uint32_t>& ids) {
    for (auto id : ids) {
      setValueAndSyncMBase(loc(id), nullptr, false);
    }
  };

  auto const setFrameCtx = [&] (const SSATmp* fp, SSATmp* ctx) {
    for (auto& frame : m_stack) {
      if (frame.fpValue != fp) continue;
      frame.ctx = ctx;
      frame.ctxType = ctx->type();
    }
  };

  switch (inst->op()) {
  case BeginInlining:  trackBeginInlining(inst); break;
  case EndInlining:    trackEndInlining(); break;
  case InlineCall:     trackInlineCall(inst); break;
  case StFrameCtx:
    setFrameCtx(inst->src(0), inst->src(1));
    break;

  case Call:
    {
      assertx(cur().checkMInstrStateDead());
      auto const extra = inst->extra<Call>();
      // Remove tracked state for the slots for args and the actrec.
      uint32_t numCells = kNumActRecCells + extra->numInputs();
      for (auto i = uint32_t{0}; i < numCells; ++i) {
        setValue(stk(extra->spOffset + i), nullptr);
      }
      // Mark out parameter locations as being at least InitCell
      auto const callee = inst->src(2)->hasConstVal(TFunc)
        ? inst->src(2)->funcVal() : nullptr;
      auto const base = extra->spOffset + numCells;
      for (auto i = uint32_t{0}; i < extra->numOut; ++i) {
        auto const ty = callee && callee->takesInOutParams()
          ? irgen::callOutType(callee, i)
          : TInitCell;
        setType(stk(base + i), ty);
      }
      // We consider popping an ActRec and args to be synced to memory.
      assertx(cur().bcSPOff == inst->marker().bcSPOff());
      cur().bcSPOff -= numCells;
    }
    break;

  case ContEnter:
    assertx(cur().checkMInstrStateDead());
    break;

  case DefFP:
  case DefFuncEntryFP:
    cur().fpValue = inst->dst();
    cur().fixupFPValue = inst->dst();
    break;

  case InitFrame:
    /* The last opcode of prologues. Does not modify any defined frame. */
    break;

  case EnterPrologue:
    cur().stublogue = true;
    break;

  case ExitPrologue:
    cur().stublogue = false;
    break;

  case RetCtrl:
    uninitStack();
    cur().fpValue = nullptr;
    cur().fixupFPValue = nullptr;
    break;

  case DefFrameRelSP:
  case DefRegSP: {
    auto const data = inst->extra<DefStackData>();
    initStack(inst->dst(), data->irSPOff, data->bcSPOff);
    break;
  }

  case LdMem:
    pointerLoad(inst->src(0), inst->dst());
    break;

  case StMem:
  case StMemMeta:
    pointerStore(inst->src(0), inst->src(1));
    break;

  case LdStk: {
    auto const offset = inst->extra<LdStk>()->offset;
    // Nearly all callers of setValue() for stack slots represent a
    // modification of the stack, so it sets stackModified. LdStk is the one
    // exception, so we compensate for that here.
    auto const oldModified = cur().stackModified;
    setValueAndSyncMBase(stk(offset), inst->dst(), true);
    cur().stackModified = oldModified;
    break;
  }

  case StStk:
  case StStkMeta:
    setValueAndSyncMBase(
      stk(inst->extra<IRSPRelOffsetData>()->offset),
      inst->src(1),
      false
    );
    break;

  case CheckType:
  case AssertType: {
    auto const oldVal = inst->src(0);
    auto const newVal = inst->dst();
    for (auto& frame : m_stack) {
      for (auto& it : frame.locals) {
        refineValue(it.second, oldVal, newVal);
      }
      for (auto& it : frame.stack) {
        refineValue(it.second, oldVal, newVal);
      }
      if (frame.ctx && canonical(frame.ctx) == canonical(inst->src(0))) {
        frame.ctx = inst->dst();
        frame.ctxType = inst->dst()->type();
      }
    }
    // MInstrState can only be live for the current frame.
    refineMBaseValue(oldVal, newVal);
    auto const canonOldVal = canonical(oldVal);
    if (cur().mTempBase.value &&
        canonOldVal == canonical(cur().mTempBase.value)) {
      setMTempBase(newVal);
    }
    if (cur().mbr.ptr && canonOldVal == canonical(cur().mbr.ptr)) {
      setMBR(newVal, true);
    }
    break;
  }

  case CheckTypeMem:
    pointerRefine(inst->src(0), inst->typeParam());
    break;
  case CheckInitMem:
    pointerRefine(inst->src(0), TInitCell);
    break;

  case AssertLoc:
  case CheckLoc: {
    auto const id = inst->extra<LocalId>()->locId;
    refineTypeAndSyncMBase(
      loc(id),
      inst->typeParam(),
      TypeSource::makeGuard(inst)
    );
    break;
  }

  case AssertStk:
  case CheckStk:
    refineTypeAndSyncMBase(
      stk(inst->extra<IRSPRelOffsetData>()->offset),
      inst->typeParam(),
      TypeSource::makeGuard(inst)
    );
    break;

  case AssertMBase:
    refineTypeAndSyncMBase(
      Location::MBase{},
      inst->typeParam(),
      TypeSource::makeGuard(inst)
    );
    break;

  case CheckMBase:
    setMBR(inst->src(0), true);
    refineTypeAndSyncMBase(
      Location::MBase{},
      inst->typeParam(),
      TypeSource::makeGuard(inst)
    );
    break;

  case StLoc:
  case StLocMeta:
    setValueAndSyncMBase(
      loc(inst->extra<LocalId>()->locId),
      inst->src(1),
      false
    );
    break;

  case LdLoc: {
    auto const id = inst->extra<LdLoc>()->locId;
    setValueAndSyncMBase(loc(id), inst->dst(), true);
    break;
  }

  case EndCatch:
    /*
     * Hitting this means we've messed up with syncing the stack in a catch
     * trace.  If the stack isn't clean or doesn't match the marker's irSPOff,
     * the unwinder won't see what we expect.
     */
    always_assert_flog(
      inst->extra<EndCatch>()->offset.to<SBInvOffset>(cur().irSPOff) ==
        inst->marker().bcSPOff(),
      "EndCatch stack didn't seem right:\n"
      "                 spOff: {}\n"
      "       EndCatch offset: {}\n"
      "        marker's spOff: {}\n",
      cur().irSPOff.offset,
      inst->extra<EndCatch>()->offset.offset,
      inst->marker().bcSPOff().offset
    );
    break;

  case InterpOne:
  case InterpOneCF: {
    auto const& extra = *inst->extra<InterpOneData>();
    assertx(!extra.smashesAllLocals || extra.nChangedLocals == 0);
    if (extra.smashesAllLocals) {
      clearLocals();
    } else {
      auto it = extra.changedLocals;
      auto const end = it + extra.nChangedLocals;
      for (; it != end; ++it) {
        auto& local = *it;
        setType(loc(local.id), local.type);
      }
    }

    // Offset of the bytecode stack top relative to the IR stack pointer.
    auto const bcSPOff = extra.spOffset;

    // Clear tracked information for slots pushed and popped.
    for (auto i = uint32_t{0}; i < extra.cellsPopped; ++i) {
      setValue(stk(bcSPOff + i), nullptr);
    }
    for (auto i = uint32_t{0}; i < extra.cellsPushed; ++i) {
      setValue(stk(bcSPOff + extra.cellsPopped - 1 - i), nullptr);
    }
    auto adjustedTop = bcSPOff + extra.cellsPopped - extra.cellsPushed;

    switch (extra.opcode) {
      case Op::CGetL2:
        setType(stk(adjustedTop + 1), inst->typeParam());
        break;
      default:
        // We don't track cells pushed by interp one except the top of the
        // stack, aside from the above special cases.
        if (inst->hasTypeParam()) {
          auto const instrInfo = getInstrInfo(extra.opcode);
          if (instrInfo.out & InstrFlags::Stack1) {
            setType(stk(adjustedTop), inst->typeParam());
          }
        }
        break;
    }

    cur().bcSPOff += extra.cellsPushed;
    cur().bcSPOff -= extra.cellsPopped;

    // Be conservative and drop minstr state
    clearMInstr();
    break;
  }

  case IterInit:
  case LIterInit:
  case IterNext:
  case LIterNext: {
    auto const& args = inst->extra<IterData>()->args;
    assertx(!args.hasKey());
    killIterLocals({safe_cast<uint32_t>(args.valId)});
    break;
  }

  case IterInitK:
  case LIterInitK:
  case IterNextK:
  case LIterNextK: {
    auto const& args = inst->extra<IterData>()->args;
    assertx(args.hasKey());
    killIterLocals({safe_cast<uint32_t>(args.keyId),
                    safe_cast<uint32_t>(args.valId)});
    break;
  }

  case LdMBase:
    setMBR(inst->dst(), true);
    break;

  case StMBase:
    setMBR(inst->src(0), false);
    break;

  case StMROProp:
    cur().mROProp = inst->src(0)->hasConstVal()
      ? yesOrNo(inst->src(0)->boolVal())
      : TriBool::Maybe;
    break;

  case CheckMROProp:
    cur().mROProp = TriBool::Yes;
    break;

  case FinishMemberOp:
    clearMInstr();
    break;

  case CallBuiltin:
    // CallBuiltin uses the same memory as mTempBase, so it had better
    // be dead.
    assertx(cur().checkMInstrStateDead());
    break;

  case PropX:
  case PropDX:
  case PropQ:
    cur().mROProp |= TriBool::Yes;
    if (!inst->src(2)->isA(TNullptr)) {
      assertx(inst->src(2)->isA(TPtrToMISTemp));
      pointerStore(inst->src(2), nullptr);
    }
    break;

  default:
    // Use precise Minstr effects if we can
    if (hasMInstrBaseEffects(*inst)) {
      handleMInstr(inst);
    } else {
      // Handle everything else conservatively according to its memory
      // effects
      handleConservatively(inst);
    }
    break;
  }

  assertx(checkInvariants());
}