take &self
[hiphop-php.git] / hphp / hhbbc / dce.cpp
blob2ab82a3ab66b7c8f900e650d62e7eb97d42cc52b
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
18 #include <vector>
19 #include <string>
20 #include <utility>
21 #include <bitset>
22 #include <sstream>
23 #include <algorithm>
24 #include <set>
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
66 * blocks.
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.
73 * Eval stack:
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.
92 * Locals:
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.
106 * Liveness analysis:
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.
133 * $foo = 12;
134 * // ... code that breaks a block ...
135 * $foo = 100;
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
150 * were correct.
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
169 * block boundaries.
173 namespace {
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;
180 return bc.numPop();
183 // The number of pushes as seen by dce.
184 uint32_t numPush(const Bytecode& bc) {
185 if (bc.op == Op::CGetL2) return 2;
186 return bc.numPush();
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.
199 enum class Use {
200 // Indicates that the cell is (possibly) used.
201 Used = 0,
203 // Indicates that the cell is (unconditionally) not used.
204 Not = 1,
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.
211 AddElemC = 3,
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
217 * neither is.
219 Linked = 4,
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));
230 bool any(Use a) {
231 return static_cast<int>(a) != 0;
234 Use mask_use(Use a) { return a & static_cast<Use>(3); }
236 struct InstrId {
237 BlockId blk;
238 uint32_t idx;
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;
251 struct LocationId {
252 BlockId blk;
253 uint32_t id;
254 bool isSlot;
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 &&
271 i1.id == i2.id &&
272 i1.isSlot == i2.isSlot;
275 using LocationIdSet = hphp_fast_set<LocationId,LocationIdHash>;
277 struct DceAction {
278 enum Action {
279 Kill,
280 PopInputs,
281 PopOutputs,
282 Replace,
283 PopAndReplace,
284 MinstrStackFinal,
285 MinstrStackFixup
286 } action;
287 using MaskType = uint32_t;
288 static constexpr size_t kMaskSize = 32;
289 MaskType mask{0};
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>>;
304 struct UseInfo {
305 explicit UseInfo(Use u) : usage(u) {}
306 UseInfo(Use u, DceActionMap&& ids) : usage(u), actions(std::move(ids)) {}
308 Use usage;
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 //////////////////////////////////////////////////////////////////////
325 struct DceState {
326 explicit DceState(const Index& index, const FuncAnalysis& ainfo) :
327 index(index), ainfo(ainfo) {}
328 const Index& index;
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
347 * liveness analysis.
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
379 * constant.
381 folly::Optional<UseInfo> minstrUI;
383 * Flag to indicate local vs global dce.
385 bool isLocal{false};
388 * When we do add elem opts, it can enable further optimization
389 * opportunities. Let the optimizer know...
391 bool didAddElemOpts{false};
394 //////////////////////////////////////////////////////////////////////
395 // debugging
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";
407 not_reached();
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) {
419 validate(u);
420 auto ret = [&] {
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();
427 not_reached();
428 }();
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;
443 return from(actions)
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');
460 } else {
461 out << locs;
463 return out.str();
466 //////////////////////////////////////////////////////////////////////
468 struct Env {
469 DceState& dceState;
470 const Bytecode& op;
471 InstrId id;
472 LocalId loc;
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) {
486 return isLinked(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 &&
498 allUnused(args...);
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) {
509 return
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) {
520 return
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 //////////////////////////////////////////////////////////////////////
541 // query eval stack
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));
551 return t;
554 //////////////////////////////////////////////////////////////////////
555 // locals
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
572 * appropriate.
574 void scheduleGenLoc(Env& env, LocalId loc) {
575 env.loc = 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.
587 return true;
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
629 * (see markUisLive).
631 void use(LocationIdSet& forcedLive,
632 std::vector<UseInfo>& uis, uint32_t i) {
633 while (true) {
634 auto& ui = uis[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;
641 ui.actions.clear();
642 if (!linked) break;
643 assert(i);
644 i--;
648 void combineActions(DceActionMap& dst, const DceActionMap& src) {
649 for (auto& i : src) {
650 auto ret = dst.insert(i);
651 if (!ret.second) {
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;
656 continue;
659 if (i.second.action == ret.first->second.action) {
660 continue;
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;
669 continue;
671 if (i.second.action == DceAction::Replace ||
672 ret.first->second.action == DceAction::Replace) {
673 ret.first->second.action = DceAction::Replace;
674 continue;
676 if (ret.first->second.action == DceAction::PopInputs ||
677 i.second.action == DceAction::PopInputs) {
678 ret.first->second.action = DceAction::Kill;
679 continue;
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) {
692 if (!linked) {
693 FTRACE(2, " committing {}: {}\n", show(env.id), show(am));
696 assert(!linked || env.dceState.stack.back().usage != Use::Used);
698 auto& dst = linked ?
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);
706 return dst;
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);
729 if (!np) return;
731 auto usage = [&] (uint32_t i) {
732 auto ret = Use::Not;
733 if (linked) ret = ret | Use::Linked;
734 return ret;
737 for (auto i = np; --i; ) {
738 pop(env, usage(i), DceActionMap {});
739 linked = true;
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:
759 * $a ? f() : 42
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) {
774 validate(ui.usage);
775 if (ui.usage != Use::Used &&
776 ui.location.blk != NoBlockId) {
777 env.dceState.forcedLiveLocations.insert(ui.location);
779 if (linked) {
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;
797 return;
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) {
809 pop(env);
813 enum class PushFlags {
814 MarkLive,
815 MarkDead,
816 MarkUnused,
817 PopOutputs,
818 AddElemC
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);
831 switch (pf) {
832 case PushFlags::MarkLive:
833 markUisLive(env, linked, uis...);
834 break;
836 case PushFlags::MarkDead:
837 commitUis(env, linked, uis...);
838 break;
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 }});
851 return;
854 case PushFlags::PopOutputs:
855 popOutputs(env, linked, uis...);
856 break;
858 case PushFlags::AddElemC: {
859 assert(!linked);
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.
872 pop(env);
873 return;
876 pop_inputs(env, numPop(env.op));
879 template<typename F>
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));
886 env.loc = NoLocalId;
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; });
895 template<typename F>
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));
905 env.loc = NoLocalId;
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
938 * destructor:
940 * CGetL $x
941 * UnsetL $x
942 * // ...
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();
954 if (isLinked(ui)) {
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
957 // it.
958 markUisLive(env, true, ui);
959 ui = UseInfo { Use::Used };
961 discard(env);
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&) {
1005 // Various cases:
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;
1024 if (dup_unused) {
1025 markUisLive(env, isLinked(orig), orig);
1026 orig.actions.clear();
1027 return PushFlags::MarkDead;
1030 if (orig_unused) {
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);
1220 if (cat.hasValue) {
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 });
1226 } else {
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) });
1245 } else {
1246 return PushFlags::MarkLive;
1248 } else if (cat.cat == Type::ArrayCat::Packed &&
1249 *postSize <= ArrayData::MaxElemsOnStack) {
1250 if (arrPost.subtypeOf(BPArrN)) {
1251 bcs.emplace_back(
1252 bc::NewPackedArray { static_cast<uint32_t>(*postSize) }
1254 } else if (arrPost.subtypeOf(BVArrN)) {
1255 bcs.emplace_back(
1256 bc::NewVArray { static_cast<uint32_t>(*postSize) }
1258 } else {
1259 return PushFlags::MarkLive;
1261 } else {
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)) {
1309 discard(env);
1310 env.dceState.actionMap[env.id] = DceAction::PopInputs;
1311 return;
1313 pop(env);
1314 if (isLocVolatile(env, op.loc1)) {
1315 addLocGen(env, op.loc1);
1316 } else {
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
1334 // with a PopL.
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);
1342 } else {
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);
1353 if (effects) {
1354 addLocGen(env, op.loc1);
1355 } else {
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:
1386 return false;
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);
1404 not_reached();
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
1412 * kill it.
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.
1492 template<class Op>
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) {
1513 no_dce(env, 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) {
1612 no_dce(env, 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>(
1633 iter,
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 {
1654 op.iter1,
1655 op.target2,
1656 op.loc3
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 {
1664 op.iter1,
1665 op.loc2,
1666 op.target3,
1667 op.loc4,
1668 op.subop6
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 {
1676 op.iter1,
1677 op.target2,
1678 op.loc3
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 {
1686 op.iter1,
1687 op.loc2,
1688 op.target3,
1689 op.loc4,
1690 op.subop6
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;
1720 while (depth--) {
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;
1725 assertx(inserted);
1730 template<class Op>
1731 void minstr_base(Env& env, const Op& op, int32_t ix) {
1732 no_dce(env, op);
1733 minstr_touch(env, ix);
1736 template<class Op>
1737 void minstr_dim(Env& env, const Op& op) {
1738 no_dce(env, op);
1739 if (op.mkey.mcode == MEC || op.mkey.mcode == MPC) {
1740 minstr_touch(env, op.mkey.idx);
1744 template<class Op>
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) {
1754 pop(env);
1755 } else {
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 }
1801 #undef O
1802 not_reached();
1805 using MaskType = DceAction::MaskType;
1807 void m_adj(uint32_t& depth, MaskType mask) {
1808 auto i = depth;
1809 if (i > DceAction::kMaskSize) i = DceAction::kMaskSize;
1810 while (i--) {
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;
1818 m_adj(d, mask);
1819 mkey.idx = d;
1823 template<typename Op>
1824 void m_adj(uint32_t& ndiscard, Op& op, MaskType mask) {
1825 auto const adjust = op.numPop() - ndiscard + 1;
1826 ndiscard += adjust;
1827 m_adj(ndiscard, mask);
1828 ndiscard -= adjust;
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) {
1840 m_adj(op.arg1, m);
1841 m_adj(op.arg2, 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 }
1855 #undef O
1856 not_reached();
1860 //////////////////////////////////////////////////////////////////////
1862 struct DceOutState {
1863 DceOutState() = default;
1864 enum Local {};
1865 explicit DceOutState(Local) : isLocal(true) {
1866 locLive.set();
1867 locLiveExn.set();
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,
1897 BlockId bid,
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.
1910 return folly::none;
1913 auto const states = locally_propagated_states(index, fa, collect,
1914 bid, stateIn);
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) {
1924 case Op::MemoGet:
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;
1934 default:
1935 return states.back().first.stack.size();
1937 }();
1939 if (dceOutState.dceStack) {
1940 dceState.stack = *dceOutState.dceStack;
1941 assert(dceState.stack.size() == dceStkSz);
1942 } else {
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 {
1952 dceState,
1954 { bid, idx },
1955 NoLocalId,
1956 states[idx].first,
1957 states[idx].second,
1958 states[idx+1].first,
1960 auto const handled = [&] {
1961 if (dceState.minstrUI) {
1962 if (visit_env.flags.wasPEI) {
1963 dceState.minstrUI.clear();
1964 return false;
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.
1970 return false;
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;
1983 } else {
1984 dceState.minstrUI->actions[visit_env.id] = DceAction::Kill;
1986 commitActions(visit_env, false, dceState.minstrUI->actions);
1987 dceState.minstrUI.clear();
1988 return true;
1991 return false;
1992 }();
1993 if (!handled) {
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
1999 * liveExn sets).
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",
2010 [&] {
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",
2018 [&] {
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();
2035 return dceState;
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,
2047 BlockId bid,
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,
2054 dceState->stack,
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) {
2070 while (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,
2085 numToPop,
2086 bc::PopC {});
2087 setloc(srcLoc, b->hhbcs.begin() + id.idx, numToPop);
2088 break;
2090 // Fall through
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;
2097 } else {
2098 b->hhbcs.erase(b->hhbcs.begin() + id.idx);
2100 break;
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,
2106 numToPop,
2107 bc::PopC {});
2108 setloc(b->hhbcs[id.idx].srcLoc,
2109 b->hhbcs.begin() + id.idx + 1, numToPop);
2110 break;
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());
2121 break;
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));
2132 if (numToPop) {
2133 b->hhbcs.insert(b->hhbcs.begin() + id.idx,
2134 numToPop,
2135 bc::PopC {});
2137 setloc(srcLoc, b->hhbcs.begin() + id.idx, numToPop + it->second.size());
2138 break;
2140 case DceAction::MinstrStackFinal:
2141 case DceAction::MinstrStackFixup:
2143 adjustMinstr(b->hhbcs[id.idx], elm.second.mask);
2144 break;
2150 struct DceOptResult {
2151 std::bitset<kMaxTrackedLocals> usedLocals;
2152 DceActionMap actionMap;
2153 DceReplaceMap replaceMap;
2154 bool didAddElemOpts{false};
2157 DceOptResult
2158 optimize_dce(const Index& index,
2159 const FuncAnalysis& fa,
2160 CollectedInfo& collect,
2161 BlockId bid,
2162 const State& stateIn,
2163 const DceOutState& dceOutState) {
2164 auto dceState = dce_visit(index, fa, collect, bid, stateIn, dceOutState);
2166 if (!dceState) {
2167 return {std::bitset<kMaxTrackedLocals>{}};
2170 return {
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
2189 * bother for now.
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) {
2199 if (loc->killed) {
2200 assert(loc->id < kMaxTrackedLocals && !usedLocals.test(loc->id));
2201 continue;
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,
2217 BlockId bid,
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)
2244 | mapped(
2245 [&] (const php::Local& l) {
2246 return folly::sformat(" {} {}\n",
2247 i++, local_string(*ai.ctx.func, l.id));
2249 | unsplit<std::string>("");
2250 }());
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
2266 * in to start.
2268 auto incompleteQ = dataflow_worklist<uint32_t,std::less<uint32_t>>(
2269 ai.rpoBlocks.size()
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;
2295 assert(i);
2296 return uis[i - 1].usage == Use::Used;
2299 auto fixupUseInfo = [&] (std::vector<UseInfo>& uis,
2300 BlockId blk,
2301 bool isSlot) {
2302 for (uint32_t i = 0; i < uis.size(); i++) {
2303 auto& out = uis[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.
2321 while (true) {
2322 auto& ui = inBase[i];
2323 auto linked = isLinked(ui);
2324 if (ui.usage != Use::Used) {
2325 forcedLiveTemp.insert({blk, i, isSlot});
2327 if (!linked) break;
2328 assert(i);
2329 i--;
2332 return false;
2335 if (in.usage == Use::Used || out.usage != in.usage) {
2336 use(forcedLiveTemp, outBase, i);
2337 return true;
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);
2343 return true;
2346 auto ret = false;
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;
2355 ret = true;
2357 return ret;
2360 auto mergeUIVecs = [&] (folly::Optional<std::vector<UseInfo>>& stkOut,
2361 const std::vector<UseInfo>& stkIn,
2362 BlockId blk, bool isSlot) {
2363 if (!stkOut) {
2364 stkOut = stkIn;
2365 fixupUseInfo(*stkOut, blk, isSlot);
2366 return true;
2369 auto ret = false;
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;
2374 return ret;
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(
2417 index,
2419 collect,
2420 bid,
2421 ai.bdata[bid].stateIn,
2422 blockState
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) {
2437 case Op::MemoGet:
2438 return lastOpc.MemoGet.target1 == succId;
2439 case Op::MemoGetEager:
2440 return lastOpc.MemoGetEager.target1 == succId;
2441 default:
2442 return false;
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;
2455 changed |= [&] {
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(),
2460 UseInfo{Use::Not});
2461 return mergeUIVecs(pbs.dceStack, stack, bid, false);
2462 } else {
2463 return mergeUIVecs(pbs.dceStack, result.stack, bid, false);
2465 }();
2466 if (changed) {
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(
2502 index,
2504 collect,
2505 bid,
2506 ai.bdata[bid].stateIn,
2507 blockStates[bid]
2509 didAddElemOpts = didAddElemOpts || ret.didAddElemOpts;
2510 usedLocals |= ret.usedLocals;
2511 if (ret.actionMap.size()) {
2512 if (!actionMap.size()) {
2513 actionMap = std::move(ret.actionMap);
2514 } else {
2515 combineActions(actionMap, ret.actionMap);
2518 if (ret.replaceMap.size()) {
2519 if (!replaceMap.size()) {
2520 replaceMap = std::move(ret.replaceMap);
2521 } else {
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 //////////////////////////////////////////////////////////////////////