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