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"
44 TRACE_SET_MOD(hhir_dce
);
46 bool canDCE(const IRInstruction
& inst
) {
121 case FuncHasReifiedGenerics
:
122 case ClassHasReifiedGenerics
:
123 case HasReifiedParent
:
125 case InstanceOfIface
:
126 case InstanceOfIfaceVtable
:
128 case InstanceOfBitmask
:
129 case NInstanceOfBitmask
:
130 case InterfaceSupportsArrLike
:
131 case InterfaceSupportsStr
:
132 case InterfaceSupportsInt
:
133 case InterfaceSupportsDbl
:
139 case IsLegacyArrLike
:
157 case LdSmashableFunc
:
158 case LdClsFromClsMeth
:
159 case LdFuncFromClsMeth
:
160 case LdClsFromRClsMeth
:
161 case LdFuncFromRClsMeth
:
162 case LdGenericsFromRClsMeth
:
163 case LdFuncFromRFunc
:
164 case LdGenericsFromRFunc
:
166 case ConvFuncPrologueFlagsToARFlags
:
168 case DefFuncPrologueCallee
:
169 case DefFuncPrologueCtx
:
170 case DefFuncPrologueFlags
:
171 case DefFuncPrologueNumArgs
:
175 case LdClsMethodCacheCls
:
186 case LdFuncInOutBits
:
187 case LdFuncNumParams
:
189 case LdMethCallerName
:
191 case LdMonotypeDictTombstones
:
192 case LdMonotypeDictKey
:
193 case LdMonotypeDictVal
:
194 case LdMonotypeVecElem
:
198 case NewLoggingArray
:
209 case CountCollection
:
225 case LdMIStateTempBaseAddr
:
229 case LdUnwinderValue
:
260 case IsFunReifiedGenericsMatched
:
261 case LdFuncRequiredCoeffects
:
263 case StrictlyIntegerConv
:
264 case GetMemoKeyScalar
:
265 case LookupSPropSlot
:
266 case ConstructClosure
:
267 case AllocBespokeStructDict
:
268 case AllocStructDict
:
272 case AdvanceDictPtrIter
:
273 case AdvanceVecPtrIter
:
277 case LdUnitPerRequestFilepath
:
278 case DirFromFilepath
:
280 case BespokeIterFirstPos
:
281 case BespokeIterLastPos
:
283 case BespokeIterGetKey
:
284 case BespokeIterGetVal
:
286 case LdResolvedTypeCnsNoCheck
:
287 case LdResolvedTypeCnsClsName
:
290 case VoidPtrAsDataType
:
292 case StructDictElemAddr
:
293 case StructDictSlotInPos
:
294 case LdStructDictKey
:
295 case LdStructDictVal
:
296 case LdImplicitContext
:
297 case CallViolatesModuleBoundary
:
298 assertx(!inst
.isControlFlow());
301 // These may raise oom, but its still ok to delete them if the
308 case AddNewElemKeyset
:
312 // Some of these conversion functions can run arbitrary PHP code.
321 case ConvArrLikeToVec
:
323 case ConvArrLikeToDict
:
325 case ConvArrLikeToKeyset
:
326 case ConvObjToKeyset
:
328 return !opcodeMayRaise(inst
.op()) &&
329 (!inst
.consumesReferences() || inst
.producesReference());
342 case CheckSmashableClass
:
382 case ProfileSwitchDest
:
383 case CheckSurpriseFlags
:
384 case CheckSurpriseAndStack
:
385 case HandleRequestSurprise
:
387 case SuspendHookAwaitEF
:
388 case SuspendHookAwaitEG
:
389 case SuspendHookAwaitR
:
390 case SuspendHookCreateCont
:
391 case SuspendHookYield
:
400 case LdClsCachedSafe
:
403 case LdTypeCnsNoThrow
:
404 case LdTypeCnsClsName
:
405 case IsTypeStructCached
:
410 case LdResolvedTypeCns
:
413 case LookupClsMethodFCache
:
414 case LookupClsMethodCache
:
415 case LookupClsMethod
:
418 case LdClsPropAddrOrNull
:
419 case LdClsPropAddrOrRaise
:
427 case LookupFuncCached
:
429 case AllocObjReified
:
433 case PropTypeRedefineCheck
:
436 case InitObjMemoSlots
:
439 case InitThrowableFileAndLine
:
440 case ConstructInstance
:
443 case InitStructPositions
:
445 case InitVecElemLoop
:
448 case NewBespokeStructDict
:
457 case AsyncFuncRetPrefetch
:
458 case AsyncFuncRetSlow
:
461 case AsyncSwitchFast
:
462 case GenericRetDecRefs
:
464 case StImplicitContext
:
465 case StImplicitContextWH
:
484 case ReqInterpBBNoTranslate
:
486 case ReqRetranslateOpt
:
492 case DecReleaseCheck
:
500 case VerifyParamCallable
:
502 case VerifyParamCoerce
:
503 case VerifyParamFail
:
504 case VerifyParamFailHard
:
505 case VerifyReifiedLocalType
:
506 case VerifyReifiedReturnType
:
508 case VerifyRetCallable
:
510 case VerifyRetCoerce
:
512 case VerifyRetFailHard
:
516 case VerifyPropCoerce
:
517 case VerifyPropCoerceAll
:
519 case VerifyPropFailHard
:
521 case ThrowUndefPropException
:
522 case RaiseTooManyArg
:
524 case RaiseErrorOnInvalidIsAsExpressionType
:
527 case ThrowArrayIndexException
:
528 case ThrowArrayKeyException
:
529 case RaiseForbiddenDynCall
:
530 case RaiseForbiddenDynConstruct
:
531 case RaiseCoeffectsCallViolation
:
532 case RaiseCoeffectsFunParamTypeViolation
:
533 case RaiseCoeffectsFunParamCoeffectRulesViolation
:
534 case RaiseStrToClassNotice
:
535 case RaiseModuleBoundaryViolation
:
536 case RaiseImplicitContextStateInvalid
:
537 case CheckClsMethFunc
:
538 case CheckClsReifiedGenericMismatch
:
540 case CheckFunReifiedGenericMismatch
:
541 case CheckInOutMismatch
:
542 case CheckReadonlyMismatch
:
556 case AFWHPrepareChild
:
563 case ContArUpdateIdx
:
564 case LdContResumeAddr
:
569 case AFWHPushTailFrame
:
574 case DbgAssertRefCount
:
576 case DbgCheckLocalsDecRefd
:
580 case RestoreErrorLevel
:
605 case CheckMissingKeyInArrLike
:
607 case ProfileDictAccess
:
608 case CheckDictOffset
:
609 case ProfileKeysetAccess
:
610 case CheckKeysetOffset
:
611 case ProfileArrayCOW
:
628 case StructDictUnset
:
639 case SetNewElemKeyset
:
640 case ReserveVecNewElem
:
648 case ProfileSubClsCns
:
650 case ProfileCoeffectFunParam
:
653 case BespokeEscalateToVanilla
:
654 case BespokeGetThrow
:
655 case LdTypeStructureVal
:
656 case LdTypeStructureValCns
:
661 case UnwindCheckSideExit
:
667 case EnterTranslation
:
668 case CheckStackOverflow
:
669 case CheckSurpriseFlagsEnter
:
671 case ThrowOutOfBounds
:
672 case ThrowInvalidArrayKey
:
673 case ThrowInvalidOperation
:
674 case ThrowCallReifiedFunctionWithoutGenerics
:
675 case ThrowDivisionByZeroException
:
676 case ThrowHasThisNeedStatic
:
677 case ThrowLateInitPropError
:
678 case ThrowMissingArg
:
679 case ThrowMissingThis
:
680 case ThrowParameterWrongType
:
681 case ThrowInOutMismatch
:
682 case ThrowReadonlyMismatch
:
683 case ThrowCannotModifyReadonlyCollection
:
684 case ThrowLocalMustBeValueTypeException
:
685 case ThrowMustBeEnclosedInReadonly
:
686 case ThrowMustBeMutableException
:
687 case ThrowMustBeReadonlyException
:
688 case ThrowMustBeValueTypeException
:
698 case LdClsMethodFCacheFunc
:
699 case LdClsMethodCacheFunc
:
701 case LogGuardFailure
:
702 case ProfileInstanceCheck
:
703 case MemoGetStaticValue
:
704 case MemoGetStaticCache
:
705 case MemoGetLSBValue
:
706 case MemoGetLSBCache
:
707 case MemoGetInstanceValue
:
708 case MemoGetInstanceCache
:
709 case MemoSetStaticValue
:
710 case MemoSetStaticCache
:
711 case MemoSetLSBValue
:
712 case MemoSetLSBCache
:
713 case MemoSetInstanceValue
:
714 case MemoSetInstanceCache
:
715 case ThrowAsTypeStructException
:
716 case RecordReifiedGenericsAndGetTSList
:
717 case ResolveTypeStruct
:
718 case CheckRDSInitialized
:
719 case MarkRDSInitialized
:
722 case ProfileIsTypeStruct
:
727 case LookupClsCtxCns
:
728 case ArrayMarkLegacyShallow
:
729 case ArrayMarkLegacyRecursive
:
730 case ArrayUnmarkLegacyShallow
:
731 case ArrayUnmarkLegacyRecursive
:
732 case ProfileArrLikeProps
:
733 case CheckFuncNeedsCoverage
:
736 case StructDictAddNextSlot
:
737 case StructDictTypeBoundCheck
:
738 case LdCoeffectFunParamNaive
:
739 case DeserializeLazyProp
:
746 return !opcodeMayRaise(inst
.op());
752 return !inst
.mayRaiseErrorWithSources();
759 /* DceFlags tracks the state of one instruction during dead code analysis. */
765 bool isDead() const { return m_state
== DEAD
; }
766 void setDead() { m_state
= DEAD
; }
767 void setLive() { m_state
= LIVE
; }
769 std::string
toString() const {
770 std::array
<const char*,2> const names
= {{
774 return folly::format(
776 m_state
< names
.size() ? names
[m_state
] : "<invalid>"
787 static_assert(sizeof(DceFlags
) == 1, "sizeof(DceFlags) should be 1 byte");
789 // DCE state indexed by instr->id().
790 using DceState
= StateVector
<IRInstruction
, DceFlags
>;
791 using UseCounts
= StateVector
<SSATmp
, uint32_t>;
792 // Set of live DefLabel operands (keyed by DefLabel and index)
793 using DefLabelLiveness
= jit::fast_set
<std::pair
<IRInstruction
*, size_t>>;
794 // Worklist is instruction to process. If the instruction is a
795 // DefLabel, the second item is the index of the operand (DefLabel is
796 // treated separately for each operand).
797 using WorkList
= jit::vector
<std::pair
<IRInstruction
*, size_t>>;
799 void removeDeadInstructions(IRUnit
& unit
,
800 const DceState
& state
,
801 const DefLabelLiveness
& defLabelLive
) {
805 auto const next
= block
->next();
806 auto const bcctx
= block
->back().bcctx();
808 [&] (const IRInstruction
& inst
) {
809 // Don't attempt to remove a Jmp feeding a DefLabel. It will
810 // be dealt with below.
811 if (inst
.is(Jmp
) && inst
.numSrcs() > 0) return false;
813 // DecReleaseCheck will be dealt with below.
814 if (inst
.is(DecReleaseCheck
)) return false;
818 if (state
[inst
].isDead()) {
819 FTRACE(1, "Removing dead instruction {}\n", inst
.toString());
823 auto const dead
= state
[inst
].isDead();
824 assertx(!dead
|| !inst
.taken() || inst
.taken()->isCatch());
829 if (!block
->empty()) {
830 auto& front
= block
->front();
831 if (front
.is(DefLabel
)) {
832 // If the DefLabel was completely dead, it would have been
834 assertx(!state
[&front
].isDead());
835 // Remove any dead individual operands
837 auto const numDsts
= front
.numDsts();
838 for (auto i
= 0; i
< numDsts
; ++i
) {
839 if (!defLabelLive
.count(std::make_pair(&front
, i
))) {
840 FTRACE(1, "Removing dead DefLabel dst {}: {}\n",
841 i
, front
.toString());
842 front
.deleteDst(dstIdx
);
847 assertx(front
.numDsts() > 0);
850 auto& back
= block
->back();
851 if (back
.is(Jmp
) && back
.numSrcs() > 0) {
852 if (state
[&back
].isDead()) {
853 // If the Jmp's operands are completely dead, we can't
854 // remove it (because it's control flow), but we can turn
855 // it into a normal Jmp.
856 FTRACE(1, "Removing dead instruction {}\n", back
.toString());
857 back
.setSrcs(0, nullptr);
859 // Otherwise, similarily to the DefLabel, remove
860 // individual dead operands.
862 auto const numSrcs
= back
.numSrcs();
863 for (auto i
= 0; i
< numSrcs
; ++i
) {
864 auto& defLabel
= block
->taken()->front();
865 assertx(defLabel
.is(DefLabel
));
866 if (!defLabelLive
.count(std::make_pair(&defLabel
, i
))) {
867 FTRACE(1, "Removing dead Jmp src {}: {}\n",
869 back
.deleteSrc(srcIdx
);
877 if (back
.is(DecReleaseCheck
) && state
[&back
].isDead()) {
878 block
->erase(block
->backIter());
879 block
->push_back(unit
.gen(Jmp
, bcctx
, next
));
883 if (block
->empty() || !block
->back().isBlockEnd()) {
885 block
->push_back(unit
.gen(Jmp
, bcctx
, next
));
891 // removeUnreachable erases unreachable blocks from unit, and returns
892 // a sorted list of the remaining blocks.
893 BlockList
prepareBlocks(IRUnit
& unit
) {
894 FTRACE(1, "RemoveUnreachable:vvvvvvvvvvvvvvvvvvvv\n");
895 SCOPE_EXIT
{ FTRACE(1, "RemoveUnreachable:^^^^^^^^^^^^^^^^^^^^\n"); };
897 auto const blocks
= rpoSortCfg(unit
);
899 // 1. perform copy propagation on every instruction
900 for (auto block
: blocks
) {
901 for (auto& inst
: *block
) {
906 // 2. erase unreachable blocks and get an rpo sorted list of what remains.
907 bool needsReflow
= removeUnreachable(unit
);
909 // 3. if we removed any whole blocks that ended in Jmp instructions, reflow
910 // all types in case they change the incoming types of DefLabel
912 if (needsReflow
) reflowTypes(unit
);
917 WorkList
initInstructions(const IRUnit
& unit
, const BlockList
& blocks
,
919 TRACE(1, "DCE(initInstructions):vvvvvvvvvvvvvvvvvvvv\n");
920 // Mark reachable, essential, instructions live and enqueue them.
922 wl
.reserve(unit
.numInsts());
923 forEachInst(blocks
, [&] (IRInstruction
* inst
) {
924 // Instructions that cannot be removed are automatically live. The
925 // exception is DefLabels and Jmps that feed a DefLabel. There
926 // aren't normally DCEable, but will be dealt with specially.
927 if (!canDCE(*inst
) &&
928 !inst
->is(DefLabel
) &&
929 !(inst
->is(Jmp
) && inst
->numSrcs() > 0)) {
930 state
[inst
].setLive();
931 wl
.push_back(std::make_pair(inst
, 0));
934 TRACE(1, "DCE:^^^^^^^^^^^^^^^^^^^^\n");
938 //////////////////////////////////////////////////////////////////////
940 struct TrackedInstr
{
941 // DecRefs which refer to the tracked instruction.
942 jit::vector
<IRInstruction
*> decs
;
944 // Auxiliary instructions which must be killed if the tracked instruction is
946 jit::vector
<IRInstruction
*> aux
;
948 // Stores to the stack in catch traces that can be killed to kill the
949 // tracked instruction.
950 jit::vector
<IRInstruction
*> stores
;
953 void processCatchBlock(IRUnit
& unit
, DceState
& state
, Block
* block
,
954 const UseCounts
& uses
,
955 jit::fast_map
<IRInstruction
*, TrackedInstr
>& rcInsts
) {
956 assertx(block
->front().is(BeginCatch
));
957 assertx(block
->back().is(EndCatch
));
959 auto constexpr numTrackedSlots
= 64;
960 auto constexpr wholeRange
= std::make_pair(0, numTrackedSlots
);
961 auto const stackTop
= block
->back().extra
<EndCatch
>()->offset
;
962 auto const stackRange
= [&] {
963 // If the catch block occurs within an inlined frame the outer stack
964 // locations (those above the inlined frame) are not dead and cannot be
965 // elided as we may not throw through the outer callers.
966 if (block
->back().src(0)->inst()->is(BeginInlining
)) {
967 auto const extra
= block
->back().src(0)->inst()->extra
<BeginInlining
>();
968 auto const spOff
= extra
->spOffset
;
969 assertx(stackTop
<= spOff
);
970 if (spOff
< stackTop
+ numTrackedSlots
) {
971 return AStack::range(stackTop
, spOff
);
974 return AStack::range(stackTop
, stackTop
+ numTrackedSlots
);
977 // Nothing to optimize if the stack is empty
978 if (stackRange
.size() == 0) return;
980 std::bitset
<numTrackedSlots
> usedLocations
= {};
981 // stores that are only read by the EndCatch
982 jit::fast_set
<IRInstruction
*> candidateStores
;
983 // Any IncRefs we see; if they correspond to stores above, we can
984 // replace the store with a store of Null, and kill the IncRef.
985 jit::fast_map
<SSATmp
*, std::vector
<Block::iterator
>> candidateIncRefs
;
988 [&] (const AliasClass
& cls
) -> std::pair
<int, int> {
989 if (!cls
.maybe(stackRange
)) return {};
990 auto const stk
= cls
.stack();
991 if (!stk
) return wholeRange
;
992 auto const lowest_upper
= std::min(stackRange
.high
, stk
->high
);
993 auto const highest_lower
= std::max(stackRange
.low
, stk
->low
);
994 if (lowest_upper
<= highest_lower
) return {};
996 highest_lower
.offset
- stackRange
.low
.offset
,
997 lowest_upper
.offset
- stackRange
.low
.offset
1001 auto const process_stack
=
1002 [&] (const AliasClass
& cls
) {
1003 auto r
= range(cls
);
1004 if (r
== wholeRange
) {
1005 usedLocations
.set();
1007 while (r
.first
< r
.second
) {
1008 usedLocations
.set(r
.first
++);
1014 auto const do_store
=
1015 [&] (const AliasClass
& cls
, IRInstruction
* store
) {
1016 if (!store
->is(StStk
, StStkMeta
)) return false;
1017 auto const stk
= cls
.is_stack();
1018 if (!stk
) return process_stack(cls
);
1019 auto const r
= range(cls
);
1020 if (r
.first
!= r
.second
) {
1021 assertx(r
.second
== r
.first
+ 1);
1022 if (!usedLocations
.test(r
.first
)) {
1023 usedLocations
.set(r
.first
);
1024 candidateStores
.insert(store
);
1031 for (auto inst
= block
->end(); inst
!= block
->begin(); ) {
1033 if (inst
->is(EndCatch
)) {
1036 if (inst
->is(IncRef
)) {
1037 candidateIncRefs
[inst
->src(0)].push_back(inst
);
1041 auto const effects
= canonicalize(memory_effects(*inst
));
1044 [&] (IrrelevantEffects
) { return false; },
1045 [&] (UnknownEffects
) { return true; },
1046 [&] (ReturnEffects x
) { return true; },
1047 [&] (CallEffects x
) { return true; },
1048 [&] (GeneralEffects x
) {
1050 process_stack(x
.loads
) ||
1051 process_stack(x
.inout
) ||
1052 process_stack(x
.stores
) ||
1053 process_stack(x
.backtrace
) ||
1054 process_stack(x
.kills
);
1056 [&] (PureLoad x
) { return process_stack(x
.src
); },
1057 [&] (PureStore x
) { return do_store(x
.dst
, &*inst
); },
1058 [&] (ExitEffects x
) { return process_stack(x
.live
); },
1059 [&] (PureInlineCall x
) {
1061 process_stack(x
.base
) ||
1062 process_stack(x
.actrec
);
1067 for (auto store
: candidateStores
) {
1068 auto const src
= store
->src(1);
1069 auto const it
= candidateIncRefs
.find(src
);
1070 if (it
!= candidateIncRefs
.end()) {
1071 FTRACE(3, "Erasing {} for {}\n",
1072 it
->second
.back()->toString(), store
->toString());
1073 block
->erase(it
->second
.back());
1074 if (it
->second
.size() > 1) {
1075 it
->second
.pop_back();
1077 candidateIncRefs
.erase(it
);
1079 } else if (src
->type().maybe(TCounted
)) {
1080 auto const srcInst
= src
->inst();
1081 if (!srcInst
->producesReference() ||
1082 !canDCE(*srcInst
) ||
1084 if (srcInst
->producesReference() && canDCE(*srcInst
)) {
1085 rcInsts
[srcInst
].stores
.emplace_back(store
);
1089 FTRACE(3, "Erasing {} for {}\n",
1090 srcInst
->toString(), store
->toString());
1091 state
[srcInst
].setDead();
1094 store
->setSrc(1, unit
.cns(TInitNull
));
1099 * A store to the stack which is post-dominated by the EndCatch and
1100 * not otherwise read is only there to ensure the unwinder DecRefs the
1101 * value it contains. If there's also an IncRef of the value in the
1102 * catch trace we can just store InitNull to the stack location and
1103 * drop the IncRef (and later, maybe adjust the sp of the
1104 * catch-trace's owner so we don't even have to do the store).
1106 void optimizeCatchBlocks(const BlockList
& blocks
,
1109 const UseCounts
& uses
,
1110 jit::fast_map
<IRInstruction
*, TrackedInstr
>& rcInsts
) {
1111 FTRACE(1, "OptimizeCatchBlocks:vvvvvvvvvvvvvvvvvvvv\n");
1112 SCOPE_EXIT
{ FTRACE(1, "OptimizeCatchBlocks:^^^^^^^^^^^^^^^^^^^^\n"); };
1113 for (auto block
: blocks
) {
1114 if (block
->back().is(EndCatch
) &&
1115 block
->back().extra
<EndCatch
>()->mode
!=
1116 EndCatchData::CatchMode::SideExit
&&
1117 block
->front().is(BeginCatch
)) {
1118 processCatchBlock(unit
, state
, block
, uses
, rcInsts
);
1123 void optimizeConcats(jit::vector
<IRInstruction
*>& concats
,
1127 jit::fast_map
<IRInstruction
*, TrackedInstr
>& rcInsts
) {
1128 FTRACE(1, "OptimizeConcats:vvvvvvvvvvvvvvvvvvvv\n");
1129 SCOPE_EXIT
{ FTRACE(1, "OptimizeConcats:^^^^^^^^^^^^^^^^^^^^\n"); };
1130 auto const incref
= [&] (auto inst
, auto src
) {
1131 auto const blk
= inst
->block();
1132 auto const ins
= unit
.gen(IncRef
, inst
->bcctx(), src
);
1133 blk
->insert(blk
->iteratorTo(inst
), ins
);
1134 state
[ins
].setLive();
1136 FTRACE(3, "Adding {}\n", ins
->toString());
1138 auto const decref
= [&] (auto inst
, auto src
) {
1139 auto const blk
= inst
->block();
1140 auto const ins
= unit
.gen(DecRef
, inst
->bcctx(), DecRefData
{}, src
);
1141 blk
->insert(blk
->iteratorTo(inst
), ins
);
1142 state
[ins
].setLive();
1144 FTRACE(3, "Adding {}\n", ins
->toString());
1146 auto const combine
= [&] (auto inst
, auto inst_prev
,
1147 auto src1
, auto src2
, auto src3
) {
1150 * t1 = ConcatStrStr a b (implicit Decref a)
1152 * t2 = ConcatStrStr t1 c (implicit Decref t1)
1159 * t1 = ConcatStrStr a b (implicit Decref a)
1161 * t2 = ConcatStr3 a b c (implicit Decref a)
1166 * t1 = ConcatStrStr a b (implicit Decref a)
1168 * t2 = ConcatStrStr c t1 (implicit Decref c)
1175 * t1 = ConcatStrStr a b (implicit Decref a)
1177 * t2 = ConcatStr3 c a b (implicit Decref c)
1182 * We need to make sure refcounts are correct. We do this by increffing
1183 * the sources of the first ConcatStrStr and then decreffing them after
1184 * ConcatStr3 unless ConcatStr3 already consumes the reference
1186 * Note that later stages of DCE will kill the extra ConcatStrStr and the
1189 assertx(inst_prev
->is(ConcatStrStr
));
1190 assertx(inst
->is(ConcatStrStr
));
1191 if (uses
[inst_prev
->dst()] == 1 + rcInsts
[inst_prev
].decs
.size() +
1192 rcInsts
[inst_prev
].stores
.size()) {
1193 FTRACE(3, "Combining {} into {}",
1194 inst_prev
->toString(), inst
->toString());
1195 auto next
= inst
->next();
1196 unit
.replace(inst
, ConcatStr3
, inst
->taken(), src1
, src2
, src3
);
1197 inst
->setNext(next
);
1198 FTRACE(3, " and got {}\n", inst
->toString());
1199 state
[inst
].setLive();
1200 --uses
[inst_prev
->dst()];
1201 ++uses
[inst_prev
->src(0)];
1202 ++uses
[inst_prev
->src(1)];
1203 // Incref the first source since the first ConcatStrStr controls
1205 incref(inst_prev
, inst_prev
->src(0));
1206 incref(inst_prev
, inst_prev
->src(1));
1207 // ConcatStr3 ends the blocks, so insert the decrefs to the next block
1208 assertx(inst
->next() && !inst
->next()->empty());
1209 decref(&inst
->next()->front(), src2
);
1210 if (src3
== inst_prev
->src(1)) decref(&inst
->next()->front(), src3
);
1214 for (auto& inst
: concats
) {
1215 if (!inst
->is(ConcatStrStr
)) continue;
1216 auto const src1
= inst
->src(0);
1217 auto const src2
= inst
->src(1);
1218 if (src1
->inst()->is(ConcatStrStr
)) {
1219 combine(inst
, src1
->inst(),
1220 src1
->inst()->src(0), src1
->inst()->src(1), src2
);
1221 } else if (src2
->inst()->is(ConcatStrStr
)) {
1222 combine(inst
, src2
->inst(),
1223 src1
, src2
->inst()->src(0), src2
->inst()->src(1));
1228 ////////////////////////////////////////////////////////////////////////////////
1230 void killInstrAdjustRC(
1233 IRInstruction
* inst
,
1234 jit::vector
<IRInstruction
*>& decs
1236 auto const insertDecRef
= [&] (auto before
, auto src
) {
1237 auto const blk
= before
->block();
1238 auto const ins
= unit
.gen(DecRef
, before
->bcctx(), DecRefData
{}, src
);
1239 blk
->insert(blk
->iteratorTo(before
), ins
);
1240 FTRACE(3, "Inserting {} before {} for {}\n",
1241 ins
->toString(), before
->toString(), inst
->toString());
1242 state
[ins
].setLive();
1244 auto anyRemaining
= false;
1245 if (inst
->consumesReferences()) {
1246 // ConsumesReference inputs that are definitely not moved can
1247 // simply be decreffed as a replacement for the dead consumesref
1250 for (auto src
: inst
->srcs()) {
1251 auto const ix
= srcIx
++;
1252 if (inst
->consumesReference(ix
) && src
->type().maybe(TCounted
)) {
1253 if (inst
->mayMoveReference(ix
)) {
1254 anyRemaining
= true;
1257 insertDecRef(inst
, src
);
1261 for (auto dec
: decs
) {
1262 auto replaced
= dec
->src(0) != inst
->dst();
1264 if (dec
->is(DecReleaseCheck
)) {
1265 if (inst
->is(ConstructClosure
) && inst
->src(0)->type().maybe(TCounted
)) {
1267 assertx(anyRemaining
);
1268 assertx(inst
->mayMoveReference(0));
1270 // While the closure is going to be released via ReleaseShallow it will
1271 // still be responsible for releasing the captured context.
1272 insertDecRef(dec
, inst
->src(0));
1274 } else if (anyRemaining
) {
1275 assertx(dec
->is(DecRef
));
1276 // The remaining inputs might be moved, so may need to survive
1277 // until this instruction is decreffed
1278 for (auto src
: inst
->srcs()) {
1279 if (inst
->mayMoveReference(srcIx
++) && src
->type().maybe(TCounted
)) {
1281 FTRACE(3, "Converting {} to ", dec
->toString());
1282 dec
->setSrc(0, src
);
1283 FTRACE(3, "{} for {}\n", dec
->toString(), inst
->toString());
1285 state
[dec
].setLive();
1287 insertDecRef(dec
, src
);
1293 FTRACE(3, "Killing {} for {}\n", dec
->toString(), inst
->toString());
1294 state
[dec
].setDead();
1297 state
[inst
].setDead();
1300 //////////////////////////////////////////////////////////////////////
1302 } // anonymous namespace
1304 void mandatoryDCE(IRUnit
& unit
) {
1305 if (removeUnreachable(unit
)) {
1306 // Removing unreachable incoming edges can change types, so if we changed
1307 // anything we have to reflow to maintain that IR invariant.
1310 assertx(checkEverything(unit
));
1313 void fullDCE(IRUnit
& unit
) {
1314 if (!RuntimeOption::EvalHHIRDeadCodeElim
) {
1315 // This portion of DCE cannot be turned off, because it restores IR
1316 // invariants, and callers of fullDCE are allowed to rely on it for that.
1317 return mandatoryDCE(unit
);
1320 Timer
dceTimer(Timer::optimize_dce
);
1322 // kill unreachable code and remove any traces that are now empty
1323 auto const blocks
= prepareBlocks(unit
);
1325 // At this point, all IR invariants must hold, because we've restored the
1326 // only one allowed to be violated before fullDCE in prepareBlocks.
1327 assertx(checkEverything(unit
));
1329 // mark the essential instructions and add them to the initial
1330 // work list; this will also mark reachable exit traces. All
1331 // other instructions marked dead.
1332 DceState
state(unit
, DceFlags());
1333 WorkList wl
= initInstructions(unit
, blocks
, state
);
1335 UseCounts
uses(unit
, 0);
1336 jit::fast_map
<IRInstruction
*, TrackedInstr
> rcInsts
;
1337 DefLabelLiveness defLabelLive
;
1338 jit::vector
<IRInstruction
*> concats
;
1340 // process the worklist
1341 while (!wl
.empty()) {
1342 auto const [inst
, defLabelIdx
] = wl
.back();
1344 assertx(!state
[inst
].isDead());
1345 // Jmps which feed a DefLabel are dealt with as part of processing
1346 // the DefLabel. They should never appear on the worklist.
1347 assertx(IMPLIES(inst
->is(Jmp
), inst
->numSrcs() == 0));
1348 assertx(IMPLIES(!inst
->is(DefLabel
), defLabelIdx
== 0));
1350 auto const process
= [&] (IRInstruction
* inst
, uint32_t ix
) {
1351 if (inst
->is(ConcatStrStr
)) concats
.emplace_back(inst
);
1352 auto const src
= inst
->src(ix
);
1353 IRInstruction
* srcInst
= src
->inst();
1354 if (srcInst
->op() == DefConst
) return;
1356 if (srcInst
->producesReference() && canDCE(*srcInst
)) {
1358 if (inst
->is(DecRef
, DecReleaseCheck
)) {
1359 rcInsts
[srcInst
].decs
.emplace_back(inst
);
1361 if (inst
->is(InitVecElem
, InitDictElem
, InitStructElem
,
1362 InitStructPositions
, ReleaseShallow
, StClosureArg
)) {
1363 if (ix
== 0) rcInsts
[srcInst
].aux
.emplace_back(inst
);
1367 if (srcInst
->is(DefLabel
)) {
1368 // If the source instruction is a DefLabel, figure out which
1369 // operand this SSATmp corresponds to, and schedule that
1370 // particular operand.
1372 for (; dstIdx
< srcInst
->numDsts(); dstIdx
++) {
1373 if (src
== srcInst
->dst(dstIdx
)) break;
1375 assertx(dstIdx
!= srcInst
->numDsts());
1376 // If the operand isn't already live, schedule it.
1377 if (defLabelLive
.emplace(std::make_pair(srcInst
, dstIdx
)).second
) {
1378 state
[srcInst
].setLive();
1379 wl
.push_back(std::make_pair(srcInst
, dstIdx
));
1381 } else if (state
[srcInst
].isDead()) {
1382 state
[srcInst
].setLive();
1383 wl
.push_back(std::make_pair(srcInst
, 0));
1387 if (inst
->is(DefLabel
)) {
1388 // For a DefLabel operand, look "through" each corresponding Jmp
1389 // and process the source as if we were processing that Jmp.
1390 assertx(defLabelIdx
< inst
->numDsts());
1391 assertx(defLabelLive
.count(std::make_pair(inst
, defLabelIdx
)));
1392 inst
->block()->forEachPred(
1393 [&, inst
= inst
, defLabelIdx
= defLabelIdx
] (Block
* pred
) {
1394 auto& jmp
= pred
->back();
1395 assertx(jmp
.is(Jmp
));
1396 assertx(jmp
.numSrcs() == inst
->numDsts());
1397 state
[&jmp
].setLive();
1398 process(&jmp
, defLabelIdx
);
1403 // For a normal instruction, just process each source
1404 for (uint32_t ix
= 0; ix
< inst
->numSrcs(); ++ix
) process(inst
, ix
);
1407 optimizeCatchBlocks(blocks
, state
, unit
, uses
, rcInsts
);
1408 optimizeConcats(concats
, state
, unit
, uses
, rcInsts
);
1410 // If every use of a dce-able PRc instruction is a DecRef or PureStore based
1411 // on its dst, then we can kill it, and DecRef any of its consumesReference
1413 for (auto& pair
: rcInsts
) {
1414 auto& info
= pair
.second
;
1415 auto const trackedUses
=
1416 info
.decs
.size() + info
.aux
.size() + info
.stores
.size();
1417 if (uses
[pair
.first
->dst()] != trackedUses
) continue;
1418 killInstrAdjustRC(state
, unit
, pair
.first
, info
.decs
);
1419 for (auto inst
: info
.aux
) killInstrAdjustRC(state
, unit
, inst
, info
.decs
);
1420 for (auto store
: info
.stores
) store
->setSrc(1, unit
.cns(TInitNull
));
1423 // Now remove instructions whose state is DEAD.
1424 removeDeadInstructions(unit
, state
, defLabelLive
);
1426 // Kill unreachable catch blocks.