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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/jit/region-selection.h"
19 #include "hphp/runtime/vm/jit/inlining-decider.h"
20 #include "hphp/runtime/vm/jit/irgen-bespoke.h"
21 #include "hphp/runtime/vm/jit/irgen-exit.h"
22 #include "hphp/runtime/vm/jit/location.h"
23 #include "hphp/runtime/vm/jit/normalized-instruction.h"
24 #include "hphp/runtime/vm/jit/print.h"
25 #include "hphp/runtime/vm/jit/punt.h"
26 #include "hphp/runtime/vm/jit/timer.h"
27 #include "hphp/runtime/vm/jit/translator.h"
28 #include "hphp/runtime/vm/jit/analysis.h"
30 #include "hphp/util/configs/jit.h"
31 #include "hphp/util/trace.h"
33 #include <folly/MapUtil.h>
38 #include "hphp/runtime/vm/jit/irgen.h"
40 // TODO(#5710324): it seems a little odd that region-tracelet is not part of
41 // irgen:: but needs access to this. Probably we don't have the right
42 // abstraction boundaries. We'll resolve this somehow later.
43 #include "hphp/runtime/vm/jit/irgen-internal.h"
45 namespace HPHP
{ namespace jit
{
47 TRACE_SET_MOD(region
);
49 using InterpSet
= hphp_hash_set
<SrcKey
, SrcKey::Hasher
>;
53 ///////////////////////////////////////////////////////////////////////////////
55 constexpr int MaxJmpsTracedThrough
= 5;
58 Env(const RegionContext
& ctx
,
66 , region(std::make_shared
<RegionDesc
>())
67 , curBlock(region
->addBlock(sk
, 0, ctx
.spOffset
))
69 // TODO(#5703534): this is using a different TransContext than actual
70 // translation will use.
78 , std::make_unique
<AnnotationData
>())
79 , irgs(unit
, nullptr, 0, nullptr)
81 , numBCInstrs(maxBCInstrs
)
82 , profiling(kind
== TransKind::Profile
)
85 irgen::defineFrameAndStack(irgs
, ctx
.spOffset
);
86 irgs
.formingRegion
= true;
87 irgs
.irb
->enableConstrainGuards();
90 const RegionContext
& ctx
;
93 NormalizedInstruction inst
;
95 RegionDesc::Block
* curBlock
;
96 jit::hash_map
<Offset
, jit::vector
<RegionDesc::Block
*>> prevBlocks
;
101 // This map memoizes reachability of IR blocks during tracelet
102 // formation. A block won't have it's reachability stored in this
103 // map until it's been computed.
104 jit::hash_map
<unsigned,bool> irReachableBlocks
;
106 const bool profiling
;
110 Env(const Env
&) = delete;
111 Env
& operator=(const Env
&) = delete;
114 const Func
* curFunc(const Env
& env
) {
115 return irgen::curFunc(env
.irgs
);
118 const Unit
* curUnit(const Env
& env
) {
119 return irgen::curUnit(env
.irgs
);
122 SBInvOffset
curSpOffset(const Env
& env
) {
123 return env
.irgs
.irb
->fs().bcSPOff();
127 * Check if the input has a known datatype or whether we need
130 bool needGuardForInput(Env
& env
, const InputInfo
& input
) {
131 if (input
.dontGuard
) return false;
132 auto const type
= irgen::provenType(env
.irgs
, input
.loc
);
134 if (type
.isKnownDataType()) return false;
136 FTRACE(1, "selectTracelet: {} input {}, needs a guard due to unknown type {}\n",
137 env
.inst
.toString(), show(input
.loc
), type
.toString());
142 * Add the current instruction to the region.
144 void addInstruction(Env
& env
) {
145 if (!env
.sk
.funcEntry()) {
146 auto prevBlocksIt
= env
.prevBlocks
.find(env
.sk
.offset());
147 if (prevBlocksIt
!= env
.prevBlocks
.end()) {
148 FTRACE(2, "selectTracelet adding new block at {} after:\n{}\n",
149 showShort(env
.sk
), show(*env
.curBlock
));
150 always_assert(env
.sk
.func() == curFunc(env
));
151 env
.curBlock
= env
.region
->addBlock(env
.sk
, 0, curSpOffset(env
));
152 for (auto block
: prevBlocksIt
->second
) {
153 env
.region
->addArc(block
->id(), env
.curBlock
->id());
158 FTRACE(2, "selectTracelet adding instruction {}\n", env
.inst
.toString());
159 env
.curBlock
->addInstruction();
163 bool instructionEndsRegion(const Env
& env
) {
164 auto const& inst
= env
.inst
;
165 if (opcodeBreaksBB(inst
.op(), env
.inlining
)) return true;
166 if (env
.profiling
&& instrBreaksProfileBB(inst
)) return true;
167 if (dontGuardAnyInputs(inst
) && opcodeChangesPC(inst
.op())) return true;
171 Type
getLiveType(const jit::vector
<RegionContext::LiveType
>& liveTypes
,
172 const Location
& loc
) {
173 for (auto const& lt
: liveTypes
) {
174 if (lt
.location
== loc
) return lt
.type
;
180 * Populate most fields of the NormalizedInstruction, assuming its sk
181 * has already been set. Returns false iff the region should be
182 * truncated before inst's SrcKey.
184 bool prepareInstruction(Env
& env
) {
185 env
.inst
.~NormalizedInstruction();
186 new (&env
.inst
) NormalizedInstruction(env
.sk
, curUnit(env
));
187 irgen::prepareForNextHHBC(env
.irgs
, env
.sk
);
189 if (env
.sk
.funcEntry()) {
194 auto inputInfos
= getInputs(env
.inst
, env
.irgs
.irb
->fs().bcSPOff());
195 for (auto const loc
: irgen::guardsForBespoke(env
.irgs
, env
.sk
)) {
196 FTRACE(1, "prepareInstruction: adding bespoke guard: {}\n", show(loc
));
197 inputInfos
.emplace_back(loc
);
200 auto const op
= env
.inst
.op();
201 auto& fs
= env
.irgs
.irb
->fs();
203 Block
* guardFailBlock
= nullptr;
204 auto addGuardIfUntracked
= [&](Location loc
) {
205 FTRACE(1, "prepareInstruction: input: {}\n", show(loc
));
206 if (!fs
.tracked(loc
) &&
207 (loc
.tag() != LTag::Local
|| !fs
.localsCleared())) {
208 auto const type
= getLiveType(env
.ctx
.liveTypes
, loc
);
209 assert_flog(type
<= TCell
, "loc = {}: type = {}", show(loc
), type
);
210 if (guardFailBlock
== nullptr) guardFailBlock
= irgen::makeExit(env
.irgs
);
211 irgen::checkType(env
.irgs
, loc
, type
, guardFailBlock
);
215 // Guard any input that hasn't been guarded yet.
216 for (auto const& input
: inputInfos
) {
217 addGuardIfUntracked(input
.loc
);
220 // Guard any output local that hasn't been guarded yet -- they'll be read to
222 auto const outputLocals
= getLocalOutputs(env
.inst
);
223 for (auto locId
: outputLocals
) {
224 addGuardIfUntracked(Location::Local
{locId
});
227 // AssertRAT* instructions are special: they refine the type of a location
228 // without taking it as an input. The location will start to be tracked by
229 // FrameState after these instructions, so we need to first guard them since
230 // the guards may provide additional type information.
231 if (op
== OpAssertRATL
) {
232 auto loc
= Location::Local
{safe_cast
<uint32_t>(env
.inst
.imm
[0].u_ILA
)};
233 addGuardIfUntracked(loc
);
235 if (op
== OpAssertRATStk
) {
236 auto const bcSPOff
= env
.irgs
.irb
->fs().bcSPOff();
237 auto const sbInvOff
=
238 BCSPRelOffset
{safe_cast
<int32_t>(env
.inst
.imm
[0].u_IVA
)}.
239 to
<SBInvOffset
>(bcSPOff
);
240 addGuardIfUntracked(Location::Stack
{sbInvOff
});
243 for (auto const& input
: inputInfos
) {
244 if (needGuardForInput(env
, input
)) {
245 FTRACE(2, "Stopping tracelet consuming {} input {}\n",
246 opcodeToName(env
.inst
.op()), show(input
.loc
));
254 auto const asyncEagerOffset
= env
.inst
.imm
[0].u_FCA
.asyncEagerOffset
;
255 if (asyncEagerOffset
!= kInvalidOffset
) {
256 // Note that the arc between the block containing asyncEagerOffset and
257 // the previous block is not added to the region on purpose, as it comes
258 // from the slow path (await of a finished Awaitable after failed async
259 // eager return, which usually produces unfinished Awaitable) with
260 // possibly unknown type pessimizing next execution.
261 auto const sk
= env
.sk
;
262 env
.prevBlocks
[sk
.advanced().offset()].push_back(env
.curBlock
);
263 env
.prevBlocks
[sk
.offset() + asyncEagerOffset
].push_back(env
.curBlock
);
270 bool traceThroughJmp(Env
& env
) {
271 // Func entry is not a jmp.
272 if (env
.sk
.funcEntry()) return false;
274 // We only trace through unconditional jumps and conditional jumps with const
275 // inputs while inlining.
276 if (!(isUnconditionalJmp(env
.inst
.op()) || isInterceptableJmp(env
.inst
.op())) &&
277 !(env
.inlining
&& isConditionalJmp(env
.inst
.op()) &&
278 irgen::publicTopType(env
.irgs
, BCSPRelOffset
{0}).hasConstVal())) {
282 // We want to keep profiling translations to basic blocks, inlining shouldn't
283 // happen in profiling translations
285 assertx(!env
.inlining
);
289 // Don't trace through too many jumps, unless we're inlining. We want to make
290 // sure we don't break a tracelet in the middle of an inlined call; if the
291 // inlined callee becomes too big that's caught in shouldIRInline.
292 if (env
.numJmps
== MaxJmpsTracedThrough
&& !env
.inlining
) {
296 auto offset
= env
.inst
.imm
[0].u_BA
;
297 // Only trace through backwards jumps if it's an Enter and we're
298 // inlining. This is to get DV funclets.
299 if (offset
<= 0 && (env
.inst
.op() != OpEnter
|| !env
.inlining
)) {
303 // Ok we're good. For unconditional jumps, just set env.sk to the dest. For
304 // known conditional jumps we have to consume the const value on the top of
305 // the stack and figure out which branch to go to.
306 if (isUnconditionalJmp(env
.inst
.op()) || isInterceptableJmp(env
.inst
.op())) {
307 env
.sk
.setOffset(env
.sk
.offset() + offset
);
309 auto value
= irgen::popC(env
.irgs
);
311 value
->variantVal().toBoolean() == (env
.inst
.op() == OpJmpNZ
);
312 FTRACE(2, "Tracing through {}taken Jmp(N)Z on constant {}\n",
313 taken
? "" : "not ", *value
->inst());
315 env
.sk
.setOffset(taken
? env
.sk
.offset() + offset
316 : env
.sk
.advanced().offset());
320 env
.prevBlocks
[env
.sk
.offset()].push_back(env
.curBlock
);
324 bool isLiteral(Op op
) {
343 bool isThisSelfOrParent(Op op
) {
356 * For every instruction in trace representing a tracelet guard, call func with
357 * its location and type.
360 void visitGuards(IRUnit
& unit
, F func
) {
361 auto blocks
= rpoSortCfg(unit
);
363 for (auto const block
: blocks
) {
364 for (auto const& inst
: *block
) {
370 Location::Local
{inst
.extra
<LocalId
>()->locId
},
374 auto const irSPRel
= inst
.extra
<IRSPRelOffsetData
>()->offset
;
376 auto const defSP
= inst
.src(0)->inst();
377 assertx(defSP
->is(DefFrameRelSP
, DefRegSP
));
378 auto const irSPOff
= defSP
->extra
<DefStackData
>()->irSPOff
;
381 Location::Stack
{irSPRel
.to
<SBInvOffset
>(irSPOff
)},
386 func(&inst
, Location::MBase
{}, inst
.typeParam());
395 * Records any type predictions we depend on in the region.
397 void recordDependencies(Env
& env
) {
398 // Relax guards and record the ones that survived.
399 auto& firstBlock
= *env
.region
->blocks().front();
400 auto& unit
= env
.irgs
.unit
;
401 auto guardMap
= std::map
<Location
,Type
>{};
402 ITRACE(2, "Visiting guards\n");
403 auto catMap
= std::map
<Location
,DataTypeCategory
>{};
404 const auto& guards
= env
.irgs
.irb
->guards()->guards
;
405 visitGuards(unit
, [&] (const IRInstruction
* guard
,
408 Trace::Indent indent
;
409 assertx(type
<= TCell
);
410 auto const gc
= folly::get_default(guards
, guard
);
412 if (DataTypeGeneric
< gc
.category
&& gc
.category
< DataTypeSpecific
) {
413 gcToRelax
= DataTypeSpecific
;
415 auto const relaxedType
= relaxToConstraint(type
, gcToRelax
);
416 ITRACE(3, "{}: {} -> {} {}\n",
417 show(loc
), type
, relaxedType
, gcToRelax
.toString());
419 auto inret
= guardMap
.insert(std::make_pair(loc
, relaxedType
));
421 catMap
[loc
] = gc
.category
;
424 inret
.first
->second
&= relaxedType
;
425 auto& oldCat
= catMap
[loc
];
426 oldCat
= std::max(oldCat
, gc
.category
);
429 for (auto& kv
: guardMap
) {
430 if (kv
.second
== TCell
) {
431 // Guard was relaxed to Cell---don't record it.
434 auto const preCond
= RegionDesc::GuardedLocation
{
438 ITRACE(1, "selectTracelet adding guard {}\n", show(preCond
));
439 firstBlock
.addPreCondition(preCond
);
443 void truncateLiterals(Env
& env
) {
444 if (!env
.region
|| env
.region
->empty() ||
445 env
.region
->blocks().back()->empty()) return;
447 // Don't finish a region with literal values or values that have a class
448 // related to the current context class. They produce valuable information
449 // for optimizations that's lost across region boundaries.
450 auto& lastBlock
= *env
.region
->blocks().back();
451 auto sk
= lastBlock
.start();
453 auto func
= lastBlock
.func();
454 for (int i
= 0, len
= lastBlock
.length(); i
< len
; ++i
, sk
.advance(func
)) {
455 if (!sk
.funcEntry()) {
456 auto const op
= sk
.op();
457 if (isLiteral(op
) || isThisSelfOrParent(op
) || isTypeAssert(op
)) continue;
460 if (i
== len
- 1) return;
464 // Don't truncate if we've decided we want to truncate the entire block.
465 // That'll mean we'll chop off the trailing N-1 opcodes, then in the next
466 // region we'll select N-1 opcodes and chop off N-2 opcodes, and so forth...
467 if (endSk
!= lastBlock
.start()) {
468 FTRACE(1, "selectTracelet truncating block after offset {}:\n{}\n",
469 endSk
.offset(), show(lastBlock
));
470 lastBlock
.truncateAfter(endSk
);
474 RegionDescPtr
form_region(Env
& env
) {
475 SCOPE_ASSERT_DETAIL("Tracelet Selector") {
476 return folly::sformat("Region:\n{}\n\nUnit:\n{}\n",
477 *env
.region
, show(env
.irgs
.irb
->unit()));
481 env
.ctx
.liveTypes
.size() <= Cfg::Jit::TraceletEagerGuardsLimit
;
483 Block
* guardFailBlock
= nullptr;
484 for (auto const& lt
: env
.ctx
.liveTypes
) {
485 // Local and stack slots are lazily guarded when there are too many live
486 // locations; but MBase is always eagerly guarded.
487 if (eager
|| lt
.location
.tag() == LTag::MBase
) {
490 if (guardFailBlock
== nullptr) guardFailBlock
= irgen::makeExit(env
.irgs
);
491 irgen::checkType(env
.irgs
, lt
.location
, t
, guardFailBlock
);
495 // EndGuards is used to mark the end of the guards, allowing visitGuards to
496 // avoid scanning through the entire unit. We only insert EndGuards if all
497 // guards were eagerly inserted because, with lazy guarding, the guards will
499 if (eager
) irgen::gen(env
.irgs
, EndGuards
);
501 for (bool firstInst
= true; true; firstInst
= false) {
502 assertx(env
.numBCInstrs
>= 0);
503 if (env
.numBCInstrs
== 0) {
504 FTRACE(1, "selectTracelet: breaking region due to size limit\n");
508 // Break translation if there's already a translation starting at the
511 auto const sr
= tc::findSrcRec(env
.sk
);
512 if (sr
!= nullptr && sr
->getTopTranslation() != nullptr) {
513 FTRACE(1, "selectTracelet: breaking region at TC entry: {}\n",
519 if (!prepareInstruction(env
)) break;
520 if (traceThroughJmp(env
)) continue;
521 env
.inst
.interp
= env
.interp
.count(env
.sk
);
524 translateInstr(env
.irgs
, env
.inst
);
525 } catch (const FailedIRGen
& exn
) {
526 FTRACE(1, "ir generation for {} failed with {}\n",
527 env
.inst
.toString(), exn
.what());
529 !env
.interp
.count(env
.sk
),
530 "Double PUNT trying to translate {}\n", env
.inst
532 env
.interp
.insert(env
.sk
);
537 irgen::finishHHBC(env
.irgs
);
539 if (!env
.sk
.funcEntry() && !instrAllowsFallThru(env
.inst
.op())) {
540 FTRACE(1, "selectTracelet: tracelet broken after instruction with no "
541 "fall-through {}\n", env
.inst
);
545 // We successfully translated the instruction, so update env.sk.
546 assertx(env
.sk
.func() == curFunc(env
));
547 env
.sk
.advance(env
.curBlock
->func());
549 if (env
.inst
.source
.funcEntry()) {
550 auto const func
= curFunc(env
);
551 if (env
.inst
.source
.trivialDVFuncEntry() ||
552 func
->hasNonTrivialDVFuncEntry()) {
553 FTRACE(1, "selectTracelet: tracelet broken after func entry\n");
559 if (instructionEndsRegion(env
)) {
560 FTRACE(1, "selectTracelet: tracelet broken after {}\n", env
.inst
);
562 } else if (isIteratorOp(env
.sk
.op())) {
563 FTRACE(1, "selectTracelet: tracelet broken before iterator op\n");
567 if (env
.irgs
.irb
->inUnreachableState()) {
568 FTRACE(1, "selectTracelet: tracelet ending at unreachable state\n");
572 const auto numGuards
= env
.irgs
.irb
->numGuards();
573 if (numGuards
>= Cfg::Jit::TraceletGuardsLimit
) {
574 FTRACE(1, "selectTracelet: tracelet broken due to too many guards ({})\n",
580 if (env
.region
&& !env
.region
->empty()) {
581 // Make sure we end the region before trying to print the IRUnit.
582 irgen::endRegion(env
.irgs
, env
.sk
);
585 kTraceletLevel
, env
.irgs
.irb
->unit(),
586 env
.inlining
? " after inlining tracelet formation "
587 : " after tracelet formation ",
589 env
.irgs
.irb
->guards()
592 recordDependencies(env
);
594 auto const truncate
= [&] () -> bool {
595 // Make sure that the IR unit contains a main exit corresponding
596 // to the last bytecode instruction in the region. Note that this
597 // check has to happen before the call to truncateLiterals()
598 // because that updates the region but not the IR unit.
599 if (env
.region
->blocks().back()->empty()) return true;
600 auto lastSk
= env
.region
->lastSrcKey();
601 auto const mainExits
= findMainExitBlocks(env
.irgs
.irb
->unit(), lastSk
);
603 * If the last instruction is an Unreachable, its probably due to
604 * unreachable code. We don't want to truncate the tracelet in that case,
605 * because we could lose the assertion (eg if the Unreachable is due to a
608 for (auto& me
: mainExits
) {
609 if (me
->back().is(Unreachable
)) return false;
615 truncateLiterals(env
);
619 return std::move(env
.region
);
622 ///////////////////////////////////////////////////////////////////////////////
625 RegionDescPtr
selectTracelet(const RegionContext
& ctx
, TransKind kind
,
626 int32_t maxBCInstrs
, bool inlining
/* = false */) {
627 Timer
_t(Timer::selectTracelet
, nullptr);
629 RegionDescPtr region
;
632 FTRACE(1, "selectTracelet: starting with maxBCInstrs = {}\n", maxBCInstrs
);
637 return tracing::Props
{}
638 .add("sk", show(ctx
.sk
))
639 .add("trans_kind", show(kind
))
640 .add("inlining", inlining
)
641 .add("max_bc_instrs", maxBCInstrs
);
645 if (ctx
.liveTypes
.size() > Cfg::Jit::TraceletLiveLocsLimit
) {
650 Env env
{ctx
, kind
, interp
, maxBCInstrs
, inlining
};
651 region
= form_region(env
);
655 if (region
->empty() || region
->blocks().front()->length() == 0) {
656 tracing::addPoint("select-tracelet-giving-up");
657 FTRACE(1, "selectTracelet giving up after {} tries\n", tries
);
661 if (region
->blocks().back()->length() == 0) {
662 // If the final block is empty because it would've only contained
663 // instructions producing literal values, kill it.
664 region
->deleteBlock(region
->blocks().back()->id());
667 tracing::annotateBlock(
669 return tracing::Props
{}
670 .add("region_size", region
->instrSize())
671 .add("tries", tries
);
675 FTRACE(1, "selectTracelet returning, {}, {} tries:\n{}\n",
676 inlining
? "inlining" : "not inlining", tries
, show(*region
));