in hphp/runtime/vm/jit/memory-effects.cpp [305:1848]
MemEffects memory_effects_impl(const IRInstruction& inst) {
switch (inst.op()) {
//////////////////////////////////////////////////////////////////////
// Region exits
// These exits don't leave the current php function, and could head to code
// that could read or write anything as far as we know (including frame
// locals).
case ReqBindJmp:
return ExitEffects {
*AUnknown.exclude_vm_reg(),
stack_below(inst.extra<ReqBindJmp>()->irSPOff)
};
case ReqInterpBBNoTranslate:
return ExitEffects {
*AUnknown.exclude_vm_reg(),
stack_below(inst.extra<ReqInterpBBNoTranslate>()->irSPOff)
};
case ReqRetranslate:
return ExitEffects {
*AUnknown.exclude_vm_reg(),
stack_below(inst.extra<ReqRetranslate>()->offset)
};
case ReqRetranslateOpt:
return ExitEffects {
*AUnknown.exclude_vm_reg(),
stack_below(inst.extra<ReqRetranslateOpt>()->offset)
};
case JmpSwitchDest:
return ExitEffects {
*AUnknown.exclude_vm_reg(),
*stack_below(inst.extra<JmpSwitchDest>()->spOffBCFromIRSP).
precise_union(AMIStateAny)
};
case JmpSSwitchDest:
return ExitEffects {
*AUnknown.exclude_vm_reg(),
*stack_below(inst.extra<JmpSSwitchDest>()->offset).
precise_union(AMIStateAny)
};
//////////////////////////////////////////////////////////////////////
// Unusual instructions
/*
* The ReturnHook sets up the ActRec so the unwinder knows everything is
* already released (i.e. it calls ar->setLocalsDecRefd()).
*
* The eval stack is also dead at this point (the return value is passed to
* ReturnHook as src(1), and the ReturnHook may not access the stack).
*/
case ReturnHook:
return may_load_store_kill(
AHeapAny | AActRec {inst.src(0)}, AHeapAny,
[&] {
auto const u =
AStackAny.precise_union(ALocalAny)->precise_union(AMIStateAny);
// The return hooks trashes the frame context in debug builds
if (debug) return *u->precise_union(AFContext { inst.src(0) });
return *u;
}()
);
// The suspend hooks can load anything (re-entering the VM), but can't write
// to frame locals.
case SuspendHookAwaitEF:
case SuspendHookAwaitEG:
case SuspendHookAwaitR:
case SuspendHookCreateCont:
case SuspendHookYield:
// We rely on the may_reenter effects to add the appropriate local asets.
return may_load_store_kill(
AHeapAny | AActRec {inst.src(0)}, AHeapAny, AMIStateAny);
/*
* If we're returning from a function, it's ReturnEffects. The RetCtrl
* opcode also suspends resumables, which we model as having any possible
* effects.
*/
case RetCtrl:
if (inst.extra<RetCtrl>()->suspendingResumed) {
// Suspending can go anywhere, and doesn't even kill locals.
return UnknownEffects {};
}
return ReturnEffects {
AStackAny | ALocalAny | AMIStateAny | AFBasePtr
};
case AsyncFuncRetPrefetch:
return IrrelevantEffects {};
case AsyncFuncRet:
case AsyncFuncRetSlow:
case AsyncGenRetR:
return ReturnEffects { AStackAny | AMIStateAny | livefp(inst.src(1)) };
case AsyncGenYieldR:
case AsyncSwitchFast:
// Suspending can go anywhere, and doesn't even kill locals.
return UnknownEffects {};
case GenericRetDecRefs:
/*
* The may-store information here is ALocalAny: even though we
* know it doesn't really "store" to the frame locals, the values
* that used to be there are no longer available because they are
* DecRef'd, which we are required to report as may-store
* information to make it visible to reference count
* optimizations. It's conceptually the same as if it was storing
* an Uninit over each of the locals, but the stores of uninits
* would be dead so we're not actually doing that.
*/
return may_load_store_kill(
ALocalAny | AHeapAny,
ALocalAny | AHeapAny,
AMIStateAny
);
case EndCatch: {
auto const stack_kills = stack_below(inst.extra<EndCatch>()->offset);
return ExitEffects {
AUnknown,
stack_kills | AMIStateAny
};
}
case EnterTCUnwind: {
auto const stack_kills = stack_below(inst.extra<EnterTCUnwind>()->offset);
return ExitEffects {
AUnknown,
stack_kills | AMIStateAny
};
}
/*
* BeginInlining must always be the first instruction in the inlined call. It
* defines a new FP for the callee but does not perform any stores or
* otherwise initialize the FP.
*/
case BeginInlining: {
/*
* SP relative offset of the firstin the inlined call.
*/
auto inlineStackOff =
inst.extra<BeginInlining>()->spOffset + kNumActRecCells;
return may_load_store_kill(
AEmpty,
AEmpty,
/*
* This prevents stack slots from the caller from being sunk into the
* callee. Note that some of these stack slots overlap with the frame
* locals of the callee-- those slots are inacessible in the inlined
* call as frame and stack locations may not alias.
*/
stack_below(inlineStackOff)
);
}
case EndInlining: {
assertx(inst.src(0)->inst()->is(BeginInlining));
auto const fp = inst.src(0);
auto const callee = inst.src(0)->inst()->extra<BeginInlining>()->func;
const AliasClass ar = AActRec { inst.src(0) };
auto const locals = [&] () -> AliasClass {
if (!callee->numLocals()) return AEmpty;
return ALocal {fp, AliasIdSet::IdRange(0, callee->numLocals())};
}();
// NB: It's okay if the AliasIdSet for locals cannot be precise. We want to
// kill *every* local in the frame so there's nothing else that can
// accidentally be included in the set.
return may_load_store_kill(AEmpty, AEmpty, ar | locals | AMIStateAny);
}
case InlineCall:
return PureInlineCall {
AFBasePtr,
inst.src(0),
// Right now when we "publish" a frame by storing it in rvmfp() we
// implicitly depend on the AFFunc and AFMeta bits being stored. In the
// future we may want to track this explicitly.
//
// We also need to ensure that all of our parent frames have this stored
// this information. To achieve this we also register a load on AFBasePtr,
// forcing them to also be published. Notice that we don't actually
// depend on this load to properly initialize m_sfp or rvmfp().
AliasClass(AFFunc { inst.src(0) }) | AFMeta { inst.src(0) } | AFBasePtr
};
case InterpOne:
return interp_one_effects(inst);
case InterpOneCF: {
auto const extra = inst.extra<InterpOneData>();
return ExitEffects {
*AUnknown.exclude_vm_reg(),
stack_below(extra->spOffset) | AMIStateAny
};
}
case NativeImpl:
return UnknownEffects {};
// These C++ helpers can invoke the user error handler and go do whatever
// they want to non-frame locations.
case VerifyParamCallable:
case VerifyParamCls:
case VerifyParamFail:
case VerifyParamFailHard:
case VerifyPropCls:
case VerifyPropFail:
case VerifyPropFailHard:
case VerifyProp:
case VerifyPropAll:
case VerifyPropCoerce:
case VerifyPropCoerceAll:
case VerifyReifiedLocalType:
case VerifyReifiedReturnType:
case VerifyRetCallable:
case VerifyRetCls:
case VerifyRetFail:
case VerifyRetFailHard:
return may_load_store(AHeapAny, AHeapAny);
case ContEnter:
{
auto const extra = inst.extra<ContEnter>();
return CallEffects {
// Kills. Everything on the stack.
stack_below(extra->spOffset) | AMIStateAny | AVMRegAny,
// No inputs. The value being sent is passed explicitly.
AEmpty,
// ActRec. It is on the heap and we already implicitly assume that
// CallEffects can perform arbitrary heap operations.
AEmpty,
// No outputs.
AEmpty,
// Locals.
backtrace_locals(inst)
};
}
case Call:
{
auto const extra = inst.extra<Call>();
return CallEffects {
// Kills. Everything on the stack below the incoming parameters.
stack_below(extra->spOffset) | AMIStateAny | AVMRegAny,
// Input arguments.
extra->numInputs() == 0 ? AEmpty : AStack::range(
extra->spOffset,
extra->spOffset + extra->numInputs()
),
// ActRec.
actrec(inst.src(0), extra->spOffset + extra->numInputs()),
// Inout outputs.
extra->numOut == 0 ? AEmpty : AStack::range(
extra->spOffset + extra->numInputs() + kNumActRecCells,
extra->spOffset + extra->numInputs() + kNumActRecCells +
extra->numOut
),
// Locals.
backtrace_locals(inst)
};
}
case CallBuiltin:
{
auto const extra = inst.extra<CallBuiltin>();
auto const callee = extra->callee;
auto const [inout, read] = [&] {
auto read = AEmpty;
auto inout = AEmpty;
auto const paramOff = callee->isMethod() ? 3 : 2;
for (auto i = paramOff; i < inst.numSrcs(); ++i) {
if (inst.src(i)->type() <= TPtr) {
auto const cls = pointee(inst.src(i));
if (callee->isInOut(i - paramOff)) {
inout = inout | cls;
} else {
read = read | cls;
}
}
}
return std::make_pair(inout, read);
}();
auto const foldable = callee->isFoldable() ? AEmpty : ARdsAny;
return GeneralEffects {
read | AHeapAny | foldable | AVMRegAny | AVMRegState,
AHeapAny | foldable | AVMRegAny,
AEmpty,
stack_below(extra->spOffset) | AMIStateAny,
inout,
AEmpty
};
}
// Resumable suspension takes everything from the frame and moves it into the
// heap.
case CreateGen:
case CreateAGen:
case CreateAFWH: {
auto const fp = canonical(inst.src(0));
auto fpInst = fp->inst();
auto const frame = [&] () -> AliasClass {
if (fpInst->is(DefFP, DefFuncEntryFP)) return ALocalAny;
assertx(fpInst->is(BeginInlining));
auto const nlocals = fpInst->extra<BeginInlining>()->func->numLocals();
return nlocals
? ALocal { fp, AliasIdSet::IdRange(0, nlocals)}
: AEmpty;
}();
return may_load_store_move(
frame | AActRec { fp },
AHeapAny,
frame
);
}
// AGWH construction updates the AsyncGenerator object.
case CreateAGWH:
return may_load_store(AHeapAny | AActRec { inst.src(0) }, AHeapAny);
case CreateAAWH:
{
auto const extra = inst.extra<CreateAAWH>();
auto const frame = ALocal {
inst.src(0),
AliasIdSet {
AliasIdSet::IdRange{ extra->first, extra->first + extra->count }
}
};
return may_load_store(frame, AHeapAny);
}
case CountWHNotDone:
{
auto const extra = inst.extra<CountWHNotDone>();
auto const frame = ALocal {
inst.src(0),
AliasIdSet {
AliasIdSet::IdRange{ extra->first, extra->first + extra->count }
}
};
return may_load_store(frame, AEmpty);
}
// This re-enters to call extension-defined instance constructors.
case ConstructInstance:
return may_load_store(AHeapAny, AHeapAny);
// Closures don't ever throw or reenter on construction
case ConstructClosure:
return IrrelevantEffects{};
case CheckStackOverflow:
case CheckSurpriseFlagsEnter:
case CheckSurpriseAndStack:
return may_load_store(AEmpty, AEmpty);
//////////////////////////////////////////////////////////////////////
// Iterator instructions
case IterInit:
case LIterInit:
case IterNext:
case LIterNext: {
auto const& args = inst.extra<IterData>()->args;
assertx(!args.hasKey());
auto const fp = inst.src(inst.op() == IterNext ? 0 : 1);
AliasClass val = ALocal { fp, safe_cast<uint32_t>(args.valId) };
return iter_effects(inst, fp, val);
}
case IterInitK:
case LIterInitK:
case IterNextK:
case LIterNextK: {
auto const& args = inst.extra<IterData>()->args;
assertx(args.hasKey());
auto const fp = inst.src(inst.op() == IterNextK ? 0 : 1);
AliasClass key = ALocal { fp, safe_cast<uint32_t>(args.keyId) };
AliasClass val = ALocal { fp, safe_cast<uint32_t>(args.valId) };
return iter_effects(inst, fp, key | val);
}
case IterFree: {
auto const base = aiter_base(inst.src(0), iterId(inst));
return may_load_store(AHeapAny | base, AHeapAny);
}
case CheckIter: {
auto const iter = inst.extra<CheckIter>()->iterId;
return may_load_store(aiter_type(inst.src(0), iter), AEmpty);
}
case LdIterBase:
return PureLoad { aiter_base(inst.src(0), iterId(inst)) };
case LdIterPos:
return PureLoad { aiter_pos(inst.src(0), iterId(inst)) };
case LdIterEnd:
return PureLoad { aiter_end(inst.src(0), iterId(inst)) };
case StIterBase:
return PureStore { aiter_base(inst.src(0), iterId(inst)), inst.src(1) };
case StIterType: {
auto const iter = inst.extra<StIterType>()->iterId;
return PureStore { aiter_type(inst.src(0), iter), nullptr };
}
case StIterPos:
return PureStore { aiter_pos(inst.src(0), iterId(inst)), inst.src(1) };
case StIterEnd:
return PureStore { aiter_end(inst.src(0), iterId(inst)), inst.src(1) };
case KillActRec:
return may_load_store_kill(AEmpty, AEmpty, AActRec { inst.src(0) });
case KillLoc: {
auto const local = inst.extra<LocalId>()->locId;
return may_load_store_kill(AEmpty, AEmpty, ALocal { inst.src(0), local });
}
case KillIter: {
auto const iters = aiter_all(inst.src(0), iterId(inst));
return may_load_store_kill(AEmpty, AEmpty, iters);
}
//////////////////////////////////////////////////////////////////////
// Instructions that explicitly manipulate locals
case StLoc:
case StLocMeta:
return PureStore {
ALocal { inst.src(0), inst.extra<LocalId>()->locId },
inst.src(1),
nullptr
};
case StLocRange:
{
auto const extra = inst.extra<StLocRange>();
auto acls = AEmpty;
for (auto locId = extra->start; locId < extra->end; ++locId) {
acls = acls | ALocal { inst.src(0), locId };
}
return PureStore { acls, inst.src(1), nullptr };
}
case LdLoc:
return PureLoad { ALocal { inst.src(0), inst.extra<LocalId>()->locId } };
case LdLocForeign:
return may_load_store(ALocalAny, AEmpty);
case CheckLoc:
return may_load_store(
ALocal { inst.src(0), inst.extra<LocalId>()->locId },
AEmpty
);
//////////////////////////////////////////////////////////////////////
// Pointer-based loads and stores
case LdMem:
return PureLoad { pointee(inst.src(0)) };
case StMem:
case StMemMeta:
return PureStore { pointee(inst.src(0)), inst.src(1), inst.src(0) };
case LdClsInitElem:
return PureLoad { AHeapAny };
case StClsInitElem:
return PureStore { AHeapAny };
case LdPairElem:
return PureLoad { AHeapAny };
case LdMBase:
return PureLoad { AMIStateBase };
case StMBase:
return PureStore { AMIStateBase, inst.src(0), nullptr };
case StMROProp:
return PureStore { AMIStateROProp, inst.src(0), nullptr };
case CheckMROProp:
return may_load_store(AMIStateROProp, AEmpty);
case FinishMemberOp:
return may_load_store_kill(AEmpty, AEmpty, AMIStateAny);
case IsNTypeMem:
case IsTypeMem:
case CheckTypeMem:
case CheckInitMem:
return may_load_store(pointee(inst.src(0)), AEmpty);
case CheckRDSInitialized:
return may_load_store(
ARds { inst.extra<CheckRDSInitialized>()->handle },
AEmpty
);
case MarkRDSInitialized:
return may_load_store(
AEmpty,
ARds { inst.extra<MarkRDSInitialized>()->handle }
);
case MarkRDSAccess:
return IrrelevantEffects{};
// LdTVFromRDS and StTVInRDS load/store aux bit, so they cannot be
// PureLoad/PureStore -- load/store elim do not track aux bit accesses.
case LdTVFromRDS:
return may_load_store(
ARds { inst.extra<LdTVFromRDS>()->handle },
AEmpty
);
case StTVInRDS:
return may_load_store(
AEmpty,
ARds { inst.extra<StTVInRDS>()->handle }
);
case InitProps:
return may_load_store(
AHeapAny,
AHeapAny | ARds { inst.extra<InitProps>()->cls->propHandle() }
);
case InitSProps:
return may_load_store(
AHeapAny,
AHeapAny | ARds { inst.extra<InitSProps>()->cls->sPropInitHandle() }
);
case LdARFunc:
case LdClsFromClsMeth:
case LdFuncFromClsMeth:
case LdFuncFromRFunc:
case LdGenericsFromRFunc:
case LdClsFromRClsMeth:
case LdFuncFromRClsMeth:
case LdGenericsFromRClsMeth:
return may_load_store(AEmpty, AEmpty);
//////////////////////////////////////////////////////////////////////
// Object/Ref loads/stores
case InitObjProps:
return may_load_store(AEmpty, APropAny);
case InitObjMemoSlots:
// Writes to memo slots, but these are not modeled.
return IrrelevantEffects {};
case LockObj:
// Writes object attributes, but these are not modeled.
return IrrelevantEffects {};
// Loads $obj->trace, stores $obj->file and $obj->line.
case InitThrowableFileAndLine:
return may_load_store(AHeapAny, APropAny);
//////////////////////////////////////////////////////////////////////
// Array loads and stores
case InitVecElem: {
auto const arr = inst.src(0);
auto const val = inst.src(1);
auto const idx = inst.extra<InitVecElem>()->index;
return PureStore { AElemI { arr, idx }, val, arr };
}
case InitDictElem: {
auto const arr = inst.src(0);
auto const val = inst.src(1);
auto const key = inst.extra<InitDictElem>()->key;
return PureStore { AElemS { arr, key }, val, arr };
}
case InitStructElem: {
auto const arr = inst.src(0);
auto const val = inst.src(1);
auto const key = inst.extra<InitStructElem>()->key;
return PureStore { AElemS { arr, key }, val, arr };
}
case LdMonotypeDictVal: {
// TODO(mcolavita): When we have a type-array-elem method to get the key
// of an arbitrary array-like type, use that to narrow this load.
return PureLoad { AElemAny };
}
case LdMonotypeVecElem:
case LdVecElem: {
auto const base = inst.src(0);
auto const key = inst.src(1);
return PureLoad {
key->hasConstVal() ? AElemI { base, key->intVal() } : AElemIAny
};
}
case DictGetK:
case KeysetGetK:
case BespokeGet:
case KeysetGetQuiet:
case DictGetQuiet: {
auto const base = inst.src(0);
auto const key = inst.src(1);
assertx(key->type().subtypeOfAny(TInt, TStr));
if (key->isA(TInt)) {
return PureLoad {
key->hasConstVal() ? AElemI { base, key->intVal() } : AElemIAny,
};
} else {
return PureLoad {
key->hasConstVal() ? AElemS { base, key->strVal() } : AElemSAny,
};
}
}
case DictIsset:
case DictIdx:
case KeysetIsset:
case KeysetIdx:
case AKExistsDict:
case AKExistsKeyset:
case BespokeGetThrow: {
auto const base = inst.src(0);
auto const key = inst.src(1);
assertx(key->type().subtypeOfAny(TInt, TStr));
auto const elem = [&] {
if (key->isA(TInt)) {
return key->hasConstVal() ? AElemI { base, key->intVal() } : AElemIAny;
} else {
return key->hasConstVal() ? AElemS { base, key->strVal() } : AElemSAny;
}
}();
return may_load_store(elem, AEmpty);
}
case InitVecElemLoop:
{
auto const extra = inst.extra<InitVecElemLoop>();
auto const stack_in = AStack::range(
extra->offset,
extra->offset + static_cast<int32_t>(extra->size)
);
return may_load_store_move(stack_in, AElemIAny, stack_in);
}
// These ops may read anything referenced by the input array or object,
// but not any of the locals or stack frame slots.
case NewLoggingArray:
case ProfileArrLikeProps:
return may_load_store(AHeapAny, AEmpty);
case NewKeysetArray:
{
// NewKeysetArray is reading elements from the stack, but writes to a
// completely new array, so we can treat the store set as empty.
auto const extra = inst.extra<NewKeysetArray>();
auto const stack_in = AStack::range(
extra->offset,
extra->offset + static_cast<int32_t>(extra->size)
);
return may_load_store_move(stack_in, AEmpty, stack_in);
}
case NewStructDict:
{
// NewStructDict reads elements from the stack, but writes to a
// completely new array, so we can treat the store set as empty.
auto const extra = inst.extra<NewStructData>();
auto const stack_in = AStack::range(
extra->offset,
extra->offset + static_cast<int32_t>(extra->numKeys)
);
return may_load_store_move(stack_in, AEmpty, stack_in);
}
case NewBespokeStructDict:
{
// NewBespokeStructDict reads elements from the stack, but writes to
// a completely new array, so we can treat the stores as empty.
auto const extra = inst.extra<NewBespokeStructDict>();
auto const stack_in = AStack::range(
extra->offset,
extra->offset + static_cast<int32_t>(extra->numSlots)
);
return may_load_store_move(stack_in, AEmpty, stack_in);
}
case MemoGetStaticValue:
case MemoGetLSBValue:
case MemoGetInstanceValue:
// Only reads the memo value (which isn't modeled here).
return may_load_store(AEmpty, AEmpty);
case MemoSetStaticValue:
case MemoSetLSBValue:
case MemoSetInstanceValue:
// Writes to the memo value (which isn't modeled)
return may_load_store(AEmpty, AEmpty);
case MemoGetStaticCache:
case MemoGetLSBCache:
case MemoSetStaticCache:
case MemoSetLSBCache: {
// Reads some (non-zero) set of locals for keys, and reads/writes from the
// memo cache (which isn't modeled).
auto const extra = inst.extra<MemoCacheStaticData>();
auto const frame = ALocal {
inst.src(0),
AliasIdSet{
AliasIdSet::IdRange{
extra->keys.first,
extra->keys.first + extra->keys.count
}
}
};
return may_load_store(frame, AEmpty);
}
case MemoGetInstanceCache:
case MemoSetInstanceCache: {
// Reads some set of locals for keys, and reads/writes from the memo cache
// (which isn't modeled).
auto const extra = inst.extra<MemoCacheInstanceData>();
auto const frame = [&]() -> AliasClass {
// Unlike MemoGet/SetStaticCache, we can have an empty key range here.
if (extra->keys.count == 0) return AEmpty;
return ALocal {
inst.src(0),
AliasIdSet{
AliasIdSet::IdRange{
extra->keys.first,
extra->keys.first + extra->keys.count
}
}
};
}();
return may_load_store(frame, AEmpty);
}
case BespokeIterGetKey:
case LdPtrIterKey:
// Array element keys are not tracked by memory effects right
// now. Be conservative and use AElemAny.
return may_load_store(AElemAny, AEmpty);
case LdPtrIterVal:
return PureLoad { AElemAny };
case BespokeIterGetVal:
return may_load_store(AElemAny, AEmpty);
case ElemDictK:
return IrrelevantEffects {};
case VecFirst: {
auto const base = inst.src(0);
return may_load_store(AElemI { base, 0 }, AEmpty);
}
case VecLast: {
auto const base = inst.src(0);
if (base->hasConstVal(TArrLike)) {
auto const index = static_cast<int64_t>(base->arrLikeVal()->size() - 1);
return may_load_store(AElemI { base, index }, AEmpty);
}
return may_load_store(AElemIAny, AEmpty);
}
case DictFirst:
case DictLast:
case KeysetFirst:
case KeysetLast:
return may_load_store(AElemAny, AEmpty);
case DictFirstKey:
case DictLastKey:
case LdMonotypeDictTombstones:
case LdMonotypeDictKey:
return may_load_store(AEmpty, AEmpty);
case CheckDictKeys:
case CheckDictOffset:
case CheckKeysetOffset:
case CheckMissingKeyInArrLike:
case ProfileDictAccess:
case ProfileKeysetAccess:
case CheckArrayCOW:
case ProfileArrayCOW:
return may_load_store(AHeapAny, AEmpty);
case SameArrLike:
case NSameArrLike:
return may_load_store(AElemAny, AEmpty);
case EqArrLike:
case NeqArrLike: {
if (inst.src(0)->type() <= TKeyset && inst.src(1)->type() <= TKeyset) {
return may_load_store(AElemAny, AEmpty);
} else {
return may_load_store(AHeapAny, AHeapAny);
}
}
case AKExistsObj:
return may_load_store(AHeapAny, AHeapAny);
//////////////////////////////////////////////////////////////////////
// Member instructions
case CheckMBase:
return may_load_store(pointee(inst.src(0)), AEmpty);
/*
* Various minstr opcodes that take a Lval in src 0, which may or may not
* point to a frame local or the evaluation stack. Some may read or write to
* that pointer while some only read. They can all re-enter the VM and access
* arbitrary heap locations.
*/
case IncDecElem:
case SetElem:
case SetNewElem:
case SetOpElem:
case SetNewElemDict:
case SetNewElemVec:
case SetNewElemKeyset:
case UnsetElem:
case ElemDictD:
case ElemDictU:
case BespokeElem:
case ElemDX:
case ElemUX:
case SetRange:
case SetRangeRev:
// These member ops will load and store from the base lval which they
// take as their first argument, which may point anywhere in the heap.
return may_load_store(
AHeapAny | all_pointees(inst),
AHeapAny | all_pointees(inst)
);
case CGetElem:
case IssetElem:
case ElemX:
case CGetProp:
case CGetPropQ:
case SetProp:
case UnsetProp:
case IssetProp:
case IncDecProp:
case SetOpProp:
case ReserveVecNewElem:
return may_load_store(AHeapAny, AHeapAny);
/*
* Intermediate minstr operations. In addition to a base pointer like the
* operations above, these may take a pointer to MInstrState::tvRef
* which they may store to (but not read from).
*/
case PropX:
case PropDX:
case PropQ:
return minstr_with_tvref(inst);
/*
* Collection accessors can read from their inner array buffer, but stores
* COW and behave as if they only affect collection memory locations. We
* don't track those, so it's returning AEmpty for now.
*/
case MapIsset:
case PairIsset:
case VectorIsset:
return may_load_store(AHeapAny, AEmpty /* Note */);
case MapGet:
case MapSet:
case VectorSet:
return may_load_store(AHeapAny, AEmpty /* Note */);
case LdInitPropAddr:
return may_load_store(
AProp {
inst.src(0),
safe_cast<uint16_t>(inst.extra<LdInitPropAddr>()->index)
},
AEmpty
);
case LdInitRDSAddr:
return may_load_store(
ARds { inst.extra<LdInitRDSAddr>()->handle },
AEmpty
);
//////////////////////////////////////////////////////////////////////
// Instructions that allocate new objects, without reading any other memory
// at all, so any effects they have on some types of memory locations we
// track are isolated from anything else we care about.
case NewClsMeth:
case NewRClsMeth:
case NewCol:
case NewColFromArray:
case NewPair:
case NewInstanceRaw:
case NewDictArray:
case NewRFunc:
case FuncCred:
case AllocVec:
case AllocStructDict:
case AllocBespokeStructDict:
case ConvDblToStr:
case ConvIntToStr:
case InitStructPositions:
case AllocInitROM:
case StPtrAt:
case StTypeAt:
case VoidPtrAsDataType:
return IrrelevantEffects {};
case AllocObj:
// AllocObj re-enters to call constructors, but if it weren't for that we
// could ignore its loads and stores since it's a new object.
return may_load_store(AEmpty, AEmpty);
case AllocObjReified:
// Similar to AllocObj but also stores the reification
return may_load_store(AEmpty, AHeapAny);
//////////////////////////////////////////////////////////////////////
// Instructions that explicitly manipulate the stack.
case LdStk:
return PureLoad { AStack::at(inst.extra<LdStk>()->offset) };
case StStk:
case StStkMeta:
return PureStore {
AStack::at(inst.extra<IRSPRelOffsetData>()->offset),
inst.src(1),
nullptr
};
case StStkRange: {
auto const extra = inst.extra<StStkRange>();
auto const startOff = extra->start;
auto const count = extra->count;
return PureStore {
AStack::range(startOff, startOff + static_cast<int32_t>(count)),
inst.src(1),
nullptr
};
}
case StOutValue:
// Technically these writes affect the caller's stack, but there is no way
// to actually observe them from within the callee. They can also only
// occur once on any exit path from a function.
return may_load_store(AEmpty, AEmpty);
case LdOutAddr:
return IrrelevantEffects{};
case CheckStk:
return may_load_store(
AStack::at(inst.extra<CheckStk>()->offset),
AEmpty
);
case DbgTraceCall:
return may_load_store(AStackAny | ALocalAny, AEmpty);
case Unreachable:
// Unreachable code kills every memory location.
return may_load_store_kill(AEmpty, AEmpty, AUnknown);
case ResolveTypeStruct: {
auto const extra = inst.extra<ResolveTypeStructData>();
auto const stack_in = AStack::range(
extra->offset,
extra->offset + static_cast<int32_t>(extra->size)
);
return may_load_store(AliasClass(stack_in)|AHeapAny, AHeapAny);
}
case DefFP:
return may_load_store(AFBasePtr, AFBasePtr);
case DefFuncEntryFP:
return may_load_store(AFBasePtr, AFBasePtr | AFMeta { inst.dst() });
case InitFrame:
/* The last opcode of prologues. Does not modify any defined frame. */
return may_load_store(AEmpty, AEmpty);
case LdARFlags:
return PureLoad { AFMeta { inst.src(0) }};
case LdUnitPerRequestFilepath:
return PureLoad {
ARds { inst.extra<LdUnitPerRequestFilepath>()->handle },
};
case LdImplicitContext:
return PureLoad { ARds { ImplicitContext::activeCtx.handle() } };
case StImplicitContext:
return PureStore {
ARds { ImplicitContext::activeCtx.handle() }, inst.src(0), nullptr
};
case StImplicitContextWH:
return may_load_store(
ARds { ImplicitContext::activeCtx.handle() },
AEmpty
);
//////////////////////////////////////////////////////////////////////
// Instructions that never read or write memory locations tracked by this
// module.
case AbsDbl:
case AddDbl:
case AddInt:
case AddIntO:
case AddOffset:
case AdvanceDictPtrIter:
case AdvanceVecPtrIter:
case AndInt:
case AssertType:
case AssertLoc:
case AssertStk:
case AssertMBase:
case BespokeIterEnd:
case BespokeIterFirstPos:
case BespokeIterLastPos:
case ConvFuncPrologueFlagsToARFlags:
case DefFrameRelSP:
case DefFuncPrologueCallee:
case DefFuncPrologueCtx:
case DefFuncPrologueFlags:
case DefFuncPrologueNumArgs:
case DefRegSP:
case EndGuards:
case EnterPrologue:
case EnterTranslation:
case EqBool:
case EqCls:
case EqLazyCls:
case EqFunc:
case EqStrPtr:
case EqArrayDataPtr:
case EqDbl:
case EqInt:
case EqPtrIter:
case ExitPrologue:
case GetDictPtrIter:
case GetVecPtrIter:
case GteBool:
case GteInt:
case GtBool:
case GtInt:
case Jmp:
case JmpNZero:
case JmpZero:
case LdPropAddr:
case LdStkAddr:
case LdVecElemAddr:
case LteBool:
case LteDbl:
case LteInt:
case LtBool:
case LtInt:
case GtDbl:
case GteDbl:
case LtDbl:
case DivDbl:
case DivInt:
case MulDbl:
case MulInt:
case MulIntO:
case NeqBool:
case NeqDbl:
case NeqInt:
case SameObj:
case NSameObj:
case EqRes:
case NeqRes:
case CmpBool:
case CmpInt:
case CmpDbl:
case SubDbl:
case SubInt:
case SubIntO:
case XorBool:
case XorInt:
case OrInt:
case AssertNonNull:
case CheckNonNull:
case CheckNullptr:
case CheckSmashableClass:
case Ceil:
case Floor:
case DefLabel:
case CheckInit:
case Nop:
case Mod:
case Conjure:
case ConjureUse:
case EndBlock:
case ConvBoolToInt:
case ConvBoolToDbl:
case DefConst:
case LdLocAddr:
case Sqrt:
case Shl:
case Shr:
case Lshr:
case IsNType:
case IsType:
case Mov:
case ConvDblToBool:
case ConvDblToInt:
case DblAsBits:
case LdMIStateTempBaseAddr:
case LdClsCns:
case LdSubClsCns:
case LdResolvedTypeCns:
case LdResolvedTypeCnsClsName:
case LdResolvedTypeCnsNoCheck:
case CheckSubClsCns:
case LdClsCnsVecLen:
case FuncHasAttr:
case ClassHasAttr:
case LdFuncRequiredCoeffects:
case IsFunReifiedGenericsMatched:
case JmpPlaceholder:
case LdSmashable:
case LdSmashableFunc:
case LdRDSAddr:
case CheckRange:
case ProfileType:
case LdIfaceMethod:
case InstanceOfIfaceVtable:
case IsTypeStructCached:
case LdTVAux:
case MethodExists:
case GetTime:
case GetTimeNs:
case ProfileInstanceCheck:
case Select:
case LookupSPropSlot:
case ConvPtrToLval:
case ProfileProp:
case ProfileIsTypeStruct:
case LdLazyClsName:
case DirFromFilepath:
case CheckFuncNeedsCoverage:
case RecordFuncCall:
case LoadBCSP:
case StructDictSlot:
case StructDictElemAddr:
case StructDictAddNextSlot:
case StructDictTypeBoundCheck:
return IrrelevantEffects {};
case LookupClsCns:
case LookupClsCtxCns:
return may_load_store(AEmpty, AEmpty);
case StClosureArg:
return PureStore {
AProp {
inst.src(0),
safe_cast<uint16_t>(inst.extra<StClosureArg>()->index)
},
inst.src(1),
inst.src(0)
};
case StArResumeAddr:
return PureStore { AFMeta { inst.src(0) }, nullptr };
case LdFrameThis:
case LdFrameCls:
return PureLoad { AFContext { inst.src(0) }};
case StFrameCtx:
return PureStore { AFContext { inst.src(0) }, inst.src(1) };
case StFrameFunc:
return PureStore { AFFunc { inst.src(0) }, nullptr };
case StFrameMeta:
return PureStore { AFMeta { inst.src(0) }, nullptr };
case StVMFP:
return PureStore { AVMFP, inst.src(0) };
case StVMSP:
return PureStore { AVMSP, inst.src(0) };
case StVMPC:
return PureStore { AVMPC, nullptr };
case StVMReturnAddr:
return PureStore { AVMRetAddr, inst.src(0) };
case StVMRegState:
return PureStore { AVMRegState, inst.src(0) };
//////////////////////////////////////////////////////////////////////
// Instructions that technically do some things w/ memory, but not in any way
// we currently care about. They however don't return IrrelevantEffects
// because we assume (in refcount-opts) that IrrelevantEffects instructions
// can't even inspect Countable reference count fields, and several of these
// can. All GeneralEffects instructions are assumed to possibly do so.
case DecRefNZ:
case ProfileDecRef:
case AFWHBlockOn:
case AFWHPushTailFrame:
case IncRef:
case LdClosureCls:
case LdClosureThis:
case LdRetVal:
case ConvStrToInt:
case ConvResToInt:
case OrdStr:
case ChrInt:
case CreateSSWH:
case CheckSurpriseFlags:
case CheckType:
case ZeroErrorLevel:
case RestoreErrorLevel:
case CheckCold:
case ContValid:
case IncProfCounter:
case IncCallCounter:
case IncStat:
case ContCheckNext:
case CountVec:
case CountDict:
case CountKeyset:
case HasReifiedGenerics:
case InstanceOf:
case InstanceOfBitmask:
case NInstanceOfBitmask:
case InstanceOfIface:
case InterfaceSupportsArrLike:
case InterfaceSupportsDbl:
case InterfaceSupportsInt:
case InterfaceSupportsStr:
case IsLegacyArrLike:
case IsWaitHandle:
case IsCol:
case HasToString:
case DbgAssertRefCount:
case DbgCheckLocalsDecRefd:
case GtStr:
case GteStr:
case LtStr:
case LteStr:
case EqStr:
case NeqStr:
case SameStr:
case NSameStr:
case CmpStr:
case GtRes:
case GteRes:
case LtRes:
case LteRes:
case CmpRes:
case LdBindAddr:
case LdSSwitchDest:
case RBTraceEntry:
case RBTraceMsg:
case ConvIntToBool:
case ConvIntToDbl:
case ConvStrToBool:
case ConvStrToDbl:
case ConvResToDbl:
case ExtendsClass:
case LdUnwinderValue:
case LdClsName:
case LdLazyCls:
case LdAFWHActRec:
case LdContActRec:
case LdContArKey:
case LdContArValue:
case LdContField:
case LdContResumeAddr:
case StContArKey:
case StContArValue:
case StContArState:
case ContArIncIdx:
case ContArIncKey:
case ContArUpdateIdx:
case LdClsCachedSafe:
case LdClsInitData:
case UnwindCheckSideExit:
case LdCns:
case LdFuncVecLen:
case LdClsMethod:
case LdClsMethodCacheCls:
case LdClsMethodCacheFunc:
case LdClsMethodFCacheFunc:
case LdTypeCns:
case LdTypeCnsNoThrow:
case LdTypeCnsClsName:
case ProfileSwitchDest:
case LdFuncCls:
case LdFuncInOutBits:
case LdFuncNumParams:
case LdFuncName:
case LdMethCallerName:
case LdObjClass:
case LdObjInvoke:
case LdObjMethodD:
case LdObjMethodS:
case LdStrLen:
case StringIsset:
case LdWHResult:
case LdWHState:
case LdWHNotDone:
case LookupClsMethod:
case LookupClsRDS:
case StrictlyIntegerConv:
case DbgAssertFunc:
case ProfileCall:
case ProfileMethod:
case DecReleaseCheck:
return may_load_store(AEmpty, AEmpty);
case BeginCatch:
return may_load_store(AEmpty, AVMRegAny | AVMRegState);
case LogArrayReach:
case LogGuardFailure:
return may_load_store(AHeapAny, AEmpty);
// Some that touch memory we might care about later, but currently don't:
case ColIsEmpty:
case ColIsNEmpty:
case ConvTVToBool:
case ConvObjToBool:
case CountCollection:
case LdVectorSize:
case CheckVecBounds:
case LdColVec:
case LdColDict:
return may_load_store(AEmpty, AEmpty);
//////////////////////////////////////////////////////////////////////
// Instructions that can re-enter the VM and touch most heap things. They
// also may generally write to the eval stack below an offset (see
// alias-class.h above AStack for more).
case DecRef:
return may_load_store(AHeapAny, AHeapAny);
case ReleaseShallow:
return may_load_store(AHeapAny, AHeapAny);
case GetMemoKey:
return may_load_store(AHeapAny, AHeapAny);
case GetMemoKeyScalar:
return IrrelevantEffects{};
case ProfileGlobal:
case LdGblAddr:
case LdGblAddrDef:
case BaseG:
return may_load_store(AEmpty, AEmpty);
case LdClsCtor:
return may_load_store(AEmpty, AEmpty);
case RaiseCoeffectsCallViolation:
case RaiseCoeffectsFunParamCoeffectRulesViolation:
case RaiseCoeffectsFunParamTypeViolation:
return may_load_store(AEmpty, AEmpty);
case LdClsPropAddrOrNull: // may run 86{s,p}init, which can autoload
case LdClsPropAddrOrRaise: // raises errors, and 86{s,p}init
return may_load_store(
AHeapAny,
AHeapAny | all_pointees(inst) | AMIStateROProp
);
case Clone:
case ThrowArrayIndexException:
case ThrowArrayKeyException:
case ThrowUninitLoc:
case ThrowUndefPropException:
case RaiseTooManyArg:
case RaiseError:
case RaiseNotice:
case RaiseWarning:
case RaiseForbiddenDynCall:
case RaiseForbiddenDynConstruct:
case RaiseStrToClassNotice:
case CheckClsMethFunc:
case CheckClsReifiedGenericMismatch:
case CheckFunReifiedGenericMismatch:
case CheckInOutMismatch:
case CheckReadonlyMismatch:
case ConvTVToStr:
case ConvObjToStr:
case Count: // re-enters on CountableClass
case GtObj:
case GteObj:
case LtObj:
case LteObj:
case EqObj:
case NeqObj:
case CmpObj:
case GtArrLike:
case GteArrLike:
case LtArrLike:
case LteArrLike:
case CmpArrLike:
case OODeclExists:
case LdCls: // autoload
case LdClsCached: // autoload
case LdFunc: // autoload
case LdFuncCached: // autoload
case InitClsCns: // autoload
case InitSubClsCns: // May run 86cinit
case ProfileSubClsCns: // May run 86cinit
case LookupClsMethodCache: // autoload
case LookupClsMethodFCache: // autoload
case LookupCnsE:
case LookupFuncCached: // autoload
case StringGet: // raise_notice
case OrdStrIdx: // raise_notice
case AddNewElemKeyset: // can re-enter
case DictGet:
case KeysetGet:
case DictSet:
case BespokeSet:
case BespokeAppend:
case BespokeUnset:
case StructDictUnset:
case ConcatStrStr:
case PrintStr:
case PrintBool:
case PrintInt:
case ConcatIntStr:
case ConcatStrInt:
case ConvObjToDbl:
case ConvObjToInt:
case ConvTVToInt:
case ConcatStr3:
case ConcatStr4:
case ConvTVToDbl:
case ConvObjToVec:
case ConvObjToDict:
case ConvObjToKeyset:
case ThrowOutOfBounds:
case ThrowInvalidArrayKey:
case ThrowInvalidOperation:
case ThrowCallReifiedFunctionWithoutGenerics:
case ThrowDivisionByZeroException:
case ThrowHasThisNeedStatic:
case ThrowInOutMismatch:
case ThrowReadonlyMismatch:
case ThrowLateInitPropError:
case ThrowMissingArg:
case ThrowMissingThis:
case ThrowParameterWrongType:
case ArrayMarkLegacyShallow:
case ArrayMarkLegacyRecursive:
case ThrowCannotModifyReadonlyCollection:
case ThrowLocalMustBeValueTypeException:
case ThrowMustBeEnclosedInReadonly:
case ThrowMustBeMutableException:
case ThrowMustBeReadonlyException:
case ThrowMustBeValueTypeException:
case ArrayUnmarkLegacyShallow:
case ArrayUnmarkLegacyRecursive:
case SetOpTV:
case OutlineSetOp:
case ThrowAsTypeStructException:
case PropTypeRedefineCheck: // Can raise and autoload
case HandleRequestSurprise:
case BespokeEscalateToVanilla:
return may_load_store(AHeapAny, AHeapAny);
case AddNewElemVec:
case RaiseErrorOnInvalidIsAsExpressionType:
case IsTypeStruct:
case RecordReifiedGenericsAndGetTSList:
case CopyArray:
return may_load_store(AElemAny, AEmpty);
case ConvArrLikeToVec:
case ConvArrLikeToDict:
case ConvArrLikeToKeyset: // Decrefs input values
return may_load_store(AElemAny, AEmpty);
// debug_backtrace() traverses stack and WaitHandles on the heap.
case DebugBacktrace:
return may_load_store(AHeapAny|ALocalAny|AStackAny, AHeapAny);
// This instruction doesn't touch memory we track, except that it may
// re-enter to construct php Exception objects. During this re-entry anything
// can happen (e.g. a surprise flag check could cause a php signal handler to
// run arbitrary code).
case AFWHPrepareChild:
return may_load_store(AActRec { inst.src(0) }, AEmpty);
//////////////////////////////////////////////////////////////////////
// The following instructions are used for debugging memory optimizations.
// We can't ignore them, because they can prevent future optimizations;
// eg t1 = LdStk<N>; DbgTrashStk<N>; StStk<N> t1
// If we ignore the DbgTrashStk it looks like the StStk is redundant
case DbgTrashStk:
return may_load_store_kill(
AEmpty, AEmpty, AStack::at(inst.extra<DbgTrashStk>()->offset));
case DbgTrashFrame:
return may_load_store_kill(
AEmpty, AEmpty, actrec(inst.src(0), inst.extra<DbgTrashFrame>()->offset));
case DbgTrashMem:
return may_load_store_kill(AEmpty, AEmpty, pointee(inst.src(0)));
//////////////////////////////////////////////////////////////////////
}
not_reached();
}