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/annotation.h"
20 #include "hphp/runtime/vm/jit/inlining-decider.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/ref-deps.h"
27 #include "hphp/runtime/vm/jit/timer.h"
28 #include "hphp/runtime/vm/jit/translator.h"
29 #include "hphp/runtime/vm/jit/analysis.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 typedef hphp_hash_set
<SrcKey
, SrcKey::Hasher
> InterpSet
;
53 ///////////////////////////////////////////////////////////////////////////////
55 constexpr int MaxJmpsTracedThrough
= 5;
58 Env(const RegionContext
& ctx
,
67 , sk
{ctx
.func
, ctx
.bcOffset
, ctx
.resumeMode
, ctx
.hasThis
}
69 , region(std::make_shared
<RegionDesc
>())
70 , curBlock(region
->addBlock(sk
, 0, ctx
.spOffset
))
71 , blockFinished(false)
72 // TODO(#5703534): this is using a different TransContext than actual
73 // translation will use.
74 , unit(TransContext
{kInvalidTransID
, kind
, TransFlags
{}, sk
, ctx
.spOffset
})
78 , numBCInstrs(maxBCInstrs
)
79 , profiling(kind
== TransKind::Profile
)
82 if (RuntimeOption::EvalRegionRelaxGuards
) {
83 irgs
.irb
->enableConstrainGuards();
87 const RegionContext
& ctx
;
92 NormalizedInstruction inst
;
94 RegionDesc::Block
* curBlock
;
98 jit::vector
<ActRecState
> arStates
;
102 // This map memoizes reachability of IR blocks during tracelet
103 // formation. A block won't have it's reachability stored in this
104 // map until it's been computed.
105 jit::hash_map
<unsigned,bool> irReachableBlocks
;
107 const bool profiling
;
111 Env(const Env
&) = delete;
112 Env
& operator=(const Env
&) = delete;
115 const Func
* curFunc(const Env
& env
) {
116 return irgen::curFunc(env
.irgs
);
119 const Unit
* curUnit(const Env
& env
) {
120 return irgen::curUnit(env
.irgs
);
123 FPInvOffset
curSpOffset(const Env
& env
) {
124 return env
.irgs
.irb
->fs().bcSPOff();
127 bool irBlockReachable(Env
& env
, Block
* block
) {
128 auto const blockId
= block
->id();
129 auto it
= env
.irReachableBlocks
.find(blockId
);
130 if (it
!= env
.irReachableBlocks
.end()) return it
->second
;
131 bool result
= block
== env
.irgs
.irb
->unit().entry();
132 for (auto& pred
: block
->preds()) {
133 if (irBlockReachable(env
, pred
.from())) {
138 env
.irReachableBlocks
[blockId
] = result
;
143 * Check if the current predicted type for the location in ii is specific
144 * enough for what the current opcode wants. If not, return false.
146 bool consumeInput(Env
& env
, const InputInfo
& input
) {
147 if (input
.dontGuard
) return true;
148 auto const type
= irgen::predictedType(env
.irgs
, input
.loc
);
150 if (/* env.profiling &&
152 * This check is only intended for profiling translations. We enabled it
153 * for live translations to avoid a bug tracking type dependences for
155 type
<= TBoxedCell
&&
156 (env
.region
->blocks().size() > 1 || !env
.region
->entry()->empty())) {
157 // We don't want side exits when profiling, so only allow instructions that
158 // consume refs at the beginning of the region.
162 if (!input
.dontBreak
&& !type
.isKnownDataType()) {
163 // Trying to consume a value without a precise enough type.
164 FTRACE(1, "selectTracelet: {} tried to consume {}, type {}\n",
165 env
.inst
.toString(), show(input
.loc
), type
.toString());
169 if (!(type
<= TBoxedCell
) ||
170 env
.inst
.ignoreInnerType
||
171 input
.dontGuardInner
) {
175 if (!type
.inner().isKnownDataType()) {
176 // Trying to consume a boxed value without a guess for the inner type.
177 FTRACE(1, "selectTracelet: {} tried to consume ref {}\n",
178 env
.inst
.toString(), show(input
.loc
));
186 * Add the current instruction to the region.
188 void addInstruction(Env
& env
) {
189 if (env
.blockFinished
) {
190 FTRACE(2, "selectTracelet adding new block at {} after:\n{}\n",
191 showShort(env
.sk
), show(*env
.curBlock
));
192 always_assert(env
.sk
.func() == curFunc(env
));
193 auto newCurBlock
= env
.region
->addBlock(env
.sk
, 0, curSpOffset(env
));
194 env
.region
->addArc(env
.curBlock
->id(), newCurBlock
->id());
195 env
.curBlock
= newCurBlock
;
196 env
.blockFinished
= false;
199 FTRACE(2, "selectTracelet adding instruction {}\n", env
.inst
.toString());
200 env
.curBlock
->addInstruction();
205 * Populate most fields of the NormalizedInstruction, assuming its sk
206 * has already been set. Returns false iff the region should be
207 * truncated before inst's SrcKey.
209 bool prepareInstruction(Env
& env
) {
210 env
.inst
.~NormalizedInstruction();
211 new (&env
.inst
) NormalizedInstruction(env
.sk
, curUnit(env
));
212 if (RuntimeOption::EvalFailJitPrologs
&& env
.inst
.op() == Op::FCallAwait
) {
215 auto const breaksBB
=
216 (env
.profiling
&& instrBreaksProfileBB(&env
.inst
)) ||
217 opcodeBreaksBB(env
.inst
.op());
218 env
.inst
.endsRegion
= breaksBB
||
219 (dontGuardAnyInputs(env
.inst
) && opcodeChangesPC(env
.inst
.op()));
220 env
.inst
.funcd
= env
.arStates
.back().knownFunc();
221 irgen::prepareForNextHHBC(env
.irgs
, &env
.inst
, env
.sk
, false);
223 auto const inputInfos
= getInputs(env
.inst
, env
.irgs
.irb
->fs().bcSPOff());
225 // This reads valueClass from the inputs so it used to need to
226 // happen after readMetaData. But now readMetaData is gone ...
229 // Check all the inputs for unknown values.
230 for (auto const& input
: inputInfos
) {
231 if (!consumeInput(env
, input
)) {
232 FTRACE(2, "Stopping tracelet consuming {} input {}\n",
233 opcodeToName(env
.inst
.op()), show(input
.loc
));
238 if (inputInfos
.needsRefCheck
) {
239 // Reffiness guards are always at the beginning of the trace for now, so
240 // calculate the delta from the original sp to the ar. The FPI delta from
241 // instrFpToArDelta includes locals and iterators, so when we're in a
242 // resumed context we have to adjust for the fact that they're in a
244 auto argNum
= env
.inst
.imm
[0].u_IVA
;
245 auto entryArDelta
= env
.ctx
.spOffset
.offset
-
246 instrFpToArDelta(curFunc(env
), env
.inst
.pc());
247 if (env
.sk
.resumeMode() != ResumeMode::None
) {
248 entryArDelta
+= curFunc(env
)->numSlotsInFrame();
252 env
.inst
.preppedByRef
=
253 env
.arStates
.back().checkByRef(argNum
, entryArDelta
, &env
.refDeps
,
255 } catch (const UnknownInputExc
& exn
) {
256 // We don't have a guess for the current ActRec.
257 FTRACE(1, "selectTracelet: don't have reffiness guess for {}\n",
258 env
.inst
.toString());
262 env
.curBlock
->setParamByRef(env
.inst
.source
, env
.inst
.preppedByRef
);
267 if (isFPush(env
.inst
.op())) env
.arStates
.back().pushFunc(env
.inst
);
272 bool traceThroughJmp(Env
& env
) {
273 // We only trace through unconditional jumps and conditional jumps with const
274 // inputs while inlining.
275 if (!isUnconditionalJmp(env
.inst
.op()) &&
276 !(env
.inlining
&& isConditionalJmp(env
.inst
.op()) &&
277 irgen::publicTopType(env
.irgs
, BCSPRelOffset
{0}).hasConstVal())) {
281 // We want to keep profiling translations to basic blocks, inlining shouldn't
282 // happen in profiling translations
284 assertx(!env
.inlining
);
288 // Don't trace through too many jumps, unless we're inlining. We want to make
289 // sure we don't break a tracelet in the middle of an inlined call; if the
290 // inlined callee becomes too big that's caught in shouldIRInline.
291 if (env
.numJmps
== MaxJmpsTracedThrough
&& !env
.inlining
) {
295 auto offset
= env
.inst
.imm
[0].u_BA
;
296 // Only trace through backwards jumps if it's a JmpNS and we're
297 // inlining. This is to get DV funclets.
298 if (offset
<= 0 && (env
.inst
.op() != OpJmpNS
|| !env
.inlining
)) {
302 // Ok we're good. For unconditional jumps, just set env.sk to the dest. For
303 // known conditional jumps we have to consume the const value on the top of
304 // the stack and figure out which branch to go to.
305 if (isUnconditionalJmp(env
.inst
.op())) {
306 env
.sk
.setOffset(env
.sk
.offset() + offset
);
308 auto value
= irgen::popC(env
.irgs
);
310 value
->variantVal().toBoolean() == (env
.inst
.op() == OpJmpNZ
);
311 FTRACE(2, "Tracing through {}taken Jmp(N)Z on constant {}\n",
312 taken
? "" : "not ", *value
->inst());
314 env
.sk
.setOffset(taken
? env
.sk
.offset() + offset
315 : env
.sk
.advanced().offset());
319 env
.blockFinished
= true;
323 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, and whether or not it's an inner hint.
360 void visitGuards(IRUnit
& unit
, F func
) {
361 auto blocks
= rpoSortCfg(unit
);
363 for (auto const block
: blocks
) {
364 for (auto const& inst
: *block
) {
371 Location::Local
{inst
.extra
<LocalId
>()->locId
},
373 inst
.is(HintLocInner
));
377 auto const irSPRel
= inst
.extra
<IRSPRelOffsetData
>()->offset
;
379 auto const defSP
= inst
.src(0)->inst();
380 assertx(defSP
->is(DefSP
));
381 auto const irSPOff
= defSP
->extra
<DefSP
>()->offset
;
384 Location::Stack
{irSPRel
.to
<FPInvOffset
>(irSPOff
)},
386 inst
.is(HintStkInner
));
391 func(&inst
, Location::MBase
{}, inst
.typeParam(),
392 inst
.is(HintMBaseInner
));
401 * Records any type/reffiness predictions we depend on in the region.
403 void recordDependencies(Env
& env
) {
404 // Record the incrementally constructed reffiness predictions.
405 assertx(!env
.region
->empty());
406 auto& frontBlock
= *env
.region
->blocks().front();
407 for (auto const& dep
: env
.refDeps
.m_arMap
) {
408 frontBlock
.addReffinessPred({dep
.second
.m_mask
, dep
.second
.m_vals
,
412 // Relax guards and record the ones that survived.
413 auto& firstBlock
= *env
.region
->blocks().front();
414 auto& unit
= env
.irgs
.unit
;
415 auto guardMap
= std::map
<Location
,Type
>{};
416 ITRACE(2, "Visiting guards\n");
417 auto hintMap
= std::map
<Location
,Type
>{};
418 auto catMap
= std::map
<Location
,DataTypeCategory
>{};
419 const auto& guards
= env
.irgs
.irb
->guards()->guards
;
420 auto predictionMap
= std::map
<Location
,Type
>{};
421 visitGuards(unit
, [&] (const IRInstruction
* guard
,
423 Type type
, bool hint
) {
424 Trace::Indent indent
;
425 ITRACE(3, "{}: {}\n", show(loc
), type
);
426 assertx(type
<= TGen
);
427 auto& whichMap
= hint
? hintMap
: guardMap
;
428 auto inret
= whichMap
.insert(std::make_pair(loc
, type
));
429 // Unconstrained pseudo-main guards will be relaxed to Gen by the guard
430 // relaxation pass. Since we don't allow loading TGen locals
431 // in pseudo-main, save the predicted type here.
432 if (guard
->marker().func()->isPseudoMain()) {
433 auto ret
= predictionMap
.insert(std::make_pair(loc
,type
));
435 FTRACE(1, "selectTracelet saving prediction for PseudoMain {}\n",
436 show(RegionDesc::TypedLocation
{loc
, type
}));
438 auto& oldTy
= ret
.first
->second
;
444 catMap
[loc
] = folly::get_default(guards
, guard
).category
;
448 auto& oldTy
= inret
.first
->second
;
451 auto& oldCat
= catMap
[loc
];
452 auto newCat
= folly::get_default(guards
, guard
).category
;
453 oldCat
= std::max(oldCat
, newCat
);
457 for (auto& kv
: guardMap
) {
458 auto const hint_it
= hintMap
.find(kv
.first
);
459 // If we have a hinted type that's better than the guarded type, we want to
460 // keep it around. This can really only when a guard is relaxed away to
461 // Gen because we knew something was a BoxedCell statically, but we may
462 // need to keep information about what inner type we were predicting.
463 if (hint_it
!= end(hintMap
) && hint_it
->second
< kv
.second
) {
464 FTRACE(1, "selectTracelet adding prediction {}\n",
465 show(RegionDesc::TypedLocation
{hint_it
->first
, hint_it
->second
}));
466 predictionMap
.insert(*hint_it
);
468 if (kv
.second
== TGen
) {
469 // Guard was relaxed to Gen---don't record it. But if there's a hint, we
470 // may have needed that (recorded already above).
473 auto const preCond
= RegionDesc::GuardedLocation
{
477 ITRACE(1, "selectTracelet adding guard {}\n", show(preCond
));
478 firstBlock
.addPreCondition(preCond
);
481 // Predictions are already sorted by location, so we can simply compare
482 // the type-prediction vectors for different blocks later.
483 for (auto& pred
: predictionMap
) {
484 firstBlock
.addPredicted(RegionDesc::TypedLocation
{pred
.first
, pred
.second
});
488 void truncateLiterals(Env
& env
) {
489 if (!env
.region
|| env
.region
->empty() ||
490 env
.region
->blocks().back()->empty()) return;
492 // Don't finish a region with literal values or values that have a class
493 // related to the current context class. They produce valuable information
494 // for optimizations that's lost across region boundaries.
495 auto& lastBlock
= *env
.region
->blocks().back();
496 auto sk
= lastBlock
.start();
498 auto unit
= lastBlock
.unit();
499 for (int i
= 0, len
= lastBlock
.length(); i
< len
; ++i
, sk
.advance(unit
)) {
500 auto const op
= sk
.op();
501 if (!isLiteral(op
) && !isThisSelfOrParent(op
) && !isTypeAssert(op
)) {
502 if (i
== len
- 1) return;
506 // Don't truncate if we've decided we want to truncate the entire block.
507 // That'll mean we'll chop off the trailing N-1 opcodes, then in the next
508 // region we'll select N-1 opcodes and chop off N-2 opcodes, and so forth...
509 if (endSk
!= lastBlock
.start()) {
510 FTRACE(1, "selectTracelet truncating block after offset {}:\n{}\n",
511 endSk
.offset(), show(lastBlock
));
512 lastBlock
.truncateAfter(endSk
);
516 RegionDescPtr
form_region(Env
& env
) {
517 SCOPE_ASSERT_DETAIL("Tracelet Selector") {
518 return folly::sformat("Region:\n{}\n\nUnit:\n{}\n",
519 *env
.region
, show(env
.irgs
.irb
->unit()));
522 env
.irgs
.irb
->setGuardFailBlock(irgen::makeExit(env
.irgs
));
524 for (auto const& lt
: env
.ctx
.liveTypes
) {
527 irgen::checkType(env
.irgs
, lt
.location
, t
, env
.ctx
.bcOffset
,
528 true /* outerOnly */);
530 env
.irgs
.irb
->resetGuardFailBlock();
532 irgen::gen(env
.irgs
, EndGuards
);
534 for (bool firstInst
= true; true; firstInst
= false) {
535 assertx(env
.numBCInstrs
>= 0);
536 if (env
.numBCInstrs
== 0) {
537 FTRACE(1, "selectTracelet: breaking region due to size limit\n");
541 if (!firstInst
&& env
.sk
== env
.breakAt
) {
542 FTRACE(1, "selectTracelet: breaking region at breakAt: {}\n",
547 // Break translation if there's already a translation starting at the
550 auto const sr
= tc::findSrcRec(env
.sk
);
551 if (sr
!= nullptr && sr
->getTopTranslation() != nullptr) {
552 FTRACE(1, "selectTracelet: breaking region at TC entry: {}\n",
558 if (!prepareInstruction(env
)) break;
560 env
.curBlock
->setKnownFunc(env
.sk
, env
.inst
.funcd
);
562 if (traceThroughJmp(env
)) continue;
564 env
.inst
.interp
= env
.interp
.count(env
.sk
);
567 translateInstr(env
.irgs
, env
.inst
, true /* checkOuterTypeOnly */,
569 } catch (const FailedIRGen
& exn
) {
570 FTRACE(1, "ir generation for {} failed with {}\n",
571 env
.inst
.toString(), exn
.what());
573 !env
.interp
.count(env
.sk
),
574 "Double PUNT trying to translate {}\n", env
.inst
576 env
.interp
.insert(env
.sk
);
581 irgen::finishHHBC(env
.irgs
);
583 if (!instrAllowsFallThru(env
.inst
.op())) {
584 FTRACE(1, "selectTracelet: tracelet broken after instruction with no "
585 "fall-through {}\n", env
.inst
);
589 // We successfully translated the instruction, so update env.sk.
590 env
.sk
.advance(env
.curBlock
->unit());
592 auto const endsRegion
= env
.inst
.endsRegion
;
595 FTRACE(1, "selectTracelet: tracelet broken after {}\n", env
.inst
);
598 assertx(env
.sk
.func() == curFunc(env
));
601 auto const curIRBlock
= env
.irgs
.irb
->curBlock();
602 if (!irBlockReachable(env
, curIRBlock
)) {
604 "selectTracelet: tracelet broken due "
605 "to unreachable code (block {})\n",
610 if (curIRBlock
->isExitNoThrow()) {
611 FTRACE(1, "selectTracelet: tracelet broken due to exiting IR instruction:"
612 "{}\n", curIRBlock
->back());
616 const auto numGuards
= env
.irgs
.irb
->numGuards();
617 if (numGuards
>= RuntimeOption::EvalJitTraceletGuardsLimit
) {
618 FTRACE(1, "selectTracelet: tracelet broken due to too many guards ({})\n",
623 if (isFCallStar(env
.inst
.op())) env
.arStates
.back().pop();
626 if (env
.region
&& !env
.region
->empty()) {
627 // Make sure we end the region before trying to print the IRUnit.
628 irgen::endRegion(env
.irgs
, env
.sk
);
631 kTraceletLevel
, env
.irgs
.irb
->unit(),
632 env
.inlining
? " after inlining tracelet formation "
633 : " after tracelet formation ",
635 env
.irgs
.irb
->guards()
638 recordDependencies(env
);
640 auto const truncate
= [&] () -> bool {
641 // Make sure that the IR unit contains a main exit corresponding
642 // to the last bytecode instruction in the region. Note that this
643 // check has to happen before the call to truncateLiterals()
644 // because that updates the region but not the IR unit.
645 if (env
.region
->blocks().back()->empty()) return true;
646 auto lastSk
= env
.region
->lastSrcKey();
647 auto const mainExit
= findMainExitBlock(env
.irgs
.irb
->unit(), lastSk
);
648 always_assert_flog(mainExit
, "No main exits found!");
650 * If the last instruction is an Unreachable, its probably due to
651 * unreachable code. We don't want to truncate the tracelet in that case,
652 * because we could lose the assertion (eg if the Unreachable is due to a
655 return !mainExit
->back().is(Unreachable
);
659 truncateLiterals(env
);
663 return std::move(env
.region
);
666 ///////////////////////////////////////////////////////////////////////////////
669 RegionDescPtr
selectTracelet(const RegionContext
& ctx
, TransKind kind
,
670 int32_t maxBCInstrs
, bool inlining
/* = false */) {
671 Timer
_t(Timer::selectTracelet
);
674 RegionDescPtr region
;
677 FTRACE(1, "selectTracelet: starting with maxBCInstrs = {}\n", maxBCInstrs
);
680 Env env
{ctx
, kind
, interp
, breakAt
, maxBCInstrs
, inlining
};
681 region
= form_region(env
);
685 if (region
->empty() || region
->blocks().front()->length() == 0) {
686 FTRACE(1, "selectTracelet giving up after {} tries\n", tries
);
690 if (region
->blocks().back()->length() == 0) {
691 // If the final block is empty because it would've only contained
692 // instructions producing literal values, kill it.
693 region
->deleteBlock(region
->blocks().back()->id());
696 if (RuntimeOption::EvalRegionRelaxGuards
) {
697 FTRACE(1, "selectTracelet: before optimizeGuards:\n{}\n",
699 optimizeGuards(*region
, kind
== TransKind::Profile
);
702 FTRACE(1, "selectTracelet returning, {}, {} tries:\n{}\n",
703 inlining
? "inlining" : "not inlining", tries
, show(*region
));