void MInstrEffects::get(const IRInstruction* inst, const FrameStateMgr& frame, LocalStateHook& hook) { // If the base for this instruction is a local address, the helper call might // have side effects on the local's value auto const base = inst->src(minstrBaseIdx(inst->op())); auto const locInstr = base->inst(); // Right now we require that the address of any affected local is the // immediate source of the base tmp. This isn't actually specified in the ir // spec right now but will intend to make it more general soon. if (locInstr->op() != LdLocAddr) return; auto const locId = locInstr->extra<LdLocAddr>()->locId; auto const baseType = frame.localType(locId); MInstrEffects effects(inst->op(), baseType.ptr(Ptr::Frame)); if (effects.baseTypeChanged || effects.baseValChanged) { auto const ty = effects.baseType.derefIfPtr(); if (ty.isBoxed()) { hook.setLocalType(locId, Type::BoxedInitCell); hook.setBoxedLocalPrediction(locId, ty); } else { hook.setLocalType(locId, ty); } } }
/** * This method changes any boxed local into a BoxedInitCell type. It's safe to * assume they're init because you can never have a reference to uninit. */ void FrameState::dropLocalRefsInnerTypes(LocalStateHook& hook) const { walkAllInlinedLocals( [&](uint32_t i, unsigned inlineIdx, const LocalState& local) { if (local.type.isBoxed()) { hook.dropLocalInnerType(i, inlineIdx); } }); }
/** * Called to clear out the tracked local values at a call site. Calls kill all * registers, so we don't want to keep locals in registers across calls. We do * continue tracking the types in locals, however. */ void FrameState::killLocalsForCall(LocalStateHook& hook, bool skipThisFrame) const { walkAllInlinedLocals( [&](uint32_t i, unsigned inlineIdx, const LocalState& local) { auto* value = local.value; if (!value || value->inst()->is(DefConst)) return; hook.killLocalForCall(i, inlineIdx, value); }, skipThisFrame); }
void FrameState::refineLocalValues(LocalStateHook& hook, SSATmp* oldVal, SSATmp* newVal) const { assert(newVal->inst()->is(CheckType)); assert(newVal->inst()->src(0) == oldVal); for (unsigned i = 0, n = m_locals.size(); i < n; ++i) { if (m_locals[i].value == oldVal) { hook.refineLocalValue(i, oldVal, newVal); } } }
// // This method updates the tracked values and types of all locals that contain // oldRef so that they now contain newRef. // This should only be called for ref/boxed types. // void FrameState::updateLocalRefValues(LocalStateHook& hook, SSATmp* oldRef, SSATmp* newRef) const { assert(oldRef->type().isBoxed()); assert(newRef->type().isBoxed()); walkAllInlinedLocals( [&](uint32_t i, unsigned inlineIdx, const LocalState& local) { if (local.value != oldRef) return; hook.updateLocalRefValue(i, inlineIdx, oldRef, newRef); }); }
void FrameState::refineLocalValues(LocalStateHook& hook, SSATmp* oldVal, SSATmp* newVal) const { assert(newVal->inst()->is(CheckType, AssertType)); assert(newVal->inst()->src(0) == oldVal); walkAllInlinedLocals( [&](uint32_t i, unsigned inlineIdx, const LocalState& local) { if (local.value == oldVal) { hook.refineLocalValue(i, inlineIdx, oldVal, newVal); } }); }
void local_effects(const FrameStateMgr& frameState, const IRInstruction* inst, LocalStateHook& hook) { auto killIterLocals = [&](const std::initializer_list<uint32_t>& ids) { for (auto id : ids) { hook.setLocalValue(id, nullptr); } }; switch (inst->op()) { case CallBuiltin: if (inst->extra<CallBuiltin>()->destroyLocals) hook.clearLocals(); break; case Call: case CallArray: case ContEnter: { auto const callDestroysLocals = (inst->is(CallArray) && inst->extra<CallArray>()->destroyLocals) || (inst->is(Call) && inst->extra<Call>()->destroyLocals); hook.killLocalsForCall(callDestroysLocals); } break; case StRef: hook.updateLocalRefPredictions(inst->src(0), inst->src(1)); break; case StLocNT: case StLoc: hook.setLocalValue(inst->extra<LocalId>()->locId, inst->src(1)); break; case LdLoc: hook.setLocalValue(inst->extra<LdLoc>()->locId, inst->dst()); break; case StLocPseudoMain: hook.predictLocalType(inst->extra<LocalId>()->locId, inst->src(1)->type()); break; case AssertLoc: case GuardLoc: case CheckLoc: { auto id = inst->extra<LocalId>()->locId; if (inst->marker().func()->isPseudoMain()) { hook.predictLocalType(id, inst->typeParam()); } else { hook.refineLocalType(id, inst->typeParam(), TypeSource::makeGuard(inst)); } break; } case HintLocInner: hook.setBoxedLocalPrediction(inst->extra<HintLocInner>()->locId, inst->typeParam()); break; case CheckType: case AssertType: hook.refineLocalValues(inst->src(0), inst->dst()); break; case IterInitK: case WIterInitK: // kill the locals to which this instruction stores iter's key and value killIterLocals({inst->extra<IterData>()->keyId, inst->extra<IterData>()->valId}); break; case IterInit: case WIterInit: // kill the local to which this instruction stores iter's value killIterLocals({inst->extra<IterData>()->valId}); break; case IterNextK: case WIterNextK: // kill the locals to which this instruction stores iter's key and value killIterLocals({inst->extra<IterData>()->keyId, inst->extra<IterData>()->valId}); break; case IterNext: case WIterNext: // kill the local to which this instruction stores iter's value killIterLocals({inst->extra<IterData>()->valId}); break; case InterpOne: case InterpOneCF: { auto const& id = *inst->extra<InterpOneData>(); assert(!id.smashesAllLocals || id.nChangedLocals == 0); if (id.smashesAllLocals || inst->marker().func()->isPseudoMain()) { hook.clearLocals(); } else { auto it = id.changedLocals; auto const end = it + id.nChangedLocals; for (; it != end; ++it) { auto& loc = *it; // If changing the inner type of a boxed local, also drop the // information about inner types for any other boxed locals. if (loc.type.isBoxed()) hook.dropLocalRefsInnerTypes(); hook.setLocalType(loc.id, loc.type); } } break; } default: break; } if (MInstrEffects::supported(inst)) { MInstrEffects::get(inst, frameState, hook); } }
///// Support helpers for getLocalEffects ///// void FrameState::clearLocals(LocalStateHook& hook) const { for (unsigned i = 0; i < m_locals.size(); ++i) { hook.setLocalValue(i, nullptr); } }
void FrameState::getLocalEffects(const IRInstruction* inst, LocalStateHook& hook) const { auto killIterLocals = [&](const std::initializer_list<uint32_t>& ids) { for (auto id : ids) { hook.setLocalValue(id, nullptr); } }; auto killedCallLocals = false; if ((inst->is(CallArray) && inst->extra<CallArrayData>()->destroyLocals) || (inst->is(Call, CallBuiltin) && inst->extra<CallData>()->destroyLocals)) { clearLocals(hook); killedCallLocals = true; } switch (inst->op()) { case Call: case CallArray: case ContEnter: killLocalsForCall(hook, killedCallLocals); break; case StRef: { SSATmp* newRef = inst->dst(); SSATmp* prevRef = inst->src(0); // update other tracked locals that also contain prevRef updateLocalRefValues(hook, prevRef, newRef); break; } case StLocNT: case StLoc: hook.setLocalValue(inst->extra<LocalId>()->locId, inst->src(1)); break; case LdLoc: hook.setLocalValue(inst->extra<LdLoc>()->locId, inst->dst()); break; case AssertLoc: case GuardLoc: case CheckLoc: hook.refineLocalType(inst->extra<LocalId>()->locId, inst->typeParam(), inst->dst()); break; case CheckType: case AssertType: { SSATmp* newVal = inst->dst(); SSATmp* oldVal = inst->src(0); refineLocalValues(hook, oldVal, newVal); break; } case IterInitK: case WIterInitK: // kill the locals to which this instruction stores iter's key and value killIterLocals({inst->extra<IterData>()->keyId, inst->extra<IterData>()->valId}); break; case IterInit: case WIterInit: // kill the local to which this instruction stores iter's value killIterLocals({inst->extra<IterData>()->valId}); break; case IterNextK: case WIterNextK: // kill the locals to which this instruction stores iter's key and value killIterLocals({inst->extra<IterData>()->keyId, inst->extra<IterData>()->valId}); break; case IterNext: case WIterNext: // kill the local to which this instruction stores iter's value killIterLocals({inst->extra<IterData>()->valId}); break; case InterpOne: case InterpOneCF: { auto const& id = *inst->extra<InterpOneData>(); assert(!id.smashesAllLocals || id.nChangedLocals == 0); if (id.smashesAllLocals) { clearLocals(hook); } else { auto it = id.changedLocals; auto const end = it + id.nChangedLocals; for (; it != end; ++it) { auto& loc = *it; // If changing the inner type of a boxed local, also drop the // information about inner types for any other boxed locals. if (loc.type.isBoxed()) dropLocalRefsInnerTypes(hook); hook.setLocalType(loc.id, loc.type); } } break; } default: break; } if (MInstrEffects::supported(inst)) MInstrEffects::get(inst, hook); }