2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/vm/jit/memory-effects.h"
18 #include "hphp/util/match.h"
19 #include "hphp/util/safe-cast.h"
20 #include "hphp/util/assertions.h"
22 #include "hphp/runtime/vm/bytecode.h"
23 #include "hphp/runtime/vm/jit/analysis.h"
24 #include "hphp/runtime/vm/jit/dce.h"
25 #include "hphp/runtime/vm/jit/ir-instruction.h"
26 #include "hphp/runtime/vm/jit/ssa-tmp.h"
28 #include <folly/Optional.h>
30 namespace HPHP
{ namespace jit
{
36 jit::flat_set
<const IRInstruction
*>* visited_labels
38 auto const type
= ptr
->type();
39 always_assert(type
<= TMemToGen
);
40 auto const maybeRef
= type
.maybe(TMemToRefGen
);
41 auto const typeNR
= type
- TMemToRefGen
;
42 auto const canonPtr
= canonical(ptr
);
43 if (!canonPtr
->isA(TMemToGen
)) {
44 // This can happen when ptr is TBottom from a passthrough instruction with
45 // a src that isn't TBottom. The most common cause of this is something
46 // like "t5:Bottom = CheckType<Str> t2:Int". It means ptr isn't really a
47 // pointer, so return AEmpty to avoid unnecessarily pessimizing any
49 always_assert(ptr
->isA(TBottom
));
53 auto const sinst
= canonPtr
->inst();
55 if (sinst
->is(UnboxPtr
)) {
56 return ARefAny
| pointee(sinst
->src(0), visited_labels
);
59 if (sinst
->is(LdRDSAddr
, LdInitRDSAddr
)) {
60 return ARds
{ sinst
->extra
<RDSHandleData
>()->handle
};
63 // For phis, union all incoming values, taking care to not recurse infinitely
64 // in the presence of loops.
65 if (sinst
->is(DefLabel
)) {
66 if (visited_labels
&& visited_labels
->count(sinst
)) {
70 auto const dsts
= sinst
->dsts();
72 std::find(dsts
.begin(), dsts
.end(), canonPtr
) - dsts
.begin();
73 always_assert(dstIdx
>= 0 && dstIdx
< sinst
->numDsts());
75 folly::Optional
<jit::flat_set
<const IRInstruction
*>> label_set
;
76 if (visited_labels
== nullptr) {
78 visited_labels
= &label_set
.value();
80 visited_labels
->insert(sinst
);
83 sinst
->block()->forEachSrc(
84 dstIdx
, [&](const IRInstruction
* /*jmp*/, const SSATmp
* thePtr
) {
85 ret
= ret
| pointee(thePtr
, visited_labels
);
90 auto specific
= [&] () -> folly::Optional
<AliasClass
> {
91 if (typeNR
<= TBottom
) return AEmpty
;
93 if (typeNR
<= TMemToFrameGen
) {
94 if (sinst
->is(LdLocAddr
)) {
96 AFrame
{ sinst
->src(0), sinst
->extra
<LdLocAddr
>()->locId
}
102 if (typeNR
<= TMemToStkGen
) {
103 if (sinst
->is(LdStkAddr
)) {
105 AStack
{ sinst
->src(0), sinst
->extra
<LdStkAddr
>()->offset
, 1 }
111 if (typeNR
<= TMemToPropGen
) {
112 if (sinst
->is(LdPropAddr
, LdInitPropAddr
)) {
116 safe_cast
<uint32_t>(sinst
->extra
<ByteOffsetData
>()->offsetBytes
)
123 if (typeNR
<= TMemToMISGen
) {
124 if (sinst
->is(LdMIStateAddr
)) {
125 return mis_from_offset(sinst
->src(0)->intVal());
127 if (ptr
->hasConstVal() && ptr
->rawVal() == 0) {
128 // nullptr tvRef pointer, representing an instruction that doesn't use
135 auto elem
= [&] () -> AliasClass
{
136 auto base
= sinst
->src(0);
137 auto key
= sinst
->src(1);
139 always_assert(base
->isA(TArrLike
));
141 if (key
->isA(TInt
)) {
142 if (key
->hasConstVal()) return AElemI
{ base
, key
->intVal() };
145 if (key
->hasConstVal(TStr
)) {
146 assertx(!base
->isA(TVec
));
147 return AElemS
{ base
, key
->strVal() };
152 if (typeNR
<= TMemToElemGen
) {
153 if (sinst
->is(LdPackedArrayDataElemAddr
)) return elem();
157 // The result of ElemArray{,W,U} is either the address of an array element,
158 // or &immutable_null_base.
159 if (typeNR
<= TMemToMembGen
) {
160 if (sinst
->is(ElemArrayX
, ElemDictX
, ElemKeysetX
)) return elem();
162 // Takes a PtrToGen as its first operand, so we can't easily grab an array
164 if (sinst
->is(ElemArrayU
, ElemVecU
, ElemDictU
, ElemKeysetU
)) {
168 // These instructions can only get at tvRef when given it as a
169 // src. Otherwise they can only return pointers to properties or
170 // &immutable_null_base.
171 if (sinst
->is(PropX
, PropDX
, PropQ
)) {
172 auto const src
= [&]{
173 if (sinst
->is(PropDX
)) {
174 assertx(sinst
->src(sinst
->numSrcs() - 2)->isA(TMemToMISGen
));
176 sinst
->src(sinst
->numSrcs() - 1)->isA(TMIPropSPtr
| TNullptr
)
178 return sinst
->src(sinst
->numSrcs() - 2);
180 assertx(sinst
->srcs().back()->isA(TPtrToMISGen
));
181 return sinst
->srcs().back();
184 return APropAny
| pointee(src
, visited_labels
);
187 // Like the Prop* instructions, but for array elements. These could also
188 // return pointers to collection elements but those don't exist in
190 if (sinst
->is(ElemX
, ElemDX
, ElemUX
)) {
191 auto const src
= [&]{
192 if (sinst
->is(ElemDX
)) {
193 assertx(sinst
->src(sinst
->numSrcs() - 2)->isA(TMemToMISGen
));
195 sinst
->src(sinst
->numSrcs() - 1)->isA(TMIPropSPtr
| TNullptr
)
197 return sinst
->src(sinst
->numSrcs() - 2);
199 assertx(sinst
->srcs().back()->isA(TPtrToMISGen
));
200 return sinst
->srcs().back();
203 return AElemAny
| pointee(src
, visited_labels
);
212 auto ret
= maybeRef
? ARefAny
: AEmpty
;
213 if (specific
) return *specific
| ret
;
216 * None of the above worked, so try to make the smallest union we can based
217 * on the pointer type.
219 if (typeNR
.maybe(TMemToStkGen
)) ret
= ret
| AStackAny
;
220 if (typeNR
.maybe(TMemToFrameGen
)) ret
= ret
| AFrameAny
;
221 if (typeNR
.maybe(TMemToPropGen
)) ret
= ret
| APropAny
;
222 if (typeNR
.maybe(TMemToElemGen
)) ret
= ret
| AElemAny
;
223 if (typeNR
.maybe(TMemToMISGen
)) ret
= ret
| AMIStateTV
;
224 if (typeNR
.maybe(TMemToClsInitGen
)) ret
= ret
| AHeapAny
;
225 if (typeNR
.maybe(TMemToClsCnsGen
)) ret
= ret
| AHeapAny
;
226 if (typeNR
.maybe(TMemToSPropGen
)) ret
= ret
| ARdsAny
;
230 //////////////////////////////////////////////////////////////////////
232 AliasClass
all_pointees(folly::Range
<SSATmp
**> srcs
) {
233 auto ret
= AliasClass
{AEmpty
};
234 for (auto const& src
: srcs
) {
235 if (src
->isA(TMemToGen
)) {
236 ret
= ret
| pointee(src
);
242 // Return an AliasClass containing all locations pointed to by any MemToGen
243 // sources to an instruction.
244 AliasClass
all_pointees(const IRInstruction
& inst
) {
245 return all_pointees(inst
.srcs());
248 // Return an AliasClass representing a range of the eval stack that contains
249 // everything below a logical depth.
250 template<typename Off
>
251 AliasClass
stack_below(SSATmp
* base
, Off offset
) {
252 return AStack
{ base
, offset
, std::numeric_limits
<int32_t>::max() };
255 //////////////////////////////////////////////////////////////////////
258 * Helper functions for alias classes representing an ActRec on the stack
259 * (whether pre-live or live).
262 // Return an AliasClass representing an entire ActRec at base + offset.
263 AliasClass
actrec(SSATmp
* base
, IRSPRelOffset offset
) {
266 // The offset is the bottom of where the ActRec is stored, but AliasClass
267 // needs an offset to the highest cell.
268 offset
+ int32_t{kNumActRecCells
} - 1,
269 int32_t{kNumActRecCells
}
273 // Return an AliasClass representing just the context field of an ActRec at base
275 AliasClass
actrec_ctx(SSATmp
* base
, IRSPRelOffset offset
) {
276 return AStack
{ base
, offset
+ int32_t{kActRecCtxCellOff
}, 1 };
279 // Return AliasClass representing just the func field of an ActRec at base +
281 AliasClass
actrec_func(SSATmp
* base
, IRSPRelOffset offset
) {
282 return AStack
{ base
, offset
+ int32_t{kActRecFuncCellOff
}, 1 };
285 InlineExitEffects
inline_exit_effects(SSATmp
* fp
) {
287 auto fpInst
= fp
->inst();
288 if (UNLIKELY(fpInst
->is(DefLabel
))) fpInst
= resolveFpDefLabel(fp
);
289 assertx(fpInst
&& fpInst
->is(DefInlineFP
));
290 auto const func
= fpInst
->extra
<DefInlineFP
>()->target
;
291 auto const frame
= [&] () -> AliasClass
{
292 if (!func
->numLocals()) return AEmpty
;
293 return AFrame
{fp
, AliasIdSet::IdRange(0, func
->numLocals())};
295 auto const stack
= stack_below(fp
, FPRelOffset
{2});
296 AliasClass clsref
= func
->numClsRefSlots() ? AClsRefSlotAny
: AEmpty
;
297 return InlineExitEffects
{ stack
, frame
, clsref
| AMIStateAny
};
300 //////////////////////////////////////////////////////////////////////
302 // Determine an AliasClass representing any locals in the instruction's frame
303 // which might be accessed via debug_backtrace().
305 AliasClass
backtrace_locals(const IRInstruction
& inst
) {
306 auto const func
= [&]() -> const Func
* {
307 auto fp
= inst
.marker().fp();
308 if (!fp
) return nullptr;
310 auto fpInst
= fp
->inst();
311 if (UNLIKELY(fpInst
->is(DefLabel
))) {
312 fpInst
= resolveFpDefLabel(fp
);
315 if (fpInst
->is(DefFP
)) return fpInst
->marker().func();
316 if (fpInst
->is(DefInlineFP
)) return fpInst
->extra
<DefInlineFP
>()->target
;
317 always_assert(false);
320 // Either there's no func or no frame-pointer. Either way, be conservative and
321 // assume anything can be read. This can happen in test code, for instance.
322 if (!func
) return AFrameAny
;
324 auto const add86meta
= [&] (AliasClass ac
) {
325 // The 86metadata variable can also exist in a VarEnv, but accessing that is
326 // considered a heap effect, so we can ignore it.
327 auto const local
= func
->lookupVarId(s_86metadata
.get());
328 if (local
== kInvalidId
) return ac
;
329 return ac
| AFrame
{ inst
.marker().fp(), (uint32_t)local
};
333 auto const numParams
= func
->numParams();
335 if (!RuntimeOption::EnableArgsInBacktraces
) return add86meta(ac
);
337 if (func
->hasReifiedGenerics()) {
338 // First non param local contains reified generics
339 AliasIdSet reifiedgenerics
{ AliasIdSet::IdRange
{numParams
, numParams
+ 1} };
340 ac
|= AFrame
{ inst
.marker().fp(), reifiedgenerics
};
343 if (func
->cls() && func
->cls()->hasReifiedGenerics()) {
344 // There is no way to access the SSATmp for ObjectData of `this` here,
345 // so be very pessimistic
349 if (!numParams
) return add86meta(ac
);
351 AliasIdSet params
{ AliasIdSet::IdRange
{0, numParams
} };
352 return add86meta(ac
| AFrame
{ inst
.marker().fp(), params
});
355 /////////////////////////////////////////////////////////////////////
358 * Modify a GeneralEffects to take potential VM re-entry into account. This
359 * affects may-load, may-store, and kills information for the instruction. The
360 * GeneralEffects should already contain AHeapAny in both loads and stores if
361 * it affects those locations for reasons other than re-entry, but does not
362 * need to if it doesn't.
364 * For loads, we need to take into account any locals potentially accessed by
367 * For kills, locations on the eval stack below the re-entry depth should all
370 * Important note: because of the `kills' set modifications, an instruction may
371 * not report that it can re-enter if it actually can't. The reason this can
372 * go wrong is that if the instruction was in an inlined function, if we've
373 * removed the DefInlineFP its spOff will not be meaningful (unless it's a
374 * DecRef instruction, which we explicitly adjust in dce.cpp). In this case
375 * the `kills' set will refer to the wrong stack locations. In general this
376 * means instructions that can re-enter must have catch traces---but a few
377 * other instructions are exceptions, either since they are not allowed in
378 * inlined functions or because they take the (possibly-inlined) FramePtr as a
381 GeneralEffects
may_reenter(const IRInstruction
& inst
, GeneralEffects x
) {
382 auto const may_reenter_is_ok
=
383 (inst
.taken() && inst
.taken()->isCatch()) ||
394 MemoSetInstanceCache
,
397 MemoSetInstanceValue
);
400 "instruction {} claimed may_reenter, but it isn't allowed to say that",
405 * We want to union `killed_stack' into whatever else the instruction already
406 * said it must kill, but if we end up with an unrepresentable AliasClass we
407 * can't return a set that's too big (the `kills' set is unlike the other
408 * AliasClasses in GeneralEffects in that means it kills /everything/ in the
409 * set, since it's must-information).
411 * If we can't represent the union, just take the stack, in part because we
412 * have some debugging asserts about this right now---but also nothing
413 * actually uses may_reenter with a non-AEmpty kills at the time of this
416 auto const new_kills
= [&] {
417 if (inst
.marker().fp() == nullptr) return AEmpty
;
419 auto const killed_stack
= stack_below(
421 -inst
.marker().spOff() - 1
423 auto const kills_union
= x
.kills
.precise_union(killed_stack
);
424 return kills_union
? *kills_union
: killed_stack
;
427 return GeneralEffects
{
428 x
.loads
| AHeapAny
| backtrace_locals(inst
),
435 //////////////////////////////////////////////////////////////////////
437 GeneralEffects
may_load_store(AliasClass loads
, AliasClass stores
) {
438 return GeneralEffects
{ loads
, stores
, AEmpty
, AEmpty
};
441 GeneralEffects
may_load_store_kill(AliasClass loads
,
444 return GeneralEffects
{ loads
, stores
, AEmpty
, kill
};
447 GeneralEffects
may_load_store_move(AliasClass loads
,
450 assertx(move
<= loads
);
451 return GeneralEffects
{ loads
, stores
, move
, AEmpty
};
454 //////////////////////////////////////////////////////////////////////
457 * Helper for iterator instructions. They all affect some locals, but are
458 * otherwise the same.
460 * N.B. Currently the memory for frame iterator slots is not part of the
461 * AliasClass lattice, since we never really manipulate them from the TC yet,
462 * so we don't report the effect these instructions have on it.
464 GeneralEffects
iter_effects(const IRInstruction
& inst
,
467 auto const iterID
= inst
.extra
<IterData
>()->iterId
;
468 AliasClass
const iterPos
= AIterPos
{ fp
, iterID
};
469 AliasClass
const iterBase
= AIterBase
{ fp
, iterID
};
470 auto const iterMem
= iterPos
| iterBase
;
474 locals
| AHeapAny
| iterMem
,
475 locals
| AHeapAny
| iterMem
,
482 * Construct effects for InterpOne, using the information in its extra data.
484 * We always consider an InterpOne as potentially doing anything to the heap,
485 * potentially re-entering, potentially raising warnings in the current frame,
486 * potentially reading any locals, and potentially reading/writing any stack
487 * location that isn't below the bottom of the stack.
489 * The extra data for the InterpOne comes with some additional information
490 * about which local(s) it may modify, which is all we try to be more precise
493 GeneralEffects
interp_one_effects(const IRInstruction
& inst
) {
494 auto const extra
= inst
.extra
<InterpOne
>();
495 auto loads
= AHeapAny
| AStackAny
| AFrameAny
| ARdsAny
;
496 auto stores
= AHeapAny
| AStackAny
| ARdsAny
;
497 if (extra
->smashesAllLocals
) {
498 stores
= stores
| AFrameAny
;
500 for (auto i
= uint32_t{0}; i
< extra
->nChangedLocals
; ++i
) {
501 stores
= stores
| AFrame
{ inst
.src(1), extra
->changedLocals
[i
].id
};
505 for (auto i
= uint32_t{0}; i
< extra
->nChangedClsRefSlots
; ++i
) {
506 auto const& slot
= extra
->changedClsRefSlots
[i
];
508 stores
= stores
| AClsRefClsSlot
{ inst
.src(1), slot
.id
} |
509 AClsRefTSSlot
{ inst
.src(1), slot
.id
};
511 loads
= loads
| AClsRefClsSlot
{ inst
.src(1), slot
.id
} |
512 AClsRefTSSlot
{ inst
.src(1), slot
.id
};
517 if (isMemberBaseOp(extra
->opcode
)) {
518 stores
= stores
| AMIStateAny
;
519 kills
= kills
| AMIStateAny
;
520 } else if (isMemberDimOp(extra
->opcode
) || isMemberFinalOp(extra
->opcode
)) {
521 stores
= stores
| AMIStateAny
;
522 loads
= loads
| AMIStateAny
;
524 kills
= kills
| AMIStateAny
;
527 return may_load_store_kill(loads
, stores
, kills
);
530 ////////////////////////////////////////////////////////////////////////////////
533 * Construct effects for member instructions that take &tvRef as their last
536 * These instructions never load tvRef, but they might store to it.
538 MemEffects
minstr_with_tvref(const IRInstruction
& inst
) {
539 auto loads
= AHeapAny
;
540 auto stores
= AHeapAny
| all_pointees(inst
);
543 auto const srcs
= inst
.srcs();
544 if (inst
.is(ElemDX
, PropDX
)) {
545 assertx(inst
.src(inst
.numSrcs() - 2)->isA(TMemToMISGen
));
546 assertx(inst
.src(inst
.numSrcs() - 1)->isA(TMIPropSPtr
| TNullptr
));
547 loads
|= all_pointees(srcs
.subpiece(0, srcs
.size() - 2));
549 auto const propPtr
= inst
.src(inst
.numSrcs() - 1);
550 if (RuntimeOption::EvalCheckPropTypeHints
<= 0 || propPtr
->isA(TNullptr
)) {
551 kills
= AMIStatePropS
;
552 } else if (inst
.is(ElemDX
)) {
553 loads
|= AMIStatePropS
;
555 assertx(inst
.is(PropDX
));
556 if (RuntimeOption::EvalPromoteEmptyObject
) loads
|= AMIStatePropS
;
557 stores
|= AMIStatePropS
;
560 assertx(srcs
.back()->isA(TMemToMISGen
));
561 loads
|= all_pointees(srcs
.subpiece(0, srcs
.size() - 1));
562 kills
= AMIStatePropS
;
565 return may_load_store_kill(loads
, stores
, kills
);
568 //////////////////////////////////////////////////////////////////////
570 MemEffects
minstr_final_with_prop_state(const IRInstruction
& inst
) {
571 auto const propSLoads
= [&]{
572 auto const propPtr
= inst
.srcs().back();
573 assertx(propPtr
->isA(TMIPropSPtr
| TNullptr
));
574 if (RuntimeOption::EvalCheckPropTypeHints
<= 0) return AEmpty
;
575 if (propPtr
->isA(TNullptr
)) return AEmpty
;
576 if (!RuntimeOption::EvalPromoteEmptyObject
&&
577 inst
.is(IncDecProp
, SetOpProp
, SetProp
)) {
580 return AMIStatePropS
;
583 return may_load_store_kill(
584 AHeapAny
| propSLoads
| all_pointees(inst
),
585 AHeapAny
| all_pointees(inst
),
590 //////////////////////////////////////////////////////////////////////
592 MemEffects
memory_effects_impl(const IRInstruction
& inst
) {
595 //////////////////////////////////////////////////////////////////////
598 // These exits don't leave the current php function, and could head to code
599 // that could read or write anything as far as we know (including frame
604 stack_below(inst
.src(0), inst
.extra
<ReqBindJmp
>()->irSPOff
- 1)
609 stack_below(inst
.src(0), inst
.extra
<ReqRetranslate
>()->irSPOff
- 1)
611 case ReqRetranslateOpt
:
614 stack_below(inst
.src(0), inst
.extra
<ReqRetranslateOpt
>()->offset
- 1)
619 *stack_below(inst
.src(1),
620 inst
.extra
<JmpSwitchDest
>()->spOffBCFromIRSP
- 1).
621 precise_union(AMIStateAny
)
626 *stack_below(inst
.src(1),
627 inst
.extra
<JmpSSwitchDest
>()->offset
- 1).
628 precise_union(AMIStateAny
)
631 //////////////////////////////////////////////////////////////////////
632 // Unusual instructions
635 * The ReturnHook sets up the ActRec so the unwinder knows everything is
636 * already released (i.e. it calls ar->setLocalsDecRefd()).
638 * The eval stack is also dead at this point (the return value is passed to
639 * ReturnHook as src(1), and the ReturnHook may not access the stack).
642 // Note, this instruction can re-enter, but doesn't need the may_reenter()
643 // treatmeant because of the special kill semantics for locals and stack.
644 return may_load_store_kill(
646 *AStackAny
.precise_union(AFrameAny
)->precise_union(AMIStateAny
)
649 // The suspend hooks can load anything (re-entering the VM), but can't write
651 case SuspendHookAwaitEF
:
652 case SuspendHookAwaitEG
:
653 case SuspendHookAwaitR
:
654 case SuspendHookCreateCont
:
655 case SuspendHookYield
:
656 // TODO: may-load here probably doesn't need to include AFrameAny normally.
657 return may_reenter(inst
,
658 may_load_store_kill(AUnknown
, AHeapAny
, AMIStateAny
));
661 * If we're returning from a function, it's ReturnEffects. The RetCtrl
662 * opcode also suspends resumables, which we model as having any possible
666 if (inst
.extra
<RetCtrl
>()->suspendingResumed
) {
667 // Suspending can go anywhere, and doesn't even kill locals.
668 return UnknownEffects
{};
670 return ReturnEffects
{
671 AStackAny
| AFrameAny
| AClsRefSlotAny
| AMIStateAny
675 case AsyncFuncRetSlow
:
676 return ReturnEffects
{ AStackAny
| AMIStateAny
};
678 case AsyncSwitchFast
:
679 // Suspending can go anywhere, and doesn't even kill locals.
680 return UnknownEffects
{};
682 case GenericRetDecRefs
:
684 * The may-store information here is AUnknown: even though we know it
685 * doesn't really "store" to the frame locals, the values that used to be
686 * there are no longer available because they are DecRef'd, which we are
687 * required to report as may-store information to make it visible to
688 * reference count optimizations. It's conceptually the same as if it was
689 * storing an Uninit over each of the locals, but the stores of uninits
690 * would be dead so we're not actually doing that.
692 return may_reenter(inst
,
693 may_load_store_kill(AUnknown
, AUnknown
, AMIStateAny
));
696 auto const stack_kills
= stack_below(
698 inst
.extra
<EndCatch
>()->offset
- 1
702 stack_kills
| AMIStateTempBase
| AMIStateBase
| AMIStatePropS
707 * DefInlineFP has some special treatment here.
709 * It's logically `publishing' a pointer to a pre-live ActRec, making it
710 * live. It doesn't actually load from this ActRec, but after it's done this
711 * the set of things that can load from it is large enough that the easiest
712 * way to model this is to consider it as a load on behalf of `publishing'
713 * the ActRec. Once it's published, it's a live activation record, and
714 * doesn't get written to as if it were a stack slot anymore (we've
715 * effectively converted AStack locations into a frame until the
718 * Note: We may push the publishing of the inline frame below the start of
719 * the inline function so that we can avoid spilling the inline frame in the
720 * common case. Because of this we cannot add the stack positions within the
721 * inline function to the kill set here as they may be live having been stored
724 * TODO(#3634984): Additionally, DefInlineFP is marking may-load on all the
725 * locals of the outer frame. This is probably not necessary anymore, but we
726 * added it originally because a store sinking prototype needed to know it
727 * can't push StLocs past a DefInlineFP, because of reserved registers.
728 * Right now it's just here because we need to think about and test it before
732 auto const func
= inst
.extra
<DefInlineFP
>()->target
;
733 AliasClass stack
= func
->numLocals()
734 ? AStack
{inst
.dst(), FPRelOffset
{-1}, func
->numLocals()}
736 AliasClass frame
= func
->numLocals()
737 ? AFrame
{inst
.dst(), AliasIdSet::IdRange(0, func
->numLocals())}
739 AliasClass clsrefs
= func
->numClsRefSlots() ? AClsRefSlotAny
: AEmpty
;
741 * Notice that the stack positions and frame locals described here are
742 * exactly the set of alias locations that are about to be overlapping
743 * inside the inlined frame. Likewise, ClsRefSlots from the caller and the
744 * callee are about to be jumbled.
746 return InlineEnterEffects
{ stack
, frame
| clsrefs
, inline_fp_frame(&inst
) };
750 * BeginInlining is similar to DefInlineFP, however, it must always be the
751 * first instruction in the inlined call and has no effect serving only as
752 * a marker to memory effects that the stack cells within the inlined call
755 * Unlike DefInlineFP it does not load the SpillFrame, which we hope to push
756 * off the main trace or elide entirely.
758 case BeginInlining
: {
760 * SP relative offset of the first non-frame cell within the inlined call.
762 auto inlineStackOff
= inst
.extra
<BeginInlining
>()->offset
;
763 return may_load_store_kill(
767 * This prevents stack slots from the caller from being sunk into the
768 * callee. Note that some of these stack slots overlap with the frame
769 * locals of the callee-- those slots are inacessible in the inlined
770 * call as frame and stack locations may not alias.
772 stack_below(inst
.src(0), inlineStackOff
)
778 return inline_exit_effects(inst
.src(0));
781 case InlineReturnNoFrame
: {
782 auto const callee
= AliasClass(AStack
{
783 inst
.extra
<InlineReturnNoFrame
>()->offset
,
784 std::numeric_limits
<int32_t>::max()
786 return may_load_store_kill(AEmpty
, AEmpty
, callee
);
790 auto const spOffset
= inst
.extra
<SyncReturnBC
>()->spOffset
;
791 auto const arStack
= actrec(inst
.src(0), spOffset
);
792 // This instruction doesn't actually load but SpillFrame cannot be pushed
794 return may_load_store(arStack
, arStack
);
798 return interp_one_effects(inst
);
802 stack_below(inst
.src(1), -inst
.marker().spOff() - 1) | AMIStateAny
806 return UnknownEffects
{};
808 // NB: on the failure path, these C++ helpers do a fixup and read frame
809 // locals before they throw. They can also invoke the user error handler and
810 // go do whatever they want to non-frame locations.
812 // TODO(#5372569): if we combine dv inits into the same regions we could
813 // possibly avoid storing KindOfUninits if we modify this.
814 case VerifyParamCallable
:
816 case VerifyParamFailHard
:
817 return may_load_store(AUnknown
, AHeapAny
);
818 // VerifyParamFail might coerce the parameter to the desired type rather than
820 case VerifyParamFail
: {
821 auto const localId
= inst
.src(0)->intVal();
822 assertx(localId
>= 0);
824 AHeapAny
| AFrame
{inst
.marker().fp(), safe_cast
<uint32_t>(localId
)};
825 return may_load_store(AUnknown
, stores
);
827 case VerifyReifiedLocalType
: {
828 auto const extra
= inst
.extra
<ParamData
>();
829 assertx(extra
->paramId
>= 0);
831 AHeapAny
| AFrame
{inst
.marker().fp(),safe_cast
<uint32_t>(extra
->paramId
)};
832 return may_load_store(AUnknown
, stores
);
834 // However the following ones can't read locals from our frame on the way
835 // out, except as a side effect of raising a warning.
836 case VerifyRetCallable
:
838 case VerifyReifiedReturnType
:
839 return may_load_store(AHeapAny
, AHeapAny
);
842 case VerifyRetFailHard
:
843 return may_load_store(AHeapAny
| AStackAny
, AHeapAny
);
847 case VerifyPropFailHard
:
849 return may_load_store(AHeapAny
, AHeapAny
);
853 auto const extra
= inst
.extra
<CallUnpack
>();
855 // Kills. Everything on the stack below the incoming parameters.
856 stack_below(inst
.src(0), extra
->spOffset
- 1) | AMIStateAny
,
857 // Stack. The act-rec, incoming parameters, and everything below.
860 extra
->spOffset
+ extra
->numParams
+ kNumActRecCells
+
864 backtrace_locals(inst
),
866 actrec_func(inst
.src(0), extra
->spOffset
+ extra
->numParams
)
872 auto const extra
= inst
.extra
<ContEnter
>();
874 // Kills. Everything on the stack below the sent value.
875 stack_below(inst
.src(0), extra
->spOffset
- 1) | AMIStateAny
,
876 // Stack. The value being sent, and everything below.
877 stack_below(inst
.src(0), extra
->spOffset
),
879 backtrace_locals(inst
),
880 // Callee. Stored inside the generator object, not used by ContEnter.
887 auto const extra
= inst
.extra
<Call
>();
889 // Kills. Everything on the stack below the incoming parameters.
890 stack_below(inst
.src(0), extra
->spOffset
- 1) | AMIStateAny
,
891 // Stack. The act-rec, incoming parameters, and everything below.
894 extra
->spOffset
+ extra
->numParams
+ kNumActRecCells
+
898 backtrace_locals(inst
),
900 actrec_func(inst
.src(0), extra
->spOffset
+ extra
->numParams
)
906 auto const stk
= [&] () -> AliasClass
{
907 AliasClass ret
= AEmpty
;
908 for (auto i
= uint32_t{2}; i
< inst
.numSrcs(); ++i
) {
909 if (inst
.src(i
)->type() <= TPtrToGen
) {
910 auto const cls
= pointee(inst
.src(i
));
911 if (cls
.maybe(AStackAny
)) {
918 auto const callee
= inst
.extra
<CallBuiltin
>()->callee
;
919 if (callee
->isFoldable()) {
920 return may_load_store_kill(stk
| AHeapAny
, AHeapAny
, AMIStateAny
);
922 return may_load_store_kill(
923 stk
| AHeapAny
| ARdsAny
,
930 // Resumable suspension takes everything from the frame and moves it into the
935 case CreateAFWHNoVV
: {
936 auto const fp
= canonical(inst
.src(0));
937 auto fpInst
= fp
->inst();
938 if (fpInst
->is(DefLabel
)) fpInst
= resolveFpDefLabel(fp
);
939 auto const frame
= [&] () -> AliasClass
{
940 if (fpInst
->is(DefFP
)) return AFrameAny
;
941 assertx(fpInst
->is(DefInlineFP
));
942 auto const nlocals
= fpInst
->extra
<DefInlineFP
>()->target
->numLocals();
944 ? AFrame
{ fp
, AliasIdSet::IdRange(0, nlocals
)}
947 auto const clsrefs
= [&] () -> AliasClass
{
948 if (fpInst
->is(DefFP
)) return AClsRefSlotAny
;
949 assertx(fpInst
->is(DefInlineFP
));
950 auto const nrefs
= fpInst
->extra
<DefInlineFP
>()->target
->numClsRefSlots();
951 return nrefs
? AClsRefSlotAny
: AEmpty
;
953 return may_load_store_move(
960 // AGWH construction updates the AsyncGenerator object.
962 return may_load_store(AHeapAny
, AHeapAny
);
966 auto const extra
= inst
.extra
<CreateAAWH
>();
967 auto const frame
= AFrame
{
970 AliasIdSet::IdRange
{ extra
->first
, extra
->first
+ extra
->count
}
973 return may_reenter(inst
, may_load_store(frame
, AHeapAny
));
978 auto const extra
= inst
.extra
<CountWHNotDone
>();
979 auto const frame
= AFrame
{
982 AliasIdSet::IdRange
{ extra
->first
, extra
->first
+ extra
->count
}
985 return may_load_store(frame
, AEmpty
);
988 // This re-enters to call extension-defined instance constructors.
989 case ConstructInstance
:
990 return may_reenter(inst
, may_load_store(AHeapAny
, AHeapAny
));
992 // Closures don't ever throw or reenter on construction
993 case ConstructClosure
:
994 return IrrelevantEffects
{};
996 case CheckStackOverflow
:
997 case CheckSurpriseFlagsEnter
:
998 case CheckSurpriseAndStack
:
999 return may_load_store(AEmpty
, AEmpty
);
1002 return UnknownEffects
{};
1004 //////////////////////////////////////////////////////////////////////
1005 // Iterator instructions
1010 return iter_effects(
1013 AFrame
{ inst
.src(1), inst
.extra
<IterData
>()->valId
}
1016 return iter_effects(
1019 AFrame
{ inst
.src(0), inst
.extra
<IterData
>()->valId
}
1026 AliasClass key
= AFrame
{ inst
.src(1), inst
.extra
<IterData
>()->keyId
};
1027 AliasClass val
= AFrame
{ inst
.src(1), inst
.extra
<IterData
>()->valId
};
1028 return iter_effects(inst
, inst
.src(1), key
| val
);
1033 AliasClass key
= AFrame
{ inst
.src(0), inst
.extra
<IterData
>()->keyId
};
1034 AliasClass val
= AFrame
{ inst
.src(0), inst
.extra
<IterData
>()->valId
};
1035 return iter_effects(inst
, inst
.src(0), key
| val
);
1039 return may_reenter(inst
, may_load_store(AHeapAny
, AHeapAny
));
1041 //////////////////////////////////////////////////////////////////////
1042 // Instructions that explicitly manipulate locals
1046 AFrame
{ inst
.src(0), inst
.extra
<StLoc
>()->locId
},
1053 auto const extra
= inst
.extra
<StLocRange
>();
1056 for (auto locId
= extra
->start
; locId
< extra
->end
; ++locId
) {
1057 acls
= acls
| AFrame
{ inst
.src(0), locId
};
1059 return PureStore
{ acls
, inst
.src(1), nullptr };
1063 return PureLoad
{ AFrame
{ inst
.src(0), inst
.extra
<LocalId
>()->locId
} };
1066 case LdLocPseudoMain
:
1067 // Note: LdLocPseudoMain is both a guard and a load, so it must not be a
1069 return may_load_store(
1070 AFrame
{ inst
.src(0), inst
.extra
<LocalId
>()->locId
},
1074 case StLocPseudoMain
:
1075 // This can store to globals or locals, but we don't have globals supported
1076 // in AliasClass yet.
1077 return PureStore
{ AUnknown
, inst
.src(1), nullptr };
1079 //////////////////////////////////////////////////////////////////////
1080 // Instructions that manipulate class-ref slots
1084 AClsRefClsSlot
{ inst
.src(0), inst
.extra
<LdClsRefCls
>()->slot
}
1089 AClsRefTSSlot
{ inst
.src(0), inst
.extra
<LdClsRefTS
>()->slot
}
1094 AClsRefClsSlot
{ inst
.src(0), inst
.extra
<StClsRefCls
>()->slot
},
1101 AClsRefTSSlot
{ inst
.src(0), inst
.extra
<StClsRefTS
>()->slot
},
1107 return may_load_store_kill(
1109 AClsRefClsSlot
{ inst
.src(0), inst
.extra
<KillClsRefCls
>()->slot
}
1113 return may_load_store_kill(
1115 AClsRefTSSlot
{ inst
.src(0), inst
.extra
<KillClsRefTS
>()->slot
}
1118 //////////////////////////////////////////////////////////////////////
1119 // Pointer-based loads and stores
1122 return PureLoad
{ pointee(inst
.src(0)) };
1124 return PureStore
{ pointee(inst
.src(0)), inst
.src(1), inst
.src(0) };
1126 // TODO(#5962341): These take non-constant offset arguments, and are
1127 // currently only used for collections and class property inits, so we aren't
1131 inst
.src(0)->type() <= TPtrToRMembCell
1139 inst
.src(0)->type() <= TPtrToRMembCell
1145 return PureLoad
{ AMIStateBase
};
1148 return PureStore
{ AMIStateBase
, inst
.src(0), nullptr };
1151 return PureStore
{ AMIStatePropS
, nullptr, nullptr };
1153 case FinishMemberOp
:
1154 return may_load_store_kill(AEmpty
, AEmpty
, AMIStateAny
);
1158 auto const mem
= pointee(inst
.src(0));
1159 return may_load_store(mem
, mem
);
1162 return may_load_store(pointee(inst
.src(0)), AEmpty
);
1168 return may_load_store(pointee(inst
.src(0)), AEmpty
);
1170 case CheckRDSInitialized
:
1171 return may_load_store(
1172 ARds
{ inst
.extra
<CheckRDSInitialized
>()->handle
},
1175 case MarkRDSInitialized
:
1176 return may_load_store(
1178 ARds
{ inst
.extra
<MarkRDSInitialized
>()->handle
}
1182 return may_load_store(
1184 AHeapAny
| ARds
{ inst
.extra
<InitProps
>()->cls
->propHandle() }
1188 return may_load_store(
1190 AHeapAny
| ARds
{ inst
.extra
<InitSProps
>()->cls
->sPropInitHandle() }
1193 case LdClsFromClsMeth
:
1194 case LdFuncFromClsMeth
:
1195 return may_load_store(AHeapAny
, AEmpty
);
1197 //////////////////////////////////////////////////////////////////////
1198 // Object/Ref loads/stores
1201 return may_load_store(ARef
{ inst
.src(0) }, AEmpty
);
1203 return PureLoad
{ ARef
{ inst
.src(0) } };
1205 return PureStore
{ ARef
{ inst
.src(0) }, inst
.src(1), inst
.src(0) };
1208 return may_load_store(AEmpty
, APropAny
);
1210 case InitObjMemoSlots
:
1211 // Writes to memo slots, but these are not modeled.
1212 return IrrelevantEffects
{};
1214 // Loads $obj->trace, stores $obj->file and $obj->line.
1215 case InitThrowableFileAndLine
:
1216 return may_load_store(AHeapAny
, APropAny
);
1218 //////////////////////////////////////////////////////////////////////
1219 // Array loads and stores
1221 case InitPackedLayoutArray
:
1223 AElemI
{ inst
.src(0), inst
.extra
<InitPackedLayoutArray
>()->index
},
1229 case LdPackedElem
: {
1230 auto const base
= inst
.src(0);
1231 auto const key
= inst
.src(1);
1233 key
->hasConstVal() ? AElemI
{ base
, key
->intVal() } : AElemIAny
1237 case InitPackedLayoutArrayLoop
:
1239 auto const extra
= inst
.extra
<InitPackedLayoutArrayLoop
>();
1240 auto const stack_in
= AStack
{
1242 extra
->offset
+ static_cast<int32_t>(extra
->size
) - 1,
1243 static_cast<int32_t>(extra
->size
)
1245 return may_load_store_move(stack_in
, AElemIAny
, stack_in
);
1248 case NewKeysetArray
:
1250 // NewKeysetArray is reading elements from the stack, but writes to a
1251 // completely new array, so we can treat the store set as empty.
1252 auto const extra
= inst
.extra
<NewKeysetArray
>();
1253 auto const stack_in
= AStack
{
1255 extra
->offset
+ static_cast<int32_t>(extra
->size
) - 1,
1256 static_cast<int32_t>(extra
->size
)
1258 return may_load_store_move(stack_in
, AEmpty
, stack_in
);
1261 case NewStructArray
:
1262 case NewStructDArray
:
1265 // NewStructArray is reading elements from the stack, but writes to a
1266 // completely new array, so we can treat the store set as empty.
1267 auto const extra
= inst
.extra
<NewStructData
>();
1268 auto const stack_in
= AStack
{
1270 extra
->offset
+ static_cast<int32_t>(extra
->numKeys
) - 1,
1271 static_cast<int32_t>(extra
->numKeys
)
1273 return may_load_store_move(stack_in
, AEmpty
, stack_in
);
1278 auto const extra
= inst
.extra
<NewStructData
>();
1279 auto const stack_in
= AStack
{
1281 extra
->offset
+ static_cast<int32_t>(extra
->numKeys
) - 1,
1282 static_cast<int32_t>(extra
->numKeys
)
1284 return may_load_store_move(stack_in
, AEmpty
, stack_in
);
1286 case MemoGetStaticValue
:
1287 case MemoGetLSBValue
:
1288 case MemoGetInstanceValue
:
1289 // Only reads the memo value (which isn't modeled here).
1290 return may_load_store(AEmpty
, AEmpty
);
1292 case MemoSetStaticValue
:
1293 case MemoSetLSBValue
:
1294 case MemoSetInstanceValue
:
1295 // Writes to the memo value (which isn't modeled), but can re-enter to run
1297 return may_reenter(inst
, may_load_store(AEmpty
, AEmpty
));
1299 case MemoGetStaticCache
:
1300 case MemoGetLSBCache
:
1301 case MemoSetStaticCache
:
1302 case MemoSetLSBCache
: {
1303 // Reads some (non-zero) set of locals for keys, and reads/writes from the
1304 // memo cache (which isn't modeled). The set can re-enter to run a
1306 auto const extra
= inst
.extra
<MemoCacheStaticData
>();
1307 auto const loc
= [&] () -> AliasClass
{
1308 if (inst
.src(0)->isA(TFramePtr
)) {
1312 AliasIdSet::IdRange
{
1314 extra
->keys
.first
+ extra
->keys
.count
1319 assertx(inst
.src(0)->isA(TStkPtr
));
1320 assertx(extra
->stackOffset
);
1323 *extra
->stackOffset
,
1324 static_cast<int32_t>(extra
->keys
.count
)
1328 auto effects
= may_load_store(loc
, AEmpty
);
1329 if (inst
.op() == MemoSetStaticCache
|| inst
.op() == MemoSetLSBCache
) {
1330 effects
= may_reenter(inst
, effects
);
1335 case MemoGetInstanceCache
:
1336 case MemoSetInstanceCache
: {
1337 // Reads some set of locals for keys, and reads/writes from the memo cache
1338 // (which isn't modeled). The set can re-enter to run a destructor.
1339 auto const extra
= inst
.extra
<MemoCacheInstanceData
>();
1340 auto const loc
= [&]() -> AliasClass
{
1341 // Unlike MemoGet/SetStaticCache, we can have an empty key range here.
1342 if (extra
->keys
.count
== 0) return AEmpty
;
1344 if (inst
.src(0)->isA(TFramePtr
)) {
1348 AliasIdSet::IdRange
{
1350 extra
->keys
.first
+ extra
->keys
.count
1355 assertx(inst
.src(0)->isA(TStkPtr
));
1356 assertx(extra
->stackOffset
);
1359 *extra
->stackOffset
,
1360 static_cast<int32_t>(extra
->keys
.count
)
1363 auto effects
= may_load_store(loc
, AEmpty
);
1364 if (inst
.op() == MemoSetInstanceCache
) effects
= may_reenter(inst
, effects
);
1368 case MixedArrayGetK
:
1371 auto const base
= inst
.src(0);
1372 auto const key
= inst
.src(1);
1373 always_assert(key
->isA(TInt
| TStr
));
1374 if (key
->isA(TInt
)) {
1376 key
->hasConstVal() ? AElemI
{ base
, key
->intVal() } : AElemIAny
1379 if (key
->isA(TStr
)) {
1381 key
->hasConstVal() ? AElemS
{ base
, key
->strVal() } : AElemSAny
1384 return PureLoad
{ AElemAny
};
1387 case ElemMixedArrayK
:
1390 return IrrelevantEffects
{};
1393 auto const base
= inst
.src(0);
1394 return may_load_store(AElemI
{ base
, 0 }, AEmpty
);
1397 auto const base
= inst
.src(0);
1398 if (base
->hasConstVal(TArr
)) {
1399 return may_load_store(
1400 AElemI
{ base
, static_cast<int64_t>(base
->arrVal()->size() - 1) },
1403 return may_load_store(AElemIAny
, AEmpty
);
1409 return may_load_store(AElemAny
, AEmpty
);
1413 return may_load_store(AEmpty
, AEmpty
);
1415 case ProfileMixedArrayAccess
:
1416 case CheckMixedArrayOffset
:
1418 case ProfileDictAccess
:
1419 case ProfileKeysetAccess
:
1420 case CheckDictOffset
:
1421 case CheckKeysetOffset
:
1422 return may_load_store(AHeapAny
, AEmpty
);
1426 return may_load_store(AElemAny
, AEmpty
);
1429 return may_load_store(AElemAny
| ARefAny
, AEmpty
);
1433 return may_load_store(AEmpty
, AEmpty
);
1439 case KeysetGetQuiet
:
1441 case KeysetEmptyElem
:
1444 case AKExistsKeyset
:
1445 return may_load_store(AElemAny
, AEmpty
);
1455 return may_load_store(AElemAny
, AEmpty
);
1459 return may_load_store(
1460 RuntimeOption::EvalHackArrDVArrs
? AElemAny
: AEmpty
,
1464 return may_load_store(AHeapAny
, AHeapAny
);
1466 //////////////////////////////////////////////////////////////////////
1467 // Member instructions
1470 return may_load_store(pointee(inst
.src(0)), AEmpty
);
1473 * Various minstr opcodes that take a PtrToGen in src 0, which may or may not
1474 * point to a frame local or the evaluation stack. Some may read or write to
1475 * that pointer while some only read. They can all re-enter the VM and access
1476 * arbitrary heap locations.
1485 return may_load_store_kill(
1486 AHeapAny
| all_pointees(inst
),
1498 return minstr_final_with_prop_state(inst
);
1501 * SetRange behaves like a simpler version of SetElem.
1505 return may_load_store_kill(
1506 AHeapAny
| all_pointees(inst
),
1507 AHeapAny
| pointee(inst
.src(0)),
1511 case SetNewElemArray
:
1513 case SetNewElemKeyset
:
1523 // Right now we generally can't limit any of these better than general
1524 // re-entry rules, since they can raise warnings and re-enter.
1525 return may_load_store_kill(
1526 AHeapAny
| all_pointees(inst
),
1527 AHeapAny
| all_pointees(inst
),
1531 case ReservePackedArrayDataNewElem
:
1532 return may_load_store(AHeapAny
, AHeapAny
);
1535 * Intermediate minstr operations. In addition to a base pointer like the
1536 * operations above, these may take a pointer to MInstrState::tvRef, which
1537 * they may store to (but not read from).
1545 return minstr_with_tvref(inst
);
1548 * Collection accessors can read from their inner array buffer, but stores
1549 * COW and behave as if they only affect collection memory locations. We
1550 * don't track those, so it's returning AEmpty for now.
1555 return may_load_store(AHeapAny
, AEmpty
/* Note */);
1559 return may_reenter(inst
, may_load_store(AHeapAny
, AEmpty
/* Note */));
1561 case LdInitPropAddr
:
1562 return may_load_store(
1565 safe_cast
<uint32_t>(inst
.extra
<LdInitPropAddr
>()->offsetBytes
)
1570 return may_load_store(
1571 ARds
{ inst
.extra
<LdInitRDSAddr
>()->handle
},
1575 //////////////////////////////////////////////////////////////////////
1576 // Instructions that allocate new objects, without reading any other memory
1577 // at all, so any effects they have on some types of memory locations we
1578 // track are isolated from anything else we care about.
1583 case NewColFromArray
:
1585 case NewInstanceRaw
:
1589 case AllocPackedArray
:
1597 case Box
: // conditional allocation
1598 return IrrelevantEffects
{};
1601 // AllocObj re-enters to call constructors, but if it weren't for that we
1602 // could ignore its loads and stores since it's a new object.
1603 return may_reenter(inst
, may_load_store(AEmpty
, AEmpty
));
1604 case AllocObjReified
:
1605 // Similar to AllocObj but also stores the reification
1606 return may_reenter(inst
, may_load_store(AEmpty
, AHeapAny
));
1608 //////////////////////////////////////////////////////////////////////
1609 // Instructions that explicitly manipulate the stack.
1613 AStack
{ inst
.src(0), inst
.extra
<LdStk
>()->offset
, 1 }
1618 AStack
{ inst
.src(0), inst
.extra
<StStk
>()->offset
, 1 },
1624 // Technically these writes affect the caller's stack, but there is no way
1625 // to actually observe them from within the callee. They can also only
1626 // occur once on any exit path from a function.
1627 return may_load_store(AEmpty
, AEmpty
);
1631 auto const spOffset
= inst
.extra
<SpillFrame
>()->spOffset
;
1632 return PureSpillFrame
{
1633 actrec(inst
.src(0), spOffset
),
1634 actrec_ctx(inst
.src(0), spOffset
),
1635 actrec_func(inst
.src(0), spOffset
),
1641 return may_load_store(
1642 AStack
{ inst
.src(0), inst
.extra
<CheckStk
>()->offset
, 1 },
1648 actrec_ctx(inst
.src(0), inst
.extra
<LdARCtx
>()->offset
)
1652 actrec_func(inst
.src(0), inst
.extra
<LdARFuncPtr
>()->offset
)
1655 case DbgAssertARFunc
:
1656 return may_load_store(
1657 actrec_func(inst
.src(0), inst
.extra
<DbgAssertARFunc
>()->offset
),
1662 // Unreachable code kills every memory location.
1663 return may_load_store_kill(AEmpty
, AEmpty
, AUnknown
);
1665 case ResolveTypeStruct
: {
1666 auto const extra
= inst
.extra
<ResolveTypeStructData
>();
1667 auto const stack_in
= AStack
{
1669 extra
->offset
+ static_cast<int32_t>(extra
->size
) - 1,
1670 static_cast<int32_t>(extra
->size
)
1672 return may_load_store(AliasClass(stack_in
)|AHeapAny
, AHeapAny
);
1675 //////////////////////////////////////////////////////////////////////
1676 // Instructions that never read or write memory locations tracked by this
1697 case EqArrayDataPtr
:
1706 case HintMBaseInner
:
1712 case LdPackedArrayDataElemAddr
:
1745 case CheckFuncMMNonMagic
:
1746 case CheckSmashableClass
:
1771 case LdMIPropStateAddr
:
1776 case LdSubClsCnsClsName
:
1778 case CheckSubClsCns
:
1779 case LdClsCnsVecLen
:
1780 case ProfileSubClsCns
:
1782 case FuncSupportsAsyncEagerReturn
:
1783 case IsFunReifiedGenericsMatched
:
1784 case IsFuncDynCallable
:
1785 case IsClsDynConstructible
:
1789 case LdSmashableFunc
:
1793 case ExitPlaceholder
:
1797 case InstanceOfIfaceVtable
:
1798 case CheckARMagicFlag
:
1799 case LdARNumArgsAndFlags
:
1800 case StARNumArgsAndFlags
:
1801 case LdARReifiedGenerics
:
1802 case KillARReifiedGenerics
:
1809 case ProfileInstanceCheck
:
1811 case LookupSPropSlot
:
1813 case MangleReifiedName
:
1814 return IrrelevantEffects
{};
1820 safe_cast
<uint32_t>(inst
.extra
<StClosureArg
>()->offsetBytes
)
1826 //////////////////////////////////////////////////////////////////////
1827 // Instructions that technically do some things w/ memory, but not in any way
1828 // we currently care about. They however don't return IrrelevantEffects
1829 // because we assume (in refcount-opts) that IrrelevantEffects instructions
1830 // can't even inspect Countable reference count fields, and several of these
1831 // can. All GeneralEffects instructions are assumed to possibly do so.
1850 case CheckSurpriseFlags
:
1854 case StArResumeAddr
:
1856 case ZeroErrorLevel
:
1857 case RestoreErrorLevel
:
1861 case ContArUpdateIdx
:
1864 case IncProfCounter
:
1867 case ContStartedCheck
:
1871 case CountArrayFast
:
1877 case InstanceOfBitmask
:
1878 case NInstanceOfBitmask
:
1879 case InstanceOfIface
:
1880 case InterfaceSupportsArr
:
1881 case InterfaceSupportsVec
:
1882 case InterfaceSupportsDict
:
1883 case InterfaceSupportsKeyset
:
1884 case InterfaceSupportsDbl
:
1885 case InterfaceSupportsInt
:
1886 case InterfaceSupportsStr
:
1891 case DbgAssertRefCount
:
1914 case LdSSwitchDestFast
:
1919 case ConvStrToArr
: // decrefs src, but src is a string
1923 case EagerSyncVMRegs
:
1925 case LdUnwinderValue
:
1936 case LdContResumeAddr
:
1937 case LdClsCachedSafe
:
1939 case UnwindCheckSideExit
:
1943 case LdClsMethodCacheCls
:
1944 case LdClsMethodCacheFunc
:
1945 case LdClsMethodFCacheFunc
:
1947 case LdClsTypeCnsClsName
:
1948 case FwdCtxStaticCall
:
1949 case ProfileArrayKind
:
1950 case ProfileSwitchDest
:
1951 case LdFuncNumParams
:
1960 case LdSwitchDblIndex
:
1961 case LdSwitchStrIndex
:
1970 case StrictlyIntegerConv
:
1973 return may_load_store(AEmpty
, AEmpty
);
1975 // Some that touch memory we might care about later, but currently don't:
1978 case ConvCellToBool
:
1980 case CountCollection
:
1982 case CheckPackedArrayDataBounds
:
1986 return may_load_store(AEmpty
, AEmpty
);
1988 //////////////////////////////////////////////////////////////////////
1989 // Instructions that can re-enter the VM and touch most heap things. They
1990 // also may generally write to the eval stack below an offset (see
1991 // alias-class.h above AStack for more).
1995 auto const src
= inst
.src(0);
1996 // It could decref the inner ref.
1997 auto affected
= src
->isA(TBoxedCell
) ? ARef
{ src
} :
1998 src
->type().maybe(TBoxedCell
) ? ARefAny
: AEmpty
;
1999 if (src
->type().maybe(TKeyset
| TBoxedKeyset
)) {
2000 // TKeyset can't re-enter, but it will decref any contained
2001 // strings. Without this, an incref of a string contained in
2002 // a Keyset could be sunk past the decref of the Keyset.
2003 affected
|= AHeapAny
;
2005 // Need to add affected to the `store' set. See comments about
2006 // `GeneralEffects' in memory-effects.h.
2007 auto const effect
= may_load_store(affected
, affected
);
2008 if (src
->type().maybe(TArr
| TVec
| TDict
| TObj
| TRes
|
2009 TBoxedArr
| TBoxedVec
| TBoxedDict
|
2010 TBoxedObj
| TBoxedRes
)) {
2011 // Could re-enter to run a destructor. Keysets are exempt because they
2012 // can only contain strings or integers.
2013 return may_reenter(inst
, effect
);
2019 return may_load_store(AHeapAny
, AHeapAny
);
2021 case GetMemoKeyScalar
:
2022 return IrrelevantEffects
{};
2025 case LdFunc
: // these all can autoload
2027 AliasClass effects
=
2028 actrec(inst
.src(1), inst
.extra
<IRSPRelOffsetData
>()->offset
);
2029 return may_load_store(effects
, effects
);
2032 case LookupClsMethod
: // autoload, and it writes part of the new actrec
2034 AliasClass effects
=
2035 actrec(inst
.src(2), inst
.extra
<LookupClsMethod
>()->calleeAROffset
);
2036 return may_load_store(effects
, effects
);
2039 case LdClsPropAddrOrNull
: // may run 86{s,p}init, which can autoload
2040 case LdClsPropAddrOrRaise
: // raises errors, and 86{s,p}init
2043 case ThrowArrayIndexException
:
2044 case ThrowArrayKeyException
:
2045 case RaiseUninitLoc
:
2046 case RaiseUndefProp
:
2047 case RaiseMissingArg
:
2048 case RaiseTooManyArg
:
2050 case RaiseErrorOnInvalidIsAsExpressionType
:
2053 case RaiseHackArrCompatNotice
:
2054 case RaiseHackArrParamNotice
:
2055 case RaiseHackArrPropNotice
:
2056 case RaiseForbiddenDynCall
:
2057 case RaiseForbiddenDynConstruct
:
2058 case RaiseRxCallViolation
:
2059 case RaiseStrToClassNotice
:
2060 case CheckClsReifiedGenericMismatch
:
2061 case CheckFunReifiedGenericMismatch
:
2064 case Count
: // re-enters on CountableClass
2095 case ConvCellToArr
: // decrefs src, may read obj props
2096 case ConvCellToObj
: // decrefs src
2097 case ConvObjToArr
: // decrefs src
2098 case ConvObjToVArr
: // can invoke PHP
2099 case ConvObjToDArr
: // can invoke PHP
2101 case DefCls
: // autoload
2102 case LdCls
: // autoload
2103 case LdClsCached
: // autoload
2104 case LdFuncCached
: // autoload
2105 case LdRecCached
: // autoload
2106 case LdSwitchObjIndex
: // decrefs arg
2107 case InitClsCns
: // autoload
2108 case LookupClsMethodCache
: // autoload
2109 case LookupClsMethodFCache
: // autoload
2111 case LookupFuncCached
: // autoload
2112 case StringGet
: // raise_notice
2113 case OrdStrIdx
: // raise_notice
2114 case ArrayAdd
: // decrefs source
2117 case AddNewElem
: // can re-enter
2118 case AddNewElemKeyset
: // can re-enter
2119 case DictAddElemIntKey
: // decrefs value
2120 case DictAddElemStrKey
: // decrefs value
2121 case ArrayGet
: // kVPackedKind warnings
2122 case ArraySet
: // kVPackedKind warnings
2123 case ArraySetRef
: // kVPackedKind warnings
2140 case LdSSwitchDestSlow
:
2150 case ConvShapeToDict
:
2153 case ConvObjToKeyset
:
2154 case ThrowOutOfBounds
:
2155 case ThrowInvalidArrayKey
:
2156 case ThrowInvalidOperation
:
2157 case ThrowArithmeticError
:
2158 case ThrowDivisionByZeroError
:
2159 case ThrowDivisionByZeroException
:
2160 case ThrowHasThisNeedStatic
:
2161 case ThrowLateInitPropError
:
2162 case ThrowMissingThis
:
2163 case ThrowParameterWrongType
:
2164 case ThrowParamRefMismatch
:
2165 case ThrowParamRefMismatchRange
:
2167 case SetOpCellVerify
:
2168 case ThrowAsTypeStructException
:
2169 case PropTypeRedefineCheck
: // Can raise and autoload
2170 case HandleRequestSurprise
:
2171 return may_load_store(AHeapAny
, AHeapAny
);
2175 case LdReifiedGeneric
:
2176 case RecordReifiedGenericsAndGetName
:
2177 case RecordReifiedGenericsAndGetTSList
:
2178 return may_load_store(AElemAny
, AEmpty
);
2180 case ConvArrToKeyset
: // Decrefs input values
2181 case ConvVecToKeyset
:
2182 case ConvDictToKeyset
:
2183 case ConvShapeToKeyset
:
2184 case ConvDictToDArr
: // These 4 may raise Hack array compat notices
2185 case ConvShapeToDArr
:
2186 case ConvKeysetToDArr
:
2188 case ConvShapeToArr
:
2189 case ConvKeysetToArr
:
2190 return may_load_store(AElemAny
, AEmpty
);
2193 case ConvArrToNonDVArr
:
2195 case ConvShapeToVec
:
2196 case ConvKeysetToVec
:
2198 case ConvKeysetToDict
:
2201 case ConvDictToVArr
:
2202 case ConvShapeToVArr
:
2203 case ConvKeysetToVArr
:
2206 return may_load_store(AElemAny
, AEmpty
);
2208 case ReleaseVVAndSkip
: // can decref ExtraArgs or VarEnv and Locals
2209 return may_reenter(inst
,
2210 may_load_store(AHeapAny
|AFrameAny
, AHeapAny
|AFrameAny
));
2212 // debug_backtrace() traverses stack and WaitHandles on the heap.
2213 case DebugBacktrace
:
2214 case DebugBacktraceFast
:
2215 return may_load_store(AHeapAny
|AFrameAny
|AStackAny
, AHeapAny
);
2217 // This instruction doesn't touch memory we track, except that it may
2218 // re-enter to construct php Exception objects. During this re-entry anything
2219 // can happen (e.g. a surprise flag check could cause a php signal handler to
2220 // run arbitrary code).
2221 case AFWHPrepareChild
:
2222 return may_reenter(inst
, may_load_store(AEmpty
, AEmpty
));
2224 //////////////////////////////////////////////////////////////////////
2225 // The following instructions are used for debugging memory optimizations.
2226 // We can't ignore them, because they can prevent future optimizations;
2227 // eg t1 = LdStk<N>; DbgTrashStk<N>; StStk<N> t1
2228 // If we ignore the DbgTrashStk it looks like the StStk is redundant
2231 return GeneralEffects
{
2232 AEmpty
, AEmpty
, AEmpty
,
2233 AStack
{ inst
.src(0), inst
.extra
<DbgTrashStk
>()->offset
, 1 }
2236 return GeneralEffects
{
2237 AEmpty
, AEmpty
, AEmpty
,
2238 actrec(inst
.src(0), inst
.extra
<DbgTrashFrame
>()->offset
)
2241 return GeneralEffects
{
2242 AEmpty
, AEmpty
, AEmpty
,
2243 pointee(inst
.src(0))
2245 case DbgTrashRetVal
:
2246 return IrrelevantEffects
{};
2248 //////////////////////////////////////////////////////////////////////
2255 //////////////////////////////////////////////////////////////////////
2257 DEBUG_ONLY
bool check_effects(const IRInstruction
& inst
, MemEffects me
) {
2258 SCOPE_ASSERT_DETAIL("Memory Effects") {
2259 return folly::sformat(" inst: {}\n effects: {}\n", inst
, show(me
));
2262 auto check_fp
= [&] (FPRelOffset base
) {
2265 "frame offset is above base frame"
2269 auto check_obj
= [&] (SSATmp
* obj
) {
2271 obj
->type() <= TObj
,
2272 "Non obj pointer in memory effects"
2276 auto check
= [&] (AliasClass a
) {
2277 if (auto const fr
= a
.frame()) check_fp(fr
->base
);
2278 if (auto const sl
= a
.clsRefClsSlot()) check_fp(sl
->base
);
2279 if (auto const sl
= a
.clsRefTSSlot()) check_fp(sl
->base
);
2280 if (auto const pr
= a
.prop()) check_obj(pr
->obj
);
2285 [&] (GeneralEffects x
) {
2291 // Locations may-moved always should also count as may-loads.
2292 always_assert(x
.moves
<= x
.loads
);
2294 if (inst
.mayRaiseError()) {
2295 // Any instruction that can raise an error can run a user error handler
2296 // and have arbitrary effects on the heap.
2297 always_assert(AHeapAny
<= x
.loads
);
2298 always_assert(AHeapAny
<= x
.stores
);
2300 * They also ought to kill /something/ on the stack, because of
2301 * possible re-entry. It's not incorrect to leave things out of the
2302 * kills set, but this assertion is here because we shouldn't do it on
2303 * purpose, so this is here until we have a reason not to assert it.
2305 * The mayRaiseError instructions should all be going through
2306 * may_reenter right now, which will kill the stack below the re-entry
2307 * depth---unless the marker for `inst' doesn't have an fp set.
2309 always_assert(inst
.marker().fp() == nullptr ||
2310 AStackAny
.maybe(x
.kills
));
2313 [&] (PureLoad x
) { check(x
.src
); },
2314 [&] (PureStore x
) { check(x
.dst
); },
2315 [&] (PureSpillFrame x
) { check(x
.stk
); check(x
.ctx
); check(x
.callee
);
2316 always_assert(x
.ctx
<= x
.stk
);
2317 always_assert(x
.callee
<= x
.stk
); },
2318 [&] (ExitEffects x
) { check(x
.live
); check(x
.kills
); },
2319 [&] (IrrelevantEffects
) {},
2320 [&] (UnknownEffects
) {},
2321 [&] (CallEffects x
) { check(x
.kills
);
2325 always_assert(x
.callee
<= x
.stack
); },
2326 [&] (InlineEnterEffects x
){ check(x
.inlStack
);
2329 [&] (InlineExitEffects x
){ check(x
.inlStack
);
2331 check(x
.inlMeta
); },
2332 [&] (ReturnEffects x
) { check(x
.kills
); }
2338 //////////////////////////////////////////////////////////////////////
2342 MemEffects
memory_effects(const IRInstruction
& inst
) {
2343 auto const inner
= memory_effects_impl(inst
);
2344 auto const ret
= [&] () -> MemEffects
{
2345 // These instructions have special handling because they occur as functions
2346 // suspend or return. Their ability to reenter is handled in
2347 // memory_effects_impl, and comments in that function explain why.
2348 auto const is_special
= inst
.is(
2353 SuspendHookCreateCont
,
2357 if (is_special
|| !inst
.mayRaiseError()) return inner
;
2362 "Instruction {} has effects {}, but has been marked as MayRaiseError "
2363 "and must use a UnknownEffects, GeneralEffects, or CallEffects type.",
2367 return may_load_store(AUnknown
, AUnknown
);
2370 // Calls are implicitly MayRaise, all other instructions must use the
2371 // GeneralEffects or UnknownEffects class of memory effects
2372 return match
<MemEffects
>(
2374 [&] (GeneralEffects x
) { return may_reenter(inst
, x
); },
2375 [&] (CallEffects x
) { return x
; },
2376 [&] (UnknownEffects x
) { return x
; },
2377 [&] (PureLoad
) { return fail(); },
2378 [&] (PureStore
) { return fail(); },
2379 [&] (PureSpillFrame
) { return fail(); },
2380 [&] (ExitEffects
) { return fail(); },
2381 [&] (InlineExitEffects
) { return fail(); },
2382 [&] (InlineEnterEffects
) { return fail(); },
2383 [&] (IrrelevantEffects
) { return fail(); },
2384 [&] (ReturnEffects
) { return fail(); }
2387 assertx(check_effects(inst
, ret
));
2391 AliasClass
pointee(const SSATmp
* tmp
) {
2392 return pointee(tmp
, nullptr);
2395 //////////////////////////////////////////////////////////////////////
2397 MemEffects
canonicalize(MemEffects me
) {
2398 using R
= MemEffects
;
2401 [&] (GeneralEffects x
) -> R
{
2402 return GeneralEffects
{
2403 canonicalize(x
.loads
),
2404 canonicalize(x
.stores
),
2405 canonicalize(x
.moves
),
2406 canonicalize(x
.kills
)
2409 [&] (PureLoad x
) -> R
{
2410 return PureLoad
{ canonicalize(x
.src
) };
2412 [&] (PureStore x
) -> R
{
2413 return PureStore
{ canonicalize(x
.dst
), x
.value
, x
.dep
};
2415 [&] (PureSpillFrame x
) -> R
{
2416 return PureSpillFrame
{
2417 canonicalize(x
.stk
),
2418 canonicalize(x
.ctx
),
2419 canonicalize(x
.callee
)
2422 [&] (ExitEffects x
) -> R
{
2423 return ExitEffects
{ canonicalize(x
.live
), canonicalize(x
.kills
) };
2425 [&] (InlineEnterEffects x
) -> R
{
2426 return InlineEnterEffects
{
2427 canonicalize(x
.inlStack
),
2428 canonicalize(x
.inlFrame
),
2429 canonicalize(x
.actrec
),
2432 [&] (InlineExitEffects x
) -> R
{
2433 return InlineExitEffects
{
2434 canonicalize(x
.inlStack
),
2435 canonicalize(x
.inlFrame
),
2436 canonicalize(x
.inlMeta
)
2439 [&] (CallEffects x
) -> R
{
2440 return CallEffects
{
2441 canonicalize(x
.kills
),
2442 canonicalize(x
.stack
),
2443 canonicalize(x
.locals
),
2444 canonicalize(x
.callee
)
2447 [&] (ReturnEffects x
) -> R
{
2448 return ReturnEffects
{ canonicalize(x
.kills
) };
2450 [&] (IrrelevantEffects x
) -> R
{ return x
; },
2451 [&] (UnknownEffects x
) -> R
{ return x
; }
2455 //////////////////////////////////////////////////////////////////////
2457 std::string
show(MemEffects effects
) {
2458 using folly::sformat
;
2459 return match
<std::string
>(
2461 [&] (GeneralEffects x
) {
2462 return sformat("mlsmk({} ; {} ; {} ; {})",
2469 [&] (ExitEffects x
) {
2470 return sformat("exit({} ; {})", show(x
.live
), show(x
.kills
));
2472 [&] (InlineEnterEffects x
) {
2473 return sformat("inline_enter({} ; {} ; {})",
2479 [&] (InlineExitEffects x
) {
2480 return sformat("inline_exit({} ; {} ; {})",
2486 [&] (CallEffects x
) {
2487 return sformat("call({} ; {} ; {} ; {})",
2494 [&] (PureSpillFrame x
) {
2495 return sformat("stFrame({} ; {} ; {})",
2501 [&] (PureLoad x
) { return sformat("ld({})", show(x
.src
)); },
2502 [&] (PureStore x
) { return sformat("st({})", show(x
.dst
)); },
2503 [&] (ReturnEffects x
) { return sformat("return({})", show(x
.kills
)); },
2504 [&] (IrrelevantEffects
) { return "IrrelevantEffects"; },
2505 [&] (UnknownEffects
) { return "UnknownEffects"; }
2509 //////////////////////////////////////////////////////////////////////
2511 AliasClass
inline_fp_frame(const IRInstruction
* inst
) {
2514 inst
->extra
<DefInlineFP
>()->spOffset
+ int32_t{kNumActRecCells
} - 1,
2515 int32_t{kNumActRecCells
}
2519 //////////////////////////////////////////////////////////////////////