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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/jit/dce.h"
20 #include <folly/MapUtil.h>
22 #include "hphp/util/low-ptr.h"
23 #include "hphp/util/match.h"
24 #include "hphp/util/trace.h"
26 #include "hphp/runtime/vm/runtime.h"
27 #include "hphp/runtime/vm/jit/analysis.h"
28 #include "hphp/runtime/vm/jit/cfg.h"
29 #include "hphp/runtime/vm/jit/check.h"
30 #include "hphp/runtime/vm/jit/id-set.h"
31 #include "hphp/runtime/vm/jit/ir-opcode.h"
32 #include "hphp/runtime/vm/jit/ir-unit.h"
33 #include "hphp/runtime/vm/jit/mutation.h"
34 #include "hphp/runtime/vm/jit/memory-effects.h"
35 #include "hphp/runtime/vm/jit/opt.h"
36 #include "hphp/runtime/vm/jit/print.h"
37 #include "hphp/runtime/vm/jit/simple-propagation.h"
38 #include "hphp/runtime/vm/jit/state-vector.h"
39 #include "hphp/runtime/vm/jit/timer.h"
40 #include "hphp/runtime/vm/jit/translator-inline.h"
42 namespace HPHP
{ namespace jit
{
45 TRACE_SET_MOD(hhir_dce
);
47 bool canDCE(IRInstruction
* inst
) {
147 case InstanceOfIface
:
148 case InstanceOfIfaceVtable
:
150 case InstanceOfBitmask
:
151 case NInstanceOfBitmask
:
152 case InstanceOfRecDesc
:
153 case InterfaceSupportsArr
:
154 case InterfaceSupportsVec
:
155 case InterfaceSupportsDict
:
156 case InterfaceSupportsKeyset
:
157 case InterfaceSupportsStr
:
158 case InterfaceSupportsInt
:
159 case InterfaceSupportsDbl
:
181 case LdSmashableFunc
:
182 case LdClsFromClsMeth
:
183 case LdFuncFromClsMeth
:
189 case LdClsMethodCacheCls
:
198 case LdFuncNumParams
:
200 case LdMethCallerName
:
204 case LdPackedArrayDataElemAddr
:
225 case CountCollection
:
231 case LdSwitchDblIndex
:
232 case LdSwitchStrIndex
:
233 case LdSSwitchDestFast
:
244 case LdMIPropStateAddr
:
249 case LdUnwinderValue
:
281 case IsFunReifiedGenericsMatched
:
282 case IsClsDynConstructible
:
284 case StrictlyIntegerConv
:
287 case GetMemoKeyScalar
:
288 case LookupSPropSlot
:
289 case ConstructClosure
:
290 case AllocPackedArray
:
291 case AllocStructArray
:
292 case AllocStructDArray
:
293 case AllocStructDict
:
296 case GetMixedPtrIter
:
297 case GetPackedPtrIter
:
298 case AdvanceMixedPtrIter
:
299 case AdvancePackedPtrIter
:
303 assertx(!inst
->isControlFlow());
306 // These may raise oom, but its still ok to delete them if the
314 case AddNewElemKeyset
:
318 // Some of these conversion functions can run arbitrary PHP code.
324 case ConvKeysetToArr
:
325 case ConvArrToNonDVArr
:
337 case ConvKeysetToVec
:
341 case ConvKeysetToDict
:
343 case ConvArrToKeyset
:
344 case ConvVecToKeyset
:
345 case ConvDictToKeyset
:
346 case ConvObjToKeyset
:
350 case ConvKeysetToVArr
:
355 case ConvKeysetToDArr
:
358 return !opcodeMayRaise(inst
->op()) &&
359 (!inst
->consumesReferences() || inst
->producesReference());
361 case ConvClsMethToArr
:
362 case ConvClsMethToDArr
:
363 case ConvClsMethToDict
:
364 case ConvClsMethToKeyset
:
365 case ConvClsMethToVArr
:
366 case ConvClsMethToVec
: {
367 bool consumeRef
= use_lowptr
? false : inst
->consumesReferences();
368 return !opcodeMayRaise(inst
->op()) &&
369 (!consumeRef
|| inst
->producesReference());
383 case CheckMixedArrayKeys
:
384 case CheckSmashableClass
:
430 case ProfileSwitchDest
:
431 case CheckSurpriseFlags
:
432 case CheckSurpriseAndStack
:
433 case HandleRequestSurprise
:
435 case SuspendHookAwaitEF
:
436 case SuspendHookAwaitEG
:
437 case SuspendHookAwaitR
:
438 case SuspendHookCreateCont
:
439 case SuspendHookYield
:
444 case LdLocPseudoMain
:
450 case LdClsCachedSafe
:
452 case LdClsTypeCnsClsName
:
453 case LdRecDescCached
:
454 case LdRecDescCachedSafe
:
460 case LdSubClsCnsClsName
:
464 case LookupClsMethodFCache
:
465 case LookupClsMethodCache
:
466 case LookupClsMethod
:
469 case LdClsPropAddrOrNull
:
470 case LdClsPropAddrOrRaise
:
478 case LookupFuncCached
:
480 case AllocObjReified
:
484 case PropTypeRedefineCheck
:
487 case InitObjMemoSlots
:
490 case DebugBacktraceFast
:
491 case InitThrowableFileAndLine
:
492 case ConstructInstance
:
493 case InitMixedLayoutArray
:
494 case InitPackedLayoutArray
:
495 case InitPackedLayoutArrayLoop
:
500 case NewStructDArray
:
511 case AsyncFuncRetSlow
:
512 case AsyncSwitchFast
:
513 case ReleaseVVAndSkip
:
514 case GenericRetDecRefs
:
522 case StLocPseudoMain
:
524 case EagerSyncVMRegs
:
527 case ReqRetranslateOpt
:
538 case VerifyParamCallable
:
539 case VerifyParamFail
:
540 case VerifyParamFailHard
:
541 case VerifyReifiedLocalType
:
542 case VerifyReifiedReturnType
:
543 case VerifyRetCallable
:
546 case VerifyRetFailHard
:
549 case VerifyPropCoerce
:
551 case VerifyPropFailHard
:
552 case VerifyParamRecDesc
:
553 case VerifyRetRecDesc
:
554 case VerifyPropRecDesc
:
555 case RaiseClsMethPropConvertNotice
:
556 case RaiseHackArrParamNotice
:
557 case RaiseHackArrPropNotice
:
560 case RaiseTooManyArg
:
562 case RaiseErrorOnInvalidIsAsExpressionType
:
565 case ThrowArrayIndexException
:
566 case ThrowArrayKeyException
:
567 case RaiseArraySerializeNotice
:
568 case RaiseHackArrCompatNotice
:
569 case RaiseForbiddenDynCall
:
570 case RaiseForbiddenDynConstruct
:
571 case RaiseRxCallViolation
:
572 case RaiseStrToClassNotice
:
573 case CheckClsReifiedGenericMismatch
:
574 case CheckFunReifiedGenericMismatch
:
580 case LdSwitchObjIndex
:
581 case LdSSwitchDestSlow
:
592 case AFWHPrepareChild
:
596 case ContStartedCheck
:
601 case ContArUpdateIdx
:
602 case LdContResumeAddr
:
607 case AFWHPushTailFrame
:
611 case DbgAssertRefCount
:
613 case DbgCheckLocalsDecRefd
:
617 case RestoreErrorLevel
:
640 case ProfileMixedArrayAccess
:
641 case CheckMixedArrayOffset
:
643 case ProfileDictAccess
:
644 case CheckDictOffset
:
645 case ProfileKeysetAccess
:
646 case CheckKeysetOffset
:
650 case ElemMixedArrayK
:
682 case SetNewElemArray
:
684 case SetNewElemKeyset
:
685 case ReservePackedArrayDataNewElem
:
690 case ProfileArrayKind
:
694 case ProfileSubClsCns
:
695 case CheckPackedArrayDataBounds
:
700 case UnwindCheckSideExit
:
706 case CheckStackOverflow
:
707 case CheckSurpriseFlagsEnter
:
709 case ThrowOutOfBounds
:
710 case ThrowInvalidArrayKey
:
711 case ThrowInvalidOperation
:
712 case ThrowCallReifiedFunctionWithoutGenerics
:
713 case ThrowDivisionByZeroException
:
714 case ThrowHasThisNeedStatic
:
715 case ThrowLateInitPropError
:
716 case ThrowMissingArg
:
717 case ThrowMissingThis
:
718 case ThrowParameterWrongType
:
719 case ThrowParamInOutMismatch
:
720 case ThrowParamInOutMismatchRange
:
724 case InlineReturnNoFrame
:
730 case LdClsMethodFCacheFunc
:
731 case LdClsMethodCacheFunc
:
732 case ProfileInstanceCheck
:
733 case MemoGetStaticValue
:
734 case MemoGetStaticCache
:
735 case MemoGetLSBValue
:
736 case MemoGetLSBCache
:
737 case MemoGetInstanceValue
:
738 case MemoGetInstanceCache
:
739 case MemoSetStaticValue
:
740 case MemoSetStaticCache
:
741 case MemoSetLSBValue
:
742 case MemoSetLSBCache
:
743 case MemoSetInstanceValue
:
744 case MemoSetInstanceCache
:
745 case ThrowAsTypeStructException
:
746 case RecordReifiedGenericsAndGetTSList
:
747 case ResolveTypeStruct
:
748 case CheckRDSInitialized
:
749 case MarkRDSInitialized
:
760 !RuntimeOption::EvalHackArrCompatCheckCompare
&&
761 !RuntimeOption::EvalHackArrCompatDVCmpNotices
;
764 return !opcodeMayRaise(IsTypeStruct
);
769 /* DceFlags tracks the state of one instruction during dead code analysis. */
776 bool isDead() const { return m_state
== DEAD
; }
777 void setDead() { m_state
= DEAD
; }
778 void setLive() { m_state
= LIVE
; }
781 * "Weak" uses are used in optimizeActRecs.
783 * If a frame pointer is used for something that can be modified to
784 * not be a use as long as the whole frame can go away, we'll track
788 if (m_weakUseCount
+ 1 > kMaxWeakUseCount
) {
789 // Too many weak uses for us to know we can optimize it away.
795 int32_t weakUseCount() const {
796 return m_weakUseCount
;
799 std::string
toString() const {
800 std::array
<const char*,2> const names
= {{
804 return folly::format(
806 m_state
< names
.size() ? names
[m_state
] : "<invalid>"
816 static constexpr uint8_t kMaxWeakUseCount
= 0x7f;
817 uint8_t m_weakUseCount
:7;
819 static_assert(sizeof(DceFlags
) == 1, "sizeof(DceFlags) should be 1 byte");
821 // DCE state indexed by instr->id().
822 typedef StateVector
<IRInstruction
, DceFlags
> DceState
;
823 typedef StateVector
<SSATmp
, uint32_t> UseCounts
;
824 typedef jit::vector
<IRInstruction
*> WorkList
;
826 void removeDeadInstructions(IRUnit
& unit
, const DceState
& state
) {
830 auto const next
= block
->next();
831 auto const bcctx
= block
->back().bcctx();
833 [&] (const IRInstruction
& inst
) {
836 if (state
[inst
].isDead()) {
837 FTRACE(1, "Removing dead instruction {}\n", inst
.toString());
841 auto const dead
= state
[inst
].isDead();
842 assertx(!dead
|| !inst
.taken() || inst
.taken()->isCatch());
846 if (block
->empty() || !block
->back().isBlockEnd()) {
848 block
->push_back(unit
.gen(Jmp
, bcctx
, next
));
854 // removeUnreachable erases unreachable blocks from unit, and returns
855 // a sorted list of the remaining blocks.
856 BlockList
prepareBlocks(IRUnit
& unit
) {
857 FTRACE(1, "RemoveUnreachable:vvvvvvvvvvvvvvvvvvvv\n");
858 SCOPE_EXIT
{ FTRACE(1, "RemoveUnreachable:^^^^^^^^^^^^^^^^^^^^\n"); };
860 auto const blocks
= rpoSortCfg(unit
);
862 // 1. perform copy propagation on every instruction
863 for (auto block
: blocks
) {
864 for (auto& inst
: *block
) {
869 // 2. erase unreachable blocks and get an rpo sorted list of what remains.
870 bool needsReflow
= removeUnreachable(unit
);
872 // 3. if we removed any whole blocks that ended in Jmp instructions, reflow
873 // all types in case they change the incoming types of DefLabel
875 if (needsReflow
) reflowTypes(unit
);
880 WorkList
initInstructions(const IRUnit
& unit
, const BlockList
& blocks
,
882 TRACE(1, "DCE(initInstructions):vvvvvvvvvvvvvvvvvvvv\n");
883 // Mark reachable, essential, instructions live and enqueue them.
885 wl
.reserve(unit
.numInsts());
886 forEachInst(blocks
, [&] (IRInstruction
* inst
) {
888 state
[inst
].setLive();
892 TRACE(1, "DCE:^^^^^^^^^^^^^^^^^^^^\n");
896 //////////////////////////////////////////////////////////////////////
899 * A use of an inlined frame that can be modified to work without the
900 * frame is called a "weak use" here. For example, storing to a local
901 * on a frame is weak because if no other uses of the frame are
902 * keeping it alive (for example a load of that same local), we can
903 * just remove the store.
905 * This routine counts the weak uses of inlined frames and marks them
906 * dead if they have no non-weak uses. Returns true if any inlined
907 * frames were marked dead.
909 bool findWeakActRecUses(const BlockList
& blocks
,
912 const UseCounts
& uses
) {
913 bool killedFrames
= false;
915 auto const incWeak
= [&] (const IRInstruction
* inst
, const SSATmp
* src
) {
916 assertx(src
->isA(TFramePtr
));
917 auto const frameInst
= src
->inst();
918 if (frameInst
->op() == DefInlineFP
) {
919 ITRACE(3, "weak use of {} from {}\n", *frameInst
, *inst
);
920 state
[frameInst
].incWeakUse();
924 forEachInst(blocks
, [&] (IRInstruction
* inst
) {
925 if (state
[inst
].isDead()) return;
927 switch (inst
->op()) {
928 // these can be made stack relative
930 auto const id
= inst
->marker().func()->lookupVarId(s_86metadata
.get());
931 if (inst
->extra
<StLoc
>()->locId
!= id
) incWeak(inst
, inst
->src(0));
938 incWeak(inst
, inst
->src(0));
941 // These can be made stack relative if they haven't been already.
942 case MemoGetStaticCache
:
943 case MemoSetStaticCache
:
944 case MemoGetLSBCache
:
945 case MemoSetLSBCache
:
946 case MemoGetInstanceCache
:
947 case MemoSetInstanceCache
:
948 if (inst
->src(0)->isA(TFramePtr
)) incWeak(inst
, inst
->src(0));
953 auto const frameInst
= inst
->src(0)->inst();
954 assertx(frameInst
->is(DefInlineFP
));
955 auto const frameUses
= uses
[frameInst
->dst()];
956 auto const weakUses
= state
[frameInst
].weakUseCount();
958 * We can kill the frame if all uses of the frame are counted
959 * as weak uses. Note that this InlineReturn counts as a weak
960 * use, but we haven't incremented for it yet, which is where
961 * the "+ 1" comes from below.
963 ITRACE(2, "frame {}: weak/strong {}/{}\n",
964 *frameInst
, weakUses
, frameUses
);
965 if (frameUses
- (weakUses
+ 1) == 0) {
966 ITRACE(1, "killing frame {}\n", *frameInst
);
968 state
[frameInst
].setDead();
970 // Ensure that the frame is still dead for the purposes of
972 convertToInlineReturnNoFrame(unit
, *inst
);
978 // Default is conservative: we don't increment a weak use if it
979 // uses the frame (or stack), so they can't be eliminated.
988 * Convert a localId in a callee frame into an SP relative offset in the caller
991 IRSPRelOffset
locToStkOff(LocalId locId
, const SSATmp
* fp
) {
992 auto const fpInst
= fp
->inst();
993 assertx(fpInst
->is(DefInlineFP
));
994 return fpInst
->extra
<DefInlineFP
>()->spOffset
- locId
.locId
- 1;
998 * The first time through, we've counted up weak uses of the frame and then
999 * finally marked it dead. The instructions in between that were weak uses may
1000 * need modifications now that their frame is going away.
1002 * Also, if we eliminated some frames, DecRef instructions (which can re-enter
1003 * the VM without requiring a materialized frame) need to have stack depths in
1004 * their markers adjusted so they can't stomp on parts of the outer function.
1005 * We handle this conservatively by just pushing all DecRef markers where the
1006 * DecRef is from a function other than the outer function down to a safe
1009 * Finally, any removed frame pointers in BCMarkers must be rewritten to point
1010 * to the outer frame of that frame.
1012 void performActRecFixups(const BlockList
& blocks
,
1015 const UseCounts
& uses
) {
1016 // We limit the total stack depth during inlining, so this is the deepest
1017 // we'll ever have to worry about.
1018 auto const outerFunc
= blocks
.front()->front().marker().func();
1019 auto const safeDepth
= outerFunc
->maxStackCells() + kStackCheckLeafPadding
;
1020 ITRACE(3, "safeDepth: {}, outerFunc depth: {}\n",
1022 outerFunc
->maxStackCells());
1024 bool needsReflow
= false;
1026 for (auto block
: blocks
) {
1027 ITRACE(2, "Visiting block {}\n", block
->id());
1028 Trace::Indent indenter
;
1030 for (auto& inst
: *block
) {
1031 ITRACE(5, "{}\n", inst
.toString());
1033 bool adjustedMarkerFp
= false;
1034 if (auto const fp
= inst
.marker().fp()) {
1035 if (state
[fp
->inst()].isDead()) {
1036 always_assert(fp
->inst()->is(DefInlineFP
));
1037 auto const prev
= fp
->inst()->src(1);
1038 inst
.marker() = inst
.marker().adjustFP(prev
);
1039 assertx(!state
[prev
->inst()].isDead());
1040 adjustedMarkerFp
= true;
1044 switch (inst
.op()) {
1046 ITRACE(3, "DefInlineFP ({}): weak/strong uses: {}/{}\n",
1047 inst
, state
[inst
].weakUseCount(), uses
[inst
.dst()]);
1055 if (state
[inst
.src(0)->inst()].isDead()) {
1056 convertToStackInst(unit
, inst
);
1062 * These are special: they're the only instructions that can reenter
1063 * but not throw. This means it's safe to elide their inlined frame, as
1064 * long as we adjust their markers to a depth that is guaranteed to not
1065 * stomp on the caller's frame if it reenters.
1068 case MemoSetStaticValue
:
1069 case MemoSetLSBValue
:
1070 case MemoSetInstanceValue
:
1071 if (adjustedMarkerFp
) {
1072 ITRACE(3, "pushing stack depth of {} to {}\n", safeDepth
, inst
);
1073 inst
.marker() = inst
.marker().adjustSP(FPInvOffset
{safeDepth
});
1077 case MemoGetStaticCache
:
1078 case MemoGetLSBCache
:
1079 case MemoGetInstanceCache
:
1080 if (inst
.src(0)->isA(TFramePtr
) &&
1081 state
[inst
.src(0)->inst()].isDead()) {
1082 convertToStackInst(unit
, inst
);
1085 case MemoSetStaticCache
:
1086 case MemoSetLSBCache
:
1087 case MemoSetInstanceCache
:
1088 if (inst
.src(0)->isA(TFramePtr
) &&
1089 state
[inst
.src(0)->inst()].isDead()) {
1090 // For the same reason as above, we need to adjust the markers for
1092 convertToStackInst(unit
, inst
);
1093 if (adjustedMarkerFp
) {
1094 ITRACE(3, "pushing stack depth of {} to {}\n", safeDepth
, inst
);
1095 inst
.marker() = inst
.marker().adjustSP(FPInvOffset
{safeDepth
});
1106 if (needsReflow
) reflowTypes(unit
);
1110 * Look for InlineReturn instructions that are the only "non-weak" use
1111 * of a DefInlineFP. In this case we can kill both, avoiding the ActRec
1114 * Prior to calling this routine, `uses' should contain the direct
1115 * (non-transitive) use counts of each DefInlineFP instruction. If
1116 * the weak references are equal to the normal references, the
1117 * instruction is not necessary and can be removed (if we make the
1118 * required changes to each instruction that used it weakly).
1120 void optimizeActRecs(const BlockList
& blocks
,
1123 const UseCounts
& uses
) {
1124 FTRACE(1, "AR:vvvvvvvvvvvvvvvvvvvvv\n");
1125 SCOPE_EXIT
{ FTRACE(1, "AR:^^^^^^^^^^^^^^^^^^^^^\n"); };
1127 // Make a pass to find if we can kill any of the frames. If so, we
1128 // have to do some fixups. These two routines are coupled---most
1129 // cases in findWeakActRecUses should have a corresponding case in
1130 // performActRecFixups to deal with the frame being removed.
1131 auto const killedFrames
= findWeakActRecUses(blocks
, state
, unit
, uses
);
1133 ITRACE(1, "Killed some frames. Iterating over blocks for fixups.\n");
1134 performActRecFixups(blocks
, state
, unit
, uses
);
1138 IRInstruction
* resolveFpDefLabelImpl(
1140 IdSet
<SSATmp
>& visited
1142 auto const inst
= fp
->inst();
1143 assertx(inst
->is(DefLabel
));
1145 // We already examined this, avoid loops.
1146 if (visited
[fp
]) return nullptr;
1148 auto const dests
= inst
->dsts();
1149 auto const destIdx
=
1150 std::find(dests
.begin(), dests
.end(), fp
) - dests
.begin();
1151 always_assert(destIdx
>= 0 && destIdx
< inst
->numDsts());
1153 // If any of the inputs to the Phi aren't Phis themselves, then just choose
1155 IRInstruction
* outInst
= nullptr;
1156 inst
->block()->forEachSrc(
1158 [&] (const IRInstruction
*, const SSATmp
* tmp
) {
1159 if (outInst
) return;
1160 auto const i
= canonical(tmp
)->inst();
1161 if (!i
->is(DefLabel
)) outInst
= i
;
1164 if (outInst
) return outInst
;
1166 // Otherwise we need to recursively look at the linked Phis, avoiding visiting
1169 inst
->block()->forEachSrc(
1171 [&] (const IRInstruction
*, const SSATmp
* tmp
) {
1172 if (outInst
) return;
1173 tmp
= canonical(tmp
);
1174 auto const DEBUG_ONLY label
= tmp
->inst();
1175 assertx(label
->is(DefLabel
));
1176 outInst
= resolveFpDefLabelImpl(tmp
, visited
);
1183 //////////////////////////////////////////////////////////////////////
1185 void processCatchBlock(IRUnit
& unit
, DceState
& state
, Block
* block
,
1186 FPRelOffset stackTop
, const UseCounts
& uses
) {
1187 using Bits
= std::bitset
<64>;
1189 auto const stackSize
= (stackTop
.offset
< -64) ? 64 : -stackTop
.offset
;
1190 if (stackSize
== 0) return;
1191 auto const stackBase
= stackTop
+ stackSize
;
1192 // subtract 1 because we want the cells at offsets -1, -2, ... -stackSize
1193 auto const stackRange
= AStack
{ stackBase
- 1, stackSize
};
1195 Bits usedLocations
= {};
1196 // stores that are only read by the EndCatch
1197 jit::fast_set
<IRInstruction
*> candidateStores
;
1198 // Any IncRefs we see; if they correspond to stores above, we can
1199 // replace the store with a store of Null, and kill the IncRef.
1200 jit::fast_map
<SSATmp
*, std::vector
<Block::iterator
>> candidateIncRefs
;
1203 [&] (const AliasClass
& cls
) -> std::pair
<int, int> {
1204 if (!cls
.maybe(stackRange
)) return {};
1205 auto const stk
= cls
.stack();
1206 if (!stk
) return { 0, stackSize
};
1207 if (stk
->offset
< stackTop
) {
1208 auto const delta
= stackTop
.offset
- stk
->offset
.offset
;
1209 if (delta
>= stk
->size
) return {};
1210 return { 0, stk
->size
- delta
};
1212 auto const base
= stk
->offset
.offset
- stackTop
.offset
;
1213 if (base
>= stackSize
) return {};
1214 auto const end
= base
+ stk
->size
< stackSize
?
1215 base
+ stk
->size
: stackSize
;
1216 return { base
, end
};
1219 auto const process_stack
=
1220 [&] (const AliasClass
& cls
) {
1221 auto r
= range(cls
);
1222 while (r
.first
< r
.second
) {
1223 usedLocations
.set(r
.first
++);
1228 auto const do_store
=
1229 [&] (const AliasClass
& cls
, IRInstruction
* store
) {
1230 if (!store
->is(StStk
)) return false;
1231 auto const stk
= cls
.is_stack();
1232 if (!stk
) return process_stack(cls
);
1233 auto const r
= range(cls
);
1234 if (r
.first
!= r
.second
) {
1235 assertx(r
.second
== r
.first
+ 1);
1236 if (!usedLocations
.test(r
.first
)) {
1237 usedLocations
.set(r
.first
);
1238 candidateStores
.insert(store
);
1245 for (auto inst
= block
->end(); inst
!= block
->begin(); ) {
1247 if (inst
->is(EndCatch
)) {
1250 if (inst
->is(IncRef
)) {
1251 candidateIncRefs
[inst
->src(0)].push_back(inst
);
1255 auto const effects
= canonicalize(memory_effects(*inst
));
1258 [&] (IrrelevantEffects
) { return false; },
1259 [&] (UnknownEffects
) { return true; },
1260 [&] (ReturnEffects x
) { return true; },
1261 [&] (CallEffects x
) { return true; },
1262 [&] (GeneralEffects x
) {
1264 process_stack(x
.loads
) ||
1265 process_stack(x
.stores
) ||
1266 process_stack(x
.kills
);
1268 [&] (PureLoad x
) { return process_stack(x
.src
); },
1269 [&] (PureStore x
) { return do_store(x
.dst
, &*inst
); },
1270 [&] (ExitEffects x
) { return process_stack(x
.live
); },
1271 [&] (InlineEnterEffects x
) {
1273 process_stack(x
.inlStack
) ||
1274 process_stack(x
.actrec
);
1276 [&] (InlineExitEffects x
) { return process_stack(x
.inlStack
); }
1280 for (auto store
: candidateStores
) {
1281 auto const src
= store
->src(1);
1282 auto const it
= candidateIncRefs
.find(src
);
1283 if (it
!= candidateIncRefs
.end()) {
1284 FTRACE(3, "Erasing {} for {}\n",
1285 it
->second
.back()->toString(), store
->toString());
1286 block
->erase(it
->second
.back());
1287 if (it
->second
.size() > 1) {
1288 it
->second
.pop_back();
1290 candidateIncRefs
.erase(it
);
1293 auto const srcInst
= src
->inst();
1294 if (!srcInst
->producesReference() ||
1299 FTRACE(3, "Erasing {} for {}\n",
1300 srcInst
->toString(), store
->toString());
1301 state
[srcInst
].setDead();
1303 store
->setSrc(1, unit
.cns(TInitNull
));
1308 * A store to the stack which is post-dominated by the EndCatch and
1309 * not otherwise read is only there to ensure the unwinder DecRefs the
1310 * value it contains. If there's also an IncRef of the value in the
1311 * catch trace we can just store InitNull to the stack location and
1312 * drop the IncRef (and later, maybe adjust the sp of the
1313 * catch-trace's owner so we don't even have to do the store).
1315 void optimizeCatchBlocks(const BlockList
& blocks
,
1318 const UseCounts
& uses
) {
1320 for (auto block
: blocks
) {
1321 if (block
->back().is(EndCatch
) &&
1322 block
->back().extra
<EndCatch
>()->mode
!=
1323 EndCatchData::CatchMode::SideExit
&&
1324 block
->front().is(BeginCatch
)) {
1325 auto const astk
= AStack
{
1326 block
->back().src(1), block
->back().extra
<EndCatch
>()->offset
, 0
1328 processCatchBlock(unit
, state
, block
, astk
.offset
, uses
);
1333 ////////////////////////////////////////////////////////////////////////////////
1335 void killInstrAdjustRC(
1338 IRInstruction
* inst
,
1339 jit::vector
<IRInstruction
*>& decs
1341 auto anyRemaining
= false;
1342 if (inst
->consumesReferences()) {
1343 // ConsumesReference inputs that are definitely not moved can
1344 // simply be decreffed as a replacement for the dead consumesref
1347 for (auto src
: inst
->srcs()) {
1348 auto const ix
= srcIx
++;
1349 if (inst
->consumesReference(ix
) && src
->type().maybe(TCounted
)) {
1350 if (inst
->mayMoveReference(ix
)) {
1351 anyRemaining
= true;
1354 auto const blk
= inst
->block();
1355 auto const ins
= unit
.gen(DecRef
, inst
->bcctx(), DecRefData
{}, src
);
1356 blk
->insert(blk
->iteratorTo(inst
), ins
);
1357 FTRACE(3, "Inserting {} to replace {}\n",
1358 ins
->toString(), inst
->toString());
1359 state
[ins
].setLive();
1363 for (auto dec
: decs
) {
1364 auto replaced
= dec
->src(0) != inst
->dst();
1367 // The remaining inputs might be moved, so may need to survive
1368 // until this instruction is decreffed
1369 for (auto src
: inst
->srcs()) {
1370 if (inst
->mayMoveReference(srcIx
++) && src
->type().maybe(TCounted
)) {
1372 FTRACE(3, "Converting {} to ", dec
->toString());
1373 dec
->setSrc(0, src
);
1374 FTRACE(3, "{} for {}\n", dec
->toString(), inst
->toString());
1376 state
[dec
].setLive();
1378 auto const blk
= dec
->block();
1379 auto const ins
= unit
.gen(DecRef
, dec
->bcctx(), DecRefData
{}, src
);
1380 blk
->insert(blk
->iteratorTo(dec
), ins
);
1381 FTRACE(3, "Inserting {} before {} for {}\n",
1382 ins
->toString(), dec
->toString(), inst
->toString());
1383 state
[ins
].setLive();
1389 FTRACE(3, "Killing {} for {}\n", dec
->toString(), inst
->toString());
1390 state
[dec
].setDead();
1393 state
[inst
].setDead();
1396 struct TrackedInstr
{
1397 // DecRefs which refer to the tracked instruction.
1398 jit::vector
<IRInstruction
*> decs
;
1400 // Auxiliary instructions which must be killed if the tracked instruction is
1402 jit::vector
<IRInstruction
*> aux
;
1405 //////////////////////////////////////////////////////////////////////
1407 } // anonymous namespace
1409 IRInstruction
* resolveFpDefLabel(const SSATmp
* fp
) {
1410 IdSet
<SSATmp
> visited
;
1411 auto const fpInst
= resolveFpDefLabelImpl(fp
, visited
);
1416 void convertToStackInst(IRUnit
& unit
, IRInstruction
& inst
) {
1417 assertx(inst
.is(CheckLoc
, AssertLoc
, LdLoc
, StLoc
, LdLocAddr
,
1418 MemoGetStaticCache
, MemoSetStaticCache
,
1419 MemoGetLSBCache
, MemoSetLSBCache
,
1420 MemoGetInstanceCache
, MemoSetInstanceCache
));
1421 assertx(inst
.src(0)->inst()->is(DefInlineFP
));
1423 auto const mainSP
= unit
.mainSP();
1425 switch (inst
.op()) {
1430 IRSPRelOffsetData
{ locToStkOff(*inst
.extra
<LocalId
>(), inst
.src(0)) },
1439 IRSPRelOffsetData
{ locToStkOff(*inst
.extra
<LocalId
>(), inst
.src(0)) },
1448 IRSPRelOffsetData
{ locToStkOff(*inst
.extra
<LocalId
>(), inst
.src(0)) },
1451 retypeDests(&inst
, &unit
);
1457 IRSPRelOffsetData
{ locToStkOff(*inst
.extra
<LocalId
>(), inst
.src(0)) },
1463 auto next
= inst
.next();
1467 IRSPRelOffsetData
{ locToStkOff(*inst
.extra
<LocalId
>(), inst
.src(0)) },
1475 case MemoGetStaticCache
:
1476 case MemoSetStaticCache
:
1477 case MemoGetLSBCache
:
1478 case MemoSetLSBCache
: {
1479 auto& extra
= *inst
.extra
<MemoCacheStaticData
>();
1480 extra
.stackOffset
= locToStkOff(LocalId
{extra
.keys
.first
}, inst
.src(0));
1481 inst
.setSrc(0, mainSP
);
1484 case MemoGetInstanceCache
:
1485 case MemoSetInstanceCache
: {
1486 auto& extra
= *inst
.extra
<MemoCacheInstanceData
>();
1487 extra
.stackOffset
= locToStkOff(LocalId
{extra
.keys
.first
}, inst
.src(0));
1488 inst
.setSrc(0, mainSP
);
1496 void convertToInlineReturnNoFrame(IRUnit
& unit
, IRInstruction
& inst
) {
1497 assertx(inst
.is(InlineReturn
));
1498 auto const frameInst
= inst
.src(0)->inst();
1499 auto const spInst
= frameInst
->src(0)->inst();
1500 assertx(spInst
->is(DefFrameRelSP
, DefRegSP
));
1502 auto const calleeAROff
= frameInst
->extra
<DefInlineFP
>()->spOffset
;
1503 auto const spOff
= spInst
->extra
<FPInvOffsetData
>()->offset
;
1505 auto const data
= FPRelOffsetData
{
1506 // Offset of the callee's return value relative to the frame pointer.
1507 calleeAROff
.to
<FPRelOffset
>(spOff
) + (kArRetOff
/ sizeof(TypedValue
))
1509 unit
.replace(&inst
, InlineReturnNoFrame
, data
);
1512 void mandatoryDCE(IRUnit
& unit
) {
1513 if (removeUnreachable(unit
)) {
1514 // Removing unreachable incoming edges can change types, so if we changed
1515 // anything we have to reflow to maintain that IR invariant.
1518 assertx(checkEverything(unit
));
1521 void fullDCE(IRUnit
& unit
) {
1522 if (!RuntimeOption::EvalHHIRDeadCodeElim
) {
1523 // This portion of DCE cannot be turned off, because it restores IR
1524 // invariants, and callers of fullDCE are allowed to rely on it for that.
1525 return mandatoryDCE(unit
);
1528 Timer
dceTimer(Timer::optimize_dce
);
1530 // kill unreachable code and remove any traces that are now empty
1531 auto const blocks
= prepareBlocks(unit
);
1533 // At this point, all IR invariants must hold, because we've restored the
1534 // only one allowed to be violated before fullDCE in prepareBlocks.
1535 assertx(checkEverything(unit
));
1537 // mark the essential instructions and add them to the initial
1538 // work list; this will also mark reachable exit traces. All
1539 // other instructions marked dead.
1540 DceState
state(unit
, DceFlags());
1541 WorkList wl
= initInstructions(unit
, blocks
, state
);
1543 UseCounts
uses(unit
, 0);
1544 jit::fast_map
<IRInstruction
*, TrackedInstr
> rcInsts
;
1546 // process the worklist
1547 while (!wl
.empty()) {
1548 auto* inst
= wl
.back();
1550 for (uint32_t ix
= 0; ix
< inst
->numSrcs(); ++ix
) {
1551 auto const src
= inst
->src(ix
);
1552 IRInstruction
* srcInst
= src
->inst();
1553 if (srcInst
->op() == DefConst
) continue;
1555 if (RuntimeOption::EvalHHIRInlineFrameOpts
) {
1556 if (srcInst
->is(DefInlineFP
)) {
1557 FTRACE(3, "adding use to {} from {}\n", *src
, *inst
);
1562 if (srcInst
->producesReference() && canDCE(srcInst
)) {
1564 if (inst
->is(DecRef
)) {
1565 rcInsts
[srcInst
].decs
.emplace_back(inst
);
1567 if (inst
->is(InitPackedLayoutArray
, StClosureArg
)) {
1568 if (ix
== 0) rcInsts
[srcInst
].aux
.emplace_back(inst
);
1572 if (state
[srcInst
].isDead()) {
1573 state
[srcInst
].setLive();
1574 wl
.push_back(srcInst
);
1579 // If every use of a dce-able PRc instruction is a DecRef or PureStore based
1580 // on its dst, then we can kill it, and DecRef any of its consumesReference
1582 for (auto& pair
: rcInsts
) {
1583 auto& info
= pair
.second
;
1584 if (uses
[pair
.first
->dst()] != info
.decs
.size() + info
.aux
.size()) continue;
1585 killInstrAdjustRC(state
, unit
, pair
.first
, info
.decs
);
1586 for (auto inst
: info
.aux
) killInstrAdjustRC(state
, unit
, inst
, info
.decs
);
1589 optimizeCatchBlocks(blocks
, state
, unit
, uses
);
1591 if (RuntimeOption::EvalHHIRInlineFrameOpts
) {
1592 optimizeActRecs(blocks
, state
, unit
, uses
);
1595 // Now remove instructions whose state is DEAD.
1596 removeDeadInstructions(unit
, state
);