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"
52 namespace HPHP
{ namespace HHBBC
{
54 TRACE_SET_MOD(hhbbc_dce
);
56 //////////////////////////////////////////////////////////////////////
59 * This module contains code to perform both local DCE and global DCE.
61 * The local DCE algorithm addresses dead eval stack manipulations and
62 * dead stores to locals that are visible within a single basic block.
64 * The global DCE performs a liveness analysis and then uses this
65 * information to allow dead stores to locals to be eliminated across
68 * Both types of DCE here need to be type-aware, but they must visit
69 * blocks backward. They accomplish this by forward-propagating the
70 * block input states from a FuncAnalysis to each instruction in the
71 * block prior to doing the backward iteration.
75 * During backward traversal of a block, we maintain a "backwards"
76 * stack, indicating which eval stack slots are going to be required
77 * in the future or not.
79 * During this traversal, each instruction that pops when going
80 * forward instead "pushes" information about whether that input
81 * will be required. If it is not required, it also pushes an
82 * accumulating set of instruction ids that must be removed if the
83 * instruction which produces the stack slot is removed. (All
84 * instructions in these sets must be removed if any are, in order
85 * to keep the stack depths correct.)
87 * Similarly, each instruction that would push a value going forward
88 * instead "pops" the information about whether its stack output is
89 * going to be needed. If not, the instruction can mark itself (and
90 * all downstream instructions that depended on it) as removable.
94 * While a block is iterated backward, the set of live locals is
95 * tracked. The initial state of this live set depends on whether
96 * we are performing global or local DCE, and in the local case
97 * includes all locals in the function.
99 * When a local may be read, it is added to the live set. When a
100 * local is definitely-written, it is removed from the set.
102 * If a instruction may write to a local that is not live, it can be
103 * marked as removable if it is known to have no other side-effects.
104 * Currently this is only hooked up to SetL.
108 * The global algorithm first performs a liveness analysis to
109 * propagate live out sets to each block.
111 * This analysis is basically normal, but slightly modified from the
112 * usual in order to deal with the way exceptional control flow is
113 * represented in our CFG.
115 * It essentially is the liveness analysis algorithm described at
116 * http://dl.acm.org/citation.cfm?id=316171, except that we don't
117 * need to track kill sets for each PEI because we don't have a
118 * means of determining which exceptional edges may be traversed by any
119 * given PEI. (Maybe that may be more usual to have in the context
120 * of a language with declared exception clauses...)
122 * Since we only deal with the most pessimistic exception case, this
123 * means for each block we just determine a gen and kill set, along
124 * with a subset of the latter set that is the locals that must be
125 * killed before any PEI. (See killBeforePEI below.)
127 * Final note about types:
129 * Global DCE can change the types of locals in a way that spans
130 * basic blocks. For a simple example, take the following code:
132 * // $foo :: Uninit here.
134 * // ... code that breaks a block ...
137 * If the first store to $foo is removed, the type of $foo before
138 * the SetL in the later block is now Uninit, instead of Int.
140 * In addition, by killing dead eval stack slots across basic
141 * blocks, the saved entry stacks in the FuncAnalysis no longer
142 * match the actual stack depths.
144 * This means that after calling global_dce on a function, its
145 * FuncAnalysis can no longer be considered accurate.
147 * Moreover, since global DCE makes use of type information to
148 * determine whether a store is dead, we need to be careful that
149 * this never changes whether the assumptions used to perform DCE
152 * This is ok right now: DCE only uses the types to figure out which
153 * values can either have lifetimes extended or shortened without
154 * visible side-effects, or which values may be refs (so we can't
155 * omit stores to them). If we omit a store that changes the type
156 * of a local globally, this means the new type cannot be different
157 * with regard to these features (or we wouldn't have omitted it),
158 * which means we won't have made different decisions elsewhere in
159 * the algorithm based on the new type.
161 * Specifically: we will never omit a store where the old local type
162 * was something that could've had side effects, and if a new value
163 * is stored into a local where destroying it could have
164 * side-effects, some point along the path to the function exit (if
165 * nothing else, the RetC) will have added it to the gen set and the
166 * store also won't be removable.
168 * In contrast, note that local DCE can not change types across
175 //////////////////////////////////////////////////////////////////////
177 // The number of pops as seen by dce.
178 uint32_t numPop(const Bytecode
& bc
) {
179 if (bc
.op
== Op::CGetL2
) return 1;
183 // The number of pushes as seen by dce.
184 uint32_t numPush(const Bytecode
& bc
) {
185 if (bc
.op
== Op::CGetL2
) return 2;
189 // Some reads could raise warnings and run arbitrary code.
190 bool readCouldHaveSideEffects(const Type
& t
) {
191 return t
.couldBe(BUninit
);
194 //////////////////////////////////////////////////////////////////////
197 * Use information of a stack cell.
200 // Indicates that the cell is (possibly) used.
203 // Indicates that the cell is (unconditionally) not used.
207 * Indicates that the stack slot contains an array-like being
208 * constructed by AddElemCs, which looks like it can be optimized to
209 * a NewStructArray, NewPackedArray, or NewVecArray.
214 * Modifier or-ed into the above to indicate that this use is linked
215 * to the stack slot below it (in stack order - the bottom of the
216 * stack is below the top); either they are both optimized, or
222 Use
operator|(Use a
, Use b
) {
223 return static_cast<Use
>(static_cast<int>(a
) | static_cast<int>(b
));
226 Use
operator&(Use a
, Use b
) {
227 return static_cast<Use
>(static_cast<int>(a
) & static_cast<int>(b
));
231 return static_cast<int>(a
) != 0;
234 Use
mask_use(Use a
) { return a
& static_cast<Use
>(3); }
241 bool operator<(const InstrId
& i1
, const InstrId
& i2
) {
242 if (i1
.blk
!= i2
.blk
) return i1
.blk
< i2
.blk
;
243 // sort by decreasing idx so that kill_marked_instrs works
244 return i1
.idx
> i2
.idx
;
247 bool operator==(const InstrId
& i1
, const InstrId
& i2
) {
248 return i1
.blk
== i2
.blk
&& i1
.idx
== i2
.idx
;
257 bool operator<(const LocationId
& i1
, const LocationId
& i2
) {
258 if (i1
.blk
!= i2
.blk
) return i1
.blk
< i2
.blk
;
259 if (i1
.id
!= i2
.id
) return i1
.id
< i2
.id
;
260 return i2
.isSlot
&& !i1
.isSlot
;
263 struct LocationIdHash
{
264 size_t operator()(const LocationId
& l
) const {
265 return folly::hash::hash_combine(hash_int64_pair(l
.blk
, l
.id
), l
.isSlot
);
269 bool operator==(const LocationId
& i1
, const LocationId
& i2
) {
270 return i1
.blk
== i2
.blk
&&
272 i1
.isSlot
== i2
.isSlot
;
275 using LocationIdSet
= hphp_fast_set
<LocationId
,LocationIdHash
>;
287 using MaskType
= uint32_t;
288 static constexpr size_t kMaskSize
= 32;
292 DceAction() = default;
293 /* implicit */ DceAction(Action a
) : action
{a
} {}
294 DceAction(Action a
, MaskType m
) : action
{a
}, mask
{m
} {}
297 bool operator==(const DceAction
& a
, const DceAction
& b
) {
298 return a
.action
== b
.action
&& a
.mask
== b
.mask
;
301 using DceActionMap
= std::map
<InstrId
, DceAction
>;
302 using DceReplaceMap
= std::map
<InstrId
, CompactVector
<Bytecode
>>;
305 explicit UseInfo(Use u
) : usage(u
) {}
306 UseInfo(Use u
, DceActionMap
&& ids
) : usage(u
), actions(std::move(ids
)) {}
310 * Set of actions that should be performed if we decide to
311 * discard the corresponding stack slot.
313 DceActionMap actions
;
315 * Used for stack slots that are live across blocks to indicate a
316 * stack slot that should be considered Used at the entry to blk.
318 * If its not live across blocks, location.blk will stay NoBlockId.
320 LocationId location
{ NoBlockId
, 0, false };
323 //////////////////////////////////////////////////////////////////////
326 explicit DceState(const Index
& index
, const FuncAnalysis
& ainfo
) :
327 index(index
), ainfo(ainfo
) {}
329 const FuncAnalysis
& ainfo
;
331 * Used to accumulate a set of blk/stack-slot pairs that
332 * should be marked used.
334 LocationIdSet forcedLiveLocations
;
337 * Eval stack use information. Stacks slots are marked as being
338 * needed or not needed. If they aren't needed, they carry a set of
339 * instructions that must be removed if the instruction that
340 * produces the stack value is also removable.
342 std::vector
<UseInfo
> stack
;
345 * Locals known to be live at a point in a DCE walk. This is used
346 * when we're actually acting on information we discovered during
349 std::bitset
<kMaxTrackedLocals
> liveLocals
;
352 * Instructions marked in this set will be processed by dce_perform.
353 * They must all be processed together to keep eval stack consumers
354 * and producers balanced.
356 DceActionMap actionMap
;
359 * Actions of type Replace in the actionMap have an entry here with
360 * the replacement bytecodes.
362 DceReplaceMap replaceMap
;
365 * The set of locals and that were ever live in this block. (This
366 * includes ones that were live going out of this block.) This set
367 * is used by global DCE to remove locals that are completely unused
368 * in the entire function.
370 std::bitset
<kMaxTrackedLocals
> usedLocals
;
373 * Can be set by a minstr-final instruction to indicate the actions
374 * to take provided all previous minstrs in the group are non-pei.
376 * eg if the result of a QueryM is unused, and the whole sequence is
377 * non-pei we can kill it. Or if the result is a literal, and the
378 * whole sequence is non-pei, we can replace it with pops plus the
381 folly::Optional
<UseInfo
> minstrUI
;
383 * Flag to indicate local vs global dce.
388 * When we do add elem opts, it can enable further optimization
389 * opportunities. Let the optimizer know...
391 bool didAddElemOpts
{false};
394 //////////////////////////////////////////////////////////////////////
397 const char* show(DceAction action
) {
398 switch (action
.action
) {
399 case DceAction::Kill
: return "Kill";
400 case DceAction::PopInputs
: return "PopInputs";
401 case DceAction::PopOutputs
: return "PopOutputs";
402 case DceAction::Replace
: return "Replace";
403 case DceAction::PopAndReplace
: return "PopAndReplace";
404 case DceAction::MinstrStackFinal
: return "MinstrStackFinal";
405 case DceAction::MinstrStackFixup
: return "MinstrStackFixup";
410 std::string
show(InstrId id
) {
411 return folly::sformat("{}:{}", id
.blk
, id
.idx
);
414 inline void validate(Use u
) {
415 assert(!any(u
& Use::Linked
) || mask_use(u
) == Use::Not
);
418 const char* show(Use u
) {
421 switch (mask_use(u
)) {
422 case Use::Used
: return "*U";
423 case Use::Not
: return "*0";
424 case Use::AddElemC
: return "*AE";
425 case Use::Linked
: not_reached();
430 return !any(u
& Use::Linked
) ? ret
+ 1 : ret
;
433 std::string
show(const LocationId
& id
) {
434 return folly::sformat("{}:{}{}", id
.blk
, id
.id
, id
.isSlot
? "(slot)" : "");
437 std::string
show(const DceActionMap::value_type
& elm
) {
438 return folly::sformat("{}={}", show(elm
.first
), show(elm
.second
));
441 std::string
show(const DceActionMap
& actions
) {
442 using namespace folly::gen
;
444 | map([](const DceActionMap::value_type
& elm
) { return show(elm
); })
445 | unsplit
<std::string
>(";")
449 std::string DEBUG_ONLY
show(const UseInfo
& ui
) {
450 return folly::sformat("{}({})", show(ui
.usage
), show(ui
.actions
));
453 std::string DEBUG_ONLY
loc_bits_string(const php::Func
* func
,
454 std::bitset
<kMaxTrackedLocals
> locs
) {
455 std::ostringstream out
;
456 if (func
->locals
.size() < kMaxTrackedLocals
) {
457 for (auto i
= func
->locals
.size(); i
-- > 0;) {
458 out
<< (locs
.test(i
) ? '1' : '0');
466 //////////////////////////////////////////////////////////////////////
473 const State
& stateBefore
;
474 const StepFlags
& flags
;
475 const State
& stateAfter
;
478 //////////////////////////////////////////////////////////////////////
479 // Properties of UseInfo
481 bool isLinked(const UseInfo
& ui
) {
482 return any(ui
.usage
& Use::Linked
);
485 bool lastUiIsLinked(const UseInfo
& ui
) {
489 template <typename
... Args
>
490 bool lastUiIsLinked(const UseInfo
& /*ui*/, const Args
&... args
) {
491 return lastUiIsLinked(args
...);
494 bool allUnused() { return true; }
495 template<class... Args
>
496 bool allUnused(const UseInfo
& ui
, const Args
&... args
) {
497 return (mask_use(ui
.usage
)) == Use::Not
&&
501 bool allUnusedIfNotLastRef() { return true; }
502 template<class... Args
>
503 bool allUnusedIfNotLastRef(const UseInfo
& ui
, const Args
&... args
) {
504 auto u
= mask_use(ui
.usage
);
505 return u
== Use::Not
&& allUnusedIfNotLastRef(args
...);
508 bool alwaysPop(const UseInfo
& ui
) {
510 ui
.location
.blk
!= NoBlockId
||
511 ui
.actions
.size() > 1;
514 template<typename
... Args
>
515 bool alwaysPop(const UseInfo
& ui
, const Args
&... args
) {
516 return alwaysPop(ui
) || alwaysPop(args
...);
519 bool maybePop(Env
& env
, const UseInfo
& ui
) {
521 (ui
.actions
.size() == 1 &&
522 ui
.actions
.begin()->first
.idx
> env
.id
.idx
+ 1);
525 template<typename
... Args
>
526 bool maybePop(Env
& env
, const UseInfo
& ui
, const Args
&... args
) {
527 return maybePop(env
, ui
) || maybePop(env
, args
...);
531 * Determine whether its worth inserting PopCs after an instruction
532 * that can't be dced in order to execute the dependent actions.
534 template<typename
... Args
>
535 bool shouldPopOutputs(Env
& env
, const Args
&... args
) {
536 if (alwaysPop(args
...)) return true;
537 return maybePop(env
, args
...);
540 //////////////////////////////////////////////////////////////////////
543 Type
topT(Env
& env
, uint32_t idx
= 0) {
544 assert(idx
< env
.stateBefore
.stack
.size());
545 return env
.stateBefore
.stack
[env
.stateBefore
.stack
.size() - idx
- 1].type
;
548 Type
topC(Env
& env
, uint32_t idx
= 0) {
549 auto const t
= topT(env
, idx
);
550 assert(t
.subtypeOf(BInitCell
));
554 //////////////////////////////////////////////////////////////////////
557 void addLocGenSet(Env
& env
, std::bitset
<kMaxTrackedLocals
> locs
) {
558 FTRACE(4, " loc-conservative: {}\n",
559 loc_bits_string(env
.dceState
.ainfo
.ctx
.func
, locs
));
560 env
.dceState
.liveLocals
|= locs
;
563 void addLocGen(Env
& env
, uint32_t id
) {
564 FTRACE(2, " loc-gen: {}\n", id
);
565 if (id
>= kMaxTrackedLocals
) return;
566 env
.dceState
.liveLocals
[id
] = 1;
570 * Indicate that this instruction will use the value of loc unless we
571 * kill it. handle_push will take care of calling addLocGen if
574 void scheduleGenLoc(Env
& env
, LocalId loc
) {
578 void addLocKill(Env
& env
, uint32_t id
) {
579 FTRACE(2, " loc-kill: {}\n", id
);
580 if (id
>= kMaxTrackedLocals
) return;
581 env
.dceState
.liveLocals
[id
] = 0;
584 bool isLocLive(Env
& env
, uint32_t id
) {
585 if (id
>= kMaxTrackedLocals
) {
586 // Conservatively assume it's potentially live.
589 return env
.dceState
.liveLocals
[id
];
592 Type
locRaw(Env
& env
, LocalId loc
) {
593 return env
.stateBefore
.locals
[loc
];
596 bool isLocVolatile(Env
& env
, uint32_t id
) {
597 if (id
>= kMaxTrackedLocals
) return true;
598 return is_volatile_local(env
.dceState
.ainfo
.ctx
.func
, id
);
601 //////////////////////////////////////////////////////////////////////
602 // manipulate eval stack
604 void pop(Env
& env
, UseInfo
&& ui
) {
605 FTRACE(2, " pop({})\n", show(ui
.usage
));
606 env
.dceState
.stack
.push_back(std::move(ui
));
609 void pop(Env
& env
, Use u
, DceActionMap actions
) {
610 pop(env
, {u
, std::move(actions
)});
613 void pop(Env
& env
, Use u
, InstrId id
) {
614 pop(env
, {u
, {{id
, DceAction::Kill
}}});
617 void pop(Env
& env
) { pop(env
, Use::Used
, DceActionMap
{}); }
619 void discard(Env
& env
) {
620 pop(env
, Use::Not
, env
.id
);
623 //////////////////////////////////////////////////////////////////////
626 * Mark a UseInfo used; if its linked also mark the ui below it used.
627 * As we mark them used, we may also need to add them to
628 * forcedLiveLocations to prevent inconsistencies in the global state
631 void use(LocationIdSet
& forcedLive
,
632 std::vector
<UseInfo
>& uis
, uint32_t i
) {
635 auto linked
= isLinked(ui
);
636 if (ui
.usage
!= Use::Used
&&
637 ui
.location
.blk
!= NoBlockId
) {
638 forcedLive
.insert(ui
.location
);
640 ui
.usage
= Use::Used
;
648 void combineActions(DceActionMap
& dst
, const DceActionMap
& src
) {
649 for (auto& i
: src
) {
650 auto ret
= dst
.insert(i
);
652 if (i
.second
.action
== DceAction::MinstrStackFixup
||
653 i
.second
.action
== DceAction::MinstrStackFinal
) {
654 assertx(i
.second
.action
== ret
.first
->second
.action
);
655 ret
.first
->second
.mask
|= i
.second
.mask
;
659 if (i
.second
.action
== ret
.first
->second
.action
) {
663 assertx(i
.second
.action
== DceAction::Kill
||
664 ret
.first
->second
.action
== DceAction::Kill
);
666 if (i
.second
.action
== DceAction::PopAndReplace
||
667 ret
.first
->second
.action
== DceAction::PopAndReplace
) {
668 ret
.first
->second
.action
= DceAction::Replace
;
671 if (i
.second
.action
== DceAction::Replace
||
672 ret
.first
->second
.action
== DceAction::Replace
) {
673 ret
.first
->second
.action
= DceAction::Replace
;
676 if (ret
.first
->second
.action
== DceAction::PopInputs
||
677 i
.second
.action
== DceAction::PopInputs
) {
678 ret
.first
->second
.action
= DceAction::Kill
;
681 always_assert(false);
687 * A UseInfo has been popped, and we want to perform its actions. If
688 * its not linked we'll just add them to the dceState; otherwise we'll
689 * add them to the UseInfo on the top of the stack.
691 DceActionMap
& commitActions(Env
& env
, bool linked
, const DceActionMap
& am
) {
693 FTRACE(2, " committing {}: {}\n", show(env
.id
), show(am
));
696 assert(!linked
|| env
.dceState
.stack
.back().usage
!= Use::Used
);
699 env
.dceState
.stack
.back().actions
: env
.dceState
.actionMap
;
701 combineActions(dst
, am
);
703 if (!am
.count(env
.id
)) {
704 dst
.emplace(env
.id
, DceAction::Kill
);
710 * Combine a set of UseInfos into the first, and return the combined one.
712 UseInfo
& combineUis(UseInfo
& ui
) { return ui
; }
713 template<class... Args
>
714 UseInfo
& combineUis(UseInfo
& accum
, const UseInfo
& ui
, const Args
&... args
) {
715 accum
.actions
.insert(begin(ui
.actions
), end(ui
.actions
));
716 if (accum
.location
.blk
== NoBlockId
||
717 accum
.location
< ui
.location
) {
718 accum
.location
= ui
.location
;
720 return combineUis(accum
, args
...);
724 * The current instruction is going to be replaced with PopCs. Perform
725 * appropriate pops (Use::Not)
727 void ignoreInputs(Env
& env
, bool linked
, DceActionMap
&& actions
) {
728 auto const np
= numPop(env
.op
);
731 auto usage
= [&] (uint32_t i
) {
733 if (linked
) ret
= ret
| Use::Linked
;
737 for (auto i
= np
; --i
; ) {
738 pop(env
, usage(i
), DceActionMap
{});
741 pop(env
, usage(0), std::move(actions
));
744 DceActionMap
& commitUis(Env
& env
, bool linked
, const UseInfo
& ui
) {
745 return commitActions(env
, linked
, ui
.actions
);
748 template<typename
... Args
>
749 DceActionMap
& commitUis(Env
& env
, bool linked
,
750 const UseInfo
& ui
, const Args
&... args
) {
751 commitUis(env
, linked
, ui
);
752 return commitUis(env
, linked
, args
...);
756 * During global dce, a particular stack element could be pushed
757 * on multiple paths. eg:
761 * If f() is known to return a non-counted type, we have FCallFuncD -> PopC
762 * on one path, and Int 42 -> PopC on another, and the PopC marks its value
763 * Use::Not. When we get to the Int 42 it thinks both instructions can be
764 * killed; but when we get to the FCallFuncD it does nothing. So any time we
765 * decide to ignore a Use::Not, we have to record that fact so we can prevent
766 * the other paths from trying to use that information. We communicate this
767 * via the ui.location field, and the forcedLiveLocations set.
769 * [ We deal with this case now by inserting a PopC after the
770 * FCallFuncD, which allows the 42/PopC to be removed - but there are
771 * other cases that are not yet handled. ]
773 void markUisLive(Env
& env
, bool linked
, const UseInfo
& ui
) {
775 if (ui
.usage
!= Use::Used
&&
776 ui
.location
.blk
!= NoBlockId
) {
777 env
.dceState
.forcedLiveLocations
.insert(ui
.location
);
780 use(env
.dceState
.forcedLiveLocations
,
781 env
.dceState
.stack
, env
.dceState
.stack
.size() - 1);
785 template<typename
... Args
>
786 void markUisLive(Env
& env
, bool linked
,
787 const UseInfo
& ui
, const Args
&... args
) {
788 markUisLive(env
, false, ui
);
789 markUisLive(env
, linked
, args
...);
792 template<typename
... Args
>
793 void popOutputs(Env
& env
, bool linked
, const Args
&... args
) {
794 if (shouldPopOutputs(env
, args
...)) {
795 auto& actions
= commitUis(env
, linked
, args
...);
796 actions
[env
.id
] = DceAction::PopOutputs
;
799 markUisLive(env
, linked
, args
...);
802 void markDead(Env
& env
) {
803 env
.dceState
.actionMap
[env
.id
] = DceAction::Kill
;
804 FTRACE(2, " Killing {}\n", show(env
.id
));
807 void pop_inputs(Env
& env
, uint32_t numPop
) {
808 for (auto i
= uint32_t{0}; i
< numPop
; ++i
) {
813 enum class PushFlags
{
821 template<typename
... Args
>
822 void handle_push(Env
& env
, PushFlags pf
, Args
&... uis
) {
823 auto linked
= lastUiIsLinked(uis
...);
825 if (env
.loc
!= NoLocalId
&& (linked
||
826 pf
== PushFlags::MarkLive
||
827 pf
== PushFlags::PopOutputs
)) {
828 addLocGen(env
, env
.loc
);
832 case PushFlags::MarkLive
:
833 markUisLive(env
, linked
, uis
...);
836 case PushFlags::MarkDead
:
837 commitUis(env
, linked
, uis
...);
840 case PushFlags::MarkUnused
: {
841 // Our outputs are unused, their consumers are being removed,
842 // and we don't care about our inputs. Replace with
843 // appropriately unused pops of the inputs.
844 auto& ui
= combineUis(uis
...);
845 // use emplace so that the callee can override the action
846 ui
.actions
.emplace(env
.id
, DceAction::PopInputs
);
847 commitActions(env
, linked
, ui
.actions
);
848 if (numPop(env
.op
)) {
849 ignoreInputs(env
, linked
, {{ env
.id
, DceAction::Kill
}});
854 case PushFlags::PopOutputs
:
855 popOutputs(env
, linked
, uis
...);
858 case PushFlags::AddElemC
: {
860 auto& ui
= combineUis(uis
...);
861 ui
.usage
= Use::AddElemC
;
862 // For the last AddElemC, we will already have added a Replace
863 // action for env.id - so the following emplace will silently
864 // fail; for the rest, we just want to kill the AddElemC
865 ui
.actions
.emplace(env
.id
, DceAction::Kill
);
866 pop(env
, std::move(ui
));
868 // The key is known; we must drop it if we convert to New*Array.
869 pop(env
, Use::Not
| Use::Linked
, DceActionMap
{});
871 // The value going into the array is a normal use.
876 pop_inputs(env
, numPop(env
.op
));
880 auto stack_ops(Env
& env
, F fun
)
881 -> decltype(fun(std::declval
<UseInfo
&>()),void(0)) {
882 always_assert(!env
.dceState
.stack
.empty());
883 auto ui
= std::move(env
.dceState
.stack
.back());
884 env
.dceState
.stack
.pop_back();
885 FTRACE(2, " stack_ops({}@{})\n", show(ui
.usage
), show(ui
.actions
));
887 auto const f
= fun(ui
);
888 handle_push(env
, f
, ui
);
891 void stack_ops(Env
& env
) {
892 stack_ops(env
, [](const UseInfo
&) { return PushFlags::MarkLive
; });
896 auto stack_ops(Env
& env
, F fun
)
897 -> decltype(fun(std::declval
<UseInfo
&>(), std::declval
<UseInfo
&>()),void(0)) {
898 always_assert(env
.dceState
.stack
.size() >= 2);
899 auto u1
= std::move(env
.dceState
.stack
.back());
900 env
.dceState
.stack
.pop_back();
901 auto u2
= std::move(env
.dceState
.stack
.back());
902 env
.dceState
.stack
.pop_back();
903 FTRACE(2, " stack_ops({}@{},{}@{})\n",
904 show(u1
.usage
), show(u1
.actions
), show(u1
.usage
), show(u2
.actions
));
906 auto const f
= fun(u1
, u2
);
907 handle_push(env
, f
, u1
, u2
);
910 void push_outputs(Env
& env
, uint32_t numPush
) {
911 for (auto i
= uint32_t{0}; i
< numPush
; ++i
) {
912 auto ui
= std::move(env
.dceState
.stack
.back());
913 env
.dceState
.stack
.pop_back();
914 markUisLive(env
, isLinked(ui
), ui
);
918 void pushRemovable(Env
& env
) {
919 stack_ops(env
,[] (const UseInfo
& ui
) {
920 return allUnused(ui
) ? PushFlags::MarkUnused
: PushFlags::MarkLive
;
924 void pushRemovableIfNoThrow(Env
& env
) {
925 stack_ops(env
,[&] (const UseInfo
& ui
) {
926 return !env
.flags
.wasPEI
&& allUnused(ui
)
927 ? PushFlags::MarkUnused
: PushFlags::MarkLive
;
931 //////////////////////////////////////////////////////////////////////
934 * Note that the instructions with popConds are relying on the consumer of the
935 * values they push to check whether lifetime changes can have side-effects.
937 * For example, in bytecode like this, assuming $x is an object with a
943 * PopC $x // dtor should be here.
945 * The PopC will decide it can't be eliminated, which prevents us from
946 * eliminating the CGetL.
949 void dce(Env
& env
, const bc::PopC
&) { discard(env
); }
950 void dce(Env
& env
, const bc::PopU
&) { discard(env
); }
951 void dce(Env
& env
, const bc::PopU2
&) {
952 auto ui
= std::move(env
.dceState
.stack
.back());
953 env
.dceState
.stack
.pop_back();
955 // this is way too conservative; but hopefully the PopU2 will be
956 // killed (it always should be) and we'll do another pass and fix
958 markUisLive(env
, true, ui
);
959 ui
= UseInfo
{ Use::Used
};
962 env
.dceState
.stack
.push_back(std::move(ui
));
964 void dce(Env
& env
, const bc::PopFrame
& op
) {
965 std::vector
<UseInfo
> uis
;
966 for (uint32_t i
= 0; i
< op
.arg1
; i
++) {
967 uis
.emplace_back(std::move(env
.dceState
.stack
.back()));
968 env
.dceState
.stack
.pop_back();
970 // As above this is overly conservative but in all likelihood won't matter
971 if (isLinked(uis
.back())) {
972 markUisLive(env
, true, uis
.back());
973 uis
.back() = UseInfo
{ Use::Used
};
977 // Pop the Uninits from the frame
978 pop(env
, Use::Not
, DceActionMap
{});
979 pop(env
, Use::Not
|Use::Linked
, DceActionMap
{});
980 pop(env
, Use::Not
|Use::Linked
, DceActionMap
{{ env
.id
, DceAction::Kill
}});
982 for (auto it
= uis
.rbegin(); it
!= uis
.rend(); it
++) {
983 env
.dceState
.stack
.push_back(std::move(*it
));
987 void dce(Env
& env
, const bc::Int
&) { pushRemovable(env
); }
988 void dce(Env
& env
, const bc::String
&) { pushRemovable(env
); }
989 void dce(Env
& env
, const bc::Dict
&) { pushRemovable(env
); }
990 void dce(Env
& env
, const bc::Vec
&) { pushRemovable(env
); }
991 void dce(Env
& env
, const bc::Keyset
&) { pushRemovable(env
); }
992 void dce(Env
& env
, const bc::Double
&) { pushRemovable(env
); }
993 void dce(Env
& env
, const bc::True
&) { pushRemovable(env
); }
994 void dce(Env
& env
, const bc::False
&) { pushRemovable(env
); }
995 void dce(Env
& env
, const bc::Null
&) { pushRemovable(env
); }
996 void dce(Env
& env
, const bc::NullUninit
&) { pushRemovable(env
); }
997 void dce(Env
& env
, const bc::File
&) { pushRemovable(env
); }
998 void dce(Env
& env
, const bc::Dir
&) { pushRemovable(env
); }
999 void dce(Env
& env
, const bc::FuncCred
&) { pushRemovable(env
); }
1000 void dce(Env
& env
, const bc::NewArray
&) { pushRemovable(env
); }
1001 void dce(Env
& env
, const bc::NewCol
&) { pushRemovable(env
); }
1002 void dce(Env
& env
, const bc::CheckProp
&) { pushRemovable(env
); }
1004 void dce(Env
& env
, const bc::Dup
&) {
1006 // - If the duplicated value is not used, delete the dup and all
1007 // its consumers, then
1008 // o If the original value is unused pass that on to let the
1009 // instruction which pushed it decide whether to kill it
1010 // o Otherwise we're done.
1011 // - Otherwise, if the original value is unused, kill the dup
1012 // and all dependent instructions.
1013 stack_ops(env
, [&] (UseInfo
& dup
, UseInfo
& orig
) {
1014 // Dup pushes a cell that is guaranteed to be not the last reference.
1015 // So, it can be eliminated if the cell it pushes is used as Use::Not.
1016 auto const dup_unused
= allUnusedIfNotLastRef(dup
);
1017 auto const orig_unused
= allUnused(orig
) &&
1018 (!isLinked(dup
) || dup_unused
);
1020 if (dup_unused
&& orig_unused
) {
1021 return PushFlags::MarkUnused
;
1025 markUisLive(env
, isLinked(orig
), orig
);
1026 orig
.actions
.clear();
1027 return PushFlags::MarkDead
;
1031 markUisLive(env
, false, dup
);
1032 dup
.actions
.clear();
1033 return PushFlags::MarkDead
;
1036 return PushFlags::MarkLive
;
1040 void cgetImpl(Env
& env
, LocalId loc
, bool quiet
) {
1041 stack_ops(env
, [&] (UseInfo
& ui
) {
1042 scheduleGenLoc(env
, loc
);
1043 if (allUnused(ui
) &&
1044 (quiet
|| !readCouldHaveSideEffects(locRaw(env
, loc
)))) {
1045 return PushFlags::MarkUnused
;
1047 if (!isLocLive(env
, loc
) &&
1048 !readCouldHaveSideEffects(locRaw(env
, loc
)) &&
1049 !isLocVolatile(env
, loc
)) {
1050 // note: PushL does not deal with Uninit, so we need the
1051 // readCouldHaveSideEffects here, regardless of quiet.
1052 env
.dceState
.replaceMap
.insert({ env
.id
, { bc::PushL
{ loc
} } });
1053 env
.dceState
.actionMap
.emplace(env
.id
, DceAction::Replace
);
1055 return PushFlags::MarkLive
;
1059 void dce(Env
& env
, const bc::CGetL
& op
) {
1060 cgetImpl(env
, op
.loc1
, false);
1063 void dce(Env
& env
, const bc::CGetQuietL
& op
) {
1064 cgetImpl(env
, op
.loc1
, true);
1067 void dce(Env
& env
, const bc::CUGetL
& op
) {
1068 cgetImpl(env
, op
.loc1
, true);
1071 void dce(Env
& env
, const bc::PushL
& op
) {
1072 stack_ops(env
, [&] (UseInfo
& ui
) {
1073 scheduleGenLoc(env
, op
.loc1
);
1074 if (allUnused(ui
)) {
1075 if (isLocLive(env
, op
.loc1
)) {
1076 env
.dceState
.replaceMap
.insert(
1077 { env
.id
, { bc::UnsetL
{ op
.loc1
} } });
1078 ui
.actions
.emplace(env
.id
, DceAction::Replace
);
1080 return PushFlags::MarkUnused
;
1082 return PushFlags::MarkLive
;
1086 void dce(Env
& env
, const bc::CGetL2
& op
) {
1087 auto const ty
= locRaw(env
, op
.loc1
);
1089 stack_ops(env
, [&] (const UseInfo
& u1
, const UseInfo
& u2
) {
1090 scheduleGenLoc(env
, op
.loc1
);
1091 if (readCouldHaveSideEffects(ty
) || !allUnused(u1
, u2
)) {
1092 return PushFlags::MarkLive
;
1094 return PushFlags::MarkUnused
;
1098 void dce(Env
& env
, const bc::BareThis
& op
) {
1099 stack_ops(env
, [&] (UseInfo
& ui
) {
1100 if (allUnusedIfNotLastRef(ui
) &&
1101 op
.subop1
!= BareThisOp::Notice
) {
1102 return PushFlags::MarkUnused
;
1104 return PushFlags::MarkLive
;
1108 void dce(Env
& env
, const bc::RetC
&) { pop(env
); }
1109 void dce(Env
& env
, const bc::RetCSuspended
&) { pop(env
); }
1110 void dce(Env
& env
, const bc::Throw
&) { pop(env
); }
1111 void dce(Env
& env
, const bc::Fatal
&) { pop(env
); }
1112 void dce(Env
& env
, const bc::Exit
&) { stack_ops(env
); }
1114 void dce(Env
& env
, const bc::IsTypeC
& op
) {
1115 stack_ops(env
, [&] (UseInfo
& ui
) {
1116 if (allUnused(ui
) &&
1117 !is_type_might_raise(type_of_istype(op
.subop1
), topC(env
))) {
1118 return PushFlags::MarkUnused
;
1120 return PushFlags::MarkLive
;
1124 void dce(Env
& env
, const bc::IsTypeL
& op
) {
1125 auto const ty
= locRaw(env
, op
.loc1
);
1126 stack_ops(env
, [&] (UseInfo
& ui
) {
1127 scheduleGenLoc(env
, op
.loc1
);
1128 if (allUnused(ui
) &&
1129 !readCouldHaveSideEffects(ty
) &&
1130 !is_type_might_raise(type_of_istype(op
.subop2
), ty
)) {
1131 return PushFlags::MarkUnused
;
1133 return PushFlags::MarkLive
;
1137 void dce(Env
& env
, const bc::Array
& op
) {
1138 stack_ops(env
, [&] (UseInfo
& ui
) {
1139 if (allUnusedIfNotLastRef(ui
)) return PushFlags::MarkUnused
;
1141 if (ui
.usage
!= Use::AddElemC
) return PushFlags::MarkLive
;
1143 assert(!env
.dceState
.isLocal
);
1145 CompactVector
<Bytecode
> bcs
;
1146 IterateV(op
.arr1
, [&] (TypedValue v
) {
1147 bcs
.push_back(gen_constant(v
));
1149 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1150 ui
.actions
[env
.id
] = DceAction::Replace
;
1151 env
.dceState
.didAddElemOpts
= true;
1152 return PushFlags::MarkUnused
;
1156 void dce(Env
& env
, const bc::NewMixedArray
&) {
1157 stack_ops(env
, [&] (const UseInfo
& ui
) {
1158 if (ui
.usage
== Use::AddElemC
|| allUnused(ui
)) {
1159 env
.dceState
.didAddElemOpts
= true;
1160 return PushFlags::MarkUnused
;
1163 return PushFlags::MarkLive
;
1167 void dce(Env
& env
, const bc::NewDictArray
&) {
1168 stack_ops(env
, [&] (const UseInfo
& ui
) {
1169 if (ui
.usage
== Use::AddElemC
|| allUnused(ui
)) {
1170 env
.dceState
.didAddElemOpts
= true;
1171 return PushFlags::MarkUnused
;
1174 return PushFlags::MarkLive
;
1178 void dce(Env
& env
, const bc::NewDArray
&) {
1179 stack_ops(env
, [&] (const UseInfo
& ui
) {
1180 if (ui
.usage
== Use::AddElemC
|| allUnused(ui
)) {
1181 env
.dceState
.didAddElemOpts
= true;
1182 return PushFlags::MarkUnused
;
1185 return PushFlags::MarkLive
;
1189 void dce(Env
& env
, const bc::AddElemC
& /*op*/) {
1190 stack_ops(env
, [&] (UseInfo
& ui
) {
1191 // If the set might throw it needs to be kept.
1192 if (env
.flags
.wasPEI
) {
1193 return PushFlags::MarkLive
;
1196 if (allUnused(ui
)) {
1197 return PushFlags::MarkUnused
;
1200 if (env
.dceState
.isLocal
) {
1201 // Converting to New*Array changes the stack layout,
1202 // which can invalidate the FuncAnalysis; only do it in global
1203 // dce, since we're going to recompute the FuncAnalysis anyway.
1204 return PushFlags::MarkLive
;
1207 auto const& arrPost
= env
.stateAfter
.stack
.back().type
;
1208 auto const& arrPre
= topC(env
, 2);
1209 auto const postSize
= arr_size(arrPost
);
1210 if (!postSize
|| postSize
== arr_size(arrPre
)) {
1211 // if postSize is known, and equal to preSize, the AddElemC
1212 // didn't add an element (duplicate key) so we have to give up.
1213 // if its not known, we also have nothing to do.
1214 return PushFlags::MarkLive
;
1216 if (ui
.usage
== Use::AddElemC
) {
1217 return PushFlags::AddElemC
;
1219 auto const cat
= categorize_array(arrPost
);
1221 if (allUnusedIfNotLastRef(ui
)) return PushFlags::MarkUnused
;
1222 auto v
= tv(arrPost
);
1223 CompactVector
<Bytecode
> bcs
;
1224 if (arrPost
.subtypeOf(BArrN
)) {
1225 bcs
.emplace_back(bc::Array
{ v
->m_data
.parr
});
1227 assert(arrPost
.subtypeOf(BDictN
));
1228 bcs
.emplace_back(bc::Dict
{ v
->m_data
.parr
});
1230 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1231 ui
.actions
[env
.id
] = DceAction::PopAndReplace
;
1232 return PushFlags::MarkUnused
;
1235 if (isLinked(ui
)) return PushFlags::MarkLive
;
1237 if (arrPost
.strictSubtypeOf(TArrN
)) {
1238 CompactVector
<Bytecode
> bcs
;
1239 if (cat
.cat
== Type::ArrayCat::Struct
&&
1240 *postSize
<= ArrayData::MaxElemsOnStack
) {
1241 if (arrPost
.subtypeOf(BPArrN
)) {
1242 bcs
.emplace_back(bc::NewStructArray
{ get_string_keys(arrPost
) });
1243 } else if (arrPost
.subtypeOf(BDArrN
)) {
1244 bcs
.emplace_back(bc::NewStructDArray
{ get_string_keys(arrPost
) });
1246 return PushFlags::MarkLive
;
1248 } else if (cat
.cat
== Type::ArrayCat::Packed
&&
1249 *postSize
<= ArrayData::MaxElemsOnStack
) {
1250 if (arrPost
.subtypeOf(BPArrN
)) {
1252 bc::NewPackedArray
{ static_cast<uint32_t>(*postSize
) }
1254 } else if (arrPost
.subtypeOf(BVArrN
)) {
1256 bc::NewVArray
{ static_cast<uint32_t>(*postSize
) }
1259 return PushFlags::MarkLive
;
1262 return PushFlags::MarkLive
;
1264 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1265 ui
.actions
[env
.id
] = DceAction::Replace
;
1266 return PushFlags::AddElemC
;
1269 if (arrPost
.strictSubtypeOf(TDictN
) &&
1270 cat
.cat
== Type::ArrayCat::Struct
&&
1271 *postSize
<= ArrayData::MaxElemsOnStack
) {
1272 CompactVector
<Bytecode
> bcs
;
1273 bcs
.emplace_back(bc::NewStructDict
{ get_string_keys(arrPost
) });
1274 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1275 ui
.actions
[env
.id
] = DceAction::Replace
;
1276 return PushFlags::AddElemC
;
1279 return PushFlags::MarkLive
;
1283 template<typename Op
>
1284 void dceNewArrayLike(Env
& env
, const Op
& op
) {
1285 if (op
.numPop() == 1 &&
1286 !env
.flags
.wasPEI
&&
1287 allUnusedIfNotLastRef(env
.dceState
.stack
.back())) {
1288 // Just an optimization for when we have a single element array,
1289 // but we care about its lifetime. By killing the array, and
1290 // leaving its element on the stack, the lifetime is unaffected.
1291 return markDead(env
);
1293 pushRemovableIfNoThrow(env
);
1296 void dce(Env
& env
, const bc::NewPackedArray
& op
) { dceNewArrayLike(env
, op
); }
1297 void dce(Env
& env
, const bc::NewStructArray
& op
) { dceNewArrayLike(env
, op
); }
1298 void dce(Env
& env
, const bc::NewStructDArray
& op
) { dceNewArrayLike(env
, op
); }
1299 void dce(Env
& env
, const bc::NewStructDict
& op
) { dceNewArrayLike(env
, op
); }
1300 void dce(Env
& env
, const bc::NewVecArray
& op
) { dceNewArrayLike(env
, op
); }
1301 void dce(Env
& env
, const bc::NewKeysetArray
& op
) { dceNewArrayLike(env
, op
); }
1302 void dce(Env
& env
, const bc::NewVArray
& op
) { dceNewArrayLike(env
, op
); }
1304 void dce(Env
& env
, const bc::NewPair
& op
) { dceNewArrayLike(env
, op
); }
1305 void dce(Env
& env
, const bc::ColFromArray
& op
) { dceNewArrayLike(env
, op
); }
1307 void dce(Env
& env
, const bc::PopL
& op
) {
1308 if (!isLocLive(env
, op
.loc1
) && !isLocVolatile(env
, op
.loc1
)) {
1310 env
.dceState
.actionMap
[env
.id
] = DceAction::PopInputs
;
1314 if (isLocVolatile(env
, op
.loc1
)) {
1315 addLocGen(env
, op
.loc1
);
1317 addLocKill(env
, op
.loc1
);
1321 void dce(Env
& env
, const bc::InitThisLoc
& op
) {
1322 if (!isLocLive(env
, op
.loc1
)) {
1323 return markDead(env
);
1327 void dce(Env
& env
, const bc::SetL
& op
) {
1328 if (!isLocLive(env
, op
.loc1
) && !isLocVolatile(env
, op
.loc1
)) {
1329 return markDead(env
);
1331 stack_ops(env
, [&] (UseInfo
& ui
) {
1332 if (!allUnusedIfNotLastRef(ui
)) return PushFlags::MarkLive
;
1333 // If the stack result of the SetL is unused, we can replace it
1335 CompactVector
<Bytecode
> bcs
{ bc::PopL
{ op
.loc1
} };
1336 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1337 ui
.actions
[env
.id
] = DceAction::Replace
;
1338 return PushFlags::MarkDead
;
1340 if (isLocVolatile(env
, op
.loc1
)) {
1341 addLocGen(env
, op
.loc1
);
1343 addLocKill(env
, op
.loc1
);
1347 void dce(Env
& env
, const bc::UnsetL
& op
) {
1348 auto const oldTy
= locRaw(env
, op
.loc1
);
1349 if (oldTy
.subtypeOf(BUninit
)) return markDead(env
);
1351 auto const effects
= isLocVolatile(env
, op
.loc1
);
1352 if (!isLocLive(env
, op
.loc1
) && !effects
) return markDead(env
);
1354 addLocGen(env
, op
.loc1
);
1356 addLocKill(env
, op
.loc1
);
1361 * IncDecL is a read-modify-write: can be removed if the local isn't
1362 * live, the set can't have side effects, and no one reads the value
1363 * it pushes. If the instruction is not dead, add the local to the
1364 * set of upward exposed uses.
1366 void dce(Env
& env
, const bc::IncDecL
& op
) {
1367 auto const oldTy
= locRaw(env
, op
.loc1
);
1368 auto const effects
= readCouldHaveSideEffects(oldTy
) ||
1369 isLocVolatile(env
, op
.loc1
);
1370 stack_ops(env
, [&] (const UseInfo
& ui
) {
1371 scheduleGenLoc(env
, op
.loc1
);
1372 if (!isLocLive(env
, op
.loc1
) && !effects
&& allUnused(ui
)) {
1373 return PushFlags::MarkUnused
;
1375 return PushFlags::MarkLive
;
1379 bool setOpLSideEffects(const bc::SetOpL
& op
, const Type
& lhs
, const Type
& rhs
) {
1380 auto const lhsOk
= lhs
.subtypeOfAny(TUninit
, TNull
, TBool
, TInt
, TDbl
, TStr
);
1381 auto const rhsOk
= rhs
.subtypeOfAny(TNull
, TBool
, TInt
, TDbl
, TStr
);
1382 if (!lhsOk
|| !rhsOk
) return true;
1384 switch (op
.subop2
) {
1385 case SetOpOp::ConcatEqual
:
1388 case SetOpOp::PlusEqual
:
1389 case SetOpOp::PlusEqualO
:
1390 case SetOpOp::MinusEqual
:
1391 case SetOpOp::MulEqual
:
1392 case SetOpOp::DivEqual
:
1393 case SetOpOp::ModEqual
:
1394 case SetOpOp::PowEqual
:
1395 case SetOpOp::AndEqual
:
1396 case SetOpOp::OrEqual
:
1397 case SetOpOp::XorEqual
:
1398 case SetOpOp::MinusEqualO
:
1399 case SetOpOp::MulEqualO
:
1400 case SetOpOp::SlEqual
:
1401 case SetOpOp::SrEqual
:
1402 return lhs
.subtypeOf(BStr
) || rhs
.subtypeOf(BStr
);
1408 * SetOpL is like IncDecL, but with the complication that we don't
1409 * know if we can mark it dead when visiting it, because it is going
1410 * to pop an input but unlike SetL doesn't push the value it popped.
1411 * However, since we've checked the input types, it *is* ok to just
1414 void dce(Env
& env
, const bc::SetOpL
& op
) {
1415 auto const oldTy
= locRaw(env
, op
.loc1
);
1417 stack_ops(env
, [&] (UseInfo
& ui
) {
1418 scheduleGenLoc(env
, op
.loc1
);
1419 if (!isLocLive(env
, op
.loc1
) && allUnused(ui
)) {
1420 if (!readCouldHaveSideEffects(oldTy
) &&
1421 !isLocVolatile(env
, op
.loc1
) &&
1422 !setOpLSideEffects(op
, oldTy
, topC(env
))) {
1423 return PushFlags::MarkUnused
;
1426 return PushFlags::MarkLive
;
1430 void dce(Env
& env
, const bc::Add
&) { pushRemovableIfNoThrow(env
); }
1431 void dce(Env
& env
, const bc::AddNewElemC
&) { pushRemovableIfNoThrow(env
); }
1432 void dce(Env
& env
, const bc::AddO
&) { pushRemovableIfNoThrow(env
); }
1433 void dce(Env
& env
, const bc::AKExists
&) { pushRemovableIfNoThrow(env
); }
1434 void dce(Env
& env
, const bc::ArrayIdx
&) { pushRemovableIfNoThrow(env
); }
1435 void dce(Env
& env
, const bc::BitAnd
&) { pushRemovableIfNoThrow(env
); }
1436 void dce(Env
& env
, const bc::BitNot
&) { pushRemovableIfNoThrow(env
); }
1437 void dce(Env
& env
, const bc::BitOr
&) { pushRemovableIfNoThrow(env
); }
1438 void dce(Env
& env
, const bc::BitXor
&) { pushRemovableIfNoThrow(env
); }
1439 void dce(Env
& env
, const bc::CastArray
&) { pushRemovableIfNoThrow(env
); }
1440 void dce(Env
& env
, const bc::CastBool
&) { pushRemovableIfNoThrow(env
); }
1441 void dce(Env
& env
, const bc::CastDArray
&) { pushRemovableIfNoThrow(env
); }
1442 void dce(Env
& env
, const bc::CastDict
&) { pushRemovableIfNoThrow(env
); }
1443 void dce(Env
& env
, const bc::CastDouble
&) { pushRemovableIfNoThrow(env
); }
1444 void dce(Env
& env
, const bc::CastInt
&) { pushRemovableIfNoThrow(env
); }
1445 void dce(Env
& env
, const bc::CastKeyset
&) { pushRemovableIfNoThrow(env
); }
1446 void dce(Env
& env
, const bc::CastString
&) { pushRemovableIfNoThrow(env
); }
1447 void dce(Env
& env
, const bc::CastVArray
&) { pushRemovableIfNoThrow(env
); }
1448 void dce(Env
& env
, const bc::CastVec
&) { pushRemovableIfNoThrow(env
); }
1449 void dce(Env
& env
, const bc::Cmp
&) { pushRemovableIfNoThrow(env
); }
1450 void dce(Env
& env
, const bc::CombineAndResolveTypeStruct
&) {
1451 pushRemovableIfNoThrow(env
);
1453 void dce(Env
& env
, const bc::Concat
&) { pushRemovableIfNoThrow(env
); }
1454 void dce(Env
& env
, const bc::ConcatN
&) { pushRemovableIfNoThrow(env
); }
1455 void dce(Env
& env
, const bc::DblAsBits
&) { pushRemovableIfNoThrow(env
); }
1456 void dce(Env
& env
, const bc::Div
&) { pushRemovableIfNoThrow(env
); }
1457 void dce(Env
& env
, const bc::Eq
&) { pushRemovableIfNoThrow(env
); }
1458 void dce(Env
& env
, const bc::Gt
&) { pushRemovableIfNoThrow(env
); }
1459 void dce(Env
& env
, const bc::Gte
&) { pushRemovableIfNoThrow(env
); }
1460 void dce(Env
& env
, const bc::Idx
&) { pushRemovableIfNoThrow(env
); }
1461 void dce(Env
& env
, const bc::IsLateBoundCls
&) { pushRemovableIfNoThrow(env
); }
1462 void dce(Env
& env
, const bc::IsTypeStructC
&) { pushRemovableIfNoThrow(env
); }
1463 void dce(Env
& env
, const bc::Lt
&) { pushRemovableIfNoThrow(env
); }
1464 void dce(Env
& env
, const bc::Lte
&) { pushRemovableIfNoThrow(env
); }
1465 void dce(Env
& env
, const bc::Mod
&) { pushRemovableIfNoThrow(env
); }
1466 void dce(Env
& env
, const bc::Mul
&) { pushRemovableIfNoThrow(env
); }
1467 void dce(Env
& env
, const bc::MulO
&) { pushRemovableIfNoThrow(env
); }
1468 void dce(Env
& env
, const bc::Neq
&) { pushRemovableIfNoThrow(env
); }
1469 void dce(Env
& env
, const bc::Not
&) { pushRemovableIfNoThrow(env
); }
1470 void dce(Env
& env
, const bc::NSame
&) { pushRemovableIfNoThrow(env
); }
1471 void dce(Env
& env
, const bc::Pow
&) { pushRemovableIfNoThrow(env
); }
1472 void dce(Env
& env
, const bc::Same
&) { pushRemovableIfNoThrow(env
); }
1473 void dce(Env
& env
, const bc::Shl
&) { pushRemovableIfNoThrow(env
); }
1474 void dce(Env
& env
, const bc::Shr
&) { pushRemovableIfNoThrow(env
); }
1475 void dce(Env
& env
, const bc::Sub
&) { pushRemovableIfNoThrow(env
); }
1476 void dce(Env
& env
, const bc::SubO
&) { pushRemovableIfNoThrow(env
); }
1477 void dce(Env
& env
, const bc::Xor
&) { pushRemovableIfNoThrow(env
); }
1478 void dce(Env
& env
, const bc::LateBoundCls
&) { pushRemovableIfNoThrow(env
); }
1479 void dce(Env
& env
, const bc::Self
&) { pushRemovableIfNoThrow(env
); }
1480 void dce(Env
& env
, const bc::Parent
&) { pushRemovableIfNoThrow(env
); }
1481 void dce(Env
& env
, const bc::ClassName
&) { pushRemovableIfNoThrow(env
); }
1482 void dce(Env
& env
, const bc::ClassGetC
&) { pushRemovableIfNoThrow(env
); }
1485 * Default implementation is conservative: assume we use all of our
1486 * inputs, and can't be removed even if our output is unused.
1488 * We also assume all the locals in the mayReadLocalSet must be
1489 * added to the live local set, and don't remove anything from it.
1493 void no_dce(Env
& env
, const Op
& op
) {
1494 addLocGenSet(env
, env
.flags
.mayReadLocalSet
);
1495 push_outputs(env
, op
.numPush());
1496 pop_inputs(env
, op
.numPop());
1499 void dce(Env
& env
, const bc::AliasCls
& op
) { no_dce(env
, op
); }
1500 void dce(Env
& env
, const bc::AssertRATL
& op
) { no_dce(env
, op
); }
1501 void dce(Env
& env
, const bc::AssertRATStk
& op
) { no_dce(env
, op
); }
1502 void dce(Env
& env
, const bc::Await
& op
) { no_dce(env
, op
); }
1503 void dce(Env
& env
, const bc::AwaitAll
& op
) { no_dce(env
, op
); }
1504 void dce(Env
& env
, const bc::BaseGL
& op
) { no_dce(env
, op
); }
1505 void dce(Env
& env
, const bc::BaseH
& op
) { no_dce(env
, op
); }
1506 void dce(Env
& env
, const bc::BaseL
& op
) { no_dce(env
, op
); }
1507 void dce(Env
& env
, const bc::BreakTraceHint
& op
) { no_dce(env
, op
); }
1508 void dce(Env
& env
, const bc::CGetCUNop
& op
) { no_dce(env
, op
); }
1509 void dce(Env
& env
, const bc::CGetG
& op
) { no_dce(env
, op
); }
1510 void dce(Env
& env
, const bc::CGetS
& op
) { no_dce(env
, op
); }
1511 void dce(Env
& env
, const bc::ChainFaults
& op
) { no_dce(env
, op
); }
1512 void dce(Env
& env
, const bc::CheckReifiedGenericMismatch
& op
) {
1515 void dce(Env
& env
, const bc::CheckThis
& op
) { no_dce(env
, op
); }
1516 void dce(Env
& env
, const bc::Clone
& op
) { no_dce(env
, op
); }
1517 void dce(Env
& env
, const bc::ClsCns
& op
) { no_dce(env
, op
); }
1518 void dce(Env
& env
, const bc::ClsCnsD
& op
) { no_dce(env
, op
); }
1519 void dce(Env
& env
, const bc::ClassGetTS
& op
) { no_dce(env
, op
); }
1520 void dce(Env
& env
, const bc::CnsE
& op
) { no_dce(env
, op
); }
1521 void dce(Env
& env
, const bc::ContAssignDelegate
& op
) { no_dce(env
, op
); }
1522 void dce(Env
& env
, const bc::ContCheck
& op
) { no_dce(env
, op
); }
1523 void dce(Env
& env
, const bc::ContCurrent
& op
) { no_dce(env
, op
); }
1524 void dce(Env
& env
, const bc::ContEnter
& op
) { no_dce(env
, op
); }
1525 void dce(Env
& env
, const bc::ContEnterDelegate
& op
) { no_dce(env
, op
); }
1526 void dce(Env
& env
, const bc::ContGetReturn
& op
) { no_dce(env
, op
); }
1527 void dce(Env
& env
, const bc::ContKey
& op
) { no_dce(env
, op
); }
1528 void dce(Env
& env
, const bc::ContRaise
& op
) { no_dce(env
, op
); }
1529 void dce(Env
& env
, const bc::ContUnsetDelegate
& op
) { no_dce(env
, op
); }
1530 void dce(Env
& env
, const bc::ContValid
& op
) { no_dce(env
, op
); }
1531 void dce(Env
& env
, const bc::CreateCl
& op
) { no_dce(env
, op
); }
1532 void dce(Env
& env
, const bc::CreateCont
& op
) { no_dce(env
, op
); }
1533 void dce(Env
& env
, const bc::DefCls
& op
) { no_dce(env
, op
); }
1534 void dce(Env
& env
, const bc::DefClsNop
& op
) { no_dce(env
, op
); }
1535 void dce(Env
& env
, const bc::DefCns
& op
) { no_dce(env
, op
); }
1536 void dce(Env
& env
, const bc::DefRecord
& op
) { no_dce(env
, op
); }
1537 void dce(Env
& env
, const bc::DefTypeAlias
& op
) { no_dce(env
, op
); }
1538 void dce(Env
& env
, const bc::EmptyG
& op
) { no_dce(env
, op
); }
1539 void dce(Env
& env
, const bc::EmptyL
& op
) { no_dce(env
, op
); }
1540 void dce(Env
& env
, const bc::EmptyS
& op
) { no_dce(env
, op
); }
1541 void dce(Env
& env
, const bc::EntryNop
& op
) { no_dce(env
, op
); }
1542 void dce(Env
& env
, const bc::Eval
& op
) { no_dce(env
, op
); }
1543 void dce(Env
& env
, const bc::FCallBuiltin
& op
) { no_dce(env
, op
); }
1544 void dce(Env
& env
, const bc::FCallClsMethod
& op
) { no_dce(env
, op
); }
1545 void dce(Env
& env
, const bc::FCallClsMethodD
& op
) { no_dce(env
, op
); }
1546 void dce(Env
& env
, const bc::FCallClsMethodS
& op
) { no_dce(env
, op
); }
1547 void dce(Env
& env
, const bc::FCallClsMethodSD
& op
) { no_dce(env
, op
); }
1548 void dce(Env
& env
, const bc::FCallCtor
& op
) { no_dce(env
, op
); }
1549 void dce(Env
& env
, const bc::FCallFunc
& op
) { no_dce(env
, op
); }
1550 void dce(Env
& env
, const bc::FCallFuncD
& op
) { no_dce(env
, op
); }
1551 void dce(Env
& env
, const bc::FCallObjMethod
& op
) { no_dce(env
, op
); }
1552 void dce(Env
& env
, const bc::FCallObjMethodD
& op
) { no_dce(env
, op
); }
1553 void dce(Env
& env
, const bc::GetMemoKeyL
& op
) { no_dce(env
, op
); }
1554 void dce(Env
& env
, const bc::IncDecG
& op
) { no_dce(env
, op
); }
1555 void dce(Env
& env
, const bc::IncDecS
& op
) { no_dce(env
, op
); }
1556 void dce(Env
& env
, const bc::Incl
& op
) { no_dce(env
, op
); }
1557 void dce(Env
& env
, const bc::InclOnce
& op
) { no_dce(env
, op
); }
1558 void dce(Env
& env
, const bc::InitProp
& op
) { no_dce(env
, op
); }
1559 void dce(Env
& env
, const bc::InstanceOf
& op
) { no_dce(env
, op
); }
1560 void dce(Env
& env
, const bc::InstanceOfD
& op
) { no_dce(env
, op
); }
1561 void dce(Env
& env
, const bc::IssetG
& op
) { no_dce(env
, op
); }
1562 void dce(Env
& env
, const bc::IssetL
& op
) { no_dce(env
, op
); }
1563 void dce(Env
& env
, const bc::IssetS
& op
) { no_dce(env
, op
); }
1564 void dce(Env
& env
, const bc::IterBreak
& op
) { no_dce(env
, op
); }
1565 void dce(Env
& env
, const bc::IterFree
& op
) { no_dce(env
, op
); }
1566 void dce(Env
& env
, const bc::IterInit
& op
) { no_dce(env
, op
); }
1567 void dce(Env
& env
, const bc::IterNext
& op
) { no_dce(env
, op
); }
1568 void dce(Env
& env
, const bc::Jmp
& op
) { no_dce(env
, op
); }
1569 void dce(Env
& env
, const bc::JmpNS
& op
) { no_dce(env
, op
); }
1570 void dce(Env
& env
, const bc::JmpNZ
& op
) { no_dce(env
, op
); }
1571 void dce(Env
& env
, const bc::JmpZ
& op
) { no_dce(env
, op
); }
1572 void dce(Env
& env
, const bc::LIterFree
& op
) { no_dce(env
, op
); }
1573 void dce(Env
& env
, const bc::LIterInit
& op
) { no_dce(env
, op
); }
1574 void dce(Env
& env
, const bc::LIterNext
& op
) { no_dce(env
, op
); }
1575 void dce(Env
& env
, const bc::MemoGet
& op
) { no_dce(env
, op
); }
1576 void dce(Env
& env
, const bc::MemoGetEager
& op
) { no_dce(env
, op
); }
1577 void dce(Env
& env
, const bc::MemoSet
& op
) { no_dce(env
, op
); }
1578 void dce(Env
& env
, const bc::MemoSetEager
& op
) { no_dce(env
, op
); }
1579 void dce(Env
& env
, const bc::Method
& op
) { no_dce(env
, op
); }
1580 void dce(Env
& env
, const bc::NativeImpl
& op
) { no_dce(env
, op
); }
1581 void dce(Env
& env
, const bc::NewLikeArrayL
& op
) { no_dce(env
, op
); }
1582 void dce(Env
& env
, const bc::NewObj
& op
) { no_dce(env
, op
); }
1583 void dce(Env
& env
, const bc::NewObjR
& op
) { no_dce(env
, op
); }
1584 void dce(Env
& env
, const bc::NewObjD
& op
) { no_dce(env
, op
); }
1585 void dce(Env
& env
, const bc::NewObjRD
& op
) { no_dce(env
, op
); }
1586 void dce(Env
& env
, const bc::NewObjS
& op
) { no_dce(env
, op
); }
1587 void dce(Env
& env
, const bc::LockObj
& op
) { no_dce(env
, op
); }
1588 void dce(Env
& env
, const bc::NewRecord
& op
) { no_dce(env
, op
); }
1589 void dce(Env
& env
, const bc::NewRecordArray
& op
) { no_dce(env
, op
); }
1590 void dce(Env
& env
, const bc::Nop
& op
) { no_dce(env
, op
); }
1591 void dce(Env
& env
, const bc::OODeclExists
& op
) { no_dce(env
, op
); }
1592 void dce(Env
& env
, const bc::Print
& op
) { no_dce(env
, op
); }
1593 void dce(Env
& env
, const bc::RecordReifiedGeneric
& op
) { no_dce(env
, op
); }
1594 void dce(Env
& env
, const bc::Req
& op
) { no_dce(env
, op
); }
1595 void dce(Env
& env
, const bc::ReqDoc
& op
) { no_dce(env
, op
); }
1596 void dce(Env
& env
, const bc::ReqOnce
& op
) { no_dce(env
, op
); }
1597 void dce(Env
& env
, const bc::ResolveClsMethod
& op
) { no_dce(env
, op
); }
1598 void dce(Env
& env
, const bc::ResolveFunc
& op
) { no_dce(env
, op
); }
1599 void dce(Env
& env
, const bc::ResolveObjMethod
& op
) { no_dce(env
, op
); }
1600 void dce(Env
& env
, const bc::RetM
& op
) { no_dce(env
, op
); }
1601 void dce(Env
& env
, const bc::Select
& op
) { no_dce(env
, op
); }
1602 void dce(Env
& env
, const bc::SetG
& op
) { no_dce(env
, op
); }
1603 void dce(Env
& env
, const bc::SetOpG
& op
) { no_dce(env
, op
); }
1604 void dce(Env
& env
, const bc::SetOpS
& op
) { no_dce(env
, op
); }
1605 void dce(Env
& env
, const bc::SetRangeM
& op
) { no_dce(env
, op
); }
1606 void dce(Env
& env
, const bc::SetS
& op
) { no_dce(env
, op
); }
1607 void dce(Env
& env
, const bc::Silence
& op
) { no_dce(env
, op
); }
1608 void dce(Env
& env
, const bc::SSwitch
& op
) { no_dce(env
, op
); }
1609 void dce(Env
& env
, const bc::Switch
& op
) { no_dce(env
, op
); }
1610 void dce(Env
& env
, const bc::This
& op
) { no_dce(env
, op
); }
1611 void dce(Env
& env
, const bc::ThrowAsTypeStructException
& op
) {
1614 void dce(Env
& env
, const bc::UGetCUNop
& op
) { no_dce(env
, op
); }
1615 void dce(Env
& env
, const bc::UnsetG
& op
) { no_dce(env
, op
); }
1616 void dce(Env
& env
, const bc::VerifyOutType
& op
) { no_dce(env
, op
); }
1617 void dce(Env
& env
, const bc::VerifyParamType
& op
) { no_dce(env
, op
); }
1618 void dce(Env
& env
, const bc::VerifyParamTypeTS
& op
) { no_dce(env
, op
); }
1619 void dce(Env
& env
, const bc::VerifyRetNonNullC
& op
) { no_dce(env
, op
); }
1620 void dce(Env
& env
, const bc::VerifyRetTypeC
& op
) { no_dce(env
, op
); }
1621 void dce(Env
& env
, const bc::VerifyRetTypeTS
& op
) { no_dce(env
, op
); }
1622 void dce(Env
& env
, const bc::WHResult
& op
) { no_dce(env
, op
); }
1623 void dce(Env
& env
, const bc::Yield
& op
) { no_dce(env
, op
); }
1624 void dce(Env
& env
, const bc::YieldFromDelegate
& op
) { no_dce(env
, op
); }
1625 void dce(Env
& env
, const bc::YieldK
& op
) { no_dce(env
, op
); }
1627 ////////////////////////////////////////////////////////////////////////////////
1629 template<class Op
, class Bc
>
1630 void iter_key_dce(Env
& env
, const Op
& op
, const Bc
& bc
, const Iter
& iter
,
1631 const LocalId base
, const LocalId value
, const LocalId key
) {
1632 auto const isIterEligible
= match
<bool>(
1634 [] (DeadIter
) { return false; },
1635 [] (const LiveIter
& it
) { return it
.baseCannotBeObject
; }
1637 if (!isIterEligible
|| key
== value
|| isLocLive(env
, key
) ||
1638 isLocVolatile(env
, key
)) {
1639 // If key and value are the same, the key gets written after the value
1640 // Lets be pessimistic about this case
1641 return no_dce(env
, op
);
1643 CompactVector
<Bytecode
> bcs
{ bc
};
1644 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1645 env
.dceState
.actionMap
.emplace(env
.id
, DceAction::Replace
);
1647 addLocGen(env
, value
);
1648 if (base
!= kInvalidId
) addLocGen(env
, base
);
1649 pop_inputs(env
, op
.numPop());
1652 void dce(Env
& env
, const bc::IterInitK
& op
) {
1653 auto bc
= bc::IterInit
{
1658 auto const& iter
= env
.stateAfter
.iters
[op
.iter1
];
1659 iter_key_dce(env
, op
, bc
, iter
, kInvalidId
, op
.loc3
, op
.loc4
);
1662 void dce(Env
& env
, const bc::LIterInitK
& op
) {
1663 auto bc
= bc::LIterInit
{
1670 auto const& iter
= env
.stateAfter
.iters
[op
.iter1
];
1671 iter_key_dce(env
, op
, bc
, iter
, op
.loc2
, op
.loc4
, op
.loc5
);
1674 void dce(Env
& env
, const bc::IterNextK
& op
) {
1675 auto bc
= bc::IterNext
{
1680 auto const& iter
= env
.stateBefore
.iters
[op
.iter1
];
1681 iter_key_dce(env
, op
, bc
, iter
, kInvalidId
, op
.loc3
, op
.loc4
);
1684 void dce(Env
& env
, const bc::LIterNextK
& op
) {
1685 auto bc
= bc::LIterNext
{
1692 auto const& iter
= env
.stateBefore
.iters
[op
.iter1
];
1693 iter_key_dce(env
, op
, bc
, iter
, op
.loc2
, op
.loc4
, op
.loc5
);
1696 ////////////////////////////////////////////////////////////////////////////////
1699 * The minstr instructions can read a cell from the stack without
1700 * popping it. They need special handling.
1702 * In addition, final minstr instructions can drop a number of
1703 * elements from the stack. Its possible that these elements are dead
1704 * (eg because an MEC somewhere in the sequence was optimized to MEI
1705 * or MET), so again we need some special handling.
1708 void minstr_touch(Env
& env
, int32_t depth
) {
1709 assertx(depth
< env
.dceState
.stack
.size());
1710 // First make sure that the stack element we reference, and any
1711 // linked ones are marked used.
1712 use(env
.dceState
.forcedLiveLocations
,
1713 env
.dceState
.stack
, env
.dceState
.stack
.size() - 1 - depth
);
1714 // If any stack slots at a lower depth get killed, we'll have to
1715 // adjust our stack index accordingly, so add a MinstrStackFixup
1716 // action to candidate stack slots.
1717 // We only track slots up to kMaskSize though - so nothing below
1718 // that will get killed.
1719 if (depth
> DceAction::kMaskSize
) depth
= DceAction::kMaskSize
;
1721 auto& ui
= env
.dceState
.stack
[env
.dceState
.stack
.size() - 1 - depth
];
1722 if (ui
.usage
!= Use::Used
) {
1723 DEBUG_ONLY
auto inserted
= ui
.actions
.emplace(
1724 env
.id
, DceAction
{ DceAction::MinstrStackFixup
, 1u << depth
}).second
;
1731 void minstr_base(Env
& env
, const Op
& op
, int32_t ix
) {
1733 minstr_touch(env
, ix
);
1737 void minstr_dim(Env
& env
, const Op
& op
) {
1739 if (op
.mkey
.mcode
== MEC
|| op
.mkey
.mcode
== MPC
) {
1740 minstr_touch(env
, op
.mkey
.idx
);
1745 void minstr_final(Env
& env
, const Op
& op
, int32_t ndiscard
) {
1746 addLocGenSet(env
, env
.flags
.mayReadLocalSet
);
1747 push_outputs(env
, op
.numPush());
1748 auto const numPop
= op
.numPop();
1749 auto const stackRead
= op
.mkey
.mcode
== MEC
|| op
.mkey
.mcode
== MPC
?
1750 op
.mkey
.idx
: numPop
;
1752 for (auto i
= numPop
; i
--; ) {
1753 if (i
== stackRead
|| i
>= DceAction::kMaskSize
|| i
< numPop
- ndiscard
) {
1756 pop(env
, {Use::Not
, {{env
.id
, {DceAction::MinstrStackFinal
, 1u << i
}}}});
1761 void dce(Env
& env
, const bc::BaseC
& op
) { minstr_base(env
, op
, op
.arg1
); }
1762 void dce(Env
& env
, const bc::BaseGC
& op
) { minstr_base(env
, op
, op
.arg1
); }
1763 void dce(Env
& env
, const bc::BaseSC
& op
) {
1764 minstr_base(env
, op
, op
.arg1
);
1765 minstr_base(env
, op
, op
.arg2
);
1768 void dce(Env
& env
, const bc::Dim
& op
) { minstr_dim(env
, op
); }
1770 void dce(Env
& env
, const bc::QueryM
& op
) {
1771 if (!env
.flags
.wasPEI
) {
1772 assertx(!env
.dceState
.minstrUI
);
1773 auto ui
= env
.dceState
.stack
.back();
1774 if (!isLinked(ui
)) {
1775 if (allUnused(ui
)) {
1776 addLocGenSet(env
, env
.flags
.mayReadLocalSet
);
1777 ui
.actions
[env
.id
] = DceAction::Kill
;
1778 ui
.location
.id
= env
.id
.idx
;
1779 env
.dceState
.minstrUI
.emplace(std::move(ui
));
1780 } else if (auto const val
= tv(env
.stateAfter
.stack
.back().type
)) {
1781 addLocGenSet(env
, env
.flags
.mayReadLocalSet
);
1782 CompactVector
<Bytecode
> bcs
{ gen_constant(*val
) };
1783 env
.dceState
.replaceMap
.emplace(env
.id
, std::move(bcs
));
1784 ui
.actions
[env
.id
] = DceAction::Replace
;
1785 ui
.location
.id
= env
.id
.idx
;
1786 env
.dceState
.minstrUI
.emplace(std::move(ui
));
1790 minstr_final(env
, op
, op
.arg1
);
1793 void dce(Env
& env
, const bc::SetM
& op
) { minstr_final(env
, op
, op
.arg1
); }
1794 void dce(Env
& env
, const bc::IncDecM
& op
) { minstr_final(env
, op
, op
.arg1
); }
1795 void dce(Env
& env
, const bc::SetOpM
& op
) { minstr_final(env
, op
, op
.arg1
); }
1796 void dce(Env
& env
, const bc::UnsetM
& op
) { minstr_final(env
, op
, op
.arg1
); }
1798 void dispatch_dce(Env
& env
, const Bytecode
& op
) {
1799 #define O(opcode, ...) case Op::opcode: dce(env, op.opcode); return;
1800 switch (op
.op
) { OPCODES
}
1805 using MaskType
= DceAction::MaskType
;
1807 void m_adj(uint32_t& depth
, MaskType mask
) {
1809 if (i
> DceAction::kMaskSize
) i
= DceAction::kMaskSize
;
1811 if ((mask
>> i
) & 1) depth
--;
1815 void m_adj(MKey
& mkey
, MaskType mask
) {
1816 if (mkey
.mcode
== MEC
|| mkey
.mcode
== MPC
) {
1817 uint32_t d
= mkey
.idx
;
1823 template<typename Op
>
1824 void m_adj(uint32_t& ndiscard
, Op
& op
, MaskType mask
) {
1825 auto const adjust
= op
.numPop() - ndiscard
+ 1;
1827 m_adj(ndiscard
, mask
);
1829 m_adj(op
.mkey
, mask
);
1832 template <typename Op
>
1833 void adjustMinstr(Op
& /*op*/, MaskType
/*mask*/) {
1834 always_assert(false);
1837 void adjustMinstr(bc::BaseC
& op
, MaskType m
) { m_adj(op
.arg1
, m
); }
1838 void adjustMinstr(bc::BaseGC
& op
, MaskType m
) { m_adj(op
.arg1
, m
); }
1839 void adjustMinstr(bc::BaseSC
& op
, MaskType m
) {
1844 void adjustMinstr(bc::Dim
& op
, MaskType m
) { m_adj(op
.mkey
, m
); }
1846 void adjustMinstr(bc::QueryM
& op
, MaskType m
) { m_adj(op
.arg1
, op
, m
); }
1847 void adjustMinstr(bc::SetM
& op
, MaskType m
) { m_adj(op
.arg1
, op
, m
); }
1848 void adjustMinstr(bc::IncDecM
& op
, MaskType m
) { m_adj(op
.arg1
, op
, m
); }
1849 void adjustMinstr(bc::SetOpM
& op
, MaskType m
) { m_adj(op
.arg1
, op
, m
); }
1850 void adjustMinstr(bc::UnsetM
& op
, MaskType m
) { m_adj(op
.arg1
, op
, m
); }
1852 void adjustMinstr(Bytecode
& op
, MaskType m
) {
1853 #define O(opcode, ...) case Op::opcode: adjustMinstr(op.opcode, m); return;
1854 switch (op
.op
) { OPCODES
}
1860 //////////////////////////////////////////////////////////////////////
1862 struct DceOutState
{
1863 DceOutState() = default;
1865 explicit DceOutState(Local
) : isLocal(true) {
1871 * The union of the liveIn states of each normal successor for
1872 * locals and stack slots respectively.
1874 std::bitset
<kMaxTrackedLocals
> locLive
;
1877 * The union of the liveIn states of each exceptional successor for
1878 * locals and stack slots respectively.
1880 std::bitset
<kMaxTrackedLocals
> locLiveExn
;
1883 * The union of the dceStacks from the start of the normal successors.
1885 folly::Optional
<std::vector
<UseInfo
>> dceStack
;
1888 * Whether this is for local_dce
1890 bool isLocal
{false};
1893 folly::Optional
<DceState
>
1894 dce_visit(const Index
& index
,
1895 const FuncAnalysis
& fa
,
1896 CollectedInfo
& collect
,
1898 const State
& stateIn
,
1899 const DceOutState
& dceOutState
) {
1900 if (!stateIn
.initialized
) {
1902 * Skip unreachable blocks.
1904 * For DCE analysis it is ok to assume the transfer function is
1905 * the identity on unreachable blocks (i.e. gen and kill sets are
1906 * empty). For optimize, we don't need to bother doing anything
1907 * to these---another pass is responsible for removing completely
1908 * unreachable blocks.
1913 auto const states
= locally_propagated_states(index
, fa
, collect
,
1916 auto dceState
= DceState
{ index
, fa
};
1917 dceState
.liveLocals
= dceOutState
.locLive
;
1918 dceState
.usedLocals
= dceOutState
.locLive
;
1919 dceState
.isLocal
= dceOutState
.isLocal
;
1921 auto const blk
= fa
.ctx
.func
->blocks
[bid
].get();
1922 auto const dceStkSz
= [&] {
1923 switch (blk
->hhbcs
.back().op
) {
1925 case Op::MemoGetEager
:
1926 // These only push on the "hit" path. If we can prove they
1927 // never hit, the final stack state won't have an extra
1928 // element. But their dce implementations assume that they
1929 // push something. Fix it by just adding one to their in
1930 // state. Note that when dceState.dceStack is populated, we
1931 // already did the equivalent during global analysis (see
1932 // isCFPushTaken below).
1933 return states
[states
.size() - 2].first
.stack
.size() + 1;
1935 return states
.back().first
.stack
.size();
1939 if (dceOutState
.dceStack
) {
1940 dceState
.stack
= *dceOutState
.dceStack
;
1941 assert(dceState
.stack
.size() == dceStkSz
);
1943 dceState
.stack
.resize(dceStkSz
, UseInfo
{ Use::Used
});
1946 for (uint32_t idx
= blk
->hhbcs
.size(); idx
-- > 0;) {
1947 auto const& op
= blk
->hhbcs
[idx
];
1949 FTRACE(2, " == #{} {}\n", idx
, show(fa
.ctx
.func
, op
));
1951 auto visit_env
= Env
{
1958 states
[idx
+1].first
,
1960 auto const handled
= [&] {
1961 if (dceState
.minstrUI
) {
1962 if (visit_env
.flags
.wasPEI
) {
1963 dceState
.minstrUI
.clear();
1966 if (isMemberDimOp(op
.op
)) {
1967 dceState
.minstrUI
->actions
[visit_env
.id
] = DceAction::Kill
;
1968 // we're almost certainly going to delete this op, but we might not,
1969 // so we need to record its local uses etc just in case.
1972 if (isMemberBaseOp(op
.op
)) {
1973 auto const& final
= blk
->hhbcs
[dceState
.minstrUI
->location
.id
];
1974 if (final
.numPop()) {
1975 CompactVector
<Bytecode
> bcs
;
1976 for (auto i
= 0; i
< final
.numPop(); i
++) {
1977 use(dceState
.forcedLiveLocations
,
1978 dceState
.stack
, dceState
.stack
.size() - 1 - i
);
1979 bcs
.push_back(bc::PopC
{});
1981 dceState
.replaceMap
.emplace(visit_env
.id
, std::move(bcs
));
1982 dceState
.minstrUI
->actions
[visit_env
.id
] = DceAction::Replace
;
1984 dceState
.minstrUI
->actions
[visit_env
.id
] = DceAction::Kill
;
1986 commitActions(visit_env
, false, dceState
.minstrUI
->actions
);
1987 dceState
.minstrUI
.clear();
1994 dispatch_dce(visit_env
, op
);
1997 * When we see a PEI, liveness must take into account the fact
1998 * that we could take an exception edge here (or'ing in the
2001 if (states
[idx
].second
.wasPEI
) {
2002 FTRACE(2, " <-- exceptions\n");
2003 dceState
.liveLocals
|= dceOutState
.locLiveExn
;
2006 dceState
.usedLocals
|= dceState
.liveLocals
;
2009 FTRACE(4, " dce stack: {}\n",
2011 using namespace folly::gen
;
2012 return from(dceState
.stack
)
2013 | map([&] (const UseInfo
& ui
) { return show(ui
); })
2014 | unsplit
<std::string
>(" ");
2017 FTRACE(4, " interp stack: {}\n",
2019 using namespace folly::gen
;
2020 return from(states
[idx
].first
.stack
)
2021 | map([&] (auto const& e
) { return show(e
.type
); })
2022 | unsplit
<std::string
>(" ");
2025 if (dceState
.minstrUI
) {
2026 FTRACE(4, " minstr ui: {}\n", show(*dceState
.minstrUI
));
2029 // We're now at the state before this instruction, so the stack
2030 // sizes must line up.
2031 assert(dceState
.stack
.size() == states
[idx
].first
.stack
.size());
2034 dceState
.minstrUI
.clear();
2038 struct DceAnalysis
{
2039 std::bitset
<kMaxTrackedLocals
> locLiveIn
;
2040 std::vector
<UseInfo
> stack
;
2041 LocationIdSet forcedLiveLocations
;
2044 DceAnalysis
analyze_dce(const Index
& index
,
2045 const FuncAnalysis
& fa
,
2046 CollectedInfo
& collect
,
2048 const State
& stateIn
,
2049 const DceOutState
& dceOutState
) {
2050 if (auto dceState
= dce_visit(index
, fa
, collect
,
2051 bid
, stateIn
, dceOutState
)) {
2052 return DceAnalysis
{
2053 dceState
->liveLocals
,
2055 dceState
->forcedLiveLocations
2058 return DceAnalysis
{};
2062 * Do the actual updates to the bytecodes.
2064 void dce_perform(php::Func
& func
,
2065 const DceActionMap
& actionMap
,
2066 const DceReplaceMap
& replaceMap
) {
2068 using It
= BytecodeVec::iterator
;
2069 auto setloc
= [] (int32_t srcLoc
, It start
, int n
) {
2071 start
++->srcLoc
= srcLoc
;
2074 for (auto const& elm
: actionMap
) {
2075 auto const& id
= elm
.first
;
2076 auto const b
= func
.blocks
[id
.blk
].mutate();
2077 FTRACE(1, "{} {}\n", show(elm
), show(func
, b
->hhbcs
[id
.idx
]));
2078 switch (elm
.second
.action
) {
2079 case DceAction::PopInputs
:
2080 // we want to replace the bytecode with pops of its inputs
2081 if (auto const numToPop
= numPop(b
->hhbcs
[id
.idx
])) {
2082 auto const srcLoc
= b
->hhbcs
[id
.idx
].srcLoc
;
2083 b
->hhbcs
.erase(b
->hhbcs
.begin() + id
.idx
);
2084 b
->hhbcs
.insert(b
->hhbcs
.begin() + id
.idx
,
2087 setloc(srcLoc
, b
->hhbcs
.begin() + id
.idx
, numToPop
);
2091 case DceAction::Kill
:
2092 if (b
->hhbcs
.size() == 1) {
2093 // we don't allow empty blocks
2094 auto const srcLoc
= b
->hhbcs
[0].srcLoc
;
2095 b
->hhbcs
[0] = bc::Nop
{};
2096 b
->hhbcs
[0].srcLoc
= srcLoc
;
2098 b
->hhbcs
.erase(b
->hhbcs
.begin() + id
.idx
);
2101 case DceAction::PopOutputs
:
2103 // we want to pop the things that the bytecode pushed
2104 auto const numToPop
= numPush(b
->hhbcs
[id
.idx
]);
2105 b
->hhbcs
.insert(b
->hhbcs
.begin() + id
.idx
+ 1,
2108 setloc(b
->hhbcs
[id
.idx
].srcLoc
,
2109 b
->hhbcs
.begin() + id
.idx
+ 1, numToPop
);
2112 case DceAction::Replace
:
2114 auto it
= replaceMap
.find(id
);
2115 always_assert(it
!= end(replaceMap
) && !it
->second
.empty());
2116 auto const srcLoc
= b
->hhbcs
[id
.idx
].srcLoc
;
2117 b
->hhbcs
.erase(b
->hhbcs
.begin() + id
.idx
);
2118 b
->hhbcs
.insert(b
->hhbcs
.begin() + id
.idx
,
2119 begin(it
->second
), end(it
->second
));
2120 setloc(srcLoc
, b
->hhbcs
.begin() + id
.idx
, it
->second
.size());
2123 case DceAction::PopAndReplace
:
2125 auto it
= replaceMap
.find(id
);
2126 always_assert(it
!= end(replaceMap
) && !it
->second
.empty());
2127 auto const srcLoc
= b
->hhbcs
[id
.idx
].srcLoc
;
2128 auto const numToPop
= numPop(b
->hhbcs
[id
.idx
]);
2129 b
->hhbcs
.erase(b
->hhbcs
.begin() + id
.idx
);
2130 b
->hhbcs
.insert(b
->hhbcs
.begin() + id
.idx
,
2131 begin(it
->second
), end(it
->second
));
2133 b
->hhbcs
.insert(b
->hhbcs
.begin() + id
.idx
,
2137 setloc(srcLoc
, b
->hhbcs
.begin() + id
.idx
, numToPop
+ it
->second
.size());
2140 case DceAction::MinstrStackFinal
:
2141 case DceAction::MinstrStackFixup
:
2143 adjustMinstr(b
->hhbcs
[id
.idx
], elm
.second
.mask
);
2150 struct DceOptResult
{
2151 std::bitset
<kMaxTrackedLocals
> usedLocals
;
2152 DceActionMap actionMap
;
2153 DceReplaceMap replaceMap
;
2154 bool didAddElemOpts
{false};
2158 optimize_dce(const Index
& index
,
2159 const FuncAnalysis
& fa
,
2160 CollectedInfo
& collect
,
2162 const State
& stateIn
,
2163 const DceOutState
& dceOutState
) {
2164 auto dceState
= dce_visit(index
, fa
, collect
, bid
, stateIn
, dceOutState
);
2167 return {std::bitset
<kMaxTrackedLocals
>{}};
2171 std::move(dceState
->usedLocals
),
2172 std::move(dceState
->actionMap
),
2173 std::move(dceState
->replaceMap
),
2174 dceState
->didAddElemOpts
2178 //////////////////////////////////////////////////////////////////////
2180 void remove_unused_locals(Context
const ctx
,
2181 std::bitset
<kMaxTrackedLocals
> usedLocals
) {
2182 if (!options
.RemoveUnusedLocals
) return;
2183 auto const func
= ctx
.func
;
2186 * Removing unused locals in closures requires checking which ones
2187 * are captured variables so we can remove the relevant properties,
2188 * and then we'd have to mutate the CreateCl callsite, so we don't
2191 * Note: many closure bodies have unused $this local, because of
2192 * some emitter quirk, so this might be worthwhile.
2194 if (func
->isClosureBody
) return;
2196 // For reified functions, skip the first non-param local
2197 auto loc
= func
->locals
.begin() + func
->params
.size() + (int)func
->isReified
;
2198 for (; loc
!= func
->locals
.end(); ++loc
) {
2200 assert(loc
->id
< kMaxTrackedLocals
&& !usedLocals
.test(loc
->id
));
2203 if (loc
->id
< kMaxTrackedLocals
&& !usedLocals
.test(loc
->id
)) {
2204 FTRACE(2, " killing: {}\n", local_string(*func
, loc
->id
));
2205 const_cast<php::Local
&>(*loc
).killed
= true;
2210 //////////////////////////////////////////////////////////////////////
2214 void local_dce(const Index
& index
,
2215 const FuncAnalysis
& ainfo
,
2216 CollectedInfo
& collect
,
2218 const State
& stateIn
) {
2219 // For local DCE, we have to assume all variables are in the
2220 // live-out set for the block.
2221 auto const ret
= optimize_dce(index
, ainfo
, collect
, bid
, stateIn
,
2222 DceOutState
{DceOutState::Local
{}});
2224 dce_perform(*ainfo
.ctx
.func
, ret
.actionMap
, ret
.replaceMap
);
2227 //////////////////////////////////////////////////////////////////////
2229 bool global_dce(const Index
& index
, const FuncAnalysis
& ai
) {
2230 auto rpoId
= [&] (BlockId blk
) {
2231 return ai
.bdata
[blk
].rpoId
;
2234 auto collect
= CollectedInfo
{
2235 index
, ai
.ctx
, nullptr,
2236 CollectionOpts
{}, &ai
2239 FTRACE(1, "|---- global DCE analyze ({})\n", show(ai
.ctx
));
2240 FTRACE(2, "{}", [&] {
2241 using namespace folly::gen
;
2242 auto i
= uint32_t{0};
2243 return from(ai
.ctx
.func
->locals
)
2245 [&] (const php::Local
& l
) {
2246 return folly::sformat(" {} {}\n",
2247 i
++, local_string(*ai
.ctx
.func
, l
.id
));
2249 | unsplit
<std::string
>("");
2253 * States for each block, indexed by block id.
2255 std::vector
<DceOutState
> blockStates(ai
.ctx
.func
->blocks
.size());
2258 * Set of block reverse post order ids that still need to be
2259 * visited. This is ordered by std::greater, so we'll generally
2260 * visit blocks before their predecessors. The algorithm does not
2261 * depend on this for correctness, but it will reduce iteration, and
2262 * the optimality of mergeDceStack depends on visiting at least one
2263 * successor prior to visiting any given block.
2265 * Every block must be visited at least once, so we throw them all
2268 auto incompleteQ
= dataflow_worklist
<uint32_t,std::less
<uint32_t>>(
2271 for (auto const bid
: ai
.rpoBlocks
) incompleteQ
.push(rpoId(bid
));
2273 auto const nonThrowPreds
= computeNonThrowPreds(*ai
.ctx
.func
, ai
.rpoBlocks
);
2274 auto const throwPreds
= computeThrowPreds(*ai
.ctx
.func
, ai
.rpoBlocks
);
2277 * Suppose a stack slot isn't used, but it was pushed on two separate
2278 * paths, one of which could be killed, but the other can't. We have
2279 * to prevent either, so any time we find a non-used stack slot that
2280 * can't be killed, we add it to this set (via markUisLive, and the
2281 * DceAnalysis), and schedule its block for re-analysis.
2283 LocationIdSet forcedLiveLocations
;
2286 * Temporary set used to collect locations that were forced live by
2287 * block merging/linked locations etc.
2289 LocationIdSet forcedLiveTemp
;
2291 auto checkLive
= [&] (std::vector
<UseInfo
>& uis
, uint32_t i
,
2292 LocationId location
) {
2293 if (forcedLiveLocations
.count(location
)) return true;
2294 if (!isLinked(uis
[i
])) return false;
2296 return uis
[i
- 1].usage
== Use::Used
;
2299 auto fixupUseInfo
= [&] (std::vector
<UseInfo
>& uis
,
2302 for (uint32_t i
= 0; i
< uis
.size(); i
++) {
2304 if (out
.location
.blk
== NoBlockId
) out
.location
= { blk
, i
, isSlot
};
2305 if (checkLive(uis
, i
, out
.location
)) {
2306 use(forcedLiveTemp
, uis
, i
);
2311 auto mergeUIs
= [&] (std::vector
<UseInfo
>& outBase
,
2312 const std::vector
<UseInfo
>& inBase
,
2313 uint32_t i
, BlockId blk
, bool isSlot
) {
2314 auto& out
= outBase
[i
];
2315 auto& in
= inBase
[i
];
2316 if (out
.usage
== Use::Used
) {
2317 if (in
.usage
!= Use::Used
&& nonThrowPreds
[blk
].size() > 1) {
2318 // This is to deal with the case where blk has multiple preds,
2319 // and one of those has multiple succs, one of which does use
2320 // this stack value.
2322 auto& ui
= inBase
[i
];
2323 auto linked
= isLinked(ui
);
2324 if (ui
.usage
!= Use::Used
) {
2325 forcedLiveTemp
.insert({blk
, i
, isSlot
});
2335 if (in
.usage
== Use::Used
|| out
.usage
!= in
.usage
) {
2336 use(forcedLiveTemp
, outBase
, i
);
2339 auto location
= in
.location
;
2340 if (location
.blk
== NoBlockId
) location
= { blk
, i
, isSlot
};
2341 if (checkLive(outBase
, i
, location
)) {
2342 use(forcedLiveTemp
, outBase
, i
);
2347 for (auto const& id
: in
.actions
) {
2348 ret
|= out
.actions
.insert(id
).second
;
2350 assert(out
.location
.blk
!= NoBlockId
);
2351 if (out
.location
< location
) {
2352 // It doesn't matter which one we choose, but it should be
2353 // independent of the visiting order.
2354 out
.location
= location
;
2360 auto mergeUIVecs
= [&] (folly::Optional
<std::vector
<UseInfo
>>& stkOut
,
2361 const std::vector
<UseInfo
>& stkIn
,
2362 BlockId blk
, bool isSlot
) {
2365 fixupUseInfo(*stkOut
, blk
, isSlot
);
2370 assert(stkOut
->size() == stkIn
.size());
2371 for (uint32_t i
= 0; i
< stkIn
.size(); i
++) {
2372 if (mergeUIs(*stkOut
, stkIn
, i
, blk
, isSlot
)) ret
= true;
2378 * If we weren't able to take advantage of any unused entries
2379 * on the dceStack that originated from another block, mark
2380 * them used to prevent other paths from trying to delete them.
2382 * Also, merge the entries into our global forcedLiveLocations, to
2383 * make sure they always get marked Used.
2385 auto processForcedLive
= [&] (const LocationIdSet
& forcedLive
) {
2386 for (auto const& id
: forcedLive
) {
2387 if (forcedLiveLocations
.insert(id
).second
) {
2388 // This is slightly inefficient; we know exactly how this will
2389 // affect the analysis, so we could get away with just
2390 // reprocessing the affected predecessors of id.blk; but this
2391 // is much simpler, and less error prone. We can revisit if it
2392 // turns out to be a performance issue.
2393 FTRACE(5, "Forcing {} live\n", show(id
));
2394 incompleteQ
.push(rpoId(id
.blk
));
2400 * Iterate on live out states until we reach a fixed point.
2402 * This algorithm treats the exceptional live-out states differently
2403 * from the live-out states during normal control flow. The
2404 * liveOutExn sets only take part in the liveIn computation when the
2405 * block has exceptional exits.
2407 while (!incompleteQ
.empty()) {
2408 auto const bid
= ai
.rpoBlocks
[incompleteQ
.pop()];
2410 // skip unreachable blocks
2411 if (!ai
.bdata
[bid
].stateIn
.initialized
) continue;
2413 FTRACE(2, "block #{}\n", bid
);
2415 auto& blockState
= blockStates
[bid
];
2416 auto const result
= analyze_dce(
2421 ai
.bdata
[bid
].stateIn
,
2425 FTRACE(2, "loc live out : {}\n"
2426 "loc out exn : {}\n"
2427 "loc live in : {}\n",
2428 loc_bits_string(ai
.ctx
.func
, blockState
.locLive
),
2429 loc_bits_string(ai
.ctx
.func
, blockState
.locLiveExn
),
2430 loc_bits_string(ai
.ctx
.func
, result
.locLiveIn
));
2432 processForcedLive(result
.forcedLiveLocations
);
2434 auto const isCFPushTaken
= [] (const php::Block
* blk
, BlockId succId
) {
2435 auto const& lastOpc
= blk
->hhbcs
.back();
2436 switch (lastOpc
.op
) {
2438 return lastOpc
.MemoGet
.target1
== succId
;
2439 case Op::MemoGetEager
:
2440 return lastOpc
.MemoGetEager
.target1
== succId
;
2446 // Merge the liveIn into the liveOut of each normal predecessor.
2447 // If the set changes, reschedule that predecessor.
2448 for (auto const pid
: nonThrowPreds
[bid
]) {
2449 FTRACE(2, " -> {}\n", pid
);
2450 auto& pbs
= blockStates
[pid
];
2451 auto const oldPredLocLive
= pbs
.locLive
;
2452 pbs
.locLive
|= result
.locLiveIn
;
2453 auto changed
= pbs
.locLive
!= oldPredLocLive
;
2456 auto const pred
= ai
.ctx
.func
->blocks
[pid
].get();
2457 if (isCFPushTaken(pred
, bid
)) {
2458 auto stack
= result
.stack
;
2459 stack
.insert(stack
.end(), pred
->hhbcs
.back().numPush(),
2461 return mergeUIVecs(pbs
.dceStack
, stack
, bid
, false);
2463 return mergeUIVecs(pbs
.dceStack
, result
.stack
, bid
, false);
2467 incompleteQ
.push(rpoId(pid
));
2471 // Merge the liveIn into the liveOutExn state for each throw predecessor.
2472 // The liveIn computation also depends on the liveOutExn state, so again
2473 // reschedule if it changes.
2474 for (auto const pid
: throwPreds
[bid
]) {
2475 FTRACE(2, " => {}\n", pid
);
2476 auto& pbs
= blockStates
[pid
];
2477 auto const oldPredLocLiveExn
= pbs
.locLiveExn
;
2478 pbs
.locLiveExn
|= result
.locLiveIn
;
2479 if (pbs
.locLiveExn
!= oldPredLocLiveExn
) {
2480 incompleteQ
.push(rpoId(pid
));
2484 while (!forcedLiveTemp
.empty()) {
2485 auto t
= std::move(forcedLiveTemp
);
2486 processForcedLive(t
);
2491 * Now that we're at a fixed point, use the propagated states to
2492 * remove instructions that don't need to be there.
2494 FTRACE(1, "|---- global DCE optimize ({})\n", show(ai
.ctx
));
2495 std::bitset
<kMaxTrackedLocals
> usedLocals
;
2496 DceActionMap actionMap
;
2497 DceReplaceMap replaceMap
;
2498 bool didAddElemOpts
= false;
2499 for (auto const bid
: ai
.rpoBlocks
) {
2500 FTRACE(2, "block #{}\n", bid
);
2501 auto ret
= optimize_dce(
2506 ai
.bdata
[bid
].stateIn
,
2509 didAddElemOpts
= didAddElemOpts
|| ret
.didAddElemOpts
;
2510 usedLocals
|= ret
.usedLocals
;
2511 if (ret
.actionMap
.size()) {
2512 if (!actionMap
.size()) {
2513 actionMap
= std::move(ret
.actionMap
);
2515 combineActions(actionMap
, ret
.actionMap
);
2518 if (ret
.replaceMap
.size()) {
2519 if (!replaceMap
.size()) {
2520 replaceMap
= std::move(ret
.replaceMap
);
2522 for (auto& elm
: ret
.replaceMap
) {
2523 replaceMap
.insert(std::move(elm
));
2529 dce_perform(*ai
.ctx
.func
, actionMap
, replaceMap
);
2531 FTRACE(1, " used locals: {}\n", loc_bits_string(ai
.ctx
.func
, usedLocals
));
2532 remove_unused_locals(ai
.ctx
, usedLocals
);
2534 return didAddElemOpts
;
2537 //////////////////////////////////////////////////////////////////////