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/hhbbc/dce.h"
26 #include <boost/dynamic_bitset.hpp>
27 #include <boost/container/flat_map.hpp>
29 #include <folly/gen/Base.h>
30 #include <folly/gen/String.h>
32 #include "hphp/runtime/base/array-iterator.h"
34 #include "hphp/util/bitset-utils.h"
35 #include "hphp/util/dataflow-worklist.h"
36 #include "hphp/util/trace.h"
38 #include "hphp/hhbbc/analyze.h"
39 #include "hphp/hhbbc/cfg-opts.h"
40 #include "hphp/hhbbc/cfg.h"
41 #include "hphp/hhbbc/func-util.h"
42 #include "hphp/hhbbc/interp-state.h"
43 #include "hphp/hhbbc/interp.h"
44 #include "hphp/hhbbc/optimize.h"
45 #include "hphp/hhbbc/options.h"
46 #include "hphp/hhbbc/representation.h"
47 #include "hphp/hhbbc/type-system.h"
48 #include "hphp/hhbbc/unit-util.h"
50 #include "hphp/runtime/base/mixed-array.h"
51 #include "hphp/runtime/base/runtime-option.h"
53 namespace HPHP
{ namespace HHBBC
{
55 TRACE_SET_MOD(hhbbc_dce
);
57 //////////////////////////////////////////////////////////////////////
60 * This module contains code to perform both local DCE and global DCE.
62 * The local DCE algorithm addresses dead eval stack manipulations and
63 * dead stores to locals that are visible within a single basic block.
65 * The global DCE performs a liveness analysis and then uses this
66 * information to allow dead stores to locals to be eliminated across
69 * Both types of DCE here need to be type-aware, but they must visit
70 * blocks backward. They accomplish this by forward-propagating the
71 * block input states from a FuncAnalysis to each instruction in the
72 * block prior to doing the backward iteration.
76 * During backward traversal of a block, we maintain a "backwards"
77 * stack, indicating which eval stack slots are going to be required
78 * in the future or not.
80 * During this traversal, each instruction that pops when going
81 * forward instead "pushes" information about whether that input
82 * will be required. If it is not required, it also pushes an
83 * accumulating set of instruction ids that must be removed if the
84 * instruction which produces the stack slot is removed. (All
85 * instructions in these sets must be removed if any are, in order
86 * to keep the stack depths correct.)
88 * Similarly, each instruction that would push a value going forward
89 * instead "pops" the information about whether its stack output is
90 * going to be needed. If not, the instruction can mark itself (and
91 * all downstream instructions that depended on it) as removable.
95 * While a block is iterated backward, the set of live locals is
96 * tracked. The initial state of this live set depends on whether
97 * we are performing global or local DCE, and in the local case
98 * includes all locals in the function.
100 * When a local may be read, it is added to the live set. When a
101 * local is definitely-written, it is removed from the set.
103 * If a instruction may write to a local that is not live, it can be
104 * marked as removable if it is known to have no other side-effects.
105 * Currently this is only hooked up to SetL.
109 * The global algorithm first performs a liveness analysis to
110 * propagate live out sets to each block.
112 * This analysis is basically normal, but slightly modified from the
113 * usual in order to deal with the way exceptional control flow is
114 * represented in our CFG.
116 * It essentially is the liveness analysis algorithm described at
117 * http://dl.acm.org/citation.cfm?id=316171, except that we don't
118 * need to track kill sets for each PEI because we don't have a
119 * means of determining which exceptional edges may be traversed by any
120 * given PEI. (Maybe that may be more usual to have in the context
121 * of a language with declared exception clauses...)
123 * Since we only deal with the most pessimistic exception case, this
124 * means for each block we just determine a gen and kill set, along
125 * with a subset of the latter set that is the locals that must be
126 * killed before any PEI. (See killBeforePEI below.)
128 * Final note about types:
130 * Global DCE can change the types of locals in a way that spans
131 * basic blocks. For a simple example, take the following code:
133 * // $foo :: Uninit here.
135 * // ... code that breaks a block ...
138 * If the first store to $foo is removed, the type of $foo before
139 * the SetL in the later block is now Uninit, instead of Int.
141 * In addition, by killing dead eval stack slots across basic
142 * blocks, the saved entry stacks in the FuncAnalysis no longer
143 * match the actual stack depths.
145 * This means that after calling global_dce on a function, its
146 * FuncAnalysis can no longer be considered accurate.
148 * Moreover, since global DCE makes use of type information to
149 * determine whether a store is dead, we need to be careful that
150 * this never changes whether the assumptions used to perform DCE
153 * This is ok right now: DCE only uses the types to figure out which
154 * values can either have lifetimes extended or shortened without
155 * visible side-effects, or which values may be refs (so we can't
156 * omit stores to them). If we omit a store that changes the type
157 * of a local globally, this means the new type cannot be different
158 * with regard to these features (or we wouldn't have omitted it),
159 * which means we won't have made different decisions elsewhere in
160 * the algorithm based on the new type.
162 * Specifically: we will never omit a store where the old local type
163 * was something that could've had side effects, and if a new value
164 * is stored into a local where destroying it could have
165 * side-effects, some point along the path to the function exit (if
166 * nothing else, the RetC) will have added it to the gen set and the
167 * store also won't be removable.
169 * In contrast, note that local DCE can not change types across
176 //////////////////////////////////////////////////////////////////////
178 // The number of pops as seen by dce.
179 uint32_t numPop(const Bytecode
& bc
) {
180 if (bc
.op
== Op::CGetL2
) return 1;
184 // The number of pushes as seen by dce.
185 uint32_t numPush(const Bytecode
& bc
) {
186 if (bc
.op
== Op::CGetL2
) return 2;
190 // Some reads could raise warnings and run arbitrary code.
191 bool readCouldHaveSideEffects(const Type
& t
) {
192 return t
.couldBe(BUninit
);
195 //////////////////////////////////////////////////////////////////////
198 * Use information of a stack cell.
201 // Indicates that the cell is (possibly) used.
204 // Indicates that the cell is (unconditionally) not used.
208 * Indicates that the stack slot contains an array-like being
209 * constructed by AddElemCs, which looks like it can be optimized to
210 * a NewStructArray, NewPackedArray, or NewVecArray.
215 * Modifier or-ed into the above to indicate that this use is linked
216 * to the stack slot below it (in stack order - the bottom of the
217 * stack is below the top); either they are both optimized, or
223 Use
operator|(Use a
, Use b
) {
224 return static_cast<Use
>(static_cast<int>(a
) | static_cast<int>(b
));
227 Use
operator&(Use a
, Use b
) {
228 return static_cast<Use
>(static_cast<int>(a
) & static_cast<int>(b
));
232 return static_cast<int>(a
) != 0;
235 Use
mask_use(Use a
) { return a
& static_cast<Use
>(3); }
242 bool operator<(const InstrId
& i1
, const InstrId
& i2
) {
243 if (i1
.blk
!= i2
.blk
) return i1
.blk
< i2
.blk
;
244 // sort by decreasing idx so that kill_marked_instrs works
245 return i1
.idx
> i2
.idx
;
248 bool operator==(const InstrId
& i1
, const InstrId
& i2
) {
249 return i1
.blk
== i2
.blk
&& i1
.idx
== i2
.idx
;
258 bool operator<(const LocationId
& i1
, const LocationId
& i2
) {
259 if (i1
.blk
!= i2
.blk
) return i1
.blk
< i2
.blk
;
260 if (i1
.id
!= i2
.id
) return i1
.id
< i2
.id
;
261 return i2
.isSlot
&& !i1
.isSlot
;
264 struct LocationIdHash
{
265 size_t operator()(const LocationId
& l
) const {
266 return folly::hash::hash_combine(hash_int64_pair(l
.blk
, l
.id
), l
.isSlot
);
270 bool operator==(const LocationId
& i1
, const LocationId
& i2
) {
271 return i1
.blk
== i2
.blk
&&
273 i1
.isSlot
== i2
.isSlot
;
276 using LocationIdSet
= hphp_fast_set
<LocationId
,LocationIdHash
>;
292 using MaskOrCountType
= uint32_t;
293 static constexpr size_t kMaskSize
= 32;
294 MaskOrCountType maskOrCount
{0};
296 DceAction() = default;
297 /* implicit */ DceAction(Action a
) : action
{a
} {}
298 DceAction(Action a
, MaskOrCountType m
) : action
{a
}, maskOrCount
{m
} {}
301 bool operator==(const DceAction
& a
, const DceAction
& b
) {
302 return a
.action
== b
.action
&& a
.maskOrCount
== b
.maskOrCount
;
305 using DceActionMap
= std::map
<InstrId
, DceAction
>;
306 using DceReplaceMap
= std::map
<InstrId
, CompactVector
<Bytecode
>>;
309 explicit UseInfo(Use u
) : usage(u
) {}
310 UseInfo(Use u
, DceActionMap
&& ids
) : usage(u
), actions(std::move(ids
)) {}
314 * Set of actions that should be performed if we decide to
315 * discard the corresponding stack slot.
317 DceActionMap actions
;
319 * Used for stack slots that are live across blocks to indicate a
320 * stack slot that should be considered Used at the entry to blk.
322 * If its not live across blocks, location.blk will stay NoBlockId.
324 LocationId location
{ NoBlockId
, 0, false };
327 struct LocalRemappingIndex
{
328 // `mrl` is the maximum number of locals we will remap.
329 explicit LocalRemappingIndex(size_t mrl
) : localInterference(mrl
) {
330 assertx(mrl
<= kMaxTrackedLocals
);
332 // localInterference[i][j] == true iff the locals i and j are both live at a
333 // program point, and therefore cannot share a local slot.
334 std::vector
<std::bitset
<kMaxTrackedLocals
>> localInterference
;
335 // pinnedLocal[i] == true iff the local i depends on its order in the local
336 // slots. This is for keeping local ranges intact for bytecodes that take
338 std::bitset
<kMaxTrackedLocals
> pinnedLocals
{};
341 //////////////////////////////////////////////////////////////////////
344 explicit DceState(const Index
& index
, const FuncAnalysis
& ainfo
) :
345 index(index
), ainfo(ainfo
) {}
347 const FuncAnalysis
& ainfo
;
349 * Used to accumulate a set of blk/stack-slot pairs that
350 * should be marked used.
352 LocationIdSet forcedLiveLocations
;
355 * Eval stack use information. Stacks slots are marked as being
356 * needed or not needed. If they aren't needed, they carry a set of
357 * instructions that must be removed if the instruction that
358 * produces the stack value is also removable.
360 std::vector
<UseInfo
> stack
;
363 * Locals known to be live at a point in a DCE walk. This is used
364 * when we're actually acting on information we discovered during
367 std::bitset
<kMaxTrackedLocals
> liveLocals
;
370 * Instructions marked in this set will be processed by dce_perform.
371 * They must all be processed together to keep eval stack consumers
372 * and producers balanced.
374 DceActionMap actionMap
;
377 * Actions of type Replace in the actionMap have an entry here with
378 * the replacement bytecodes.
380 DceReplaceMap replaceMap
;
383 * Dead across Call, Yield or Await.
385 * At a given instruction during the DCE analysis a local is dead across if
386 * it is dead after the current instruction, and remains dead through the
387 * next Call, Yield or Await opcode along some possible control flow path.
388 * This information is useful in deciding if an UnsetL may be advantageous
389 * vs. when it should be DCEd. We take this a step further and use this
390 * information to insert UnsetL where we believe they are advantageous.
392 std::bitset
<kMaxTrackedLocals
> deadAcrossLocals
;
395 * The set of local names thate were ever referenced in this block. This set
396 * is used by global DCE to remove local names that are completely unused
397 * in the entire function.
399 std::bitset
<kMaxTrackedLocals
> usedLocalNames
;
402 * Interference graph between local slots we build up over the course of
403 * Global DCE and then use to share local slots to reduce frame size.
405 LocalRemappingIndex
* remappingIndex
{nullptr};
408 * Can be set by a minstr-final instruction to indicate the actions
409 * to take provided all previous minstrs in the group are non-pei.
411 * eg if the result of a QueryM is unused, and the whole sequence is
412 * non-pei we can kill it. Or if the result is a literal, and the
413 * whole sequence is non-pei, we can replace it with pops plus the
416 folly::Optional
<UseInfo
> minstrUI
;
418 * Flag to indicate local vs global dce.
423 * When we do add opts, in some cases it can enable further optimization
424 * opportunities. Let the optimizer know...
426 bool didAddOpts
{false};
429 //////////////////////////////////////////////////////////////////////
432 const char* show(DceAction action
) {
433 switch (action
.action
) {
434 case DceAction::Kill
: return "Kill";
435 case DceAction::PopInputs
: return "PopInputs";
436 case DceAction::PopOutputs
: return "PopOutputs";
437 case DceAction::Replace
: return "Replace";
438 case DceAction::PopAndReplace
: return "PopAndReplace";
439 case DceAction::MinstrStackFinal
: return "MinstrStackFinal";
440 case DceAction::MinstrStackFixup
: return "MinstrStackFixup";
441 case DceAction::MinstrPushBase
: return "MinstrPushBase";
442 case DceAction::AdjustPop
: return "AdjustPop";
443 case DceAction::UnsetLocalsAfter
: return "UnsetLocalsAfter";
444 case DceAction::UnsetLocalsBefore
:return "UnsetLocalsBefore";
449 std::string
show(InstrId id
) {
450 return folly::sformat("{}:{}", id
.blk
, id
.idx
);
453 inline void validate(Use u
) {
454 assert(!any(u
& Use::Linked
) || mask_use(u
) == Use::Not
);
457 const char* show(Use u
) {
460 switch (mask_use(u
)) {
461 case Use::Used
: return "*U";
462 case Use::Not
: return "*0";
463 case Use::AddElemC
: return "*AE";
464 case Use::Linked
: not_reached();
469 return !any(u
& Use::Linked
) ? ret
+ 1 : ret
;
472 std::string
show(const LocationId
& id
) {
473 return folly::sformat("{}:{}{}", id
.blk
, id
.id
, id
.isSlot
? "(slot)" : "");
476 std::string
show(const DceActionMap::value_type
& elm
) {
477 return folly::sformat("{}={}", show(elm
.first
), show(elm
.second
));
480 std::string
show(const DceActionMap
& actions
) {
481 using namespace folly::gen
;
483 | map([](const DceActionMap::value_type
& elm
) { return show(elm
); })
484 | unsplit
<std::string
>(";")
488 std::string DEBUG_ONLY
show(const UseInfo
& ui
) {
489 return folly::sformat("{}({})", show(ui
.usage
), show(ui
.actions
));
492 std::string DEBUG_ONLY
loc_bits_string(const php::Func
* func
,
493 std::bitset
<kMaxTrackedLocals
> locs
) {
494 std::ostringstream out
;
495 if (func
->locals
.size() < kMaxTrackedLocals
) {
496 for (auto i
= func
->locals
.size(); i
-- > 0;) {
497 out
<< (locs
.test(i
) ? '1' : '0');
505 //////////////////////////////////////////////////////////////////////
512 const State
& stateBefore
;
513 const StepFlags
& flags
;
514 const State
& stateAfter
;
516 * A local is "liveInside" an op if the local is used in the op, but is not
517 * live before or after the op. This is used to ensure the local slot is
518 * usable during the op unless the op is DCEd away. When building an
519 * interference graph it is important to know which instruction has the op
520 * "live inside", vs just knowing the local is used at some point.
522 * An example of such an op is UnsetL. It stores to a local slot, but its
523 * behavior is unaltered by the contents of the local slot. So we need the
524 * local slot to exist, but don't need it to exist before or after the Op.
526 std::bitset
<kMaxTrackedLocals
> liveInside
;
529 //////////////////////////////////////////////////////////////////////
530 // Properties of UseInfo
532 bool isLinked(const UseInfo
& ui
) {
533 return any(ui
.usage
& Use::Linked
);
536 bool lastUiIsLinked(const UseInfo
& ui
) {
540 template <typename
... Args
>
541 bool lastUiIsLinked(const UseInfo
& /*ui*/, const Args
&... args
) {
542 return lastUiIsLinked(args
...);
545 bool allUnused() { return true; }
546 template<class... Args
>
547 bool allUnused(const UseInfo
& ui
, const Args
&... args
) {
548 return (mask_use(ui
.usage
)) == Use::Not
&&
552 bool allUnusedIfNotLastRef() { return true; }
553 template<class... Args
>
554 bool allUnusedIfNotLastRef(const UseInfo
& ui
, const Args
&... args
) {
555 auto u
= mask_use(ui
.usage
);
556 return u
== Use::Not
&& allUnusedIfNotLastRef(args
...);
559 bool alwaysPop(const UseInfo
& ui
) {
561 ui
.location
.blk
!= NoBlockId
||
562 ui
.actions
.size() > 1;
565 template<typename
... Args
>
566 bool alwaysPop(const UseInfo
& ui
, const Args
&... args
) {
567 return alwaysPop(ui
) || alwaysPop(args
...);
570 bool maybePop(Env
& env
, const UseInfo
& ui
) {
572 (ui
.actions
.size() == 1 &&
573 ui
.actions
.begin()->first
.idx
> env
.id
.idx
+ 1);
576 template<typename
... Args
>
577 bool maybePop(Env
& env
, const UseInfo
& ui
, const Args
&... args
) {
578 return maybePop(env
, ui
) || maybePop(env
, args
...);
582 * Determine whether its worth inserting PopCs after an instruction
583 * that can't be dced in order to execute the dependent actions.
585 template<typename
... Args
>
586 bool shouldPopOutputs(Env
& env
, const Args
&... args
) {
587 if (alwaysPop(args
...)) return true;
588 return maybePop(env
, args
...);
591 //////////////////////////////////////////////////////////////////////
594 Type
topT(Env
& env
, uint32_t idx
= 0) {
595 assert(idx
< env
.stateBefore
.stack
.size());
596 return env
.stateBefore
.stack
[env
.stateBefore
.stack
.size() - idx
- 1].type
;
599 Type
topC(Env
& env
, uint32_t idx
= 0) {
600 auto const t
= topT(env
, idx
);
601 assert(t
.subtypeOf(BInitCell
));
605 //////////////////////////////////////////////////////////////////////
607 void addInterference(LocalRemappingIndex
* index
,
608 std::bitset
<kMaxTrackedLocals
> live
) {
609 auto& li
= index
->localInterference
;
610 for (auto i
= li
.size(); i
-- > 0;) {
617 void addInterference(Env
& env
, std::bitset
<kMaxTrackedLocals
> live
) {
618 // We don't track interfrence until the optimize round of the global dce.
619 if (!env
.dceState
.remappingIndex
) return;
620 addInterference(env
.dceState
.remappingIndex
, live
);
623 void pinLocals(Env
& env
, std::bitset
<kMaxTrackedLocals
> pinned
) {
624 // We mark pinned locals to guarantee their index does not change during
626 if (!env
.dceState
.remappingIndex
) return;
628 env
.dceState
.remappingIndex
->pinnedLocals
|= pinned
;
632 void addLocGenSet(Env
& env
, std::bitset
<kMaxTrackedLocals
> locs
) {
633 FTRACE(4, " loc-conservative: {}\n",
634 loc_bits_string(env
.dceState
.ainfo
.ctx
.func
, locs
));
635 env
.dceState
.liveLocals
|= locs
;
638 void addLocGen(Env
& env
, uint32_t id
) {
639 FTRACE(2, " loc-gen: {}\n", id
);
640 if (id
>= kMaxTrackedLocals
) return;
641 env
.dceState
.liveLocals
[id
] = 1;
645 * This is marking an op that logically kills a local (like a def), but we
646 * cannot remove the write to the slot, so we mark it as used so the local is
647 * not killed and removed.
649 void addLocUse(Env
& env
, uint32_t id
) {
650 FTRACE(2, " loc-use: {}\n", id
);
651 if (id
>= kMaxTrackedLocals
) return;
652 env
.liveInside
[id
] = 1;
655 void addLocNameUse(Env
& env
, NamedLocal nloc
) {
656 if (nloc
.name
>= kMaxTrackedLocals
|| nloc
.name
< 0) return;
657 env
.dceState
.usedLocalNames
[nloc
.name
] = 1;
661 * Indicate that this instruction will use the value of loc unless we
662 * kill it. handle_push will take care of calling addLocGen if
665 void scheduleGenLoc(Env
& env
, LocalId loc
) {
669 void addLocKill(Env
& env
, uint32_t id
) {
670 FTRACE(2, " loc-kill: {}\n", id
);
671 if (id
>= kMaxTrackedLocals
) return;
672 env
.dceState
.liveLocals
[id
] = 0;
673 // Logically unless this op is markedDead we need the local to exist.
674 env
.liveInside
[id
] = 1;
677 bool isLocLive(Env
& env
, uint32_t id
) {
678 if (id
>= kMaxTrackedLocals
) {
679 // Conservatively assume it's potentially live.
682 return env
.dceState
.liveLocals
[id
];
685 bool isDeadAcross(Env
& env
, uint32_t id
) {
686 if (id
>= kMaxTrackedLocals
) {
687 // Conservatively assume it is not dead across.
690 return env
.dceState
.deadAcrossLocals
[id
];
693 Type
locRaw(Env
& env
, LocalId loc
) {
694 return env
.stateBefore
.locals
[loc
];
697 bool isLocVolatile(Env
& env
, uint32_t id
) {
698 if (id
>= kMaxTrackedLocals
) return true;
699 return is_volatile_local(env
.dceState
.ainfo
.ctx
.func
, id
);
702 //////////////////////////////////////////////////////////////////////
703 // manipulate eval stack
705 void pop(Env
& env
, UseInfo
&& ui
) {
706 FTRACE(2, " pop({})\n", show(ui
.usage
));
707 env
.dceState
.stack
.push_back(std::move(ui
));
710 void pop(Env
& env
, Use u
, DceActionMap actions
) {
711 pop(env
, {u
, std::move(actions
)});
714 void pop(Env
& env
, Use u
, InstrId id
) {
715 pop(env
, {u
, {{id
, DceAction::Kill
}}});
718 void pop(Env
& env
) { pop(env
, Use::Used
, DceActionMap
{}); }
720 void discard(Env
& env
) {
721 pop(env
, Use::Not
, env
.id
);
724 //////////////////////////////////////////////////////////////////////
727 * Mark a UseInfo used; if its linked also mark the ui below it used.
728 * As we mark them used, we may also need to add them to
729 * forcedLiveLocations to prevent inconsistencies in the global state
732 void use(LocationIdSet
& forcedLive
,
733 std::vector
<UseInfo
>& uis
, uint32_t i
) {
736 auto linked
= isLinked(ui
);
737 if (ui
.usage
!= Use::Used
&&
738 ui
.location
.blk
!= NoBlockId
) {
739 forcedLive
.insert(ui
.location
);
741 ui
.usage
= Use::Used
;
749 void combineActions(DceActionMap
& dst
, const DceActionMap
& src
) {
750 for (auto& i
: src
) {
751 auto ret
= dst
.insert(i
);
753 if (i
.second
.action
== DceAction::UnsetLocalsBefore
||
754 i
.second
.action
== DceAction::UnsetLocalsAfter
) {
758 if (ret
.first
->second
.action
== DceAction::UnsetLocalsBefore
||
759 ret
.first
->second
.action
== DceAction::UnsetLocalsAfter
) {
760 if (i
.second
.action
== DceAction::Replace
||
761 i
.second
.action
== DceAction::PopAndReplace
) {
762 // The UnsetLocals replace map will take precedence, so leave the
763 // action as UnsetLocals.
766 ret
.first
->second
= i
.second
;
770 if (i
.second
.action
== DceAction::MinstrStackFixup
||
771 i
.second
.action
== DceAction::MinstrStackFinal
) {
772 assertx(i
.second
.action
== ret
.first
->second
.action
);
773 ret
.first
->second
.maskOrCount
|= i
.second
.maskOrCount
;
777 if (i
.second
.action
== DceAction::AdjustPop
&&
778 ret
.first
->second
.action
== DceAction::AdjustPop
) {
779 assertx(ret
.first
->second
.maskOrCount
> 0);
780 assertx(i
.second
.maskOrCount
> 0);
781 ret
.first
->second
.maskOrCount
+= i
.second
.maskOrCount
;
785 if (i
.second
.action
== ret
.first
->second
.action
) {
789 assertx(i
.second
.action
== DceAction::Kill
||
790 ret
.first
->second
.action
== DceAction::Kill
);
792 if (i
.second
.action
== DceAction::PopAndReplace
||
793 ret
.first
->second
.action
== DceAction::PopAndReplace
) {
794 ret
.first
->second
.action
= DceAction::Replace
;
797 if (i
.second
.action
== DceAction::Replace
||
798 ret
.first
->second
.action
== DceAction::Replace
) {
799 ret
.first
->second
.action
= DceAction::Replace
;
802 if (ret
.first
->second
.action
== DceAction::PopInputs
||
803 i
.second
.action
== DceAction::PopInputs
) {
804 ret
.first
->second
.action
= DceAction::Kill
;
807 if (ret
.first
->second
.action
== DceAction::AdjustPop
||
808 i
.second
.action
== DceAction::AdjustPop
) {
809 ret
.first
->second
.action
= DceAction::Kill
;
810 ret
.first
->second
.maskOrCount
= 0;
814 always_assert(false);
820 * A UseInfo has been popped, and we want to perform its actions. If
821 * its not linked we'll just add them to the dceState; otherwise we'll
822 * add them to the UseInfo on the top of the stack.
824 DceActionMap
& commitActions(Env
& env
, bool linked
, const DceActionMap
& am
) {
826 FTRACE(2, " committing {}: {}\n", show(env
.id
), show(am
));
829 assert(!linked
|| env
.dceState
.stack
.back().usage
!= Use::Used
);
832 env
.dceState
.stack
.back().actions
: env
.dceState
.actionMap
;
834 combineActions(dst
, am
);
836 if (!am
.count(env
.id
)) {
837 dst
.emplace(env
.id
, DceAction::Kill
);
843 * Combine a set of UseInfos into the first, and return the combined one.
845 UseInfo
& combineUis(UseInfo
& ui
) { return ui
; }
846 template<class... Args
>
847 UseInfo
& combineUis(UseInfo
& accum
, const UseInfo
& ui
, const Args
&... args
) {
848 accum
.actions
.insert(begin(ui
.actions
), end(ui
.actions
));
849 if (accum
.location
.blk
== NoBlockId
||
850 accum
.location
< ui
.location
) {
851 accum
.location
= ui
.location
;
853 return combineUis(accum
, args
...);
857 * The current instruction is going to be replaced with PopCs. Perform
858 * appropriate pops (Use::Not)
860 void ignoreInputs(Env
& env
, bool linked
, DceActionMap
&& actions
) {
861 auto const np
= numPop(env
.op
);
864 auto usage
= [&] (uint32_t i
) {
866 if (linked
) ret
= ret
| Use::Linked
;
870 for (auto i
= np
; --i
; ) {
871 pop(env
, usage(i
), DceActionMap
{});
874 pop(env
, usage(0), std::move(actions
));
877 DceActionMap
& commitUis(Env
& env
, bool linked
, const UseInfo
& ui
) {
878 return commitActions(env
, linked
, ui
.actions
);
881 template<typename
... Args
>
882 DceActionMap
& commitUis(Env
& env
, bool linked
,
883 const UseInfo
& ui
, const Args
&... args
) {
884 commitUis(env
, linked
, ui
);
885 return commitUis(env
, linked
, args
...);
889 * During global dce, a particular stack element could be pushed
890 * on multiple paths. eg:
894 * If f() is known to return a non-counted type, we have FCallFuncD -> PopC
895 * on one path, and Int 42 -> PopC on another, and the PopC marks its value
896 * Use::Not. When we get to the Int 42 it thinks both instructions can be
897 * killed; but when we get to the FCallFuncD it does nothing. So any time we
898 * decide to ignore a Use::Not, we have to record that fact so we can prevent
899 * the other paths from trying to use that information. We communicate this
900 * via the ui.location field, and the forcedLiveLocations set.
902 * [ We deal with this case now by inserting a PopC after the
903 * FCallFuncD, which allows the 42/PopC to be removed - but there are
904 * other cases that are not yet handled. ]
906 void markUisLive(Env
& env
, bool linked
, const UseInfo
& ui
) {
908 if (ui
.usage
!= Use::Used
&&
909 ui
.location
.blk
!= NoBlockId
) {
910 env
.dceState
.forcedLiveLocations
.insert(ui
.location
);
913 use(env
.dceState
.forcedLiveLocations
,
914 env
.dceState
.stack
, env
.dceState
.stack
.size() - 1);
918 template<typename
... Args
>
919 void markUisLive(Env
& env
, bool linked
,
920 const UseInfo
& ui
, const Args
&... args
) {
921 markUisLive(env
, false, ui
);
922 markUisLive(env
, linked
, args
...);
925 template<typename
... Args
>
926 void popOutputs(Env
& env
, bool linked
, const Args
&... args
) {
927 if (shouldPopOutputs(env
, args
...)) {
928 auto& actions
= commitUis(env
, linked
, args
...);
929 actions
[env
.id
] = DceAction::PopOutputs
;
932 markUisLive(env
, linked
, args
...);
935 void markDead(Env
& env
) {
936 env
.dceState
.actionMap
[env
.id
] = DceAction::Kill
;
937 FTRACE(2, " Killing {}\n", show(env
.id
));
940 void pop_inputs(Env
& env
, uint32_t numPop
) {
941 for (auto i
= uint32_t{0}; i
< numPop
; ++i
) {
946 enum class PushFlags
{
954 template<typename
... Args
>
955 void handle_push(Env
& env
, PushFlags pf
, Args
&... uis
) {
956 auto linked
= lastUiIsLinked(uis
...);
958 if (env
.loc
!= NoLocalId
&& (linked
||
959 pf
== PushFlags::MarkLive
||
960 pf
== PushFlags::PopOutputs
)) {
961 addLocGen(env
, env
.loc
);
965 case PushFlags::MarkLive
:
966 markUisLive(env
, linked
, uis
...);
969 case PushFlags::MarkDead
:
970 commitUis(env
, linked
, uis
...);
973 case PushFlags::MarkUnused
: {
974 // Our outputs are unused, their consumers are being removed,
975 // and we don't care about our inputs. Replace with
976 // appropriately unused pops of the inputs.
977 auto& ui
= combineUis(uis
...);
978 // use emplace so that the callee can override the action
979 ui
.actions
.emplace(env
.id
, DceAction::PopInputs
);
980 commitActions(env
, linked
, ui
.actions
);
981 if (numPop(env
.op
)) {
982 ignoreInputs(env
, linked
, {{ env
.id
, DceAction::Kill
}});
987 case PushFlags::PopOutputs
:
988 popOutputs(env
, linked
, uis
...);
991 case PushFlags::AddElemC
: {
993 auto& ui
= combineUis(uis
...);
994 ui
.usage
= Use::AddElemC
;
995 // For the last AddElemC, we will already have added a Replace
996 // action for env.id - so the following emplace will silently
997 // fail; for the rest, we just want to kill the AddElemC
998 ui
.actions
.emplace(env
.id
, DceAction::Kill
);
999 pop(env
, std::move(ui
));
1001 // The key is known; we must drop it if we convert to New*Array.
1002 pop(env
, Use::Not
| Use::Linked
, DceActionMap
{});
1004 // The value going into the array is a normal use.
1009 pop_inputs(env
, numPop(env
.op
));
1012 template<typename F
>
1013 auto stack_ops(Env
& env
, F fun
)
1014 -> decltype(fun(std::declval
<UseInfo
&>()),void(0)) {
1015 always_assert(!env
.dceState
.stack
.empty());
1016 auto ui
= std::move(env
.dceState
.stack
.back());
1017 env
.dceState
.stack
.pop_back();
1018 FTRACE(2, " stack_ops({}@{})\n", show(ui
.usage
), show(ui
.actions
));
1019 env
.loc
= NoLocalId
;
1020 auto const f
= fun(ui
);
1021 handle_push(env
, f
, ui
);
1024 void stack_ops(Env
& env
) {
1025 stack_ops(env
, [](const UseInfo
&) { return PushFlags::MarkLive
; });
1028 template<typename F
>
1029 auto stack_ops(Env
& env
, F fun
)
1030 -> decltype(fun(std::declval
<UseInfo
&>(), std::declval
<UseInfo
&>()),void(0)) {
1031 always_assert(env
.dceState
.stack
.size() >= 2);
1032 auto u1
= std::move(env
.dceState
.stack
.back());
1033 env
.dceState
.stack
.pop_back();
1034 auto u2
= std::move(env
.dceState
.stack
.back());
1035 env
.dceState
.stack
.pop_back();
1036 FTRACE(2, " stack_ops({}@{},{}@{})\n",
1037 show(u1
.usage
), show(u1
.actions
), show(u1
.usage
), show(u2
.actions
));
1038 env
.loc
= NoLocalId
;
1039 auto const f
= fun(u1
, u2
);
1040 handle_push(env
, f
, u1
, u2
);
1043 void push_outputs(Env
& env
, uint32_t numPush
) {
1044 for (auto i
= uint32_t{0}; i
< numPush
; ++i
) {
1045 auto ui
= std::move(env
.dceState
.stack
.back());
1046 env
.dceState
.stack
.pop_back();
1047 markUisLive(env
, isLinked(ui
), ui
);
1051 void pushRemovable(Env
& env
) {
1052 stack_ops(env
,[] (const UseInfo
& ui
) {
1053 return allUnused(ui
) ? PushFlags::MarkUnused
: PushFlags::MarkLive
;
1057 void pushRemovableIfNoThrow(Env
& env
) {
1058 stack_ops(env
,[&] (const UseInfo
& ui
) {
1059 return !env
.flags
.wasPEI
&& allUnused(ui
)
1060 ? PushFlags::MarkUnused
: PushFlags::MarkLive
;
1064 //////////////////////////////////////////////////////////////////////
1067 * Note that the instructions with popConds are relying on the consumer of the
1068 * values they push to check whether lifetime changes can have side-effects.
1070 * For example, in bytecode like this, assuming $x is an object with a
1076 * PopC $x // dtor should be here.
1078 * The PopC will decide it can't be eliminated, which prevents us from
1079 * eliminating the CGetL.
1082 void dce(Env
& env
, const bc::PopC
&) { discard(env
); }
1083 void dce(Env
& env
, const bc::PopU
&) { discard(env
); }
1084 void dce(Env
& env
, const bc::PopU2
&) {
1085 auto ui
= std::move(env
.dceState
.stack
.back());
1086 env
.dceState
.stack
.pop_back();
1088 // this is way too conservative; but hopefully the PopU2 will be
1089 // killed (it always should be) and we'll do another pass and fix
1091 markUisLive(env
, true, ui
);
1092 ui
= UseInfo
{ Use::Used
};
1094 ui
.actions
.emplace(env
.id
, DceAction
{ DceAction::AdjustPop
, 1 });
1097 env
.dceState
.stack
.push_back(std::move(ui
));
1099 void dce(Env
& env
, const bc::PopFrame
& op
) {
1100 std::vector
<UseInfo
> uis
;
1101 for (uint32_t i
= 0; i
< op
.arg1
; i
++) {
1102 uis
.emplace_back(std::move(env
.dceState
.stack
.back()));
1103 env
.dceState
.stack
.pop_back();
1105 // As above this is overly conservative but in all likelihood won't matter
1106 if (isLinked(uis
.back())) {
1107 markUisLive(env
, true, uis
.back());
1108 uis
.back() = UseInfo
{ Use::Used
};
1110 uis
.back().actions
.emplace(env
.id
, DceAction
{ DceAction::AdjustPop
, 1 });
1114 // Pop the Uninits from the frame
1115 pop(env
, Use::Not
, DceActionMap
{});
1116 pop(env
, Use::Not
|Use::Linked
, DceActionMap
{});
1117 pop(env
, Use::Not
|Use::Linked
, DceActionMap
{{ env
.id
, DceAction::Kill
}});
1119 for (auto it
= uis
.rbegin(); it
!= uis
.rend(); it
++) {
1120 env
.dceState
.stack
.push_back(std::move(*it
));
1124 void dce(Env
& env
, const bc::Int
&) { pushRemovable(env
); }
1125 void dce(Env
& env
, const bc::String
&) { pushRemovable(env
); }
1126 void dce(Env
& env
, const bc::Dict
&) { pushRemovable(env
); }
1127 void dce(Env
& env
, const bc::Vec
&) { pushRemovable(env
); }
1128 void dce(Env
& env
, const bc::Keyset
&) { pushRemovable(env
); }
1129 void dce(Env
& env
, const bc::Double
&) { pushRemovable(env
); }
1130 void dce(Env
& env
, const bc::True
&) { pushRemovable(env
); }
1131 void dce(Env
& env
, const bc::False
&) { pushRemovable(env
); }
1132 void dce(Env
& env
, const bc::Null
&) { pushRemovable(env
); }
1133 void dce(Env
& env
, const bc::NullUninit
&) { pushRemovable(env
); }
1134 void dce(Env
& env
, const bc::File
&) { pushRemovable(env
); }
1135 void dce(Env
& env
, const bc::Dir
&) { pushRemovable(env
); }
1136 void dce(Env
& env
, const bc::FuncCred
&) { pushRemovable(env
); }
1137 void dce(Env
& env
, const bc::NewArray
&) { pushRemovable(env
); }
1138 void dce(Env
& env
, const bc::NewCol
&) { pushRemovable(env
); }
1139 void dce(Env
& env
, const bc::CheckProp
&) { pushRemovable(env
); }
1141 void dce(Env
& env
, const bc::Dup
&) {
1143 // - If the duplicated value is not used, delete the dup and all
1144 // its consumers, then
1145 // o If the original value is unused pass that on to let the
1146 // instruction which pushed it decide whether to kill it
1147 // o Otherwise we're done.
1148 // - Otherwise, if the original value is unused, kill the dup
1149 // and all dependent instructions.
1150 stack_ops(env
, [&] (UseInfo
& dup
, UseInfo
& orig
) {
1151 // Dup pushes a cell that is guaranteed to be not the last reference.
1152 // So, it can be eliminated if the cell it pushes is used as Use::Not.
1153 auto const dup_unused
= allUnusedIfNotLastRef(dup
);
1154 auto const orig_unused
= allUnused(orig
) &&
1155 (!isLinked(dup
) || dup_unused
);
1157 if (dup_unused
&& orig_unused
) {
1158 return PushFlags::MarkUnused
;
1162 markUisLive(env
, isLinked(orig
), orig
);
1163 orig
.actions
.clear();
1164 return PushFlags::MarkDead
;
1168 markUisLive(env
, false, dup
);
1169 dup
.actions
.clear();
1170 return PushFlags::MarkDead
;
1173 return PushFlags::MarkLive
;
1177 void cgetImpl(Env
& env
, LocalId loc
, bool quiet
) {
1178 stack_ops(env
, [&] (UseInfo
& ui
) {
1179 scheduleGenLoc(env
, loc
);
1180 if (allUnused(ui
) &&
1181 (quiet
|| !readCouldHaveSideEffects(locRaw(env
, loc
)))) {
1182 return PushFlags::MarkUnused
;
1184 if (!isLocLive(env
, loc
) &&
1185 !readCouldHaveSideEffects(locRaw(env
, loc
)) &&
1186 !isLocVolatile(env
, loc
)) {
1187 // note: PushL does not deal with Uninit, so we need the
1188 // readCouldHaveSideEffects here, regardless of quiet.
1189 env
.dceState
.replaceMap
.insert({ env
.id
, { bc::PushL
{ loc
} } });
1190 env
.dceState
.actionMap
.emplace(env
.id
, DceAction::Replace
);
1192 return PushFlags::MarkLive
;
1196 void dce(Env
& env
, const bc::CGetL
& op
) {
1197 addLocNameUse(env
, op
.nloc1
);
1198 cgetImpl(env
, op
.nloc1
.id
, false);
1201 void dce(Env
& env
, const bc::CGetQuietL
& op
) {
1202 cgetImpl(env
, op
.loc1
, true);
1205 void dce(Env
& env
, const bc::CUGetL
& op
) {
1206 cgetImpl(env
, op
.loc1
, true);
1209 void dce(Env
& env
, const bc::PushL
& op
) {
1210 stack_ops(env
, [&] (UseInfo
& ui
) {
1211 scheduleGenLoc(env
, op
.loc1
);
1212 if (allUnused(ui
)) {
1213 if (isLocLive(env
, op
.loc1
)) {
1214 env
.dceState
.replaceMap
.insert(
1215 { env
.id
, { bc::UnsetL
{ op
.loc1
} } });
1216 ui
.actions
.emplace(env
.id
, DceAction::Replace
);
1218 return PushFlags::MarkUnused
;
1220 return PushFlags::MarkLive
;
1224 void dce(Env
& env
, const bc::CGetL2
& op
) {
1225 addLocNameUse(env
, op
.nloc1
);
1226 auto const ty
= locRaw(env
, op
.nloc1
.id
);
1228 stack_ops(env
, [&] (const UseInfo
& u1
, const UseInfo
& u2
) {
1229 scheduleGenLoc(env
, op
.nloc1
.id
);
1230 if (readCouldHaveSideEffects(ty
) || !allUnused(u1
, u2
)) {
1231 return PushFlags::MarkLive
;
1233 return PushFlags::MarkUnused
;
1237 void dce(Env
& env
, const bc::BareThis
& op
) {
1238 stack_ops(env
, [&] (UseInfo
& ui
) {
1239 if (allUnusedIfNotLastRef(ui
) &&
1240 op
.subop1
!= BareThisOp::Notice
) {
1241 return PushFlags::MarkUnused
;
1243 return PushFlags::MarkLive
;
1247 void dce(Env
& env
, const bc::RetC
&) { pop(env
); }
1248 void dce(Env
& env
, const bc::RetCSuspended
&) { pop(env
); }
1249 void dce(Env
& env
, const bc::Throw
&) { pop(env
); }
1250 void dce(Env
& env
, const bc::Fatal
&) { pop(env
); }
1251 void dce(Env
& env
, const bc::Exit
&) { stack_ops(env
); }
1253 void dce(Env
& env
, const bc::IsTypeC
& op
) {
1254 stack_ops(env
, [&] (UseInfo
& ui
) {
1255 if (allUnused(ui
) &&
1256 !is_type_might_raise(type_of_istype(op
.subop1
), topC(env
))) {
1257 return PushFlags::MarkUnused
;
1259 return PushFlags::MarkLive
;
1263 void dce(Env
& env
, const bc::IsTypeL
& op
) {
1264 addLocNameUse(env
, op
.nloc1
);
1265 auto const ty
= locRaw(env
, op
.nloc1
.id
);
1266 stack_ops(env
, [&] (UseInfo
& ui
) {
1267 scheduleGenLoc(env
, op
.nloc1
.id
);
1268 if (allUnused(ui
) &&
1269 !readCouldHaveSideEffects(ty
) &&
1270 !is_type_might_raise(type_of_istype(op
.subop2
), ty
)) {
1271 return PushFlags::MarkUnused
;
1273 return PushFlags::MarkLive
;
1277 void dce(Env
& env
, const bc::Array
& op
) {
1278 stack_ops(env
, [&] (UseInfo
& ui
) {
1279 if (allUnusedIfNotLastRef(ui
)) return PushFlags::MarkUnused
;
1281 if (ui
.usage
!= Use::AddElemC
) return PushFlags::MarkLive
;
1283 assert(!env
.dceState
.isLocal
);
1285 CompactVector
<Bytecode
> bcs
;
1286 IterateV(op
.arr1
, [&] (TypedValue v
) {
1287 bcs
.push_back(gen_constant(v
));
1289 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1290 ui
.actions
[env
.id
] = DceAction::Replace
;
1291 env
.dceState
.didAddOpts
= true;
1292 return PushFlags::MarkUnused
;
1296 void dce(Env
& env
, const bc::NewMixedArray
&) {
1297 stack_ops(env
, [&] (const UseInfo
& ui
) {
1298 if (ui
.usage
== Use::AddElemC
|| allUnused(ui
)) {
1299 env
.dceState
.didAddOpts
= true;
1300 return PushFlags::MarkUnused
;
1303 return PushFlags::MarkLive
;
1307 void dce(Env
& env
, const bc::NewDictArray
&) {
1308 stack_ops(env
, [&] (const UseInfo
& ui
) {
1309 if (ui
.usage
== Use::AddElemC
|| allUnused(ui
)) {
1310 env
.dceState
.didAddOpts
= true;
1311 return PushFlags::MarkUnused
;
1314 return PushFlags::MarkLive
;
1318 void dce(Env
& env
, const bc::NewDArray
&) {
1319 stack_ops(env
, [&] (const UseInfo
& ui
) {
1320 if (ui
.usage
== Use::AddElemC
|| allUnused(ui
)) {
1321 env
.dceState
.didAddOpts
= true;
1322 return PushFlags::MarkUnused
;
1325 return PushFlags::MarkLive
;
1329 void dce(Env
& env
, const bc::AddElemC
& /*op*/) {
1330 stack_ops(env
, [&] (UseInfo
& ui
) {
1331 // If the set might throw it needs to be kept.
1332 if (env
.flags
.wasPEI
) {
1333 return PushFlags::MarkLive
;
1336 if (allUnused(ui
)) {
1337 return PushFlags::MarkUnused
;
1340 if (env
.dceState
.isLocal
) {
1341 // Converting to New*Array changes the stack layout,
1342 // which can invalidate the FuncAnalysis; only do it in global
1343 // dce, since we're going to recompute the FuncAnalysis anyway.
1344 return PushFlags::MarkLive
;
1347 auto const& arrPost
= env
.stateAfter
.stack
.back().type
;
1348 auto const& arrPre
= topC(env
, 2);
1349 auto const postSize
= arr_size(arrPost
);
1350 if (!postSize
|| postSize
== arr_size(arrPre
)) {
1351 // if postSize is known, and equal to preSize, the AddElemC
1352 // didn't add an element (duplicate key) so we have to give up.
1353 // if its not known, we also have nothing to do.
1354 return PushFlags::MarkLive
;
1356 if (ui
.usage
== Use::AddElemC
) {
1357 return PushFlags::AddElemC
;
1359 auto const cat
= categorize_array(arrPost
);
1361 if (allUnusedIfNotLastRef(ui
)) return PushFlags::MarkUnused
;
1362 auto v
= tv(arrPost
);
1363 CompactVector
<Bytecode
> bcs
;
1364 if (arrPost
.subtypeOf(BArrN
)) {
1365 bcs
.emplace_back(bc::Array
{ v
->m_data
.parr
});
1367 assert(arrPost
.subtypeOf(BDictN
));
1368 bcs
.emplace_back(bc::Dict
{ v
->m_data
.parr
});
1370 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1371 ui
.actions
[env
.id
] = DceAction::PopAndReplace
;
1372 return PushFlags::MarkUnused
;
1375 if (isLinked(ui
)) return PushFlags::MarkLive
;
1377 if (arrPost
.strictSubtypeOf(TArrN
)) {
1378 CompactVector
<Bytecode
> bcs
;
1379 if (cat
.cat
== Type::ArrayCat::Struct
&&
1380 *postSize
<= ArrayData::MaxElemsOnStack
) {
1381 if (arrPost
.subtypeOf(BPArrN
)) {
1382 bcs
.emplace_back(bc::NewStructArray
{ get_string_keys(arrPost
) });
1383 } else if (arrPost
.subtypeOf(BDArrN
)) {
1384 bcs
.emplace_back(bc::NewStructDArray
{ get_string_keys(arrPost
) });
1386 return PushFlags::MarkLive
;
1388 } else if (cat
.cat
== Type::ArrayCat::Packed
&&
1389 *postSize
<= ArrayData::MaxElemsOnStack
) {
1390 if (arrPost
.subtypeOf(BPArrN
)) {
1392 bc::NewPackedArray
{ static_cast<uint32_t>(*postSize
) }
1394 } else if (arrPost
.subtypeOf(BVArrN
)) {
1396 bc::NewVArray
{ static_cast<uint32_t>(*postSize
) }
1399 return PushFlags::MarkLive
;
1402 return PushFlags::MarkLive
;
1404 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1405 ui
.actions
[env
.id
] = DceAction::Replace
;
1406 return PushFlags::AddElemC
;
1409 if (arrPost
.strictSubtypeOf(TDictN
) &&
1410 cat
.cat
== Type::ArrayCat::Struct
&&
1411 *postSize
<= ArrayData::MaxElemsOnStack
) {
1412 CompactVector
<Bytecode
> bcs
;
1413 bcs
.emplace_back(bc::NewStructDict
{ get_string_keys(arrPost
) });
1414 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1415 ui
.actions
[env
.id
] = DceAction::Replace
;
1416 return PushFlags::AddElemC
;
1419 return PushFlags::MarkLive
;
1423 template<typename Op
>
1424 void dceNewArrayLike(Env
& env
, const Op
& op
) {
1425 if (op
.numPop() == 1 &&
1426 !env
.flags
.wasPEI
&&
1427 allUnusedIfNotLastRef(env
.dceState
.stack
.back())) {
1428 // Just an optimization for when we have a single element array,
1429 // but we care about its lifetime. By killing the array, and
1430 // leaving its element on the stack, the lifetime is unaffected.
1431 return markDead(env
);
1433 pushRemovableIfNoThrow(env
);
1436 void dce(Env
& env
, const bc::NewPackedArray
& op
) { dceNewArrayLike(env
, op
); }
1437 void dce(Env
& env
, const bc::NewStructArray
& op
) { dceNewArrayLike(env
, op
); }
1438 void dce(Env
& env
, const bc::NewStructDArray
& op
) { dceNewArrayLike(env
, op
); }
1439 void dce(Env
& env
, const bc::NewStructDict
& op
) { dceNewArrayLike(env
, op
); }
1440 void dce(Env
& env
, const bc::NewVecArray
& op
) { dceNewArrayLike(env
, op
); }
1441 void dce(Env
& env
, const bc::NewKeysetArray
& op
) { dceNewArrayLike(env
, op
); }
1442 void dce(Env
& env
, const bc::NewVArray
& op
) { dceNewArrayLike(env
, op
); }
1444 void dce(Env
& env
, const bc::NewPair
& op
) { dceNewArrayLike(env
, op
); }
1445 void dce(Env
& env
, const bc::ColFromArray
& op
) { dceNewArrayLike(env
, op
); }
1447 void dce(Env
& env
, const bc::PopL
& op
) {
1448 if (!isLocLive(env
, op
.loc1
) && !isLocVolatile(env
, op
.loc1
)) {
1450 env
.dceState
.actionMap
[env
.id
] = DceAction::PopInputs
;
1454 if (isLocVolatile(env
, op
.loc1
)) {
1455 addLocGen(env
, op
.loc1
);
1457 addLocKill(env
, op
.loc1
);
1461 void dce(Env
& env
, const bc::InitThisLoc
& op
) {
1462 if (!isLocLive(env
, op
.loc1
)) {
1463 return markDead(env
);
1467 void dce(Env
& env
, const bc::SetL
& op
) {
1468 if (!isLocLive(env
, op
.loc1
) && !isLocVolatile(env
, op
.loc1
)) {
1469 return markDead(env
);
1471 stack_ops(env
, [&] (UseInfo
& ui
) {
1472 if (!allUnusedIfNotLastRef(ui
)) return PushFlags::MarkLive
;
1473 // If the stack result of the SetL is unused, we can replace it
1475 CompactVector
<Bytecode
> bcs
{ bc::PopL
{ op
.loc1
} };
1476 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1477 ui
.actions
[env
.id
] = DceAction::Replace
;
1478 return PushFlags::MarkDead
;
1480 if (isLocVolatile(env
, op
.loc1
)) {
1481 addLocGen(env
, op
.loc1
);
1483 addLocKill(env
, op
.loc1
);
1487 void dce(Env
& env
, const bc::UnsetL
& op
) {
1488 auto const oldTy
= locRaw(env
, op
.loc1
);
1489 if (oldTy
.subtypeOf(BUninit
)) return markDead(env
);
1491 // During local dce we assume all unsets that could free heap objects are
1492 // beneficial. This prevents local dce from removing UnsetLs that global
1493 // dce inserted using global knowledge. This is unnecessary right now,
1494 // because local dce assumes that all locals are live at the end of the
1495 // block, but this future proofs the UnsetL insertion logic in the event this
1497 auto const couldFreeHeapObj
= !oldTy
.subtypeOf(TUnc
);
1498 auto const beneficial
= couldFreeHeapObj
&&
1499 (env
.dceState
.isLocal
|| isDeadAcross(env
, op
.loc1
));
1500 auto const effects
= isLocVolatile(env
, op
.loc1
);
1501 if (!isLocLive(env
, op
.loc1
) && !effects
&& !beneficial
) {
1502 return markDead(env
);
1505 addLocGen(env
, op
.loc1
);
1507 addLocKill(env
, op
.loc1
);
1512 * IncDecL is a read-modify-write: can be removed if the local isn't
1513 * live, the set can't have side effects, and no one reads the value
1514 * it pushes. If the instruction is not dead, add the local to the
1515 * set of upward exposed uses.
1517 void dce(Env
& env
, const bc::IncDecL
& op
) {
1518 addLocNameUse(env
, op
.nloc1
);
1519 auto const oldTy
= locRaw(env
, op
.nloc1
.id
);
1520 auto const effects
= readCouldHaveSideEffects(oldTy
) ||
1521 isLocVolatile(env
, op
.nloc1
.id
);
1522 stack_ops(env
, [&] (const UseInfo
& ui
) {
1523 scheduleGenLoc(env
, op
.nloc1
.id
);
1524 if (!isLocLive(env
, op
.nloc1
.id
) && !effects
&& allUnused(ui
)) {
1525 return PushFlags::MarkUnused
;
1527 return PushFlags::MarkLive
;
1531 bool setOpLSideEffects(const bc::SetOpL
& op
, const Type
& lhs
, const Type
& rhs
) {
1532 auto const lhsOk
= lhs
.subtypeOfAny(TUninit
, TNull
, TBool
, TInt
, TDbl
, TStr
);
1533 auto const rhsOk
= rhs
.subtypeOfAny(TNull
, TBool
, TInt
, TDbl
, TStr
);
1534 if (!lhsOk
|| !rhsOk
) return true;
1536 switch (op
.subop2
) {
1537 case SetOpOp::ConcatEqual
:
1540 case SetOpOp::PlusEqual
:
1541 case SetOpOp::PlusEqualO
:
1542 case SetOpOp::MinusEqual
:
1543 case SetOpOp::MulEqual
:
1544 case SetOpOp::DivEqual
:
1545 case SetOpOp::ModEqual
:
1546 case SetOpOp::PowEqual
:
1547 case SetOpOp::AndEqual
:
1548 case SetOpOp::OrEqual
:
1549 case SetOpOp::XorEqual
:
1550 case SetOpOp::MinusEqualO
:
1551 case SetOpOp::MulEqualO
:
1552 case SetOpOp::SlEqual
:
1553 case SetOpOp::SrEqual
:
1554 return lhs
.subtypeOf(BStr
) || rhs
.subtypeOf(BStr
);
1560 * SetOpL is like IncDecL, but with the complication that we don't
1561 * know if we can mark it dead when visiting it, because it is going
1562 * to pop an input but unlike SetL doesn't push the value it popped.
1563 * However, since we've checked the input types, it *is* ok to just
1566 void dce(Env
& env
, const bc::SetOpL
& op
) {
1567 auto const oldTy
= locRaw(env
, op
.loc1
);
1569 stack_ops(env
, [&] (UseInfo
& ui
) {
1570 scheduleGenLoc(env
, op
.loc1
);
1571 if (!isLocLive(env
, op
.loc1
) && allUnused(ui
)) {
1572 if (!readCouldHaveSideEffects(oldTy
) &&
1573 !isLocVolatile(env
, op
.loc1
) &&
1574 !setOpLSideEffects(op
, oldTy
, topC(env
))) {
1575 return PushFlags::MarkUnused
;
1578 return PushFlags::MarkLive
;
1582 void dce(Env
& env
, const bc::Add
&) { pushRemovableIfNoThrow(env
); }
1583 void dce(Env
& env
, const bc::AddNewElemC
&) { pushRemovableIfNoThrow(env
); }
1584 void dce(Env
& env
, const bc::AddO
&) { pushRemovableIfNoThrow(env
); }
1585 void dce(Env
& env
, const bc::AKExists
&) { pushRemovableIfNoThrow(env
); }
1586 void dce(Env
& env
, const bc::ArrayIdx
&) { pushRemovableIfNoThrow(env
); }
1587 void dce(Env
& env
, const bc::BitAnd
&) { pushRemovableIfNoThrow(env
); }
1588 void dce(Env
& env
, const bc::BitNot
&) { pushRemovableIfNoThrow(env
); }
1589 void dce(Env
& env
, const bc::BitOr
&) { pushRemovableIfNoThrow(env
); }
1590 void dce(Env
& env
, const bc::BitXor
&) { pushRemovableIfNoThrow(env
); }
1591 void dce(Env
& env
, const bc::CastArray
&) { pushRemovableIfNoThrow(env
); }
1592 void dce(Env
& env
, const bc::CastBool
&) { pushRemovableIfNoThrow(env
); }
1593 void dce(Env
& env
, const bc::CastDArray
&) { pushRemovableIfNoThrow(env
); }
1594 void dce(Env
& env
, const bc::CastDict
&) { pushRemovableIfNoThrow(env
); }
1595 void dce(Env
& env
, const bc::CastDouble
&) { pushRemovableIfNoThrow(env
); }
1596 void dce(Env
& env
, const bc::CastInt
&) { pushRemovableIfNoThrow(env
); }
1597 void dce(Env
& env
, const bc::CastKeyset
&) { pushRemovableIfNoThrow(env
); }
1598 void dce(Env
& env
, const bc::CastString
&) { pushRemovableIfNoThrow(env
); }
1599 void dce(Env
& env
, const bc::CastVArray
&) { pushRemovableIfNoThrow(env
); }
1600 void dce(Env
& env
, const bc::CastVec
&) { pushRemovableIfNoThrow(env
); }
1601 void dce(Env
& env
, const bc::Cmp
&) { pushRemovableIfNoThrow(env
); }
1602 void dce(Env
& env
, const bc::CombineAndResolveTypeStruct
&) {
1603 pushRemovableIfNoThrow(env
);
1605 void dce(Env
& env
, const bc::Concat
&) { pushRemovableIfNoThrow(env
); }
1606 void dce(Env
& env
, const bc::ConcatN
&) { pushRemovableIfNoThrow(env
); }
1607 void dce(Env
& env
, const bc::DblAsBits
&) { pushRemovableIfNoThrow(env
); }
1608 void dce(Env
& env
, const bc::Div
&) { pushRemovableIfNoThrow(env
); }
1609 void dce(Env
& env
, const bc::Eq
&) { pushRemovableIfNoThrow(env
); }
1610 void dce(Env
& env
, const bc::Gt
&) { pushRemovableIfNoThrow(env
); }
1611 void dce(Env
& env
, const bc::Gte
&) { pushRemovableIfNoThrow(env
); }
1612 void dce(Env
& env
, const bc::Idx
&) { pushRemovableIfNoThrow(env
); }
1613 void dce(Env
& env
, const bc::IsLateBoundCls
&) { pushRemovableIfNoThrow(env
); }
1614 void dce(Env
& env
, const bc::IsTypeStructC
&) { pushRemovableIfNoThrow(env
); }
1615 void dce(Env
& env
, const bc::Lt
&) { pushRemovableIfNoThrow(env
); }
1616 void dce(Env
& env
, const bc::Lte
&) { pushRemovableIfNoThrow(env
); }
1617 void dce(Env
& env
, const bc::Mod
&) { pushRemovableIfNoThrow(env
); }
1618 void dce(Env
& env
, const bc::Mul
&) { pushRemovableIfNoThrow(env
); }
1619 void dce(Env
& env
, const bc::MulO
&) { pushRemovableIfNoThrow(env
); }
1620 void dce(Env
& env
, const bc::Neq
&) { pushRemovableIfNoThrow(env
); }
1621 void dce(Env
& env
, const bc::Not
&) { pushRemovableIfNoThrow(env
); }
1622 void dce(Env
& env
, const bc::NSame
&) { pushRemovableIfNoThrow(env
); }
1623 void dce(Env
& env
, const bc::Pow
&) { pushRemovableIfNoThrow(env
); }
1624 void dce(Env
& env
, const bc::Same
&) { pushRemovableIfNoThrow(env
); }
1625 void dce(Env
& env
, const bc::Shl
&) { pushRemovableIfNoThrow(env
); }
1626 void dce(Env
& env
, const bc::Shr
&) { pushRemovableIfNoThrow(env
); }
1627 void dce(Env
& env
, const bc::Sub
&) { pushRemovableIfNoThrow(env
); }
1628 void dce(Env
& env
, const bc::SubO
&) { pushRemovableIfNoThrow(env
); }
1629 void dce(Env
& env
, const bc::Xor
&) { pushRemovableIfNoThrow(env
); }
1630 void dce(Env
& env
, const bc::LateBoundCls
&) { pushRemovableIfNoThrow(env
); }
1631 void dce(Env
& env
, const bc::Self
&) { pushRemovableIfNoThrow(env
); }
1632 void dce(Env
& env
, const bc::Parent
&) { pushRemovableIfNoThrow(env
); }
1633 void dce(Env
& env
, const bc::ClassName
&) { pushRemovableIfNoThrow(env
); }
1634 void dce(Env
& env
, const bc::ClassGetC
&) { pushRemovableIfNoThrow(env
); }
1637 * Default implementation is conservative: assume we use all of our
1638 * inputs, and can't be removed even if our output is unused.
1640 * We also assume all the locals in the mayReadLocalSet must be
1641 * added to the live local set, and don't remove anything from it.
1645 void no_dce(Env
& env
, const Op
& op
) {
1646 addLocGenSet(env
, env
.flags
.mayReadLocalSet
);
1647 push_outputs(env
, op
.numPush());
1648 pop_inputs(env
, op
.numPop());
1651 void dce(Env
& env
, const bc::AssertRATL
& op
) { no_dce(env
, op
); }
1652 void dce(Env
& env
, const bc::AssertRATStk
& op
) { no_dce(env
, op
); }
1653 void dce(Env
& env
, const bc::Await
& op
) { no_dce(env
, op
); }
1654 void dce(Env
& env
, const bc::AwaitAll
& op
) {
1655 pinLocals(env
, env
.flags
.mayReadLocalSet
);
1658 void dce(Env
& env
, const bc::BaseGL
& op
) { no_dce(env
, op
); }
1659 void dce(Env
& env
, const bc::BaseH
& op
) { no_dce(env
, op
); }
1660 void dce(Env
& env
, const bc::BreakTraceHint
& op
) { no_dce(env
, op
); }
1661 void dce(Env
& env
, const bc::CGetCUNop
& op
) { no_dce(env
, op
); }
1662 void dce(Env
& env
, const bc::CGetG
& op
) { no_dce(env
, op
); }
1663 void dce(Env
& env
, const bc::CGetS
& op
) { no_dce(env
, op
); }
1664 void dce(Env
& env
, const bc::ChainFaults
& op
) { no_dce(env
, op
); }
1665 void dce(Env
& env
, const bc::CheckReifiedGenericMismatch
& op
) {
1668 void dce(Env
& env
, const bc::CheckThis
& op
) { no_dce(env
, op
); }
1669 void dce(Env
& env
, const bc::Clone
& op
) { no_dce(env
, op
); }
1670 void dce(Env
& env
, const bc::ClsCns
& op
) { no_dce(env
, op
); }
1671 void dce(Env
& env
, const bc::ClsCnsD
& op
) { no_dce(env
, op
); }
1672 void dce(Env
& env
, const bc::ClassGetTS
& op
) { no_dce(env
, op
); }
1673 void dce(Env
& env
, const bc::CnsE
& op
) { no_dce(env
, op
); }
1674 void dce(Env
& env
, const bc::ContAssignDelegate
& op
) { no_dce(env
, op
); }
1675 void dce(Env
& env
, const bc::ContCheck
& op
) { no_dce(env
, op
); }
1676 void dce(Env
& env
, const bc::ContCurrent
& op
) { no_dce(env
, op
); }
1677 void dce(Env
& env
, const bc::ContEnter
& op
) { no_dce(env
, op
); }
1678 void dce(Env
& env
, const bc::ContEnterDelegate
& op
) { no_dce(env
, op
); }
1679 void dce(Env
& env
, const bc::ContGetReturn
& op
) { no_dce(env
, op
); }
1680 void dce(Env
& env
, const bc::ContKey
& op
) { no_dce(env
, op
); }
1681 void dce(Env
& env
, const bc::ContRaise
& op
) { no_dce(env
, op
); }
1682 void dce(Env
& env
, const bc::ContUnsetDelegate
& op
) { no_dce(env
, op
); }
1683 void dce(Env
& env
, const bc::ContValid
& op
) { no_dce(env
, op
); }
1684 void dce(Env
& env
, const bc::CreateCl
& op
) { no_dce(env
, op
); }
1685 void dce(Env
& env
, const bc::CreateCont
& op
) { no_dce(env
, op
); }
1686 void dce(Env
& env
, const bc::DefCls
& op
) { no_dce(env
, op
); }
1687 void dce(Env
& env
, const bc::DefClsNop
& op
) { no_dce(env
, op
); }
1688 void dce(Env
& env
, const bc::DefCns
& op
) { no_dce(env
, op
); }
1689 void dce(Env
& env
, const bc::DefRecord
& op
) { no_dce(env
, op
); }
1690 void dce(Env
& env
, const bc::DefTypeAlias
& op
) { no_dce(env
, op
); }
1691 void dce(Env
& env
, const bc::EntryNop
& op
) { no_dce(env
, op
); }
1692 void dce(Env
& env
, const bc::Eval
& op
) { no_dce(env
, op
); }
1693 void dce(Env
& env
, const bc::FCallBuiltin
& op
) { no_dce(env
, op
); }
1694 void dce(Env
& env
, const bc::FCallClsMethod
& op
) { no_dce(env
, op
); }
1695 void dce(Env
& env
, const bc::FCallClsMethodD
& op
) { no_dce(env
, op
); }
1696 void dce(Env
& env
, const bc::FCallClsMethodS
& op
) { no_dce(env
, op
); }
1697 void dce(Env
& env
, const bc::FCallClsMethodSD
& op
) { no_dce(env
, op
); }
1698 void dce(Env
& env
, const bc::FCallCtor
& op
) { no_dce(env
, op
); }
1699 void dce(Env
& env
, const bc::FCallFunc
& op
) { no_dce(env
, op
); }
1700 void dce(Env
& env
, const bc::FCallFuncD
& op
) { no_dce(env
, op
); }
1701 void dce(Env
& env
, const bc::FCallObjMethod
& op
) { no_dce(env
, op
); }
1702 void dce(Env
& env
, const bc::FCallObjMethodD
& op
) { no_dce(env
, op
); }
1703 void dce(Env
& env
, const bc::GetMemoKeyL
& op
) { no_dce(env
, op
); }
1704 void dce(Env
& env
, const bc::IncDecG
& op
) { no_dce(env
, op
); }
1705 void dce(Env
& env
, const bc::IncDecS
& op
) { no_dce(env
, op
); }
1706 void dce(Env
& env
, const bc::Incl
& op
) { no_dce(env
, op
); }
1707 void dce(Env
& env
, const bc::InclOnce
& op
) { no_dce(env
, op
); }
1708 void dce(Env
& env
, const bc::InitProp
& op
) { no_dce(env
, op
); }
1709 void dce(Env
& env
, const bc::InstanceOf
& op
) { no_dce(env
, op
); }
1710 void dce(Env
& env
, const bc::InstanceOfD
& op
) { no_dce(env
, op
); }
1711 void dce(Env
& env
, const bc::IssetG
& op
) { no_dce(env
, op
); }
1712 void dce(Env
& env
, const bc::IssetL
& op
) { no_dce(env
, op
); }
1713 void dce(Env
& env
, const bc::IssetS
& op
) { no_dce(env
, op
); }
1714 void dce(Env
& env
, const bc::IterFree
& op
) { no_dce(env
, op
); }
1715 void dce(Env
& env
, const bc::Jmp
& op
) { no_dce(env
, op
); }
1716 void dce(Env
& env
, const bc::JmpNS
& op
) { no_dce(env
, op
); }
1717 void dce(Env
& env
, const bc::JmpNZ
& op
) { no_dce(env
, op
); }
1718 void dce(Env
& env
, const bc::JmpZ
& op
) { no_dce(env
, op
); }
1719 void dce(Env
& env
, const bc::LIterFree
& op
) { no_dce(env
, op
); }
1720 void dce(Env
& env
, const bc::MemoGet
& op
) {
1721 pinLocals(env
, env
.flags
.mayReadLocalSet
);
1724 void dce(Env
& env
, const bc::MemoGetEager
& op
) {
1725 pinLocals(env
, env
.flags
.mayReadLocalSet
);
1728 void dce(Env
& env
, const bc::MemoSet
& op
) {
1729 pinLocals(env
, env
.flags
.mayReadLocalSet
);
1732 void dce(Env
& env
, const bc::MemoSetEager
& op
) {
1733 pinLocals(env
, env
.flags
.mayReadLocalSet
);
1736 void dce(Env
& env
, const bc::Method
& op
) { no_dce(env
, op
); }
1737 void dce(Env
& env
, const bc::NativeImpl
& op
) { no_dce(env
, op
); }
1738 void dce(Env
& env
, const bc::NewLikeArrayL
& op
) { no_dce(env
, op
); }
1739 void dce(Env
& env
, const bc::NewObj
& op
) { no_dce(env
, op
); }
1740 void dce(Env
& env
, const bc::NewObjR
& op
) { no_dce(env
, op
); }
1741 void dce(Env
& env
, const bc::NewObjD
& op
) { no_dce(env
, op
); }
1742 void dce(Env
& env
, const bc::NewObjRD
& op
) { no_dce(env
, op
); }
1743 void dce(Env
& env
, const bc::NewObjS
& op
) { no_dce(env
, op
); }
1744 void dce(Env
& env
, const bc::LockObj
& op
) { no_dce(env
, op
); }
1745 void dce(Env
& env
, const bc::NewRecord
& op
) { no_dce(env
, op
); }
1746 void dce(Env
& env
, const bc::NewRecordArray
& op
) { no_dce(env
, op
); }
1747 void dce(Env
& env
, const bc::Nop
& op
) { no_dce(env
, op
); }
1748 void dce(Env
& env
, const bc::OODeclExists
& op
) { no_dce(env
, op
); }
1749 void dce(Env
& env
, const bc::Print
& op
) { no_dce(env
, op
); }
1750 void dce(Env
& env
, const bc::RecordReifiedGeneric
& op
) { no_dce(env
, op
); }
1751 void dce(Env
& env
, const bc::Req
& op
) { no_dce(env
, op
); }
1752 void dce(Env
& env
, const bc::ReqDoc
& op
) { no_dce(env
, op
); }
1753 void dce(Env
& env
, const bc::ReqOnce
& op
) { no_dce(env
, op
); }
1754 void dce(Env
& env
, const bc::ResolveClsMethod
& op
) { no_dce(env
, op
); }
1755 void dce(Env
& env
, const bc::ResolveClsMethodD
& op
) { no_dce(env
, op
); }
1756 void dce(Env
& env
, const bc::ResolveClsMethodS
& op
) { no_dce(env
, op
); }
1757 void dce(Env
& env
, const bc::ResolveFunc
& op
) { no_dce(env
, op
); }
1758 void dce(Env
& env
, const bc::ResolveMethCaller
& op
) { no_dce(env
, op
); }
1759 void dce(Env
& env
, const bc::ResolveObjMethod
& op
) { no_dce(env
, op
); }
1760 void dce(Env
& env
, const bc::RetM
& op
) { no_dce(env
, op
); }
1761 void dce(Env
& env
, const bc::Select
& op
) { no_dce(env
, op
); }
1762 void dce(Env
& env
, const bc::SetG
& op
) { no_dce(env
, op
); }
1763 void dce(Env
& env
, const bc::SetOpG
& op
) { no_dce(env
, op
); }
1764 void dce(Env
& env
, const bc::SetOpS
& op
) { no_dce(env
, op
); }
1765 void dce(Env
& env
, const bc::SetRangeM
& op
) { no_dce(env
, op
); }
1766 void dce(Env
& env
, const bc::SetS
& op
) { no_dce(env
, op
); }
1767 void dce(Env
& env
, const bc::Silence
& op
) {
1768 pinLocals(env
, env
.flags
.mayReadLocalSet
);
1771 void dce(Env
& env
, const bc::SSwitch
& op
) { no_dce(env
, op
); }
1772 void dce(Env
& env
, const bc::Switch
& op
) { no_dce(env
, op
); }
1773 void dce(Env
& env
, const bc::This
& op
) { no_dce(env
, op
); }
1774 void dce(Env
& env
, const bc::ThrowAsTypeStructException
& op
) {
1777 void dce(Env
& env
, const bc::ThrowNonExhaustiveSwitch
& op
) { no_dce(env
, op
); }
1778 void dce(Env
& env
, const bc::UGetCUNop
& op
) { no_dce(env
, op
); }
1779 void dce(Env
& env
, const bc::UnsetG
& op
) { no_dce(env
, op
); }
1780 void dce(Env
& env
, const bc::VerifyOutType
& op
) { no_dce(env
, op
); }
1781 void dce(Env
& env
, const bc::VerifyParamType
& op
) { no_dce(env
, op
); }
1782 void dce(Env
& env
, const bc::VerifyParamTypeTS
& op
) { no_dce(env
, op
); }
1783 void dce(Env
& env
, const bc::VerifyRetNonNullC
& op
) { no_dce(env
, op
); }
1784 void dce(Env
& env
, const bc::VerifyRetTypeC
& op
) { no_dce(env
, op
); }
1785 void dce(Env
& env
, const bc::VerifyRetTypeTS
& op
) { no_dce(env
, op
); }
1786 void dce(Env
& env
, const bc::WHResult
& op
) { no_dce(env
, op
); }
1787 void dce(Env
& env
, const bc::Yield
& op
) { no_dce(env
, op
); }
1788 void dce(Env
& env
, const bc::YieldFromDelegate
& op
) { no_dce(env
, op
); }
1789 void dce(Env
& env
, const bc::YieldK
& op
) { no_dce(env
, op
); }
1791 ////////////////////////////////////////////////////////////////////////////////
1793 // Iterator ops don't really read their key and value output locals; they just
1794 // dec-ref the old value there before storing the new one (i.e. SetL semantics).
1796 // We have to mark these values as live inside (else they could be
1797 // completely killed - that is, remap locals could reused their local
1798 // slot), but we don't have to may-read them.
1799 void iter_dce(Env
& env
, const IterArgs
& ita
, LocalId baseId
, int numPop
) {
1800 addLocUse(env
, ita
.valId
);
1801 if (ita
.hasKey()) addLocUse(env
, ita
.keyId
);
1802 if (baseId
!= NoLocalId
) addLocGen(env
, baseId
);
1803 pop_inputs(env
, numPop
);
1806 void dce(Env
& env
, const bc::IterInit
& op
) {
1807 iter_dce(env
, op
.ita
, NoLocalId
, op
.numPop());
1810 void dce(Env
& env
, const bc::LIterInit
& op
) {
1811 iter_dce(env
, op
.ita
, op
.loc2
, op
.numPop());
1814 void dce(Env
& env
, const bc::IterNext
& op
) {
1815 iter_dce(env
, op
.ita
, NoLocalId
, op
.numPop());
1818 void dce(Env
& env
, const bc::LIterNext
& op
) {
1819 iter_dce(env
, op
.ita
, op
.loc2
, op
.numPop());
1822 ////////////////////////////////////////////////////////////////////////////////
1825 * The minstr instructions can read a cell from the stack without
1826 * popping it. They need special handling.
1828 * In addition, final minstr instructions can drop a number of
1829 * elements from the stack. Its possible that these elements are dead
1830 * (eg because an MEC somewhere in the sequence was optimized to MEI
1831 * or MET), so again we need some special handling.
1834 void minstr_touch(Env
& env
, int32_t depth
) {
1835 assertx(depth
< env
.dceState
.stack
.size());
1836 // First make sure that the stack element we reference, and any
1837 // linked ones are marked used.
1838 use(env
.dceState
.forcedLiveLocations
,
1839 env
.dceState
.stack
, env
.dceState
.stack
.size() - 1 - depth
);
1840 // If any stack slots at a lower depth get killed, we'll have to
1841 // adjust our stack index accordingly, so add a MinstrStackFixup
1842 // action to candidate stack slots.
1843 // We only track slots up to kMaskSize though - so nothing below
1844 // that will get killed.
1845 if (depth
> DceAction::kMaskSize
) depth
= DceAction::kMaskSize
;
1847 auto& ui
= env
.dceState
.stack
[env
.dceState
.stack
.size() - 1 - depth
];
1848 if (ui
.usage
!= Use::Used
) {
1849 auto const result
= ui
.actions
.emplace(
1850 env
.id
, DceAction
{ DceAction::MinstrStackFixup
, 1u << depth
}
1852 if (!result
.second
) {
1854 result
.first
->second
.action
== DceAction::MinstrStackFixup
1856 result
.first
->second
.maskOrCount
|= (1u << depth
);
1863 void minstr_base(Env
& env
, const Op
& op
, int32_t ix
) {
1865 minstr_touch(env
, ix
);
1869 void minstr_dim(Env
& env
, const Op
& op
) {
1871 if (op
.mkey
.mcode
== MEC
|| op
.mkey
.mcode
== MPC
) {
1872 minstr_touch(env
, op
.mkey
.idx
);
1874 if (op
.mkey
.mcode
== MEL
|| op
.mkey
.mcode
== MPL
) {
1875 addLocNameUse(env
, op
.mkey
.local
);
1880 void minstr_final(Env
& env
, const Op
& op
, int32_t ndiscard
) {
1881 addLocGenSet(env
, env
.flags
.mayReadLocalSet
);
1882 if (op
.mkey
.mcode
== MEL
|| op
.mkey
.mcode
== MPL
) {
1883 addLocNameUse(env
, op
.mkey
.local
);
1886 push_outputs(env
, op
.numPush());
1887 auto const numPop
= op
.numPop();
1888 auto const stackRead
= op
.mkey
.mcode
== MEC
|| op
.mkey
.mcode
== MPC
?
1891 for (auto i
= numPop
; i
--; ) {
1892 if (i
== stackRead
|| i
>= DceAction::kMaskSize
|| i
< numPop
- ndiscard
) {
1895 pop(env
, {Use::Not
, {{env
.id
, {DceAction::MinstrStackFinal
, 1u << i
}}}});
1899 if (stackRead
>= numPop
) {
1900 assertx(stackRead
< env
.dceState
.stack
.size());
1901 use(env
.dceState
.forcedLiveLocations
,
1902 env
.dceState
.stack
, env
.dceState
.stack
.size() - 1 - stackRead
);
1906 void dce(Env
& env
, const bc::BaseC
& op
) { minstr_base(env
, op
, op
.arg1
); }
1907 void dce(Env
& env
, const bc::BaseGC
& op
) { minstr_base(env
, op
, op
.arg1
); }
1908 void dce(Env
& env
, const bc::BaseSC
& op
) {
1909 minstr_base(env
, op
, op
.arg1
);
1910 minstr_base(env
, op
, op
.arg2
);
1913 void dce(Env
& env
, const bc::BaseL
& op
) {
1914 addLocNameUse(env
, op
.nloc1
);
1915 if (!isLocLive(env
, op
.nloc1
.id
) &&
1916 !readCouldHaveSideEffects(locRaw(env
, op
.nloc1
.id
)) &&
1917 !isLocVolatile(env
, op
.nloc1
.id
)) {
1918 env
.dceState
.actionMap
[env
.id
] = DceAction::MinstrPushBase
;
1923 void dce(Env
& env
, const bc::Dim
& op
) { minstr_dim(env
, op
); }
1925 void dce(Env
& env
, const bc::QueryM
& op
) {
1926 if (!env
.flags
.wasPEI
) {
1927 assertx(!env
.dceState
.minstrUI
);
1928 auto ui
= env
.dceState
.stack
.back();
1929 if (!isLinked(ui
)) {
1930 if (allUnused(ui
)) {
1931 addLocGenSet(env
, env
.flags
.mayReadLocalSet
);
1932 ui
.actions
[env
.id
] = DceAction::Kill
;
1933 ui
.location
.id
= env
.id
.idx
;
1934 env
.dceState
.minstrUI
.emplace(std::move(ui
));
1935 } else if (auto const val
= tv(env
.stateAfter
.stack
.back().type
)) {
1936 addLocGenSet(env
, env
.flags
.mayReadLocalSet
);
1937 CompactVector
<Bytecode
> bcs
{ gen_constant(*val
) };
1938 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1939 ui
.actions
[env
.id
] = DceAction::Replace
;
1940 ui
.location
.id
= env
.id
.idx
;
1941 env
.dceState
.minstrUI
.emplace(std::move(ui
));
1945 minstr_final(env
, op
, op
.arg1
);
1948 void dce(Env
& env
, const bc::SetM
& op
) { minstr_final(env
, op
, op
.arg1
); }
1949 void dce(Env
& env
, const bc::IncDecM
& op
) { minstr_final(env
, op
, op
.arg1
); }
1950 void dce(Env
& env
, const bc::SetOpM
& op
) { minstr_final(env
, op
, op
.arg1
); }
1951 void dce(Env
& env
, const bc::UnsetM
& op
) { minstr_final(env
, op
, op
.arg1
); }
1953 void dispatch_dce(Env
& env
, const Bytecode
& op
) {
1954 #define O(opcode, ...) case Op::opcode: dce(env, op.opcode); return;
1955 switch (op
.op
) { OPCODES
}
1960 using MaskType
= DceAction::MaskOrCountType
;
1962 void m_adj(uint32_t& depth
, MaskType mask
) {
1964 if (i
> DceAction::kMaskSize
) i
= DceAction::kMaskSize
;
1966 if ((mask
>> i
) & 1) depth
--;
1970 void m_adj(MKey
& mkey
, MaskType mask
) {
1971 if (mkey
.mcode
== MEC
|| mkey
.mcode
== MPC
) {
1972 uint32_t d
= mkey
.idx
;
1978 template<typename Op
>
1979 void m_adj(uint32_t& ndiscard
, Op
& op
, MaskType mask
) {
1980 auto const adjust
= op
.numPop() - ndiscard
+ 1;
1982 m_adj(ndiscard
, mask
);
1984 m_adj(op
.mkey
, mask
);
1987 template <typename Op
>
1988 void adjustMinstr(Op
& /*op*/, MaskType
/*mask*/) {
1989 always_assert(false);
1992 void adjustMinstr(bc::BaseC
& op
, MaskType m
) { m_adj(op
.arg1
, m
); }
1993 void adjustMinstr(bc::BaseGC
& op
, MaskType m
) { m_adj(op
.arg1
, m
); }
1994 void adjustMinstr(bc::BaseSC
& op
, MaskType m
) {
1999 void adjustMinstr(bc::Dim
& op
, MaskType m
) { m_adj(op
.mkey
, m
); }
2001 void adjustMinstr(bc::QueryM
& op
, MaskType m
) { m_adj(op
.arg1
, op
, m
); }
2002 void adjustMinstr(bc::SetM
& op
, MaskType m
) { m_adj(op
.arg1
, op
, m
); }
2003 void adjustMinstr(bc::IncDecM
& op
, MaskType m
) { m_adj(op
.arg1
, op
, m
); }
2004 void adjustMinstr(bc::SetOpM
& op
, MaskType m
) { m_adj(op
.arg1
, op
, m
); }
2005 void adjustMinstr(bc::UnsetM
& op
, MaskType m
) { m_adj(op
.arg1
, op
, m
); }
2007 void adjustMinstr(Bytecode
& op
, MaskType m
) {
2008 #define O(opcode, ...) case Op::opcode: adjustMinstr(op.opcode, m); return;
2009 switch (op
.op
) { OPCODES
}
2014 template<typename LocRaw
>
2015 CompactVector
<Bytecode
> eager_unsets(std::bitset
<kMaxTrackedLocals
> candidates
,
2016 const php::Func
* func
,
2018 auto loc
= std::min(safe_cast
<uint32_t>(func
->locals
.size()),
2019 safe_cast
<uint32_t>(kMaxTrackedLocals
));
2020 auto const end
= RuntimeOption::EnableArgsInBacktraces
2021 ? func
->params
.size() + (uint32_t)func
->isReified
2023 CompactVector
<Bytecode
> bcs
;
2024 while (loc
-- > end
) {
2025 if (candidates
[loc
] && !is_volatile_local(func
, loc
)) {
2026 auto const t
= type(loc
);
2027 if (!t
.subtypeOf(TUnc
)) {
2028 bcs
.emplace_back(bc::UnsetL
{ loc
});
2035 //////////////////////////////////////////////////////////////////////
2037 struct DceOutState
{
2038 DceOutState() = default;
2040 explicit DceOutState(Local
) : isLocal(true) {
2046 * The union of the liveIn states of each normal successor for
2047 * locals and stack slots respectively.
2049 std::bitset
<kMaxTrackedLocals
> locLive
;
2052 * The union of the liveIn states of each exceptional successor for
2053 * locals and stack slots respectively.
2055 std::bitset
<kMaxTrackedLocals
> locLiveExn
;
2058 * The union of the deadAcrossIn states of each normal successor for
2061 std::bitset
<kMaxTrackedLocals
> locDeadAcross
;
2064 * The union of the dceStacks from the start of the normal successors.
2066 folly::Optional
<std::vector
<UseInfo
>> dceStack
;
2069 * Whether this is for local_dce
2071 bool isLocal
{false};
2074 folly::Optional
<DceState
>
2075 dce_visit(const Index
& index
,
2076 const FuncAnalysis
& fa
,
2077 CollectedInfo
& collect
,
2079 const State
& stateIn
,
2080 const DceOutState
& dceOutState
,
2081 LocalRemappingIndex
* localRemappingIndex
= nullptr) {
2082 if (!stateIn
.initialized
) {
2084 * Skip unreachable blocks.
2086 * For DCE analysis it is ok to assume the transfer function is
2087 * the identity on unreachable blocks (i.e. gen and kill sets are
2088 * empty). For optimize, we don't need to bother doing anything
2089 * to these---another pass is responsible for removing completely
2090 * unreachable blocks.
2095 auto const states
= locally_propagated_states(index
, fa
, collect
,
2098 auto dceState
= DceState
{ index
, fa
};
2099 dceState
.liveLocals
= dceOutState
.locLive
;
2100 dceState
.deadAcrossLocals
= dceOutState
.locDeadAcross
;
2101 dceState
.isLocal
= dceOutState
.isLocal
;
2102 dceState
.remappingIndex
= localRemappingIndex
;
2104 auto const blk
= fa
.ctx
.func
->blocks
[bid
].get();
2105 auto const dceStkSz
= [&] {
2106 switch (blk
->hhbcs
.back().op
) {
2108 case Op::MemoGetEager
:
2109 // These only push on the "hit" path. If we can prove they
2110 // never hit, the final stack state won't have an extra
2111 // element. But their dce implementations assume that they
2112 // push something. Fix it by just adding one to their in
2113 // state. Note that when dceState.dceStack is populated, we
2114 // already did the equivalent during global analysis (see
2115 // isCFPushTaken below).
2116 return states
[states
.size() - 2].first
.stack
.size() + 1;
2118 return states
.back().first
.stack
.size();
2122 if (dceOutState
.dceStack
) {
2123 dceState
.stack
= *dceOutState
.dceStack
;
2124 assert(dceState
.stack
.size() == dceStkSz
);
2126 dceState
.stack
.resize(dceStkSz
, UseInfo
{ Use::Used
});
2129 for (uint32_t idx
= blk
->hhbcs
.size(); idx
-- > 0;) {
2130 auto const& op
= blk
->hhbcs
[idx
];
2132 FTRACE(2, " == #{} {}\n", idx
, show(fa
.ctx
.func
, op
));
2134 auto visit_env
= Env
{
2141 states
[idx
+1].first
,
2144 auto const handled
= [&] {
2145 if (dceState
.minstrUI
) {
2146 if (visit_env
.flags
.wasPEI
) {
2147 dceState
.minstrUI
.reset();
2150 if (isMemberDimOp(op
.op
)) {
2151 dceState
.minstrUI
->actions
[visit_env
.id
] = DceAction::Kill
;
2152 // we're almost certainly going to delete this op, but we might not,
2153 // so we need to record its local uses etc just in case.
2156 if (isMemberBaseOp(op
.op
)) {
2157 auto const& final
= blk
->hhbcs
[dceState
.minstrUI
->location
.id
];
2158 if (final
.numPop()) {
2159 CompactVector
<Bytecode
> bcs
;
2160 for (auto i
= 0; i
< final
.numPop(); i
++) {
2161 use(dceState
.forcedLiveLocations
,
2162 dceState
.stack
, dceState
.stack
.size() - 1 - i
);
2163 bcs
.push_back(bc::PopC
{});
2165 dceState
.replaceMap
.emplace(visit_env
.id
, std::move(bcs
));
2166 dceState
.minstrUI
->actions
[visit_env
.id
] = DceAction::Replace
;
2168 dceState
.minstrUI
->actions
[visit_env
.id
] = DceAction::Kill
;
2170 commitActions(visit_env
, false, dceState
.minstrUI
->actions
);
2171 dceState
.minstrUI
.reset();
2178 dispatch_dce(visit_env
, op
);
2181 * When we see a PEI, liveness must take into account the fact that we
2182 * could take an exception edge here by or-ing in the locLiveExn set.
2184 if (states
[idx
].second
.wasPEI
) {
2185 FTRACE(2, " <-- exceptions\n");
2186 dceState
.liveLocals
|= dceOutState
.locLiveExn
;
2189 addInterference(visit_env
, dceState
.liveLocals
| visit_env
.liveInside
);
2192 // Any local that is live before this op, dead after this op, and remains
2193 // dead through a Call, Await or Yield may be worth unsetting. We also
2194 // check that we won't be inserting these UnsetLs as the last op in a
2195 // block to prevent a control flow ops from becoming non terminal.
2196 auto const unsetable
= dceState
.deadAcrossLocals
& dceState
.liveLocals
;
2197 if (unsetable
.any() && idx
!= blk
->hhbcs
.size() - 1) {
2198 auto bcs
= eager_unsets(unsetable
, fa
.ctx
.func
, [&](uint32_t i
) {
2199 return visit_env
.stateAfter
.locals
[i
];
2202 dceState
.replaceMap
.emplace(visit_env
.id
, std::move(bcs
));
2203 dceState
.actionMap
.emplace(visit_env
.id
, DceAction::UnsetLocalsAfter
);
2205 // We flag that we shoud rerun DCE since this Unset might be redudant if
2206 // a CGetL gets replaced with a PushL.
2207 visit_env
.dceState
.didAddOpts
= true;
2211 // Clear out anything live.
2212 dceState
.deadAcrossLocals
&= ~(dceState
.liveLocals
| visit_env
.liveInside
);
2214 case Op::Await
: case Op::AwaitAll
: case Op::Yield
:
2215 case Op::YieldK
: case Op::YieldFromDelegate
:
2216 case Op::FCallBuiltin
: case Op::FCallClsMethod
:
2217 case Op::FCallClsMethodD
: case Op::FCallClsMethodS
:
2218 case Op::FCallClsMethodSD
: case Op::FCallCtor
:
2219 case Op::FCallFunc
: case Op::FCallFuncD
:
2220 case Op::FCallObjMethod
: case Op::FCallObjMethodD
:
2221 dceState
.deadAcrossLocals
|= ~dceState
.liveLocals
;
2227 FTRACE(4, " dce frame: {}\n",
2228 loc_bits_string(fa
.ctx
.func
, dceState
.liveLocals
));
2229 FTRACE(4, " dce stack: {}\n",
2231 using namespace folly::gen
;
2232 return from(dceState
.stack
)
2233 | map([&] (const UseInfo
& ui
) { return show(ui
); })
2234 | unsplit
<std::string
>(" ");
2237 FTRACE(4, " interp stack: {}\n",
2239 using namespace folly::gen
;
2240 return from(states
[idx
].first
.stack
)
2241 | map([&] (auto const& e
) { return show(e
.type
); })
2242 | unsplit
<std::string
>(" ");
2245 if (dceState
.minstrUI
) {
2246 FTRACE(4, " minstr ui: {}\n", show(*dceState
.minstrUI
));
2249 // We're now at the state before this instruction, so the stack
2250 // sizes must line up.
2251 assert(dceState
.stack
.size() == states
[idx
].first
.stack
.size());
2254 dceState
.minstrUI
.reset();
2258 struct DceAnalysis
{
2259 std::bitset
<kMaxTrackedLocals
> locLiveIn
;
2260 std::bitset
<kMaxTrackedLocals
> locDeadAcrossIn
;
2261 std::vector
<UseInfo
> stack
;
2262 LocationIdSet forcedLiveLocations
;
2265 DceAnalysis
analyze_dce(const Index
& index
,
2266 const FuncAnalysis
& fa
,
2267 CollectedInfo
& collect
,
2269 const State
& stateIn
,
2270 const DceOutState
& dceOutState
,
2271 LocalRemappingIndex
* localRemappingIndex
= nullptr) {
2272 if (auto dceState
= dce_visit(index
, fa
, collect
,
2273 bid
, stateIn
, dceOutState
,
2274 localRemappingIndex
)) {
2275 return DceAnalysis
{
2276 dceState
->liveLocals
,
2277 dceState
->deadAcrossLocals
,
2279 dceState
->forcedLiveLocations
2282 return DceAnalysis
{};
2286 using has_mkey
= std::is_same
<decltype(((Op
*)0)->mkey
), MKey
>;
2289 void adjust_mkey(Op
& bc
, bool) {}
2292 typename
std::enable_if
<has_mkey
<Op
>::value
>::type
2293 adjust_mkey(Op
& bc
, int64_t adj
) {
2294 if (bc
.mkey
.mcode
== MEC
|| bc
.mkey
.mcode
== MPC
) {
2299 void adjust_member_key(Bytecode
& bc
, int64_t adj
) {
2300 #define O(opcode, ...) case Op::opcode: adjust_mkey(bc.opcode, adj); break;
2301 switch (bc
.op
) { OPCODES
}
2306 using has_arg1
= std::is_same
<decltype(((Op
*)0)->arg1
), uint32_t>;
2309 void adjust_arg1(Op
& bc
, bool) {}
2312 typename
std::enable_if
<has_arg1
<Op
>::value
>::type
2313 adjust_arg1(Op
& bc
, int64_t adj
) {
2317 void adjust_ndiscard(Bytecode
& bc
, int64_t adj
) {
2318 #define O(opcode, ...) \
2320 if (isMemberFinalOp(Op::opcode)) adjust_arg1(bc.opcode, adj); \
2322 switch (bc
.op
) { OPCODES
}
2327 * Do the actual updates to the bytecodes.
2329 void dce_perform(php::Func
& func
,
2330 const DceActionMap
& actionMap
,
2331 const DceReplaceMap
& replaceMap
) {
2333 using It
= BytecodeVec::iterator
;
2334 auto setloc
= [] (int32_t srcLoc
, It start
, int n
) {
2336 start
++->srcLoc
= srcLoc
;
2340 for (auto const& elm
: actionMap
) {
2341 auto const& id
= elm
.first
;
2342 auto const b
= func
.blocks
[id
.blk
].mutate();
2343 FTRACE(1, "{} {}\n", show(elm
), show(func
, b
->hhbcs
[id
.idx
]));
2344 switch (elm
.second
.action
) {
2345 case DceAction::PopInputs
:
2346 // we want to replace the bytecode with pops of its inputs
2347 if (auto const numToPop
= numPop(b
->hhbcs
[id
.idx
])) {
2348 auto const srcLoc
= b
->hhbcs
[id
.idx
].srcLoc
;
2349 b
->hhbcs
.erase(b
->hhbcs
.begin() + id
.idx
);
2350 b
->hhbcs
.insert(b
->hhbcs
.begin() + id
.idx
,
2353 setloc(srcLoc
, b
->hhbcs
.begin() + id
.idx
, numToPop
);
2357 case DceAction::Kill
:
2358 if (b
->hhbcs
.size() == 1) {
2359 // we don't allow empty blocks
2360 auto const srcLoc
= b
->hhbcs
[0].srcLoc
;
2361 b
->hhbcs
[0] = bc::Nop
{};
2362 b
->hhbcs
[0].srcLoc
= srcLoc
;
2364 b
->hhbcs
.erase(b
->hhbcs
.begin() + id
.idx
);
2367 case DceAction::PopOutputs
:
2369 // we want to pop the things that the bytecode pushed
2370 auto const numToPop
= numPush(b
->hhbcs
[id
.idx
]);
2371 b
->hhbcs
.insert(b
->hhbcs
.begin() + id
.idx
+ 1,
2374 setloc(b
->hhbcs
[id
.idx
].srcLoc
,
2375 b
->hhbcs
.begin() + id
.idx
+ 1, numToPop
);
2378 case DceAction::Replace
:
2380 auto it
= replaceMap
.find(id
);
2381 always_assert(it
!= end(replaceMap
) && !it
->second
.empty());
2382 auto const srcLoc
= b
->hhbcs
[id
.idx
].srcLoc
;
2383 b
->hhbcs
.erase(b
->hhbcs
.begin() + id
.idx
);
2384 b
->hhbcs
.insert(b
->hhbcs
.begin() + id
.idx
,
2385 begin(it
->second
), end(it
->second
));
2386 setloc(srcLoc
, b
->hhbcs
.begin() + id
.idx
, it
->second
.size());
2389 case DceAction::PopAndReplace
:
2391 auto it
= replaceMap
.find(id
);
2392 always_assert(it
!= end(replaceMap
) && !it
->second
.empty());
2393 auto const srcLoc
= b
->hhbcs
[id
.idx
].srcLoc
;
2394 auto const numToPop
= numPop(b
->hhbcs
[id
.idx
]);
2395 b
->hhbcs
.erase(b
->hhbcs
.begin() + id
.idx
);
2396 b
->hhbcs
.insert(b
->hhbcs
.begin() + id
.idx
,
2397 begin(it
->second
), end(it
->second
));
2399 b
->hhbcs
.insert(b
->hhbcs
.begin() + id
.idx
,
2403 setloc(srcLoc
, b
->hhbcs
.begin() + id
.idx
, numToPop
+ it
->second
.size());
2406 case DceAction::MinstrStackFinal
:
2407 case DceAction::MinstrStackFixup
:
2409 adjustMinstr(b
->hhbcs
[id
.idx
], elm
.second
.maskOrCount
);
2412 case DceAction::MinstrPushBase
:
2414 assertx(b
->hhbcs
[id
.idx
].op
== OpBaseL
);
2415 auto const base
= b
->hhbcs
[id
.idx
].BaseL
;
2416 auto const srcLoc
= b
->hhbcs
[id
.idx
].srcLoc
;
2417 b
->hhbcs
[id
.idx
] = bc::PushL
{base
.nloc1
.id
};
2419 b
->hhbcs
.begin() + id
.idx
+ 1, bc::BaseC
{0, base
.subop2
});
2420 setloc(srcLoc
, b
->hhbcs
.begin() + id
.idx
, 2);
2421 for (auto p
= id
.idx
+ 2; p
< b
->hhbcs
.size(); ++p
) {
2422 auto const& bc
= b
->hhbcs
[p
];
2424 // Sometimes parts of a minstr will be unreachable, hhbbc marks these
2426 if (bc
.op
== OpFatal
) break;
2428 assertx(p
+ 1 < b
->hhbcs
.size() || isMemberFinalOp(bc
.op
));
2429 adjust_member_key(b
->hhbcs
[p
], 1);
2430 adjust_ndiscard(b
->hhbcs
[p
], 1);
2431 if (isMemberFinalOp(bc
.op
)) break;
2435 case DceAction::AdjustPop
:
2437 auto const op
= b
->hhbcs
[id
.idx
].op
;
2438 always_assert(op
== Op::PopU2
|| op
== Op::PopFrame
);
2439 if (op
== Op::PopU2
) {
2440 assertx(elm
.second
.maskOrCount
== 1);
2441 b
->hhbcs
[id
.idx
].PopU
= bc::PopU
{};
2442 b
->hhbcs
[id
.idx
].op
= Op::PopU
;
2444 assertx(elm
.second
.maskOrCount
> 0);
2445 auto& popFrame
= b
->hhbcs
[id
.idx
].PopFrame
;
2446 assertx(elm
.second
.maskOrCount
<= popFrame
.arg1
);
2447 popFrame
.arg1
-= elm
.second
.maskOrCount
;
2448 if (popFrame
.arg1
<= 1) {
2449 auto const remaining
= popFrame
.arg1
;
2450 auto const srcLoc
= b
->hhbcs
[id
.idx
].srcLoc
;
2451 b
->hhbcs
.erase(b
->hhbcs
.begin() + id
.idx
);
2453 b
->hhbcs
.begin() + id
.idx
,
2456 ? Bytecode
{ bc::PopU
{} }
2457 : Bytecode
{ bc::PopU2
{} }
2459 setloc(srcLoc
, b
->hhbcs
.begin() + id
.idx
, 3);
2464 case DceAction::UnsetLocalsBefore
: {
2465 assertx(id
.idx
== 0);
2466 auto it
= replaceMap
.find(id
);
2467 always_assert(it
!= end(replaceMap
) && !it
->second
.empty());
2468 auto const srcLoc
= b
->hhbcs
[id
.idx
].srcLoc
;
2469 b
->hhbcs
.insert(b
->hhbcs
.begin() + id
.idx
,
2470 begin(it
->second
), end(it
->second
));
2471 setloc(srcLoc
, b
->hhbcs
.begin() + id
.idx
, it
->second
.size());
2474 case DceAction::UnsetLocalsAfter
:
2476 auto it
= replaceMap
.find(id
);
2477 always_assert(it
!= end(replaceMap
) && !it
->second
.empty());
2478 // If this is in the middle of a member op sequence, we walk to the
2479 // end, and place the UnsetLs there.
2481 for (auto bc
= b
->hhbcs
[idx
];
2482 (isMemberBaseOp(bc
.op
) || isMemberDimOp(bc
.op
)) &&
2483 idx
< b
->hhbcs
.size();
2484 bc
= b
->hhbcs
[++idx
]) {
2485 if (b
->hhbcs
[idx
+ 1].op
== OpFatal
) {
2486 // We should not have tried to insert UnsetLs in a member op
2487 // sequence that is known to fatal.
2488 always_assert(false);
2491 // The MBR must not be alive across control flow edges.
2492 always_assert(idx
< b
->hhbcs
.size());
2493 assertx(idx
== id
.idx
|| isMemberFinalOp(b
->hhbcs
[idx
].op
));
2494 auto const srcLoc
= b
->hhbcs
[idx
].srcLoc
;
2495 b
->hhbcs
.insert(b
->hhbcs
.begin() + idx
+ 1,
2496 begin(it
->second
), end(it
->second
));
2497 setloc(srcLoc
, b
->hhbcs
.begin() + idx
+ 1, it
->second
.size());
2504 struct DceOptResult
{
2505 std::bitset
<kMaxTrackedLocals
> usedLocalNames
;
2506 DceActionMap actionMap
;
2507 DceReplaceMap replaceMap
;
2508 std::bitset
<kMaxTrackedLocals
> deadAcrossLocals
;
2509 bool didAddOpts
{false};
2513 optimize_dce(const Index
& index
,
2514 const FuncAnalysis
& fa
,
2515 CollectedInfo
& collect
,
2517 const State
& stateIn
,
2518 const DceOutState
& dceOutState
) {
2519 auto dceState
= dce_visit(index
, fa
, collect
, bid
, stateIn
, dceOutState
);
2522 return {std::bitset
<kMaxTrackedLocals
>{}};
2526 std::move(dceState
->usedLocalNames
),
2527 std::move(dceState
->actionMap
),
2528 std::move(dceState
->replaceMap
),
2529 std::move(dceState
->deadAcrossLocals
),
2530 dceState
->didAddOpts
2534 //////////////////////////////////////////////////////////////////////
2536 void remove_unused_local_names(const FuncAnalysis
& ainfo
,
2537 std::bitset
<kMaxTrackedLocals
> usedLocalNames
) {
2538 if (!options
.RemoveUnusedLocalNames
) return;
2539 auto const func
= ainfo
.ctx
.func
;
2541 * Closures currently rely on name information being available.
2543 if (func
->isClosureBody
) return;
2544 if (is_pseudomain(func
) || ainfo
.mayUseVV
) return;
2546 // For reified functions, skip the first non-param local
2547 auto loc
= func
->locals
.begin() + func
->params
.size() + (int)func
->isReified
;
2548 for (; loc
!= func
->locals
.end(); ++loc
) {
2549 // We can't remove the names of volatile locals, as they can be accessed by
2551 assertx(loc
->id
== loc
->nameId
);
2552 if (is_volatile_local(func
, loc
->id
)) continue;
2553 if (loc
->unusedName
) {
2554 assertx(loc
->id
< kMaxTrackedLocals
&& !usedLocalNames
.test(loc
->id
));
2556 if (loc
->id
< kMaxTrackedLocals
&& !usedLocalNames
.test(loc
->id
)) {
2557 FTRACE(2, " killing: {}\n", local_string(*func
, loc
->id
));
2558 const_cast<php::Local
&>(*loc
).unusedName
= true;
2563 // Take a vector mapping local ids to new local ids, and apply it to the
2564 // function passed in via ainfo.
2565 void apply_remapping(const FuncAnalysis
& ainfo
,
2566 std::vector
<LocalId
>&& remapping
) {
2567 auto const maxRemappedLocals
= remapping
.size();
2568 // During Global DCE we are for the most part free to modify the BCs since
2569 // we have frozen the index, and are no longer performing context sensitive
2571 // Walk the bytecode modifying the local usage according to the mapping.
2572 for (auto const bid
: ainfo
.rpoBlocks
) {
2573 FTRACE(2, "Remapping block #{}\n", bid
);
2575 auto const blk
= ainfo
.ctx
.func
->blocks
[bid
].mutate();
2576 for (uint32_t idx
= blk
->hhbcs
.size(); idx
-- > 0;) {
2577 auto& o
= blk
->hhbcs
[idx
];
2579 auto const fixupLoc
= [&](LocalId
& id
) {
2580 if (0 <= id
&& id
< maxRemappedLocals
) {
2585 auto const fixupMKey
= [&](MKey
& mk
) {
2587 case MemberCode::MEL
:
2588 case MemberCode::MPL
:
2589 fixupLoc(mk
.local
.id
);
2596 auto const fixupLAR
= [&](const LocalRange
& lr
) {
2598 if (lr
.first
< maxRemappedLocals
) {
2599 assertx(lr
.first
== remapping
[lr
.first
]);
2603 auto const fixupITA
= [&](IterArgs
& ita
) {
2605 if (0 <= ita
.keyId
&& ita
.keyId
< maxRemappedLocals
) {
2606 ita
.keyId
= remapping
[ita
.keyId
];
2609 if (0 <= ita
.valId
&& ita
.valId
< maxRemappedLocals
) {
2610 ita
.valId
= remapping
[ita
.valId
];
2618 #define IMM_LA(n) fixupLoc(op.loc##n)
2619 #define IMM_NLA(n) fixupLoc(op.nloc##n.id)
2620 #define IMM_ILA(n) fixupLoc(op.loc##n)
2627 #define IMM_OA_IMPL(n)
2628 #define IMM_OA(type)
2630 #define IMM_KA(n) fixupMKey(op.mkey)
2631 #define IMM_LAR(n) fixupLAR(op.locrange)
2632 #define IMM_ITA(n) fixupITA(op.ita)
2635 #define IMM(which, n) IMM_##which(n)
2637 #define IMM_ONE(x) IMM(x, 1);
2638 #define IMM_TWO(x, y) IMM(x, 1); IMM(y, 2);
2639 #define IMM_THREE(x, y, z) IMM(x, 1); IMM(y, 2); \
2641 #define IMM_FOUR(x, y, z, l) IMM(x, 1); IMM(y, 2); \
2642 IMM(z, 3); IMM(l, 4);
2643 #define IMM_FIVE(x, y, z, l, m) IMM(x, 1); IMM(y, 2); \
2644 IMM(z, 3); IMM(l, 4); \
2646 #define IMM_SIX(x, y, z, l, m, n) IMM(x, 1); IMM(y, 2); \
2647 IMM(z, 3); IMM(l, 4); \
2648 IMM(m, 5); IMM(n, 6);
2650 #define O(opcode, imms, ...) \
2652 UNUSED auto& op = o.opcode; IMM_##imms; \
2655 switch (o
.op
) { OPCODES
}
2691 void remap_locals(const FuncAnalysis
& ainfo
,
2692 LocalRemappingIndex
&& remappingIndex
) {
2693 if (!options
.CompactLocalSlots
) return;
2694 auto const func
= ainfo
.ctx
.func
;
2696 * Remapping locals in closures requires checking which ones
2697 * are captured variables so we can remove the relevant properties,
2698 * and then we'd have to mutate the CreateCl callsite, so we don't
2701 * Note: many closure bodies have unused $this local, because of
2702 * some emitter quirk, so this might be worthwhile.
2704 if (func
->isClosureBody
) return;
2705 if (is_pseudomain(func
) || ainfo
.mayUseVV
) return;
2707 auto& localInterference
= remappingIndex
.localInterference
;
2708 auto const& pinned
= remappingIndex
.pinnedLocals
;
2709 auto const maxRemappedLocals
= localInterference
.size();
2711 auto const localsInterfere
= [&](LocalId l1
, LocalId l2
) -> bool {
2712 if (l1
>= maxRemappedLocals
|| l2
>= maxRemappedLocals
) return true;
2713 if (is_volatile_local(func
, l1
) || is_volatile_local(func
, l2
)) return true;
2715 assertx(localInterference
[l1
][l2
] == localInterference
[l2
][l1
]);
2716 return localInterference
[l1
][l2
];
2720 // It's worth noting this invalidates the localInterference
2721 // info for the larger id of l1, l2.
2722 auto const unifyLocals
= [&](LocalId l1
, LocalId l2
) {
2723 // We can't join locals that interfere.
2724 assertx(!localsInterfere(l1
, l2
));
2726 // We unify into the smaller localid. The larger one will no longer be
2730 // Everything that was uniquely a conflict to l2 is now also a conflict to
2731 // l1 and should be updated.
2732 auto const newConflicts
= localInterference
[l2
] ^
2733 (localInterference
[l1
] & localInterference
[l2
]);
2734 for (auto i
= maxRemappedLocals
; i
-- > 0;) {
2735 if (newConflicts
[i
]) {
2736 localInterference
[i
][l1
] = true;
2739 localInterference
[l1
] |= localInterference
[l2
];
2742 auto const isParam
= [&](uint32_t i
) {
2743 return i
< (func
->params
.size() + (int)func
->isReified
);
2746 std::vector
<LocalId
> remapping(maxRemappedLocals
);
2747 // Greedy merge the locals. This could probably be sorted by live range
2748 // length or something to achieve better coalescing. Or if live range
2749 // splitting is added, even be guided by profile info.
2750 for (auto i
= maxRemappedLocals
; i
-- > 0;) {
2752 if (func
->locals
[i
].killed
) {
2753 // Killed in a previous round of DCE.
2754 assertx(localInterference
[i
].none());
2757 if (isParam(i
) || pinned
[i
]) continue;
2758 for (auto j
= i
; j
-- > 0;) {
2759 if (func
->locals
[j
].killed
) continue;
2760 if (RuntimeOption::EnableArgsInBacktraces
&& isParam(j
)) continue;
2761 if (localsInterfere(i
, j
)) continue;
2762 // Remap the local and update interference sets.
2763 func
->locals
[i
].killed
= true;
2770 bool identityMapping
= true;
2771 // Canonicalize the local remapping.
2772 for (auto i
= 0; i
< maxRemappedLocals
; i
++) {
2773 if (remapping
[i
] != i
) {
2774 identityMapping
= false;
2775 // We only have to check one deep because we know that all earlier locals
2776 // are already cononicalized.
2777 auto const newId
= remapping
[remapping
[i
]];
2778 assertx(remapping
[newId
] == newId
);
2779 remapping
[i
] = newId
;
2783 if (!identityMapping
) {
2784 apply_remapping(ainfo
, std::move(remapping
));
2788 //////////////////////////////////////////////////////////////////////
2792 void local_dce(const Index
& index
,
2793 const FuncAnalysis
& ainfo
,
2794 CollectedInfo
& collect
,
2796 const State
& stateIn
) {
2797 // For local DCE, we have to assume all variables are in the
2798 // live-out set for the block.
2799 auto const ret
= optimize_dce(index
, ainfo
, collect
, bid
, stateIn
,
2800 DceOutState
{DceOutState::Local
{}});
2802 dce_perform(*ainfo
.ctx
.func
, ret
.actionMap
, ret
.replaceMap
);
2805 //////////////////////////////////////////////////////////////////////
2807 bool global_dce(const Index
& index
, const FuncAnalysis
& ai
) {
2808 auto rpoId
= [&] (BlockId blk
) {
2809 return ai
.bdata
[blk
].rpoId
;
2812 auto collect
= CollectedInfo
{
2813 index
, ai
.ctx
, nullptr,
2814 CollectionOpts
{}, &ai
2817 FTRACE(1, "|---- global DCE analyze ({})\n", show(ai
.ctx
));
2818 FTRACE(2, "{}", [&] {
2819 using namespace folly::gen
;
2820 auto i
= uint32_t{0};
2821 return from(ai
.ctx
.func
->locals
)
2823 [&] (const php::Local
& l
) {
2824 return folly::sformat(" {} {}\n",
2825 i
++, local_string(*ai
.ctx
.func
, l
.id
));
2827 | unsplit
<std::string
>("");
2831 * States for each block, indexed by block id.
2833 std::vector
<DceOutState
> blockStates(ai
.ctx
.func
->blocks
.size());
2836 * If EnableArgsInBacktraces is true, then argument locals (and the reified
2837 * generics local) are included in the backtrace attached to exceptions, so
2838 * they are live for any instruction that may throw. In order to converge
2839 * quickly in this case, we update the locLiveExn sets early.
2841 if (RuntimeOption::EnableArgsInBacktraces
) {
2842 auto const func
= ai
.ctx
.func
;
2843 auto const args
= func
->params
.size() + (int)func
->isReified
;
2844 auto locLiveExn
= std::bitset
<kMaxTrackedLocals
>();
2845 if (args
< kMaxTrackedLocals
) {
2846 for (auto i
= 0; i
< args
; i
++) locLiveExn
.set(i
);
2850 for (auto& blockState
: blockStates
) {
2851 blockState
.locLiveExn
|= locLiveExn
;
2856 * Set of block reverse post order ids that still need to be
2857 * visited. This is ordered by std::greater, so we'll generally
2858 * visit blocks before their predecessors. The algorithm does not
2859 * depend on this for correctness, but it will reduce iteration, and
2860 * the optimality of mergeDceStack depends on visiting at least one
2861 * successor prior to visiting any given block.
2863 * Every block must be visited at least once, so we throw them all
2866 auto incompleteQ
= dataflow_worklist
<uint32_t,std::less
<uint32_t>>(
2869 for (auto const bid
: ai
.rpoBlocks
) incompleteQ
.push(rpoId(bid
));
2871 auto const nonThrowPreds
= computeNonThrowPreds(*ai
.ctx
.func
, ai
.rpoBlocks
);
2872 auto const throwPreds
= computeThrowPreds(*ai
.ctx
.func
, ai
.rpoBlocks
);
2875 * Suppose a stack slot isn't used, but it was pushed on two separate
2876 * paths, one of which could be killed, but the other can't. We have
2877 * to prevent either, so any time we find a non-used stack slot that
2878 * can't be killed, we add it to this set (via markUisLive, and the
2879 * DceAnalysis), and schedule its block for re-analysis.
2881 LocationIdSet forcedLiveLocations
;
2884 * Temporary set used to collect locations that were forced live by
2885 * block merging/linked locations etc.
2887 LocationIdSet forcedLiveTemp
;
2889 auto checkLive
= [&] (std::vector
<UseInfo
>& uis
, uint32_t i
,
2890 LocationId location
) {
2891 if (forcedLiveLocations
.count(location
)) return true;
2892 if (!isLinked(uis
[i
])) return false;
2894 return uis
[i
- 1].usage
== Use::Used
;
2897 auto fixupUseInfo
= [&] (std::vector
<UseInfo
>& uis
,
2900 for (uint32_t i
= 0; i
< uis
.size(); i
++) {
2902 if (out
.location
.blk
== NoBlockId
) out
.location
= { blk
, i
, isSlot
};
2903 if (checkLive(uis
, i
, out
.location
)) {
2904 use(forcedLiveTemp
, uis
, i
);
2909 auto mergeUIs
= [&] (std::vector
<UseInfo
>& outBase
,
2910 const std::vector
<UseInfo
>& inBase
,
2911 uint32_t i
, BlockId blk
, bool isSlot
) {
2912 auto& out
= outBase
[i
];
2913 auto& in
= inBase
[i
];
2914 if (out
.usage
== Use::Used
) {
2915 if (in
.usage
!= Use::Used
&& nonThrowPreds
[blk
].size() > 1) {
2916 // This is to deal with the case where blk has multiple preds,
2917 // and one of those has multiple succs, one of which does use
2918 // this stack value.
2920 auto& ui
= inBase
[i
];
2921 auto linked
= isLinked(ui
);
2922 if (ui
.usage
!= Use::Used
) {
2923 forcedLiveTemp
.insert({blk
, i
, isSlot
});
2933 if (in
.usage
== Use::Used
|| out
.usage
!= in
.usage
) {
2934 use(forcedLiveTemp
, outBase
, i
);
2937 auto location
= in
.location
;
2938 if (location
.blk
== NoBlockId
) location
= { blk
, i
, isSlot
};
2939 if (checkLive(outBase
, i
, location
)) {
2940 use(forcedLiveTemp
, outBase
, i
);
2945 for (auto const& id
: in
.actions
) {
2946 ret
|= out
.actions
.insert(id
).second
;
2948 assert(out
.location
.blk
!= NoBlockId
);
2949 if (out
.location
< location
) {
2950 // It doesn't matter which one we choose, but it should be
2951 // independent of the visiting order.
2952 out
.location
= location
;
2958 auto mergeUIVecs
= [&] (folly::Optional
<std::vector
<UseInfo
>>& stkOut
,
2959 const std::vector
<UseInfo
>& stkIn
,
2960 BlockId blk
, bool isSlot
) {
2963 fixupUseInfo(*stkOut
, blk
, isSlot
);
2968 assert(stkOut
->size() == stkIn
.size());
2969 for (uint32_t i
= 0; i
< stkIn
.size(); i
++) {
2970 if (mergeUIs(*stkOut
, stkIn
, i
, blk
, isSlot
)) ret
= true;
2976 * If we weren't able to take advantage of any unused entries
2977 * on the dceStack that originated from another block, mark
2978 * them used to prevent other paths from trying to delete them.
2980 * Also, merge the entries into our global forcedLiveLocations, to
2981 * make sure they always get marked Used.
2983 auto processForcedLive
= [&] (const LocationIdSet
& forcedLive
) {
2984 for (auto const& id
: forcedLive
) {
2985 if (forcedLiveLocations
.insert(id
).second
) {
2986 // This is slightly inefficient; we know exactly how this will
2987 // affect the analysis, so we could get away with just
2988 // reprocessing the affected predecessors of id.blk; but this
2989 // is much simpler, and less error prone. We can revisit if it
2990 // turns out to be a performance issue.
2991 FTRACE(5, "Forcing {} live\n", show(id
));
2992 incompleteQ
.push(rpoId(id
.blk
));
2998 * We track interfering locals in order to compact the number of them.
2999 * This is useful to reduce fame space at runtime. The decision made
3000 * later could benefit from profile info, but HHBBC currently doesn't
3001 * get fed any such information.
3003 auto const maxRemappedLocals
= std::min(
3004 (size_t)ai
.ctx
.func
->locals
.size(),
3005 (size_t)kMaxTrackedLocals
);
3006 LocalRemappingIndex
localRemappingIndex(maxRemappedLocals
);
3009 * Parameters are not uninit at the entry to the function even if they go
3010 * unused throughout the function, they conflict with any local that is live
3011 * at an entrypoint. Such a local might be depending on the local slot being
3012 * Unset from the function entry.
3014 std::bitset
<kMaxTrackedLocals
> paramSet
;
3016 paramSet
>>= kMaxTrackedLocals
-
3017 (ai
.ctx
.func
->params
.size() + (uint32_t)ai
.ctx
.func
->isReified
);
3018 boost::dynamic_bitset
<> entrypoint(ai
.ctx
.func
->blocks
.size());
3019 entrypoint
[ai
.ctx
.func
->mainEntry
] = true;
3020 for (auto const blkId
: ai
.ctx
.func
->dvEntries
) {
3021 if (blkId
!= NoBlockId
) {
3022 entrypoint
[blkId
]= true;
3027 * Iterate on live out states until we reach a fixed point.
3029 * This algorithm treats the exceptional live-out states differently
3030 * from the live-out states during normal control flow. The
3031 * liveOutExn sets only take part in the liveIn computation when the
3032 * block has exceptional exits.
3034 while (!incompleteQ
.empty()) {
3035 auto const bid
= ai
.rpoBlocks
[incompleteQ
.pop()];
3037 // skip unreachable blocks
3038 if (!ai
.bdata
[bid
].stateIn
.initialized
) continue;
3040 FTRACE(2, "block #{}\n", bid
);
3042 auto& blockState
= blockStates
[bid
];
3043 auto const result
= analyze_dce(
3048 ai
.bdata
[bid
].stateIn
,
3050 &localRemappingIndex
3053 FTRACE(2, "loc live out : {}\n"
3054 "loc out exn : {}\n"
3055 "loc live in : {}\n",
3056 loc_bits_string(ai
.ctx
.func
, blockState
.locLive
),
3057 loc_bits_string(ai
.ctx
.func
, blockState
.locLiveExn
),
3058 loc_bits_string(ai
.ctx
.func
, result
.locLiveIn
));
3060 processForcedLive(result
.forcedLiveLocations
);
3062 // If blk ends with an iterator block, and succId is the loop body for
3063 // this iterator, this method returns the iterator's arguments so that
3064 // we can kill its key and value output locals.
3066 // We can't do this optimization if succId is also the loop end block.
3067 // This case should never occur, because then the iter would have to
3068 // be both live and dead at succId, but we guard against it anyway.
3070 // It would be nice to move this logic to the iter_dce method itself,
3071 // but this analysis pass only tracks a single out-state for each block,
3072 // so we must do it here to apply it to the in-state of the body and not
3073 // to the in-state of the done block. (Catch blocks are handled further
3074 // down and this logic never applies to them.)
3075 auto const killIterOutputs
= [] (const php::Block
* blk
, BlockId succId
)
3076 -> folly::Optional
<IterArgs
> {
3077 auto const& lastOpc
= blk
->hhbcs
.back();
3078 auto const next
= blk
->fallthrough
== succId
;
3079 auto ita
= folly::Optional
<IterArgs
>{};
3080 auto const kill
= [&]{
3081 switch (lastOpc
.op
) {
3083 ita
= lastOpc
.IterInit
.ita
;
3084 return next
&& lastOpc
.IterInit
.target2
!= succId
;
3086 ita
= lastOpc
.LIterInit
.ita
;
3087 return next
&& lastOpc
.LIterInit
.target3
!= succId
;
3089 ita
= lastOpc
.IterNext
.ita
;
3090 return !next
&& lastOpc
.IterNext
.target2
== succId
;
3092 ita
= lastOpc
.LIterNext
.ita
;
3093 return !next
&& lastOpc
.LIterNext
.target3
== succId
;
3098 return kill
? ita
: folly::none
;
3101 auto const isCFPushTaken
= [] (const php::Block
* blk
, BlockId succId
) {
3102 auto const& lastOpc
= blk
->hhbcs
.back();
3103 switch (lastOpc
.op
) {
3105 return lastOpc
.MemoGet
.target1
== succId
;
3106 case Op::MemoGetEager
:
3107 return lastOpc
.MemoGetEager
.target1
== succId
;
3113 // As mentioned above, at entrypoints all live locals conflict with
3114 // parameters. (Any live local that is not a param is depending upon their
3115 // slot being uninit)
3116 if (entrypoint
[bid
]) {
3117 addInterference(&localRemappingIndex
, result
.locLiveIn
| paramSet
);
3120 // Merge the liveIn into the liveOut of each normal predecessor.
3121 // If the set changes, reschedule that predecessor.
3122 for (auto const pid
: nonThrowPreds
[bid
]) {
3123 FTRACE(2, " -> {}\n", pid
);
3124 auto& pbs
= blockStates
[pid
];
3125 auto const oldPredLocLive
= pbs
.locLive
;
3126 auto const oldPredLocDeadAcross
= pbs
.locDeadAcross
;
3127 auto const pred
= ai
.ctx
.func
->blocks
[pid
].get();
3128 if (auto const ita
= killIterOutputs(pred
, bid
)) {
3129 auto const key
= ita
->hasKey();
3130 FTRACE(3, " Killing iterator output locals: {}\n",
3131 key
? folly::to
<std::string
>(ita
->valId
, ", ", ita
->keyId
)
3132 : folly::to
<std::string
>(ita
->valId
));
3133 auto liveIn
= result
.locLiveIn
;
3134 if (ita
->valId
< kMaxTrackedLocals
) liveIn
[ita
->valId
] = false;
3135 if (key
&& ita
->keyId
< kMaxTrackedLocals
) liveIn
[ita
->keyId
] = false;
3136 pbs
.locLive
|= liveIn
;
3138 pbs
.locLive
|= result
.locLiveIn
;
3140 pbs
.locDeadAcross
|= result
.locDeadAcrossIn
;
3141 pbs
.locDeadAcross
&= ~pbs
.locLive
;
3142 auto changed
= pbs
.locLive
!= oldPredLocLive
||
3143 pbs
.locDeadAcross
!= oldPredLocDeadAcross
;
3146 if (isCFPushTaken(pred
, bid
)) {
3147 auto stack
= result
.stack
;
3148 stack
.insert(stack
.end(), pred
->hhbcs
.back().numPush(),
3150 return mergeUIVecs(pbs
.dceStack
, stack
, bid
, false);
3152 return mergeUIVecs(pbs
.dceStack
, result
.stack
, bid
, false);
3156 incompleteQ
.push(rpoId(pid
));
3160 // Merge the liveIn into the liveOutExn state for each throw predecessor.
3161 // The liveIn computation also depends on the liveOutExn state, so again
3162 // reschedule if it changes.
3163 for (auto const pid
: throwPreds
[bid
]) {
3164 FTRACE(2, " => {}\n", pid
);
3165 auto& pbs
= blockStates
[pid
];
3166 auto const oldPredLocLiveExn
= pbs
.locLiveExn
;
3167 pbs
.locLiveExn
|= result
.locLiveIn
;
3168 if (pbs
.locLiveExn
!= oldPredLocLiveExn
) {
3169 incompleteQ
.push(rpoId(pid
));
3173 while (!forcedLiveTemp
.empty()) {
3174 auto t
= std::move(forcedLiveTemp
);
3175 processForcedLive(t
);
3179 auto const predLiveLocals
= [&] (BlockId bid
){
3180 std::bitset
<kMaxTrackedLocals
> live
;
3181 for (auto const pid
: nonThrowPreds
[bid
]) {
3182 auto const& pbs
= blockStates
[pid
];
3183 live
|= pbs
.locLive
;
3189 * Now that we're at a fixed point, use the propagated states to
3190 * remove instructions that don't need to be there.
3192 FTRACE(1, "|---- global DCE optimize ({})\n", show(ai
.ctx
));
3193 std::bitset
<kMaxTrackedLocals
> usedLocalNames
;
3194 DceActionMap actionMap
;
3195 DceReplaceMap replaceMap
;
3196 bool didAddOpts
= false;
3197 for (auto const bid
: ai
.rpoBlocks
) {
3198 FTRACE(2, "block #{}\n", bid
);
3199 auto ret
= optimize_dce(
3204 ai
.bdata
[bid
].stateIn
,
3208 auto const unsetable
= ret
.deadAcrossLocals
& predLiveLocals(bid
);
3209 if (unsetable
.any()) {
3210 auto bcs
= eager_unsets(unsetable
, ai
.ctx
.func
, [&](uint32_t i
) {
3211 return ai
.bdata
[bid
].stateIn
.locals
[i
];
3214 ret
.replaceMap
.emplace(InstrId
{ bid
, 0 }, std::move(bcs
));
3215 ret
.actionMap
.emplace(InstrId
{ bid
, 0 }, DceAction::UnsetLocalsBefore
);
3217 // We flag that we shoud rerun DCE since this Unset might be redudant if
3218 // a CGetL gets replaced with a PushL.
3223 didAddOpts
= didAddOpts
|| ret
.didAddOpts
;
3224 usedLocalNames
|= ret
.usedLocalNames
;
3225 if (ret
.actionMap
.size()) {
3226 if (!actionMap
.size()) {
3227 actionMap
= std::move(ret
.actionMap
);
3229 combineActions(actionMap
, ret
.actionMap
);
3232 if (ret
.replaceMap
.size()) {
3233 if (!replaceMap
.size()) {
3234 replaceMap
= std::move(ret
.replaceMap
);
3236 for (auto& elm
: ret
.replaceMap
) {
3237 replaceMap
.insert(std::move(elm
));
3243 dce_perform(*ai
.ctx
.func
, actionMap
, replaceMap
);
3245 remove_unused_local_names(ai
, usedLocalNames
);
3246 remap_locals(ai
, std::move(localRemappingIndex
));
3251 //////////////////////////////////////////////////////////////////////