2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/optimize.h"
25 #include <boost/dynamic_bitset.hpp>
27 #include <folly/Optional.h>
28 #include <folly/gen/Base.h>
29 #include <folly/gen/String.h>
31 #include "hphp/util/trace.h"
32 #include "hphp/util/match.h"
34 #include "hphp/runtime/vm/hhbc.h"
35 #include "hphp/runtime/base/datatype.h"
37 #include "hphp/hhbbc/hhbbc.h"
38 #include "hphp/hhbbc/analyze.h"
39 #include "hphp/hhbbc/cfg.h"
40 #include "hphp/hhbbc/cfg-opts.h"
41 #include "hphp/hhbbc/dce.h"
42 #include "hphp/hhbbc/func-util.h"
43 #include "hphp/hhbbc/interp.h"
44 #include "hphp/hhbbc/interp-internal.h"
45 #include "hphp/hhbbc/interp-state.h"
46 #include "hphp/hhbbc/misc.h"
47 #include "hphp/hhbbc/options-util.h"
48 #include "hphp/hhbbc/representation.h"
49 #include "hphp/hhbbc/type-system.h"
50 #include "hphp/hhbbc/unit-util.h"
52 namespace HPHP
{ namespace HHBBC
{
54 //////////////////////////////////////////////////////////////////////
58 //////////////////////////////////////////////////////////////////////
61 * For filtering assertions, some opcodes are considered to have no
62 * use for a stack input assertion.
64 * For now this is restricted to opcodes that do literally nothing.
66 bool ignoresStackInput(Op op
) {
78 template<class TyBC
, class ArgType
>
79 folly::Optional
<Bytecode
> makeAssert(ArrayTypeTable::Builder
& arrTable
,
82 if (t
.subtypeOf(BBottom
)) return folly::none
;
83 auto const rat
= make_repo_type(arrTable
, t
);
84 using T
= RepoAuthType::Tag
;
85 if (options
.FilterAssertions
) {
86 // Gen and InitGen don't add any useful information, so leave them
88 if (rat
== RepoAuthType
{T::Gen
}) return folly::none
;
89 if (rat
== RepoAuthType
{T::InitGen
}) return folly::none
;
91 return Bytecode
{ TyBC
{ arg
, rat
} };
95 void insert_assertions_step(ArrayTypeTable::Builder
& arrTable
,
96 const php::Func
& func
,
97 const Bytecode
& bcode
,
99 std::bitset
<kMaxTrackedLocals
> mayReadLocalSet
,
100 std::vector
<uint8_t> obviousStackOutputs
,
102 if (state
.unreachable
) return;
104 for (LocalId i
= 0; i
< state
.locals
.size(); ++i
) {
105 if (func
.locals
[i
].killed
) continue;
106 if (options
.FilterAssertions
) {
107 // Do not emit assertions for untracked locals.
108 if (i
>= mayReadLocalSet
.size()) break;
109 if (!mayReadLocalSet
.test(i
)) continue;
111 auto const realT
= state
.locals
[i
];
112 auto const op
= makeAssert
<bc::AssertRATL
>(arrTable
, i
, realT
);
116 if (!options
.InsertStackAssertions
) return;
118 assert(obviousStackOutputs
.size() == state
.stack
.size());
120 auto const assert_stack
= [&] (size_t idx
) {
121 assert(idx
< state
.stack
.size());
122 if (obviousStackOutputs
[state
.stack
.size() - idx
- 1]) return;
123 if (ignoresStackInput(bcode
.op
)) return;
124 auto const realT
= state
.stack
[state
.stack
.size() - idx
- 1].type
;
125 auto const flav
= stack_flav(realT
);
127 if (options
.FilterAssertions
&& !realT
.strictSubtypeOf(flav
)) {
132 makeAssert
<bc::AssertRATStk
>(
134 static_cast<uint32_t>(idx
),
141 * This doesn't need to account for ActRecs on the fpiStack, because
142 * no instruction in an FPI region can ever consume a stack value
143 * from above the pre-live ActRec.
145 for (auto i
= size_t{0}; i
< bcode
.numPop(); ++i
) assert_stack(i
);
147 // The base instructions are special in that they don't pop anything, but do
148 // read from the stack. We want type assertions on the stack slots they'll
151 case Op::BaseC
: assert_stack(bcode
.BaseC
.arg1
); break;
152 case Op::BaseGC
: assert_stack(bcode
.BaseGC
.arg1
); break;
153 case Op::BaseSC
: assert_stack(bcode
.BaseSC
.arg1
); break;
155 switch (bcode
.Dim
.mkey
.mcode
) {
157 assert_stack(bcode
.Dim
.mkey
.idx
);
159 case MW
: case MEL
: case MPL
: case MEI
:
160 case MET
: case MPT
: case MQT
:
169 * When filter assertions is on, we use this to avoid putting stack
170 * assertions on some "obvious" instructions.
172 * These are instructions that push an output type that is always the
173 * same, i.e. where an AssertT is never going to add information.
174 * (E.g. "Int 3" obviously pushes an Int, and the JIT has no trouble
175 * figuring that out, so there's no reason to assert about it.)
177 * TODO(#3676101): It turns out many hhbc opcodes have known output
178 * types---there are some super polymorphic ones, but many always do
179 * bools or objects, etc. We might consider making stack flavors have
180 * subtypes and adding this to the opcode table.
182 bool hasObviousStackOutput(const Bytecode
& op
, const Interp
& interp
) {
183 // Generally consider CGetL obvious because if we knew the type of the local,
184 // we'll assert that right before the CGetL.
185 auto cgetlObvious
= [&] (LocalId l
, int idx
) {
186 return !interp
.state
.locals
[l
].couldBe(BRef
) ||
187 !interp
.state
.stack
[interp
.state
.stack
.size() - idx
- 1].
188 type
.strictSubtypeOf(TInitCell
);
204 case Op::NewDictArray
:
205 case Op::NewPackedArray
:
207 case Op::NewStructArray
:
208 case Op::NewStructDArray
:
209 case Op::NewStructDict
:
210 case Op::NewVecArray
:
211 case Op::NewKeysetArray
:
212 case Op::AddNewElemC
:
245 case Op::InstanceOfD
:
246 case Op::IsLateBoundCls
:
247 case Op::IsTypeStructC
:
248 case Op::CombineAndResolveTypeStruct
:
249 case Op::RecordReifiedGeneric
:
250 case Op::ReifiedName
:
263 case Op::OODeclExists
:
269 if (auto tt
= thisType(interp
.index
, interp
.ctx
)) {
270 auto t
= interp
.state
.stack
.back().type
;
271 if (is_opt(t
)) t
= unopt(std::move(t
));
272 return !t
.strictSubtypeOf(*tt
);
277 return cgetlObvious(op
.CGetL
.loc1
, 0);
279 return cgetlObvious(op
.CGetQuietL
.loc1
, 0);
281 return cgetlObvious(op
.CUGetL
.loc1
, 0);
283 return cgetlObvious(op
.CGetL2
.loc1
, 1);
285 return cgetlObvious(op
.PushL
.loc1
, 0);
287 // The output of SetL is obvious if you know what its input is
288 // (which we'll assert if we know).
292 // The output of SetM isn't quite as obvious as SetL, but the jit
293 // can work it out from the input just as well as hhbbc (if not better).
302 void insert_assertions(const Index
& index
,
303 const FuncAnalysis
& ainfo
,
304 CollectedInfo
& collect
,
308 auto& cblk
= ainfo
.ctx
.func
->blocks
[bid
];
309 newBCs
.reserve(cblk
->hhbcs
.size());
311 auto& arrTable
= *index
.array_table_builder();
312 auto const ctx
= ainfo
.ctx
;
314 std::vector
<uint8_t> obviousStackOutputs(state
.stack
.size(), false);
316 auto interp
= Interp
{ index
, ctx
, collect
, bid
, cblk
.get(), state
};
317 for (auto& op
: cblk
->hhbcs
) {
318 FTRACE(2, " == {}\n", show(ctx
.func
, op
));
320 auto gen
= [&] (const Bytecode
& newb
) {
321 newBCs
.push_back(newb
);
322 newBCs
.back().srcLoc
= op
.srcLoc
;
323 FTRACE(2, " + {}\n", show(ctx
.func
, newBCs
.back()));
326 if (state
.unreachable
) {
327 if (cblk
->fallthrough
!= NoBlockId
) {
328 auto const blk
= cblk
.mutate();
329 blk
->fallthrough
= NoBlockId
;
331 if (!(instrFlags(op
.op
) & TF
)) {
332 gen(bc::BreakTraceHint
{});
333 gen(bc::String
{ s_unreachable
.get() });
334 gen(bc::Fatal
{ FatalOp::Runtime
});
339 auto const preState
= state
;
340 auto const flags
= step(interp
, op
);
342 insert_assertions_step(
347 flags
.mayReadLocalSet
,
352 if (op
.op
== Op::CGetL2
) {
353 obviousStackOutputs
.emplace(obviousStackOutputs
.end() - 1,
354 hasObviousStackOutput(op
, interp
));
356 for (int i
= 0; i
< op
.numPop(); i
++) {
357 obviousStackOutputs
.pop_back();
359 for (auto i
= op
.numPush(); i
--; ) {
360 obviousStackOutputs
.emplace_back(hasObviousStackOutput(op
, interp
));
367 if (cblk
->hhbcs
!= newBCs
) cblk
.mutate()->hhbcs
= std::move(newBCs
);
370 bool persistence_check(const php::Func
* const func
) {
371 auto bid
= func
->mainEntry
;
372 while (bid
!= NoBlockId
) {
373 auto const& blk
= func
->blocks
[bid
];
374 for (auto& op
: blk
->hhbcs
) {
380 case Op::DefTypeAlias
:
393 // Not strictly no-side effects, but as long as the rest of
394 // the unit is limited to the above, we're fine (and we expect
395 // one following a DefCns).
403 bid
= blk
->fallthrough
;
408 //////////////////////////////////////////////////////////////////////
411 bool propagate_constants(const Bytecode
& op
, State
& state
, Gen gen
) {
412 auto const numPop
= op
.numPop();
413 auto const numPush
= op
.numPush();
414 auto const stkSize
= state
.stack
.size();
415 constexpr auto numCells
= 4;
416 Cell constVals
[numCells
];
418 // All outputs of the instruction must have constant types for this
420 for (auto i
= size_t{0}; i
< numPush
; ++i
) {
421 auto const& ty
= state
.stack
[stkSize
- i
- 1].type
;
423 auto const v
= tv(ty
);
424 if (!v
) return false;
426 } else if (!is_scalar(ty
)) {
431 auto const slot
= visit(op
, ReadClsRefSlotVisitor
{});
432 if (slot
!= NoClsRefSlotId
) gen(bc::DiscardClsRef
{ slot
});
434 // Pop the inputs, and push the constants.
435 for (auto i
= size_t{0}; i
< numPop
; ++i
) {
436 switch (op
.popFlavor(i
)) {
437 case Flavor::C
: gen(bc::PopC
{}); break;
438 case Flavor::V
: gen(bc::PopV
{}); break;
439 case Flavor::U
: not_reached(); break;
441 // We only support C's for CU right now.
444 case Flavor::CV
: not_reached(); break;
446 // Note that we only support C's for CVU so far (this only comes up with
447 // FCallBuiltin)---we'll fail the verifier if something changes to send
448 // V's or U's through here.
454 for (auto i
= size_t{0}; i
< numPush
; ++i
) {
455 auto const v
= i
< numCells
?
456 constVals
[i
] : *tv(state
.stack
[stkSize
- i
- 1].type
);
457 gen(gen_constant(v
));
458 state
.stack
[stkSize
- i
- 1].type
= from_cell(v
);
464 bool propagate_constants(const Bytecode
& bc
, State
& state
,
466 return propagate_constants(bc
, state
, [&] (const Bytecode
& bc
) {
471 //////////////////////////////////////////////////////////////////////
474 * Create a block similar to another block (but with no bytecode in it yet).
476 BlockId
make_block(php::Func
* func
,
477 const php::Block
* srcBlk
) {
478 FTRACE(1, " ++ new block {}\n", func
->blocks
.size());
480 auto newBlk
= copy_ptr
<php::Block
>{php::Block
{}};
481 auto const blk
= newBlk
.mutate();
482 blk
->exnNodeId
= srcBlk
->exnNodeId
;
483 blk
->throwExit
= srcBlk
->throwExit
;
484 auto const bid
= func
->blocks
.size();
485 func
->blocks
.push_back(std::move(newBlk
));
490 BlockId
make_fatal_block(php::Func
* func
,
491 const php::Block
* srcBlk
) {
492 auto bid
= make_block(func
, srcBlk
);
493 auto const blk
= func
->blocks
[bid
].mutate();
494 auto const srcLoc
= srcBlk
->hhbcs
.back().srcLoc
;
496 bc_with_loc(srcLoc
, bc::String
{ s_unreachable
.get() }),
497 bc_with_loc(srcLoc
, bc::Fatal
{ FatalOp::Runtime
})
499 blk
->fallthrough
= NoBlockId
;
500 blk
->throwExit
= NoBlockId
;
501 blk
->exnNodeId
= NoExnNodeId
;
505 void sync_ainfo(BlockId bid
, FuncAnalysis
& ainfo
, const State
& state
) {
506 assertx(ainfo
.bdata
.size() == bid
);
507 assertx(bid
+ 1 == ainfo
.ctx
.func
->blocks
.size());
509 ainfo
.rpoBlocks
.push_back(bid
);
510 ainfo
.bdata
.push_back(FuncAnalysis::BlockData
{
511 static_cast<uint32_t>(ainfo
.rpoBlocks
.size() - 1),
517 * Create a block similar to another block (but with no bytecode in it yet).
519 BlockId
make_block(FuncAnalysis
& ainfo
,
520 const php::Block
* srcBlk
,
521 const State
& state
) {
522 auto const bid
= make_block(ainfo
.ctx
.func
, srcBlk
);
523 sync_ainfo(bid
, ainfo
, state
);
527 BlockId
make_fatal_block(FuncAnalysis
& ainfo
,
528 const php::Block
* srcBlk
,
529 const State
& state
) {
530 auto bid
= make_fatal_block(ainfo
.ctx
.func
, srcBlk
);
531 sync_ainfo(bid
, ainfo
, state
);
535 //////////////////////////////////////////////////////////////////////
537 template<class BlockContainer
, class AInfo
, class Fun
>
538 void visit_blocks_impl(const char* what
,
541 CollectedInfo
& collect
,
542 const BlockContainer
& rpoBlocks
,
545 BlockId curBlk
= NoBlockId
;
546 SCOPE_ASSERT_DETAIL(what
) {
547 if (curBlk
== NoBlockId
) return std::string
{"\nNo block processed\n"};
548 return folly::sformat(
549 "block #{}\nin-{}", curBlk
,
550 state_string(*ainfo
.ctx
.func
, ainfo
.bdata
[curBlk
].stateIn
, collect
)
554 FTRACE(1, "|---- {}\n", what
);
555 for (auto const bid
: rpoBlocks
) {
557 FTRACE(2, "block #{}\n", bid
);
558 auto const& state
= ainfo
.bdata
[bid
].stateIn
;
559 if (!state
.initialized
) {
560 FTRACE(2, " unreachable\n");
563 // TODO(#3732260): this should probably spend an extra interp pass
564 // in debug builds to check that no transformation to the bytecode
565 // was made that changes the block output state.
566 fun(index
, ainfo
, collect
, bid
, state
);
568 assert(check(*ainfo
.ctx
.func
));
572 void visit_blocks_mutable(const char* what
,
575 CollectedInfo
& collect
,
577 // Make a copy of the block list so it can be mutated by the visitor.
578 auto const blocksCopy
= ainfo
.rpoBlocks
;
579 visit_blocks_impl(what
, index
, ainfo
, collect
,
580 blocksCopy
, std::forward
<Fun
>(fun
));
584 void visit_blocks(const char* what
,
586 const FuncAnalysis
& ainfo
,
587 CollectedInfo
& collect
,
589 visit_blocks_impl(what
, index
, ainfo
, collect
,
590 ainfo
.rpoBlocks
, std::forward
<Fun
>(fun
));
593 //////////////////////////////////////////////////////////////////////
595 IterId
iterFromInit(const php::Func
& func
, BlockId initBlock
) {
596 auto const& op
= func
.blocks
[initBlock
]->hhbcs
.back();
597 if (op
.op
== Op::IterInit
) return op
.IterInit
.iter1
;
598 if (op
.op
== Op::IterInitK
) return op
.IterInitK
.iter1
;
599 if (op
.op
== Op::LIterInit
) return op
.LIterInit
.iter1
;
600 if (op
.op
== Op::LIterInitK
) return op
.LIterInitK
.iter1
;
601 always_assert(false);
605 * Attempt to convert normal iterators into liters. In order for an iterator to
606 * be converted to a liter, the following needs to be true:
608 * - The iterator is initialized with the value in a local at exactly one block.
610 * - That same local is not modified on all possible paths from the
611 * initialization to every usage of that iterator.
613 * The first condition is actually more restrictive than necessary, but
614 * enforcing that the iterator is initialized at exactly one place simplifies
615 * the bookkeeping and is always true with how we currently emit bytecode.
618 struct OptimizeIterState
{
619 void operator()(const Index
& index
,
620 const FuncAnalysis
& ainfo
,
621 CollectedInfo
& collect
,
624 auto const ctx
= ainfo
.ctx
;
625 auto const blk
= ctx
.func
->blocks
[bid
].get();
626 auto interp
= Interp
{ index
, ctx
, collect
, bid
, blk
, state
};
627 for (uint32_t opIdx
= 0; opIdx
< blk
->hhbcs
.size(); ++opIdx
) {
628 // If we've already determined that nothing is eligible, we can just stop.
629 if (!eligible
.any()) break;
631 auto const& op
= blk
->hhbcs
[opIdx
];
632 FTRACE(2, " == {}\n", show(ctx
.func
, op
));
634 if (state
.unreachable
) break;
636 // At every op, we check the known state of all live iterators and mark it
637 // as ineligible as necessary.
638 for (IterId it
= 0; it
< state
.iters
.size(); ++it
) {
642 [&] (const LiveIter
& ti
) {
643 FTRACE(4, " iter {: <2} :: {}\n",
644 it
, show(*ctx
.func
, state
.iters
[it
]));
645 // The init block is unknown. This can only happen if there's more
646 // than one block where this iterator was initialized. This makes
647 // tracking the iteration loop ambiguous, and can't happen with how
648 // we currently emit bytecode, so just pessimize everything.
649 if (ti
.initBlock
== NoBlockId
) {
650 FTRACE(2, " - pessimize all\n");
654 // Otherwise, if the iterator doesn't have an equivalent local,
655 // either it was never initialized with a local to begin with, or
656 // that local got changed within the loop. Either way, this
657 // iteration loop isn't eligible.
658 if (eligible
[ti
.initBlock
] && ti
.baseLocal
== NoLocalId
) {
659 FTRACE(2, " - blk:{} ineligible\n", ti
.initBlock
);
660 eligible
[ti
.initBlock
] = false;
666 auto const fixupForInit
= [&] {
667 auto const base
= topStkLocal(state
);
668 if (base
== NoLocalId
&& eligible
[bid
]) {
669 FTRACE(2, " - blk:{} ineligible\n", bid
);
670 eligible
[bid
] = false;
672 fixups
.emplace_back(Fixup
{bid
, opIdx
, bid
, base
});
673 FTRACE(2, " + fixup ({})\n", fixups
.back().show(*ctx
.func
));
676 auto const fixupFromState
= [&] (IterId it
) {
680 [&] (const LiveIter
& ti
) {
681 if (ti
.initBlock
!= NoBlockId
) {
682 assertx(iterFromInit(*ctx
.func
, ti
.initBlock
) == it
);
684 Fixup
{bid
, opIdx
, ti
.initBlock
, ti
.baseLocal
}
686 FTRACE(2, " + fixup ({})\n", fixups
.back().show(*ctx
.func
));
692 // Record a fixup for this iteration op. This iteration loop may not be
693 // ultimately eligible, but we'll check that before actually doing the
698 assertx(opIdx
== blk
->hhbcs
.size() - 1);
702 fixupFromState(op
.IterNext
.iter1
);
705 fixupFromState(op
.IterNextK
.iter1
);
708 fixupFromState(op
.IterFree
.iter1
);
711 for (auto const& it
: op
.IterBreak
.iterTab
) {
712 if (it
.kind
== KindOfIter
) fixupFromState(it
.id
);
723 // We identify iteration loops by the block of the initialization op (which we
724 // enforce is exactly one block). A fixup describes a transformation to an
725 // iteration instruction which must be applied only if its associated loop is
728 BlockId block
; // Block of the op
729 uint32_t op
; // Index into the block of the op
730 BlockId init
; // Block of the loop's initializer
731 LocalId base
; // Invariant base of the iterator
733 std::string
show(const php::Func
& f
) const {
734 return folly::sformat(
735 "blk:{},{},blk:{},{}",
737 base
!= NoLocalId
? local_string(f
, base
) : "-"
741 std::vector
<Fixup
> fixups
;
742 // All of the associated iterator operations within an iterator loop can be
743 // optimized to liter if the iterator's initialization block is eligible.
744 boost::dynamic_bitset
<> eligible
;
747 void optimize_iterators(const Index
& index
,
748 const FuncAnalysis
& ainfo
,
749 CollectedInfo
& collect
) {
750 auto const func
= ainfo
.ctx
.func
;
751 // Quick exit. If there's no iterators, or if no associated local survives to
752 // the end of the iterator, there's nothing to do.
753 if (!func
->numIters
|| !ainfo
.hasInvariantIterBase
) return;
755 OptimizeIterState state
;
756 // Everything starts out as eligible. We'll remove initialization blocks as we
758 state
.eligible
.resize(func
->blocks
.size(), true);
760 // Visit all the blocks and build up the fixup state.
761 visit_blocks("optimize_iterators", index
, ainfo
, collect
, state
);
762 if (!state
.eligible
.any()) return;
764 FTRACE(2, "Rewrites:\n");
765 for (auto const& fixup
: state
.fixups
) {
766 auto& cblk
= func
->blocks
[fixup
.block
];
767 auto const& op
= cblk
->hhbcs
[fixup
.op
];
769 if (!state
.eligible
[fixup
.init
]) {
770 // This iteration loop isn't eligible, so don't apply the fixup
771 FTRACE(2, " * ({}): {}\n", fixup
.show(*func
), show(func
, op
));
776 assertx(fixup
.base
!= NoLocalId
);
778 // Rewrite the iteration op to its liter equivalent:
782 bc_with_loc(op
.srcLoc
, bc::PopC
{}),
796 bc_with_loc(op
.srcLoc
, bc::PopC
{}),
802 op
.IterInitK
.target2
,
829 op
.IterNextK
.target2
,
840 bc::LIterFree
{ op
.IterFree
.iter1
, fixup
.base
}
844 case Op::IterBreak
: {
845 auto const iter
= iterFromInit(*func
, fixup
.init
);
847 for (auto& it
: newOps
.back().IterBreak
.iterTab
) {
849 assertx(it
.kind
== KindOfIter
);
850 it
.kind
= KindOfLIter
;
851 it
.local
= fixup
.base
;
857 always_assert(false);
861 2, " ({}): {} ==> {}\n",
862 fixup
.show(*func
), show(func
, op
),
864 using namespace folly::gen
;
866 | map([&] (const Bytecode
& bc
) { return show(func
, bc
); })
867 | unsplit
<std::string
>(",");
871 auto const blk
= cblk
.mutate();
872 blk
->hhbcs
.erase(blk
->hhbcs
.begin() + fixup
.op
);
873 blk
->hhbcs
.insert(blk
->hhbcs
.begin() + fixup
.op
,
874 newOps
.begin(), newOps
.end());
877 FTRACE(10, "{}", show(*func
));
880 //////////////////////////////////////////////////////////////////////
883 * Use the information in the index to resolve a type-constraint to its
884 * underlying type, if possible.
886 void fixTypeConstraint(const Index
& index
, TypeConstraint
& tc
) {
887 if (!tc
.isCheckable() || !tc
.isObject() || tc
.isResolved()) return;
889 if (interface_supports_non_objects(tc
.typeName())) return;
890 auto const resolved
= index
.resolve_type_name(tc
.typeName());
892 assertx(!RuntimeOption::EvalHackArrDVArrs
||
893 (resolved
.type
!= AnnotType::VArray
&&
894 resolved
.type
!= AnnotType::DArray
));
896 if (resolved
.type
== AnnotType::Object
) {
897 if (!resolved
.value
|| !resolved
.value
->resolved()) return;
898 // Can't resolve if it resolves to a magic interface. If we mark it as
899 // resolved, we'll think its an object and not do the special magic
900 // interface checks at runtime.
901 if (interface_supports_non_objects(resolved
.value
->name())) return;
902 if (!resolved
.value
->couldHaveMockedDerivedClass()) tc
.setNoMockObjects();
905 tc
.resolveType(resolved
.type
, tc
.isNullable() || resolved
.nullable
);
906 FTRACE(1, "Retype tc {} -> {}\n", tc
.typeName(), tc
.displayName());
909 //////////////////////////////////////////////////////////////////////
911 void do_optimize(const Index
& index
, FuncAnalysis
&& ainfo
, bool isFinal
) {
912 FTRACE(2, "{:-^70} {}\n", "Optimize Func", ainfo
.ctx
.func
->name
);
915 folly::Optional
<CollectedInfo
> collect
;
918 index
, ainfo
.ctx
, nullptr,
919 CollectionOpts
{}, &ainfo
922 update_bytecode(ainfo
.ctx
.func
, std::move(ainfo
.blockUpdates
));
923 optimize_iterators(index
, ainfo
, *collect
);
927 FTRACE(10, "{}", show(*ainfo
.ctx
.func
));
929 * Note: it's useful to do dead block removal before DCE, so it can remove
930 * code relating to the branch to the dead block.
932 remove_unreachable_blocks(ainfo
);
934 if (options
.LocalDCE
) {
935 visit_blocks("local DCE", index
, ainfo
, *collect
, local_dce
);
937 if (options
.GlobalDCE
) {
938 if (global_dce(index
, ainfo
)) again
= true;
939 if (control_flow_opts(ainfo
)) again
= true;
940 assert(check(*ainfo
.ctx
.func
));
942 * Global DCE can change types of locals across blocks. See
943 * dce.cpp for an explanation.
945 * We need to perform a final type analysis before we do
948 ainfo
= analyze_func(index
, ainfo
.ctx
, CollectionOpts
{});
949 update_bytecode(ainfo
.ctx
.func
, std::move(ainfo
.blockUpdates
));
951 index
, ainfo
.ctx
, nullptr,
952 CollectionOpts
{}, &ainfo
956 // If we merged blocks, there could be new optimization opportunities
959 if (!isFinal
) return;
961 auto const func
= ainfo
.ctx
.func
;
962 if (func
->name
== s_86pinit
.get() ||
963 func
->name
== s_86sinit
.get() ||
964 func
->name
== s_86linit
.get()) {
965 auto const& blk
= *func
->blocks
[func
->mainEntry
];
966 if (blk
.hhbcs
.size() == 2 &&
967 blk
.hhbcs
[0].op
== Op::Null
&&
968 blk
.hhbcs
[1].op
== Op::RetC
) {
969 FTRACE(2, "Erasing {}::{}\n", func
->cls
->name
, func
->name
);
970 func
->cls
->methods
.erase(
971 std::find_if(func
->cls
->methods
.begin(),
972 func
->cls
->methods
.end(),
973 [&](const std::unique_ptr
<php::Func
>& f
) {
974 return f
.get() == func
;
980 attrSetter(func
->attrs
,
981 is_pseudomain(func
) ||
982 func
->attrs
& AttrInterceptable
||
986 if (options
.InsertAssertions
) {
987 visit_blocks("insert assertions", index
, ainfo
, *collect
,
991 for (auto& p
: func
->params
) fixTypeConstraint(index
, p
.typeConstraint
);
993 if (RuntimeOption::EvalCheckReturnTypeHints
>= 3) {
994 fixTypeConstraint(index
, func
->retTypeConstraint
);
998 //////////////////////////////////////////////////////////////////////
1002 //////////////////////////////////////////////////////////////////////
1004 Bytecode
gen_constant(const Cell
& cell
) {
1005 switch (cell
.m_type
) {
1007 return bc::NullUninit
{};
1011 if (cell
.m_data
.num
) {
1014 return bc::False
{};
1017 return bc::Int
{ cell
.m_data
.num
};
1019 return bc::Double
{ cell
.m_data
.dbl
};
1021 assert(cell
.m_data
.pstr
->isStatic());
1022 case KindOfPersistentString
:
1023 return bc::String
{ cell
.m_data
.pstr
};
1025 assert(cell
.m_data
.parr
->isStatic());
1026 case KindOfPersistentVec
:
1027 assert(cell
.m_data
.parr
->isVecArray());
1028 return bc::Vec
{ cell
.m_data
.parr
};
1030 assert(cell
.m_data
.parr
->isStatic());
1031 case KindOfPersistentDict
:
1032 assert(cell
.m_data
.parr
->isDict());
1033 return bc::Dict
{ cell
.m_data
.parr
};
1035 assert(cell
.m_data
.parr
->isStatic());
1036 case KindOfPersistentKeyset
:
1037 assert(cell
.m_data
.parr
->isKeyset());
1038 return bc::Keyset
{ cell
.m_data
.parr
};
1040 case KindOfPersistentShape
:
1043 assert(cell
.m_data
.parr
->isStatic());
1044 case KindOfPersistentArray
:
1045 assert(cell
.m_data
.parr
->isPHPArray());
1046 return bc::Array
{ cell
.m_data
.parr
};
1049 case KindOfResource
:
1054 case KindOfRecord
: // TODO(arnabde)
1055 always_assert(0 && "invalid constant in propagate_constants");
1060 void optimize_func(const Index
& index
, FuncAnalysis
&& ainfo
, bool isFinal
) {
1061 auto const bump
= trace_bump_for(ainfo
.ctx
.cls
, ainfo
.ctx
.func
);
1063 SCOPE_ASSERT_DETAIL("optimize_func") {
1064 return "Optimizing:" + show(ainfo
.ctx
);
1067 Trace::Bump bumper1
{Trace::hhbbc
, bump
};
1068 Trace::Bump bumper2
{Trace::hhbbc_cfg
, bump
};
1069 Trace::Bump bumper3
{Trace::hhbbc_dce
, bump
};
1070 do_optimize(index
, std::move(ainfo
), isFinal
);
1073 void update_bytecode(
1075 CompactVector
<std::pair
<BlockId
, BlockUpdateInfo
>>&& blockUpdates
) {
1077 for (auto& ent
: blockUpdates
) {
1078 auto blk
= func
->blocks
[ent
.first
].mutate();
1079 auto const srcLoc
= blk
->hhbcs
.front().srcLoc
;
1080 if (!ent
.second
.unchangedBcs
) {
1081 if (ent
.second
.replacedBcs
.size()) {
1082 blk
->hhbcs
= std::move(ent
.second
.replacedBcs
);
1084 blk
->hhbcs
= { bc_with_loc(blk
->hhbcs
.front().srcLoc
, bc::Nop
{}) };
1087 blk
->hhbcs
.erase(blk
->hhbcs
.begin() + ent
.second
.unchangedBcs
,
1089 blk
->hhbcs
.reserve(blk
->hhbcs
.size() + ent
.second
.replacedBcs
.size());
1090 for (auto& bc
: ent
.second
.replacedBcs
) {
1091 blk
->hhbcs
.push_back(std::move(bc
));
1094 if (blk
->hhbcs
.empty()) {
1095 blk
->hhbcs
.push_back(bc_with_loc(srcLoc
, bc::Nop
{}));
1097 auto fatal_block
= NoBlockId
;
1099 if (fatal_block
== NoBlockId
) fatal_block
= make_fatal_block(func
, blk
);
1102 blk
->fallthrough
= ent
.second
.fallthrough
;
1104 forEachTakenEdge(blk
->hhbcs
.back(),
1105 [&] (BlockId
& bid
) {
1107 if (bid
== NoBlockId
) bid
= fatal();
1109 if (blk
->fallthrough
== NoBlockId
&&
1110 !(instrFlags(blk
->hhbcs
.back().op
) & TF
)) {
1112 blk
->fallthrough
= fatal();
1114 blk
->hhbcs
.push_back(bc::BreakTraceHint
{});
1115 blk
->hhbcs
.push_back(bc::String
{ s_unreachable
.get() });
1116 blk
->hhbcs
.push_back(bc::Fatal
{ FatalOp::Runtime
});
1120 blockUpdates
.clear();
1122 if (is_pseudomain(func
) &&
1123 func
->unit
->persistent
.load(std::memory_order_relaxed
)) {
1124 func
->unit
->persistent_pseudomain
.store(
1125 persistence_check(func
), std::memory_order_relaxed
1130 //////////////////////////////////////////////////////////////////////
1132 void optimize_class_prop_type_hints(const Index
& index
, Context ctx
) {
1134 auto const bump
= trace_bump_for(ctx
.cls
, nullptr);
1135 Trace::Bump bumper
{Trace::hhbbc
, bump
};
1136 for (auto& prop
: ctx
.cls
->properties
) {
1139 const_cast<TypeConstraint
&>(prop
.typeConstraint
)
1144 //////////////////////////////////////////////////////////////////////