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 +----------------------------------------------------------------------+
22 #include "hphp/util/assertions.h"
23 #include "hphp/runtime/base/runtime-option.h"
24 #include "hphp/runtime/vm/jit/mcgen.h"
25 #include "hphp/runtime/vm/jit/prof-data.h"
26 #include "hphp/runtime/vm/jit/region-selection.h"
27 #include "hphp/runtime/vm/jit/tc.h"
28 #include "hphp/runtime/vm/jit/timer.h"
29 #include "hphp/runtime/vm/jit/trans-cfg.h"
30 #include "hphp/runtime/vm/jit/translator-inline.h"
31 #include "hphp/runtime/vm/jit/translator.h"
33 namespace HPHP
{ namespace jit
{
37 //////////////////////////////////////////////////////////////////////
41 using TransIDToRegionMap
= hphp_hash_map
<TransID
, RegionDescPtr
>;
42 using RegionToTransIDsMap
= hphp_hash_map
<
45 smart_pointer_hash
<RegionDescPtr
>
49 * Returns the set of of TransIDs that are in `bid's retranslation
52 TransIDSet
findRetransSet(const RegionDesc
& region
, RegionDesc::BlockId bid
) {
54 auto insert
= [&](RegionDesc::BlockId id
) {
56 auto& merged
= region
.merged(id
);
57 set
.insert(merged
.begin(), merged
.end());
60 while (auto next
= region
.nextRetrans(bid
)) {
68 * Add to `coveredArcs' all `cfg's arcs going from `src' to 'dst' or
69 * any of `dst's retranslations in `dstRegion'.
71 void markCoveredArc(TransID src
,
74 const RegionDesc
& dstRegion
,
75 TransCFG::ArcPtrSet
& coveredArcs
) {
76 auto dstRetransSet
= findRetransSet(dstRegion
, dst
);
77 for (auto outArc
: cfg
.outArcs(src
)) {
78 if (dstRetransSet
.count(outArc
->dst())) {
79 coveredArcs
.insert(outArc
);
85 * Add to sets coveredNodes and coveredArcs the cfg arcs that are now
86 * covered given the new region containing the translations in `region'.
88 void markCovered(const TransCFG
& cfg
, const RegionDescPtr region
,
90 TransIDToRegionMap
& headToRegion
,
91 TransIDSet
& coveredNodes
,
92 TransCFG::ArcPtrSet
& coveredArcs
) {
93 assertx(!region
->empty());
94 const auto entryId
= region
->entry()->id();
96 // Mark all region's nodes as covered.
97 for (auto& b
: region
->blocks()) {
98 coveredNodes
.insert(b
->id());
99 const auto& merged
= region
->merged(b
->id());
100 coveredNodes
.insert(merged
.begin(), merged
.end());
103 // Mark as covered all incoming arcs from already covered nodes into the entry
104 // of the region or one of its retranslations/merged blocks.
105 for (auto newHead
: findRetransSet(*region
, entryId
)) {
106 heads
.insert(newHead
);
107 headToRegion
[newHead
] = region
;
108 for (auto arc
: cfg
.inArcs(newHead
)) {
109 const auto src
= arc
->src();
110 if (coveredNodes
.count(src
)) {
111 markCoveredArc(src
, entryId
, cfg
, *region
, coveredArcs
);
116 // Mark all CFG arcs within the region as covered.
117 region
->forEachArc([&](RegionDesc::BlockId src
, RegionDesc::BlockId dst
) {
118 auto const srcIds
= findRetransSet(*region
, src
);
119 auto const dstIds
= findRetransSet(*region
, dst
);
121 for (auto srcId
: srcIds
) {
122 for (auto arc
: cfg
.outArcs(srcId
)) {
123 if (dstIds
.count(arc
->dst())) {
124 coveredArcs
.insert(arc
);
130 // Mark all outgoing arcs from the region to a head node as covered.
131 for (auto& b
: region
->blocks()) {
132 for (auto srcId
: findRetransSet(*region
, b
->id())) {
133 for (auto arc
: cfg
.outArcs(srcId
)) {
134 if (heads
.count(arc
->dst())) {
135 auto dstRegionEntryId
= headToRegion
[arc
->dst()]->entry()->id();
136 markCoveredArc(arc
->src(), dstRegionEntryId
, cfg
,
137 *headToRegion
[arc
->dst()], coveredArcs
);
145 * Returns the sum of the weights of the arcs going from srcs to dst.
147 int64_t interRegionWeight(const TransIDVec
& srcs
,
149 const TransCFG
& cfg
) {
150 int64_t totalWeight
= 0;
151 for (auto src
: srcs
) {
152 for (auto arc
: cfg
.outArcs(src
)) {
153 if (arc
->dst() == dst
) {
154 totalWeight
+= arc
->weight();
161 const TransIDVec
& getRegionTransIDVec(const RegionToTransIDsMap
& map
,
162 RegionDescPtr region
) {
163 auto it
= map
.find(region
);
164 assertx(it
!= map
.end());
169 * Sorts the regions vector in a linear order to be used for
170 * translation. The goal is to obtain an order that improves locality
171 * when the function is executed. Each region is translated separately.
173 void sortRegions(RegionVec
& regions
, const Func
* /*func*/, const TransCFG
& cfg
,
174 const ProfData
* profData
,
175 const TransIDToRegionMap
& /*headToRegion*/,
176 const RegionToTransIDsMap
& regionToTransIds
) {
177 if (regions
.empty()) return;
179 // First, pick the region starting at the lowest bytecode offset.
180 // This will normally correspond to the main function entry (for
181 // normal, regular bytecode), but it may not be for irregular
182 // functions written in hhas (like array_map and array_filter). If
183 // there multiple regions starting at the lowest bytecode offset,
184 // pick the one with the largest profile weight.
185 RegionDescPtr entryRegion
= nullptr;
186 int64_t maxEntryWeight
= -1;
187 auto lowestOffset
= kInvalidOffset
;
188 for (const auto& pair
: regionToTransIds
) {
190 auto& tids
= pair
.second
;
191 auto const firstTid
= tids
[0];
192 auto const firstOffset
= profData
->transRec(firstTid
)->srcKey().offset();
193 auto const weight
= cfg
.weight(firstTid
);
194 if (lowestOffset
== kInvalidOffset
|| firstOffset
< lowestOffset
||
195 (firstOffset
== lowestOffset
&& weight
> maxEntryWeight
)) {
197 maxEntryWeight
= weight
;
198 lowestOffset
= firstOffset
;
202 assertx(entryRegion
);
203 RegionVec sorted
{entryRegion
};
205 selected
.insert(entryRegion
);
207 RegionDescPtr region
= entryRegion
;
208 // Select the remaining regions, iteratively picking the most likely
209 // region to execute next.
210 for (auto i
= 1; i
< regions
.size(); i
++) {
211 int64_t maxWeight
= -1;
212 int64_t maxHeadWeight
= -1;
213 RegionDescPtr bestNext
= nullptr;
214 auto regionTransIds
= getRegionTransIDVec(regionToTransIds
, region
);
215 for (auto next
: regions
) {
216 if (selected
.count(next
)) continue;
217 auto nextTransIds
= getRegionTransIDVec(regionToTransIds
, next
);
218 int64_t weight
= interRegionWeight(regionTransIds
, nextTransIds
[0], cfg
);
219 int64_t headWeight
= cfg
.weight(nextTransIds
[0]);
220 if ((weight
> maxWeight
) ||
221 (weight
== maxWeight
&& headWeight
> maxHeadWeight
)) {
223 maxHeadWeight
= headWeight
;
228 sorted
.push_back(bestNext
);
229 selected
.insert(bestNext
);
233 assertx(sorted
.size() == regions
.size());
236 if (debug
&& Trace::moduleEnabled(HPHP::Trace::pgo
, 5)) {
237 for (size_t i
= 0; i
< regions
.size(); i
++) {
239 auto tids
= getRegionTransIDVec(regionToTransIds
, r
);
240 std::string transIds
= folly::join(", ", tids
);
241 FTRACE(6, "sortRegions: region[{}]: {}\n", i
, transIds
);
246 bool allArcsCovered(const TransCFG::ArcPtrVec
& arcs
,
247 const TransCFG::ArcPtrSet
& coveredArcs
) {
248 for (auto arc
: arcs
) {
249 if (!coveredArcs
.count(arc
)) {
258 //////////////////////////////////////////////////////////////////////
261 * Regionize a func, so that each node and each arc in its TransCFG is
262 * "covered". A node is covered if any region contains it. An arc T1->T2
263 * is covered if either:
265 * a) T1 and T2 are in the same region R and R contains arc T1->T2.
266 * b) T2 is the head (first translation) of a region.
270 * 1) sort nodes in decreasing weight order
271 * 2) for each node N:
272 * 2.1) if N and all its incoming arcs are covered, then continue
273 * 2.2) select a region starting at this node and mark nodes/arcs as
274 * covered appropriately
276 RegionVec
regionizeFunc(const Func
* func
, std::string
& transCFGAnnot
) {
277 const Timer
rf_timer(Timer::regionizeFunc
);
280 tracing::Block _
{"regionize-func", [&] { return traceProps(func
); }};
282 auto regionMode
= pgoRegionMode(*func
);
284 auto const funcId
= func
->getFuncId();
285 auto const profData
= jit::profData();
286 TransCFG
cfg(funcId
, profData
);
288 if (Trace::moduleEnabled(HPHP::Trace::pgo
, 5)) {
289 auto dotFileName
= folly::to
<std::string
>(
290 "/tmp/func-cfg-", funcId
.toInt(), ".dot");
291 std::ofstream
outFile(dotFileName
);
292 if (outFile
.is_open()) {
293 cfg
.print(outFile
, funcId
, profData
);
294 FTRACE(5, "regionizeFunc: initial CFG for func {} saved to file {}\n",
295 funcId
, dotFileName
);
299 if (mcgen::dumpTCAnnotation(TransKind::Optimize
) &&
300 RuntimeOption::EvalDumpRegion
>= 2) {
301 std::ostringstream cfgStream
;
302 cfg
.print(cfgStream
, funcId
, profData
);
303 transCFGAnnot
= cfgStream
.str();
306 auto arcs
= cfg
.arcs();
307 auto nodes
= cfg
.nodes();
312 [&](TransID tid1
, TransID tid2
) -> bool {
313 if (regionMode
== PGORegionMode::WholeCFG
||
314 regionMode
== PGORegionMode::HotCFG
) {
315 auto sk1
= profData
->transRec(tid1
)->srcKey();
316 auto sk2
= profData
->transRec(tid2
)->srcKey();
317 if (sk1
!= sk2
) return sk1
.offset() < sk2
.offset();
319 if (cfg
.weight(tid1
) != cfg
.weight(tid2
)) {
320 return cfg
.weight(tid1
) > cfg
.weight(tid2
);
322 // In case of ties, pick older translations first, in an attempt to start
323 // loops at their headers.
328 TransCFG::ArcPtrSet coveredArcs
;
329 TransIDSet coveredNodes
;
331 TransIDToRegionMap headToRegion
;
332 RegionToTransIDsMap regionToTransIds
;
335 for (auto node
: nodes
) {
336 if (!coveredNodes
.count(node
) ||
337 !allArcsCovered(cfg
.inArcs(node
), coveredArcs
)) {
339 // If the weight of node is too low, we mark it and its incoming arcs as
340 // covered but skip generating a region starting at it to reduce code
341 // size. This node will probably trigger a live translation instead.
342 auto const minBlkPerc
= RuntimeOption::EvalJitPGOMinBlockCountPercent
;
343 if (cfg
.weight(node
) < cfg
.weight(nodes
[0]) * minBlkPerc
/ 100) {
344 FTRACE(3, "regionizeFunc: skipping forming a region to cover node {}\n",
346 coveredNodes
.insert(node
);
347 auto const& inArcs
= cfg
.inArcs(node
);
348 coveredArcs
.insert(inArcs
.begin(), inArcs
.end());
349 for (auto outArc
: cfg
.outArcs(node
)) {
350 if (heads
.count(outArc
->dst())) {
351 coveredArcs
.insert(outArc
);
356 FTRACE(6, "regionizeFunc: selecting trace to cover node {}\n", newHead
);
357 RegionDescPtr region
;
360 ctx
.profData
= profData
;
361 ctx
.entries
= {newHead
};
362 ctx
.maxBCInstrs
= RuntimeOption::EvalJitMaxRegionInstrs
;
363 switch (regionMode
) {
364 case PGORegionMode::Hottrace
:
365 region
= selectHotTrace(ctx
);
368 case PGORegionMode::WholeCFG
:
369 case PGORegionMode::HotCFG
:
370 region
= selectHotCFG(ctx
);
373 case PGORegionMode::Hotblock
:
374 always_assert(0 && "Invalid value for EvalJitPGORegionSelector");
376 FTRACE(6, "regionizeFunc: selected region to cover node {}\n{}\n",
377 newHead
, show(*region
));
378 profData
->setOptimized(profData
->transRec(newHead
)->srcKey());
380 for (auto& b
: region
->blocks()) {
381 const auto bid
= b
->id();
382 assertx(hasTransID(bid
) &&
383 bid
== getTransID(bid
) &&
384 bid
== b
->profTransID());
385 regionToTransIds
[region
].push_back(bid
);
388 regions
.emplace_back(region
);
389 markCovered(cfg
, region
, heads
, headToRegion
, coveredNodes
, coveredArcs
);
393 assertx(coveredNodes
.size() == cfg
.nodes().size());
394 assertx(coveredArcs
.size() == arcs
.size());
396 sortRegions(regions
, func
, cfg
, profData
, headToRegion
, regionToTransIds
);
398 if (debug
&& Trace::moduleEnabled(HPHP::Trace::pgo
, 5)) {
399 FTRACE(5, "\n--------------------------------------------\n"
400 "regionizeFunc({}): computed regions:\n", funcId
);
401 for (auto region
: regions
) {
402 FTRACE(5, "{}\n\n", show(*region
));