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/cfg-opts.h"
21 #include "hphp/hhbbc/analyze.h"
22 #include "hphp/hhbbc/bc.h"
23 #include "hphp/hhbbc/cfg.h"
24 #include "hphp/hhbbc/dce.h"
25 #include "hphp/hhbbc/unit-util.h"
27 namespace HPHP
{ namespace HHBBC
{
29 TRACE_SET_MOD(hhbbc_cfg
);
31 //////////////////////////////////////////////////////////////////////
33 void remove_unreachable_blocks(const FuncAnalysis
& ainfo
) {
34 auto& func
= *ainfo
.ctx
.func
;
36 Trace::hhbbc_cfg
, kSystemLibBump
, is_systemlib_part(*func
.unit
)
38 auto done_header
= false;
40 if (done_header
) return;
42 FTRACE(2, "Remove unreachable blocks: {}\n", func
.name
);
45 auto make_unreachable
= [&](borrowed_ptr
<php::Block
> blk
) {
46 if (blk
->id
== NoBlockId
) return false;
47 auto const& state
= ainfo
.bdata
[blk
->id
].stateIn
;
48 if (!state
.initialized
) return true;
49 if (!state
.unreachable
) return false;
50 return blk
->hhbcs
.size() != 2 ||
51 blk
->hhbcs
.back().op
!= Op::Fatal
;
54 for (auto const& blk
: ainfo
.ctx
.func
->blocks
) {
55 if (!make_unreachable(borrow(blk
))) continue;
57 FTRACE(2, "Marking {} unreachable\n", blk
->id
);
58 auto const srcLoc
= blk
->hhbcs
.front().srcLoc
;
60 bc_with_loc(srcLoc
, bc::String
{ s_unreachable
.get() }),
61 bc_with_loc(srcLoc
, bc::Fatal
{ FatalOp::Runtime
})
63 blk
->fallthrough
= NoBlockId
;
64 blk
->exnNode
= nullptr;
67 if (!options
.RemoveDeadBlocks
) return;
69 auto reachable
= [&](BlockId id
) {
70 if (id
== NoBlockId
) return false;
71 auto const& state
= ainfo
.bdata
[id
].stateIn
;
72 return state
.initialized
&& !state
.unreachable
;
75 for (auto const& blk
: ainfo
.rpoBlocks
) {
76 if (!reachable(blk
->id
)) continue;
77 auto reachableTarget
= NoBlockId
;
78 auto hasUnreachableTargets
= false;
79 forEachTakenEdge(blk
->hhbcs
.back(), [&] (BlockId id
) {
83 hasUnreachableTargets
= true;
86 if (!hasUnreachableTargets
) continue;
88 switch (blk
->hhbcs
.back().op
) {
91 FTRACE(2, "blk: {} - jcc -> jmp {}\n", blk
->id
, reachableTarget
);
92 blk
->hhbcs
.back() = bc_with_loc(blk
->hhbcs
.back().srcLoc
, bc::PopC
{});
93 blk
->fallthrough
= reachableTarget
;
96 FTRACE(2, "blk: {} -", blk
->id
, reachableTarget
);
97 forEachTakenEdge(blk
->hhbcs
.back(), [&] (const BlockId
& id
) {
99 FTRACE(2, " {}->{}", id
, reachableTarget
);
100 const_cast<BlockId
&>(id
) = reachableTarget
;
111 struct MergeBlockInfo
{
112 // This block has a predecessor; used to set the multiplePreds flag
114 // Block has more than one pred, or is an entry block
115 uint8_t multiplePreds
: 1;
116 // Block has more than one successor
117 uint8_t multipleSuccs
: 1;
119 // Block contains a sequence that could be part of a switch
120 uint8_t couldBeSwitch
: 1;
121 // Block contains a sequence that could be part of a switch, and nothing else
122 uint8_t onlySwitch
: 1;
123 // Block follows the "default" of a prior switch sequence
124 uint8_t followsSwitch
: 1;
128 union Case
{ SString s
; int64_t i
; };
129 std::vector
<std::pair
<Case
,BlockId
>> cases
;
130 BlockId defaultBlock
= NoBlockId
;
131 LocalId switchLoc
= NoLocalId
;
135 bool analyzeSwitch(const php::Block
& blk
,
136 std::vector
<MergeBlockInfo
>& blkInfos
,
137 SwitchInfo
* switchInfo
) {
138 auto const jmp
= &blk
.hhbcs
.back();
139 auto& blkInfo
= blkInfos
[blk
.id
];
144 if (blk
.hhbcs
.size() < 4) return false;
145 auto const& cmp
= jmp
[-1];
146 if (cmp
.op
!= Op::Eq
&& cmp
.op
!= Op::Neq
) return false;
147 auto check
= [&] (const Bytecode
& arg1
, const Bytecode
& arg2
) -> bool {
149 if (arg2
.op
== Op::CGetL
) {
150 loc
= arg2
.CGetL
.loc1
;
151 } else if (arg2
.op
== Op::CGetL2
&& &arg2
== &arg1
+ 1) {
152 loc
= arg2
.CGetL2
.loc1
;
157 if (arg1
.op
== Op::Int
) {
159 } else if (arg1
.op
== Op::String
) {
160 c
.s
= arg1
.String
.str1
;
165 auto const dt
= arg1
.op
== Op::Int
? KindOfInt64
: KindOfString
;
166 if (switchInfo
->cases
.size()) {
167 if (loc
!= switchInfo
->switchLoc
) return false;
168 if (dt
!= switchInfo
->kind
) return false;
170 switchInfo
->switchLoc
= loc
;
171 switchInfo
->kind
= dt
;
174 auto const jmpTarget
= jmp
->op
== Op::JmpNZ
?
175 jmp
->JmpNZ
.target
: jmp
->JmpZ
.target
;
176 BlockId caseTarget
, defaultBlock
;
177 if ((jmp
->op
== Op::JmpNZ
) == (cmp
.op
== Op::Eq
)) {
178 defaultBlock
= blk
.fallthrough
;
179 caseTarget
= jmpTarget
;
181 defaultBlock
= jmpTarget
;
182 caseTarget
= blk
.fallthrough
;
184 blkInfo
.couldBeSwitch
= true;
185 blkInfo
.onlySwitch
= blk
.hhbcs
.size() == 4;
186 blkInfos
[defaultBlock
].followsSwitch
= true;
188 switchInfo
->cases
.emplace_back(c
, caseTarget
);
189 switchInfo
->defaultBlock
= defaultBlock
;
193 return check(jmp
[-2], jmp
[-3]) || check(jmp
[-3], jmp
[-2]);
197 if (blk
.hhbcs
.size() < 2) return false;
198 auto const& cgetl
= jmp
[-1];
199 if (cgetl
.op
!= Op::CGetL
) return false;
200 auto const dt
= jmp
->op
== Op::Switch
? KindOfInt64
: KindOfString
;
202 if (switchInfo
->cases
.size()) {
203 if (cgetl
.CGetL
.loc1
!= switchInfo
->switchLoc
) return false;
204 if (dt
!= switchInfo
->kind
) return false;
206 switchInfo
->switchLoc
= cgetl
.CGetL
.loc1
;
207 switchInfo
->kind
= dt
;
210 if (jmp
->op
== Op::Switch
) {
211 if (jmp
->Switch
.subop1
!= SwitchKind::Bounded
) return false;
212 auto const db
= jmp
->Switch
.targets
.back();
213 auto const min
= jmp
->Switch
.arg2
;
214 blkInfos
[db
].followsSwitch
= true;
216 switchInfo
->defaultBlock
= db
;
217 for (size_t i
= 0; i
< jmp
->Switch
.targets
.size() - 2; i
++) {
218 auto const t
= jmp
->Switch
.targets
[i
];
219 if (t
== db
) continue;
222 switchInfo
->cases
.emplace_back(c
, t
);
226 auto const db
= jmp
->SSwitch
.targets
.back().second
;
227 blkInfos
[db
].followsSwitch
= true;
229 switchInfo
->defaultBlock
= db
;
230 for (auto& kv
: jmp
->SSwitch
.targets
) {
231 if (kv
.second
== db
) continue;
234 switchInfo
->cases
.emplace_back(c
, kv
.second
);
238 blkInfo
.couldBeSwitch
= true;
239 blkInfo
.onlySwitch
= blk
.hhbcs
.size() == 2;
247 Bytecode
buildIntSwitch(SwitchInfo
& switchInfo
) {
248 auto min
= switchInfo
.cases
[0].first
.i
;
250 for (size_t i
= 1; i
< switchInfo
.cases
.size(); ++i
) {
251 auto v
= switchInfo
.cases
[i
].first
.i
;
252 if (v
< min
) min
= v
;
253 if (v
> max
) max
= v
;
255 if (switchInfo
.cases
.size() / ((double)max
- (double)min
+ 1) < .5) {
256 return { bc::Nop
{} };
258 CompactVector
<BlockId
> switchTab
;
259 switchTab
.resize(max
- min
+ 3, switchInfo
.defaultBlock
);
260 for (auto i
= switchInfo
.cases
.size(); i
--; ) {
261 auto const& c
= switchInfo
.cases
[i
];
262 switchTab
[c
.first
.i
- min
] = c
.second
;
263 if (c
.first
.i
) switchTab
[max
- min
+ 1] = c
.second
;
265 return { bc::Switch
{ SwitchKind::Bounded
, min
, std::move(switchTab
) } };
268 Bytecode
buildStringSwitch(SwitchInfo
& switchInfo
) {
269 std::set
<SString
> seen
;
270 SSwitchTab sswitchTab
;
271 for (auto& c
: switchInfo
.cases
) {
272 if (seen
.insert(c
.first
.s
).second
) {
273 sswitchTab
.emplace_back(c
.first
.s
, c
.second
);
276 sswitchTab
.emplace_back(nullptr, switchInfo
.defaultBlock
);
277 return { bc::SSwitch
{ std::move(sswitchTab
) } };
280 bool buildSwitches(php::Func
& func
,
281 borrowed_ptr
<php::Block
> blk
,
282 std::vector
<MergeBlockInfo
>& blkInfos
) {
283 SwitchInfo switchInfo
;
284 std::vector
<BlockId
> blocks
;
285 if (!analyzeSwitch(*blk
, blkInfos
, &switchInfo
)) return false;
286 blkInfos
[blk
->id
].couldBeSwitch
= false;
287 blkInfos
[blk
->id
].onlySwitch
= false;
289 auto const& bInfo
= blkInfos
[switchInfo
.defaultBlock
];
290 auto const nxt
= borrow(func
.blocks
[switchInfo
.defaultBlock
]);
291 if (bInfo
.onlySwitch
&& !bInfo
.multiplePreds
&&
292 analyzeSwitch(*nxt
, blkInfos
, &switchInfo
)) {
293 blocks
.push_back(switchInfo
.defaultBlock
);
297 auto const minSize
= switchInfo
.kind
== KindOfInt64
? 1 : 8;
298 if (switchInfo
.cases
.size() >= minSize
&& blocks
.size()) {
299 switchInfo
.defaultBlock
= next_real_block(func
, switchInfo
.defaultBlock
);
300 auto bc
= switchInfo
.kind
== KindOfInt64
?
301 buildIntSwitch(switchInfo
) : buildStringSwitch(switchInfo
);
302 if (bc
.op
!= Op::Nop
) {
303 auto it
= blk
->hhbcs
.end();
304 // blk->fallthrough implies it was a JmpZ JmpNZ block,
305 // which means we have exactly 4 instructions making up
306 // the switch (see analyzeSwitch). Otherwise it was a
307 // [S]Switch, and there were exactly two instructions.
308 if (blk
->fallthrough
!= NoBlockId
) {
313 blkInfos
[switchInfo
.defaultBlock
].multiplePreds
= true;
314 blk
->hhbcs
.erase(it
, blk
->hhbcs
.end());
315 blk
->hhbcs
.emplace_back(bc::CGetL
{ switchInfo
.switchLoc
});
316 blk
->hhbcs
.push_back(std::move(bc
));
317 blk
->fallthrough
= NoBlockId
;
318 for (auto id
: blocks
) {
319 if (blkInfos
[id
].multiplePreds
) continue;
320 auto const removed
= borrow(func
.blocks
[id
]);
321 removed
->id
= NoBlockId
;
322 removed
->hhbcs
= { bc::Nop
{} };
323 removed
->fallthrough
= NoBlockId
;
324 removed
->factoredExits
= {};
329 return (bInfo
.couldBeSwitch
&& buildSwitches(func
, nxt
, blkInfos
)) || ret
;
334 bool strip_exn_tree(const php::Func
& func
,
335 CompactVector
<std::unique_ptr
<php::ExnNode
>>& nodes
,
336 const std::set
<borrowed_ptr
<php::ExnNode
>> &seenExnNodes
,
339 auto it
= std::remove_if(nodes
.begin(), nodes
.end(),
340 [&] (const std::unique_ptr
<php::ExnNode
>& node
) {
341 if (seenExnNodes
.count(borrow(node
))) return false;
342 FTRACE(2, "Stripping ExnNode {}\n", node
->id
);
346 if (it
!= nodes
.end()) {
347 nodes
.erase(it
, nodes
.end());
350 for (auto& n
: nodes
) {
355 [&] (const php::CatchRegion
& cr
) {},
356 [&] (const php::FaultRegion
& fr
) {
357 auto pentry
= match
<BlockId
>(
359 [&] (const php::CatchRegion
& cr2
) { return cr2
.catchEntry
; },
360 [&] (const php::FaultRegion
& fr2
) { return fr2
.faultEntry
; }
362 auto const sectionId
=
363 static_cast<size_t>(func
.blocks
[fr
.faultEntry
]->section
);
364 sectionExits
[sectionId
].insert(pentry
);
367 if (strip_exn_tree(func
, n
->children
, seenExnNodes
, nextId
, sectionExits
)) {
377 bool rebuild_exn_tree(const FuncAnalysis
& ainfo
) {
378 auto& func
= *ainfo
.ctx
.func
;
380 Trace::hhbbc_cfg
, kSystemLibBump
, is_systemlib_part(*func
.unit
)
382 FTRACE(4, "Rebuild exn tree: {}\n", func
.name
);
384 auto reachable
= [&](BlockId id
) {
385 if (id
== NoBlockId
) return false;
386 auto const& state
= ainfo
.bdata
[id
].stateIn
;
387 return state
.initialized
&& !state
.unreachable
;
389 std::unordered_map
<BlockId
,std::set
<BlockId
>> factoredExits
;
390 std::unordered_map
<size_t, std::set
<BlockId
>> sectionExits
;
391 std::set
<borrowed_ptr
<php::ExnNode
>> seenExnNodes
;
393 for (auto const& blk
: ainfo
.rpoBlocks
) {
394 if (!reachable(blk
->id
)) {
395 FTRACE(4, "Unreachable: {}\n", blk
->id
);
398 if (auto node
= blk
->exnNode
) {
399 auto entry
= match
<BlockId
>(
401 [&] (const php::CatchRegion
& cr
) { return cr
.catchEntry
; },
402 [&] (const php::FaultRegion
& fr
) { return fr
.faultEntry
; }
404 factoredExits
[blk
->id
].insert(entry
);
406 if (!seenExnNodes
.insert(node
).second
) break;
407 } while ((node
= node
->parent
) != nullptr);
412 if (!strip_exn_tree(func
, func
.exnNodes
,
413 seenExnNodes
, nextId
, sectionExits
)) {
417 for (auto const& blk
: func
.blocks
) {
418 if (!reachable(blk
->id
)) {
419 blk
->exnNode
= nullptr;
422 auto &fe
= factoredExits
[blk
->id
];
423 auto it
= sectionExits
.find(static_cast<size_t>(blk
->section
));
424 if (it
!= sectionExits
.end()) {
425 fe
.insert(it
->second
.begin(), it
->second
.end());
428 if (blk
->factoredExits
.size() != fe
.size()) {
430 FTRACE(2, "Old factored edges: blk:{} -", blk
->id
);
431 for (auto DEBUG_ONLY id
: blk
->factoredExits
) FTRACE(2, " {}", id
);
433 blk
->factoredExits
.resize(fe
.size());
438 if (blk
->factoredExits
[i
] != b
) {
440 blk
->factoredExits
[i
] = b
;
445 FTRACE(2, "New factored edges: blk:{} -", blk
->id
);
446 for (auto DEBUG_ONLY id
: blk
->factoredExits
) FTRACE(2, " {}", id
);
454 bool control_flow_opts(const FuncAnalysis
& ainfo
) {
455 auto& func
= *ainfo
.ctx
.func
;
457 Trace::hhbbc_cfg
, kSystemLibBump
, is_systemlib_part(*func
.unit
)
459 FTRACE(2, "control_flow_opts: {}\n", func
.name
);
461 std::vector
<MergeBlockInfo
> blockInfo(func
.blocks
.size(), MergeBlockInfo
{});
463 bool anyChanges
= false;
465 auto reachable
= [&](BlockId id
) {
466 auto const& state
= ainfo
.bdata
[id
].stateIn
;
467 return state
.initialized
&& !state
.unreachable
;
469 // find all the blocks with multiple preds; they can't be merged
470 // into their predecessors
471 for (auto const& blk
: func
.blocks
) {
472 if (blk
->id
== NoBlockId
) continue;
473 auto& bbi
= blockInfo
[blk
->id
];
475 if (!reachable(blk
->id
)) {
476 bbi
.multiplePreds
= true;
477 bbi
.multipleSuccs
= true;
480 analyzeSwitch(*blk
, blockInfo
, nullptr);
482 auto handleSucc
= [&] (BlockId succId
) {
483 auto& bsi
= blockInfo
[succId
];
485 bsi
.multiplePreds
= true;
491 forEachNormalSuccessor(*blk
, [&](const BlockId
& succId
) {
492 auto skip
= next_real_block(func
, succId
);
493 if (skip
!= succId
) {
494 const_cast<BlockId
&>(succId
) = skip
;
499 for (auto& ex
: blk
->factoredExits
) handleSucc(ex
);
500 if (numSucc
> 1) bbi
.multipleSuccs
= true;
502 blockInfo
[func
.mainEntry
].multiplePreds
= true;
503 for (auto const blkId
: func
.dvEntries
) {
504 if (blkId
!= NoBlockId
) {
505 blockInfo
[blkId
].multiplePreds
= true;
509 for (auto& blk
: func
.blocks
) {
510 if (blk
->id
== NoBlockId
) continue;
511 while (blk
->fallthrough
!= NoBlockId
) {
512 auto nxt
= borrow(func
.blocks
[blk
->fallthrough
]);
513 if (blockInfo
[blk
->id
].multipleSuccs
||
514 blockInfo
[nxt
->id
].multiplePreds
||
515 blk
->exnNode
!= nxt
->exnNode
||
516 blk
->section
!= nxt
->section
) {
520 FTRACE(1, "merging: {} into {}\n", (void*)nxt
, (void*)blk
.get());
521 auto& bInfo
= blockInfo
[blk
->id
];
522 auto const& nInfo
= blockInfo
[nxt
->id
];
523 bInfo
.multipleSuccs
= nInfo
.multipleSuccs
;
524 bInfo
.couldBeSwitch
= nInfo
.couldBeSwitch
;
525 bInfo
.onlySwitch
= false;
527 blk
->fallthrough
= nxt
->fallthrough
;
528 blk
->fallthroughNS
= nxt
->fallthroughNS
;
529 if (nxt
->factoredExits
.size()) {
530 if (blk
->factoredExits
.size()) {
531 std::set
<BlockId
> exitSet
;
532 std::copy(begin(blk
->factoredExits
), end(blk
->factoredExits
),
533 std::inserter(exitSet
, begin(exitSet
)));
534 std::copy(nxt
->factoredExits
.begin(), nxt
->factoredExits
.end(),
535 std::inserter(exitSet
, begin(exitSet
)));
536 blk
->factoredExits
.resize(exitSet
.size());
537 std::copy(begin(exitSet
), end(exitSet
), blk
->factoredExits
.begin());
538 nxt
->factoredExits
= decltype(nxt
->factoredExits
) {};
540 blk
->factoredExits
= std::move(nxt
->factoredExits
);
543 std::copy(nxt
->hhbcs
.begin(), nxt
->hhbcs
.end(),
544 std::back_inserter(blk
->hhbcs
));
545 nxt
->fallthrough
= NoBlockId
;
547 nxt
->hhbcs
= { bc::Nop
{} };
550 auto const& bInfo
= blockInfo
[blk
->id
];
551 if (bInfo
.couldBeSwitch
&&
552 (bInfo
.multiplePreds
|| !bInfo
.onlySwitch
|| !bInfo
.followsSwitch
)) {
553 // This block looks like it could be part of a switch, and it's
554 // not in the middle of a sequence of such blocks.
555 if (buildSwitches(func
, borrow(blk
), blockInfo
)) {
564 //////////////////////////////////////////////////////////////////////