hphp/runtime/vm/jit/memory-effects.cpp (1,748 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/vm/jit/memory-effects.h"
#include "hphp/util/match.h"
#include "hphp/util/safe-cast.h"
#include "hphp/util/assertions.h"
#include "hphp/runtime/base/implicit-context.h"
#include "hphp/runtime/vm/bytecode.h"
#include "hphp/runtime/vm/jit/analysis.h"
#include "hphp/runtime/vm/jit/ir-instruction.h"
#include "hphp/runtime/vm/jit/ssa-tmp.h"
#include "hphp/runtime/vm/jit/type-array-elem.h"
namespace HPHP::jit {
namespace {
const StaticString s_GLOBALS("GLOBALS");
uint32_t iterId(const IRInstruction& inst) {
return inst.extra<IterId>()->iterId;
}
//////////////////////////////////////////////////////////////////////
AliasClass all_pointees(folly::Range<SSATmp**> srcs) {
auto ret = AliasClass{AEmpty};
for (auto const& src : srcs) {
if (src->isA(TMem)) ret = ret | pointee(src);
}
return ret;
}
// Return an AliasClass containing all locations pointed to by any MemToCell
// sources to an instruction.
AliasClass all_pointees(const IRInstruction& inst) {
return all_pointees(inst.srcs());
}
// Return an AliasClass representing a range of the eval stack that contains
// everything below a logical depth.
AliasClass stack_below(IRSPRelOffset offset) {
return AStack::below(offset);
}
//////////////////////////////////////////////////////////////////////
// Return an AliasClass representing an entire ActRec at base + offset.
AliasClass actrec(SSATmp* base, IRSPRelOffset offset) {
return AStack::range(offset, offset + int32_t{kNumActRecCells});
}
/*
* AliasClass that can be used to represent effects on liveFrame().
*/
AliasClass livefp(SSATmp* fp) {
return AFBasePtr | AActRec { fp };
}
AliasClass livefp(const IRInstruction& inst) {
return livefp(inst.marker().fixupFP());
}
//////////////////////////////////////////////////////////////////////
// Determine an AliasClass representing any locals in the instruction's frame
// which might be accessed via debug_backtrace().
std::pair<const Func*, uint32_t> func_and_depth_from_fp(SSATmp* fp) {
if (!fp) return {nullptr, 0};
return {funcFromFP(fp), frameDepthIndex(fp)};
}
AliasClass backtrace_locals(const IRInstruction& inst) {
auto eachFunc = [&] (auto fn) {
auto ac = AEmpty;
for (auto fp = inst.marker().fp(); fp; ) {
auto const [func, depth] = func_and_depth_from_fp(fp);
ac |= fn(func, depth);
if (fp->inst()->is(BeginInlining)) {
// Walking the marker fp chain in this manner is suspect, but here we
// are careful to only materialize func, depth pair using it.
fp = fp->inst()->marker().fp();
} else {
fp = nullptr;
}
}
return ac;
};
auto const addInspectable =
[&] (const Func* func, uint32_t depth) -> AliasClass {
auto const meta = func->lookupVarId(s_86metadata.get());
return meta != kInvalidId ? ALocal { depth, (uint32_t)meta } : AEmpty;;
};
// Either there's no func or no frame-pointer. Either way, be conservative and
// assume anything can be read. This can happen in test code, for instance.
if (!inst.marker().fp()) return ALocalAny;
if (!RuntimeOption::EnableArgsInBacktraces) return eachFunc(addInspectable);
return eachFunc([&] (const Func* func, uint32_t depth) {
auto ac = AEmpty;
auto const numParams = func->numParams();
if (func->hasReifiedGenerics()) {
// First non param local contains reified generics
AliasIdSet reifiedgenerics{ AliasIdSet::IdRange{numParams, numParams + 1} };
ac |= ALocal { depth, reifiedgenerics };
}
if (func->cls() && func->cls()->hasReifiedGenerics()) {
// There is no way to access the SSATmp for ObjectData of `this` here,
// so be very pessimistic
ac |= APropAny;
}
if (!numParams) return addInspectable(func, depth) | ac;
AliasIdSet params{ AliasIdSet::IdRange{0, numParams} };
return addInspectable(func, depth) | ac | ALocal { depth, params };
});
}
/////////////////////////////////////////////////////////////////////
/*
* Modify a GeneralEffects to take potential VM re-entry into account. This
* affects may-load, may-store, and kills information for the instruction. The
* GeneralEffects should already contain AHeapAny in both loads and stores if
* it affects those locations for reasons other than re-entry, but does not
* need to if it doesn't.
*
* For loads, we need to take into account any locals potentially accessed by
* debug_backtrace().
*
* For kills, locations on the eval stack below the re-entry depth should all
* be added.
*/
GeneralEffects may_reenter(const IRInstruction& inst, GeneralEffects x) {
auto const may_reenter_is_ok =
inst.taken() && inst.taken()->isCatch();
always_assert_flog(
may_reenter_is_ok,
"instruction {} claimed may_reenter, but it isn't allowed to say that",
inst
);
/*
* We want to union `killed_stack' into whatever else the instruction already
* said it must kill, but if we end up with an unrepresentable AliasClass we
* can't return a set that's too big (the `kills' set is unlike the other
* AliasClasses in GeneralEffects in that means it kills /everything/ in the
* set, since it's must-information).
*
* If we can't represent the union, just take the stack, in part because we
* have some debugging asserts about this right now---but also nothing
* actually uses may_reenter with a non-AEmpty kills at the time of this
* writing anyway.
*/
auto const new_kills = [&] {
if (inst.marker().fp() == nullptr) return AEmpty;
auto const offset = [&]() -> IRSPRelOffset {
auto const fp = canonical(inst.marker().fp());
if (fp->inst()->is(BeginInlining)) {
auto const extra = fp->inst()->extra<BeginInlining>();
auto const fpOffset = extra->spOffset;
auto const numSlotsInFrame = extra->func->numSlotsInFrame();
auto const numStackElems = inst.marker().bcSPOff() - SBInvOffset{0};
return fpOffset - numSlotsInFrame - numStackElems;
}
assertx(fp->inst()->is(DefFP, DefFuncEntryFP));
auto const sp = inst.marker().sp();
auto const irSPOff = sp->inst()->extra<DefStackData>()->irSPOff;
return inst.marker().bcSPOff().to<IRSPRelOffset>(irSPOff);
}();
auto const killed_stack = stack_below(offset);
auto const kills_union = x.kills.precise_union(killed_stack);
return kills_union ? *kills_union : killed_stack;
}();
return GeneralEffects {
x.loads | AHeapAny | ARdsAny | AVMRegAny | AVMRegState,
x.stores | AHeapAny | ARdsAny | AVMRegAny,
x.moves,
new_kills,
x.inout,
backtrace_locals(inst)
};
}
//////////////////////////////////////////////////////////////////////
GeneralEffects may_load_store(AliasClass loads, AliasClass stores) {
return GeneralEffects { loads, stores, AEmpty, AEmpty, AEmpty, AEmpty };
}
GeneralEffects may_load_store_kill(AliasClass loads,
AliasClass stores,
AliasClass kill) {
return GeneralEffects { loads, stores, AEmpty, kill, AEmpty, AEmpty };
}
GeneralEffects may_load_store_move(AliasClass loads,
AliasClass stores,
AliasClass move) {
assertx(move <= loads);
return GeneralEffects { loads, stores, move, AEmpty, AEmpty, AEmpty };
}
//////////////////////////////////////////////////////////////////////
/*
* Helper for iterator instructions. They all affect some locals, but are
* otherwise the same. Value iters touch one local; key-value iters touch two.
*/
GeneralEffects iter_effects(const IRInstruction& inst,
SSATmp* fp,
AliasClass locals) {
auto const iters =
AliasClass { aiter_all(fp, inst.extra<IterData>()->args.iterId) };
return may_load_store_kill(
iters | locals | AHeapAny,
iters | locals | AHeapAny,
AMIStateAny
);
}
/*
* Construct effects for InterpOne, using the information in its extra data.
*
* We always consider an InterpOne as potentially doing anything to the heap,
* potentially re-entering, potentially raising warnings in the current frame,
* potentially reading any locals, and potentially reading/writing any stack
* location that isn't below the bottom of the stack.
*
* The extra data for the InterpOne comes with some additional information
* about which local(s) it may modify, which is all we try to be more precise
* about right now.
*/
GeneralEffects interp_one_effects(const IRInstruction& inst) {
auto const extra = inst.extra<InterpOne>();
auto loads = AHeapAny | AStackAny | ALocalAny | ARdsAny | livefp(inst);
auto stores = AHeapAny | AStackAny | ARdsAny | AVMRegAny | AVMRegState;
if (extra->smashesAllLocals) {
stores = stores | ALocalAny;
} else {
for (auto i = uint32_t{0}; i < extra->nChangedLocals; ++i) {
stores = stores | ALocal { inst.src(1), extra->changedLocals[i].id };
}
}
auto kills = AEmpty;
if (isMemberBaseOp(extra->opcode)) {
stores = stores | AMIStateAny;
kills = kills | AMIStateAny;
} else if (isMemberDimOp(extra->opcode) || isMemberFinalOp(extra->opcode)) {
stores = stores | AMIStateAny;
loads = loads | AMIStateAny;
} else {
kills = kills | AMIStateAny;
}
return may_load_store_kill(loads, stores, kills);
}
////////////////////////////////////////////////////////////////////////////////
/*
* Construct effects for member instructions that take &tvRef as their last
* argument.
*
* These instructions never load tvRef or roProp, but they might store to it.
*/
MemEffects minstr_with_tvref(const IRInstruction& inst) {
auto const tvRef = inst.src(2);
assertx(tvRef->isA(TMemToMISTemp) || tvRef->isA(TNullptr));
auto stores = AHeapAny | AMIStateROProp;
if (!tvRef->isA(TNullptr)) stores |= pointee(tvRef);
return may_load_store(AHeapAny, stores);
}
//////////////////////////////////////////////////////////////////////
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();
}
//////////////////////////////////////////////////////////////////////
DEBUG_ONLY bool check_effects(const IRInstruction& inst, MemEffects me) {
SCOPE_ASSERT_DETAIL("Memory Effects") {
return folly::sformat(" inst: {}\n effects: {}\n", inst, show(me));
};
auto const check_obj = [&] (SSATmp* obj) {
// canonicalize() may have replaced the SSATmp with a less refined
// one, so we cannot assert <= TObj.
always_assert_flog(
obj->type() <= TBottom ||
obj->type().maybe(TObj),
"Non obj pointer in memory effects"
);
};
auto const check = [&] (AliasClass a) {
if (auto const pr = a.prop()) check_obj(pr->obj);
};
match<void>(
me,
[&] (GeneralEffects x) {
check(x.loads);
check(x.stores);
check(x.moves);
check(x.kills);
check(x.inout);
check(x.backtrace);
// Locations may-moved always should also count as may-loads.
always_assert(x.moves <= x.loads);
if (inst.mayRaiseErrorWithSources()) {
// Any instruction that can raise an error can run a user error handler
// and have arbitrary effects on the heap.
always_assert(AHeapAny <= x.loads);
always_assert(AHeapAny <= x.stores);
/*
* They also ought to kill /something/ on the stack, because of
* possible re-entry. It's not incorrect to leave things out of the
* kills set, but this assertion is here because we shouldn't do it on
* purpose, so this is here until we have a reason not to assert it.
*
* The mayRaiseError instructions should all be going through
* may_reenter right now, which will kill the stack below the re-entry
* depth---unless the marker for `inst' doesn't have an fp set.
*/
always_assert(inst.marker().fixupFP() == nullptr ||
AStackAny.maybe(x.kills));
}
},
[&] (PureLoad x) { check(x.src); },
[&] (PureStore x) { check(x.dst); },
[&] (ExitEffects x) { check(x.live); check(x.kills); },
[&] (IrrelevantEffects) {},
[&] (UnknownEffects) {},
[&] (CallEffects x) { check(x.kills);
check(x.inputs);
check(x.actrec);
check(x.outputs);
check(x.locals); },
[&] (PureInlineCall x) { check(x.base);
check(x.actrec); },
[&] (ReturnEffects x) { check(x.kills); }
);
return true;
}
//////////////////////////////////////////////////////////////////////
}
MemEffects memory_effects(const IRInstruction& inst) {
auto const inner = memory_effects_impl(inst);
auto const ret = [&] () -> MemEffects {
if (!inst.mayRaiseErrorWithSources()) {
if (inst.maySyncVMRegsWithSources()) {
auto fail = [&] {
always_assert_flog(
false,
"Instruction {} has effects {} but has been marked as MaySyncVMRegs "
"without MayRaiseError.",
inst,
show(inner)
);
return may_load_store(AUnknown, AUnknown);
};
return match<MemEffects>(
inner,
[&] (GeneralEffects x) {
return GeneralEffects {
x.loads | AVMRegAny | AVMRegState,
x.stores | AVMRegAny,
x.moves, x.kills, x.inout, x.backtrace,
};
},
[&] (CallEffects x) { return fail(); },
[&] (UnknownEffects x) { return fail(); },
[&] (PureLoad x) {
return may_load_store(
x.src | AVMRegAny | AVMRegState,
AVMRegAny
);
},
[&] (PureStore) { return fail(); },
[&] (ExitEffects) { return fail(); },
[&] (PureInlineCall) { return fail(); },
[&] (IrrelevantEffects) { return fail(); },
[&] (ReturnEffects) { return fail(); }
);
}
return inner;
}
auto fail = [&] {
always_assert_flog(
false,
"Instruction {} has effects {}, but has been marked as MayRaiseError "
"and must use a UnknownEffects, GeneralEffects, or CallEffects type.",
inst,
show(inner)
);
return may_load_store(AUnknown, AUnknown);
};
// Calls are implicitly MayRaise, all other instructions must use the
// GeneralEffects or UnknownEffects class of memory effects
return match<MemEffects>(
inner,
[&] (GeneralEffects x) { return may_reenter(inst, x); },
[&] (CallEffects x) { return x; },
[&] (UnknownEffects x) { return x; },
[&] (PureLoad) { return fail(); },
[&] (PureStore) { return fail(); },
[&] (ExitEffects) { return fail(); },
[&] (PureInlineCall) { return fail(); },
[&] (IrrelevantEffects) { return fail(); },
[&] (ReturnEffects) { return fail(); }
);
}();
assertx(check_effects(inst, ret));
return ret;
}
AliasClass pointee(const SSATmp* tmp) {
auto acls = AEmpty;
auto const visit = [&] (const IRInstruction* sinst, const SSATmp* ptr) {
acls |= [&] () -> AliasClass {
auto const type = ptr->type() & TMem;
always_assert(type != TBottom);
if (sinst->is(LdMBase)) return sinst->extra<LdMBase>()->acls;
if (sinst->is(LdRDSAddr, LdInitRDSAddr)) {
return ARds { sinst->extra<RDSHandleData>()->handle };
}
auto const specific = [&] () -> Optional<AliasClass> {
if (type <= TMemToFrame) {
if (sinst->is(LdLocAddr)) {
return AliasClass {
ALocal { sinst->src(0), sinst->extra<LdLocAddr>()->locId }
};
}
return ALocalAny;
}
if (type <= TMemToStk) {
if (sinst->is(LdStkAddr)) {
return AliasClass {
AStack::at(sinst->extra<LdStkAddr>()->offset)
};
}
return AStackAny;
}
if (type <= TMemToProp) {
if (sinst->is(LdPropAddr, LdInitPropAddr)) {
return AliasClass {
AProp {
sinst->src(0),
safe_cast<uint16_t>(sinst->extra<IndexData>()->index)
}
};
}
return APropAny;
}
auto const elem = [&] () -> AliasClass {
auto const base = sinst->src(0);
auto const key = sinst->src(1);
always_assert(base->isA(TArrLike));
if (key->isA(TInt)) {
if (key->hasConstVal()) return AElemI { base, key->intVal() };
return AElemIAny;
}
if (key->isA(TStr)) {
assertx(base->isA(TBottom) || !base->isA(TVec));
if (key->hasConstVal()) return AElemS { base, key->strVal() };
return AElemSAny;
}
return AElemAny;
};
if (type <= TMemToElem) {
if (sinst->is(LdVecElemAddr, ElemDictK, StructDictElemAddr)) {
return elem();
}
return AElemAny;
}
return std::nullopt;
}();
if (specific) {
// A pointer has to point at *something*, so we should not get
// AEmpty here.
assertx(*specific != AEmpty);
// We don't currently ever form pointers to something that's not a
// TypedValue.
assertx(*specific <= AUnknownTV);
return *specific;
}
/*
* None of the above worked, so try to make the smallest union
* we can based on the pointer type.
*/
return pointee(type);
}();
return true;
};
visitEveryDefiningInst(tmp, visit);
return acls;
}
AliasClass pointee(const Type& type) {
assertx(type.maybe(TMem));
auto ret = AEmpty;
if (type.maybe(TMemToStk)) ret = ret | AStackAny;
if (type.maybe(TMemToFrame)) ret = ret | ALocalAny;
if (type.maybe(TMemToProp)) ret = ret | APropAny;
if (type.maybe(TMemToElem)) ret = ret | AElemAny;
if (type.maybe(TMemToMISTemp)) ret = ret | AMIStateTempBase;
if (type.maybe(TMemToClsInit)) ret = ret | AHeapAny;
if (type.maybe(TMemToSProp)) ret = ret | ARdsAny;
if (type.maybe(TMemToGbl)) ret = ret | AOther | ARdsAny;
if (type.maybe(TMemToOther)) ret = ret | AOther | ARdsAny;
if (type.maybe(TMemToConst)) ret = ret | AOther;
// The pointer type should lie completely within the above
// locations.
assertx(type <= (TMemToStk|TMemToFrame|TMemToProp|TMemToElem|
TMemToMISTemp|TMemToClsInit|TMemToSProp|TMemToGbl|
TMemToOther|TMemToConst));
assertx(ret != AEmpty);
assertx(ret <= AUnknownTV);
return ret;
}
//////////////////////////////////////////////////////////////////////
MemEffects canonicalize(MemEffects me) {
using R = MemEffects;
return match<R>(
me,
[&] (GeneralEffects x) -> R {
return GeneralEffects {
canonicalize(x.loads),
canonicalize(x.stores),
canonicalize(x.moves),
canonicalize(x.kills),
canonicalize(x.inout),
canonicalize(x.backtrace),
};
},
[&] (PureLoad x) -> R {
return PureLoad { canonicalize(x.src) };
},
[&] (PureStore x) -> R {
return PureStore { canonicalize(x.dst), x.value, x.dep };
},
[&] (ExitEffects x) -> R {
return ExitEffects { canonicalize(x.live), canonicalize(x.kills) };
},
[&] (PureInlineCall x) -> R {
return PureInlineCall {
canonicalize(x.base),
x.fp,
canonicalize(x.actrec)
};
},
[&] (CallEffects x) -> R {
return CallEffects {
canonicalize(x.kills),
canonicalize(x.inputs),
canonicalize(x.actrec),
canonicalize(x.outputs),
canonicalize(x.locals)
};
},
[&] (ReturnEffects x) -> R {
return ReturnEffects { canonicalize(x.kills) };
},
[&] (IrrelevantEffects x) -> R { return x; },
[&] (UnknownEffects x) -> R { return x; }
);
}
//////////////////////////////////////////////////////////////////////
std::string show(MemEffects effects) {
using folly::sformat;
return match<std::string>(
effects,
[&] (GeneralEffects x) {
return sformat("mlsmkib({} ; {} ; {} ; {} ; {} ; {})",
show(x.loads),
show(x.stores),
show(x.moves),
show(x.kills),
show(x.inout),
show(x.backtrace)
);
},
[&] (ExitEffects x) {
return sformat("exit({} ; {})", show(x.live), show(x.kills));
},
[&] (PureInlineCall x) {
return sformat("inline_call({} ; {})",
show(x.base),
show(x.actrec)
);
},
[&] (CallEffects x) {
return sformat("call({} ; {} ; {} ; {} ; {})",
show(x.kills),
show(x.inputs),
show(x.actrec),
show(x.outputs),
show(x.locals)
);
},
[&] (PureLoad x) { return sformat("ld({})", show(x.src)); },
[&] (PureStore x) { return sformat("st({})", show(x.dst)); },
[&] (ReturnEffects x) { return sformat("return({})", show(x.kills)); },
[&] (IrrelevantEffects) { return "IrrelevantEffects"; },
[&] (UnknownEffects) { return "UnknownEffects"; }
);
}
//////////////////////////////////////////////////////////////////////
GeneralEffects general_effects_for_vmreg_liveness(
GeneralEffects l, KnownRegState liveness) {
auto ret = GeneralEffects { l.loads, l.stores, l.moves, l.kills, l.inout, l.backtrace };
if (liveness == KnownRegState::Dead) {
ret.loads = l.loads.exclude_vm_reg().value_or(AEmpty);
} else if (liveness == KnownRegState::Live) {
ret.stores = l.stores.exclude_vm_reg().value_or(AEmpty);
}
return ret;
}
//////////////////////////////////////////////////////////////////////
bool hasMInstrBaseEffects(const IRInstruction& inst) {
switch (inst.op()) {
case ElemDictD:
case ElemDX:
case BespokeElem:
case ElemDictU:
case ElemUX:
case SetElem:
case UnsetElem:
case SetOpElem:
case IncDecElem:
case SetNewElem:
case SetNewElemVec:
case SetNewElemDict:
case SetNewElemKeyset:
case SetRange:
case SetRangeRev:
return true;
default:
return false;
}
}
Optional<Type> mInstrBaseEffects(const IRInstruction& inst, Type old) {
assertx(hasMInstrBaseEffects(inst));
switch (inst.op()) {
case ElemDictD:
case ElemDX:
case SetOpElem:
case IncDecElem:
// Always COWs arrays, leaves strings alone
return old.maybe(TArrLike)
? make_optional(
((old & TArrLike).modified() & TCounted) | (old - TArrLike)
)
: std::nullopt;
case ElemDictU:
case ElemUX:
case UnsetElem:
// Might COW arrays (depending if key is present), leaves strings alone
return old.maybe(TArrLike)
? make_optional(((old & TArrLike).modified() & TCounted) | old)
: std::nullopt;
case SetElem:
// COWs both arrays and strings
return old.maybe(TArrLike | TStr)
? make_optional(old.modified() & TCounted)
: std::nullopt;
case SetNewElem:
case SetNewElemVec:
case SetNewElemDict:
case SetNewElemKeyset: {
// Vecs and keysets will always COW. Dicts will COW in almost
// all situations except if the "next key" hits the limit.
if (!old.maybe(TArrLike)) return std::nullopt;
return
((old & TArrLike).modified() & TCounted) | (old - (TVec | TKeyset));
}
case SetRange:
case SetRangeRev:
// Always COWs strings
return old.maybe(TStr)
? make_optional(((old & TStr).modified() & TCounted) | (old - TStr))
: std::nullopt;
case BespokeElem: {
// Behaves like define if S2 is true, unset if false
if (!old.maybe(TArrLike)) return std::nullopt;
auto const t = (old & TArrLike).modified() & TCounted;
return inst.src(2)->boolVal()
? t | (old - TArrLike)
: t | old;
}
default:
not_reached();
}
}
//////////////////////////////////////////////////////////////////////
}