2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2016 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/mc-generator.h"
22 #include "hphp/runtime/vm/jit/normalized-instruction.h"
23 #include "hphp/runtime/vm/jit/print.h"
24 #include "hphp/runtime/vm/jit/punt.h"
25 #include "hphp/runtime/vm/jit/ref-deps.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/trace.h"
32 #include <folly/MapUtil.h>
37 #include "hphp/runtime/vm/jit/irgen.h"
39 // TODO(#5710324): it seems a little odd that region-tracelet is not part of
40 // irgen:: but needs access to this. Probably we don't have the right
41 // abstraction boundaries. We'll resolve this somehow later.
42 #include "hphp/runtime/vm/jit/irgen-internal.h"
44 namespace HPHP
{ namespace jit
{
46 TRACE_SET_MOD(region
);
48 typedef hphp_hash_set
<SrcKey
, SrcKey::Hasher
> InterpSet
;
52 ///////////////////////////////////////////////////////////////////////////////
55 Env(const RegionContext
& ctx
,
64 , sk(ctx
.func
, ctx
.bcOffset
, ctx
.resumed
)
66 , region(std::make_shared
<RegionDesc
>())
67 , curBlock(region
->addBlock(sk
, 0, ctx
.spOffset
))
68 , blockFinished(false)
69 // TODO(#5703534): this is using a different TransContext than actual
70 // translation will use.
71 , unit(TransContext
{kInvalidTransID
, kind
, TransFlags
{}, sk
, ctx
.spOffset
})
75 , numBCInstrs(maxBCInstrs
)
76 , profiling(kind
== TransKind::Profile
)
80 const RegionContext
& ctx
;
85 NormalizedInstruction inst
;
87 RegionDesc::Block
* curBlock
;
91 jit::vector
<ActRecState
> arStates
;
96 // This map memoizes reachability of IR blocks during tracelet
97 // formation. A block won't have it's reachability stored in this
98 // map until it's been computed.
99 jit::hash_map
<unsigned,bool> irReachableBlocks
;
101 const bool profiling
;
105 Env(const Env
&) = delete;
106 Env
& operator=(const Env
&) = delete;
109 const Func
* curFunc(const Env
& env
) {
110 return irgen::curFunc(env
.irgs
);
113 const Unit
* curUnit(const Env
& env
) {
114 return irgen::curUnit(env
.irgs
);
117 FPInvOffset
curSpOffset(const Env
& env
) {
118 return env
.irgs
.irb
->fs().syncedSpLevel();
121 bool irBlockReachable(Env
& env
, Block
* block
) {
122 auto const blockId
= block
->id();
123 auto it
= env
.irReachableBlocks
.find(blockId
);
124 if (it
!= env
.irReachableBlocks
.end()) return it
->second
;
125 bool result
= block
== env
.irgs
.irb
->unit().entry();
126 for (auto& pred
: block
->preds()) {
127 if (irBlockReachable(env
, pred
.from())) {
132 env
.irReachableBlocks
[blockId
] = result
;
137 * Check if the current predicted type for the location in ii is specific
138 * enough for what the current opcode wants. If not, return false.
140 bool consumeInput(Env
& env
, int i
, const InputInfo
& ii
) {
141 if (ii
.dontGuard
) return true;
142 auto const type
= irgen::predictedType(env
.irgs
, ii
.loc
);
144 if (env
.profiling
&& type
<= TBoxedCell
&&
145 (env
.region
->blocks().size() > 1 || !env
.region
->entry()->empty())) {
146 // We don't want side exits when profiling, so only allow instructions that
147 // consume refs at the beginning of the region.
151 if (!ii
.dontBreak
&& !type
.isKnownDataType()) {
152 // Trying to consume a value without a precise enough type.
153 FTRACE(1, "selectTracelet: {} tried to consume {}\n",
154 env
.inst
.toString(), env
.inst
.inputs
[i
].pretty());
158 if (!(type
<= TBoxedCell
) || env
.inst
.ignoreInnerType
|| ii
.dontGuardInner
) {
162 if (!type
.inner().isKnownDataType()) {
163 // Trying to consume a boxed value without a guess for the inner type.
164 FTRACE(1, "selectTracelet: {} tried to consume ref {}\n",
165 env
.inst
.toString(), env
.inst
.inputs
[i
].pretty());
173 * Add the current instruction to the region.
175 void addInstruction(Env
& env
) {
176 if (env
.blockFinished
) {
177 FTRACE(2, "selectTracelet adding new block at {} after:\n{}\n",
178 showShort(env
.sk
), show(*env
.curBlock
));
179 always_assert(env
.sk
.func() == curFunc(env
));
180 auto newCurBlock
= env
.region
->addBlock(env
.sk
, 0, curSpOffset(env
));
181 env
.region
->addArc(env
.curBlock
->id(), newCurBlock
->id());
182 env
.curBlock
= newCurBlock
;
183 env
.blockFinished
= false;
186 FTRACE(2, "selectTracelet adding instruction {}\n", env
.inst
.toString());
187 env
.curBlock
->addInstruction();
192 * Populate most fields of the NormalizedInstruction, assuming its sk
193 * has already been set. Returns false iff the region should be
194 * truncated before inst's SrcKey.
196 bool prepareInstruction(Env
& env
) {
197 env
.inst
.~NormalizedInstruction();
198 new (&env
.inst
) NormalizedInstruction(env
.sk
, curUnit(env
));
199 if (RuntimeOption::EvalFailJitPrologs
&& env
.inst
.op() == Op::FCallAwait
) {
202 auto const breaksBB
=
203 (env
.profiling
&& instrBreaksProfileBB(&env
.inst
)) ||
204 opcodeBreaksBB(env
.inst
.op());
205 env
.inst
.endsRegion
= breaksBB
||
206 (dontGuardAnyInputs(env
.inst
.op()) && opcodeChangesPC(env
.inst
.op()));
207 env
.inst
.funcd
= env
.arStates
.back().knownFunc();
208 irgen::prepareForNextHHBC(env
.irgs
, &env
.inst
, env
.sk
, false);
210 auto const inputInfos
= getInputs(env
.inst
);
212 for (auto const& ii
: inputInfos
) env
.inst
.inputs
.push_back(ii
.loc
);
214 // This reads valueClass from the inputs so it used to need to
215 // happen after readMetaData. But now readMetaData is gone ...
218 // Check all the inputs for unknown values.
219 assertx(inputInfos
.size() == env
.inst
.inputs
.size());
220 for (unsigned i
= 0; i
< inputInfos
.size(); ++i
) {
221 if (!consumeInput(env
, i
, inputInfos
[i
])) {
222 FTRACE(2, "Stopping tracelet consuming {} input {}\n",
223 opcodeToName(env
.inst
.op()), i
);
228 if (inputInfos
.needsRefCheck
) {
229 // Reffiness guards are always at the beginning of the trace for now, so
230 // calculate the delta from the original sp to the ar. The FPI delta from
231 // instrFpToArDelta includes locals and iterators, so when we're in a
232 // resumed context we have to adjust for the fact that they're in a
234 auto argNum
= env
.inst
.imm
[0].u_IVA
;
235 auto entryArDelta
= env
.ctx
.spOffset
.offset
-
236 instrFpToArDelta(curFunc(env
), env
.inst
.pc());
237 if (env
.sk
.resumed()) entryArDelta
+= curFunc(env
)->numSlotsInFrame();
240 env
.inst
.preppedByRef
=
241 env
.arStates
.back().checkByRef(argNum
, entryArDelta
, &env
.refDeps
);
242 } catch (const UnknownInputExc
& exn
) {
243 // We don't have a guess for the current ActRec.
244 FTRACE(1, "selectTracelet: don't have reffiness guess for {}\n",
245 env
.inst
.toString());
249 env
.curBlock
->setParamByRef(env
.inst
.source
, env
.inst
.preppedByRef
);
254 if (isFPush(env
.inst
.op())) env
.arStates
.back().pushFunc(env
.inst
);
259 bool traceThroughJmp(Env
& env
) {
260 // We only trace through unconditional jumps and conditional jumps with const
261 // inputs while inlining.
262 if (!isUnconditionalJmp(env
.inst
.op()) &&
263 !(env
.inlining
&& isConditionalJmp(env
.inst
.op()) &&
264 irgen::publicTopType(env
.irgs
, BCSPOffset
{0}).hasConstVal())) {
268 // We want to keep profiling translations to basic blocks, inlining shouldn't
269 // happen in profiling translations
271 assert(!env
.inlining
);
275 // Don't trace through too many jumps, unless we're inlining. We want to make
276 // sure we don't break a tracelet in the middle of an inlined call; if the
277 // inlined callee becomes too big that's caught in shouldIRInline.
278 if (env
.numJmps
== Translator::MaxJmpsTracedThrough
&& !env
.inlining
) {
282 auto offset
= env
.inst
.imm
[0].u_BA
;
283 // Only trace through backwards jumps if it's a JmpNS and we're
284 // inlining. This is to get DV funclets.
285 if (offset
<= 0 && (env
.inst
.op() != OpJmpNS
|| !env
.inlining
)) {
289 // Ok we're good. For unconditional jumps, just set env.sk to the dest. For
290 // known conditional jumps we have to consume the const value on the top of
291 // the stack and figure out which branch to go to.
292 if (isUnconditionalJmp(env
.inst
.op())) {
293 env
.sk
.setOffset(env
.sk
.offset() + offset
);
295 auto value
= irgen::popC(env
.irgs
);
297 value
->variantVal().toBoolean() == (env
.inst
.op() == OpJmpNZ
);
298 FTRACE(2, "Tracing through {}taken Jmp(N)Z on constant {}\n",
299 taken
? "" : "not ", *value
->inst());
301 env
.sk
.setOffset(taken
? env
.sk
.offset() + offset
302 : env
.sk
.advanced().offset());
306 env
.blockFinished
= true;
310 bool isLiteral(Op op
) {
327 bool isThisSelfOrParent(Op op
) {
340 * For every instruction in trace representing a tracelet guard, call func with
341 * its location and type, and whether or not it's an inner hint.
344 void visitGuards(IRUnit
& unit
, F func
) {
345 using L
= RegionDesc::Location
;
346 auto blocks
= rpoSortCfg(unit
);
347 for (auto* block
: blocks
) {
348 for (auto& inst
: *block
) {
355 L::Local
{inst
.extra
<LocalId
>()->locId
},
357 inst
.is(HintLocInner
));
363 * BCSPOffset is optional but should --always-- be set for CheckStk
364 * instructions that appear within the guards for a translation.
366 auto const bcSPOff
= inst
.extra
<RelOffsetData
>()->bcSpOffset
;
367 assertx(inst
.extra
<RelOffsetData
>()->hasBcSpOffset
);
369 auto const offsetFromFP
=
370 bcSPOff
.to
<FPInvOffset
>(inst
.marker().spOff());
372 L::Stack
{offsetFromFP
},
374 inst
.is(HintStkInner
));
384 * Records any type/reffiness predictions we depend on in the region.
386 void recordDependencies(Env
& env
) {
387 // Record the incrementally constructed reffiness predictions.
388 assertx(!env
.region
->empty());
389 auto& frontBlock
= *env
.region
->blocks().front();
390 for (auto const& dep
: env
.refDeps
.m_arMap
) {
391 frontBlock
.addReffinessPred({dep
.second
.m_mask
, dep
.second
.m_vals
,
395 // Relax guards and record the ones that survived.
396 auto& firstBlock
= *env
.region
->blocks().front();
397 auto& unit
= env
.irgs
.unit
;
398 auto guardMap
= std::map
<RegionDesc::Location
,Type
>{};
399 ITRACE(2, "Visiting guards\n");
400 auto hintMap
= std::map
<RegionDesc::Location
,Type
>{};
401 auto catMap
= std::map
<RegionDesc::Location
,DataTypeCategory
>{};
402 const auto& guards
= env
.irgs
.irb
->guards()->guards
;
403 visitGuards(unit
, [&](IRInstruction
* guard
, const RegionDesc::Location
& loc
,
404 Type type
, bool hint
) {
405 Trace::Indent indent
;
406 ITRACE(3, "{}: {}\n", show(loc
), type
);
407 if (type
<= TCls
) return;
408 auto& whichMap
= hint
? hintMap
: guardMap
;
409 auto inret
= whichMap
.insert(std::make_pair(loc
, type
));
412 catMap
[loc
] = folly::get_default(guards
, guard
).category
;
416 auto& oldTy
= inret
.first
->second
;
419 auto& oldCat
= catMap
[loc
];
420 auto newCat
= folly::get_default(guards
, guard
).category
;
421 oldCat
= std::max(oldCat
, newCat
);
425 for (auto& kv
: guardMap
) {
426 auto const hint_it
= hintMap
.find(kv
.first
);
427 // If we have a hinted type that's better than the guarded type, we want to
428 // keep it around. This can really only when a guard is relaxed away to
429 // Gen because we knew something was a BoxedCell statically, but we may
430 // need to keep information about what inner type we were predicting.
431 if (hint_it
!= end(hintMap
) && hint_it
->second
< kv
.second
) {
432 auto const pred
= RegionDesc::TypedLocation
{
436 FTRACE(1, "selectTracelet adding prediction {}\n", show(pred
));
437 firstBlock
.addPredicted(pred
);
439 if (kv
.second
== TGen
) {
440 // Guard was relaxed to Gen---don't record it. But if there's a hint, we
441 // may have needed that (recorded already above).
444 auto const preCond
= RegionDesc::GuardedLocation
{ kv
.first
, kv
.second
,
446 ITRACE(1, "selectTracelet adding guard {}\n", show(preCond
));
447 firstBlock
.addPreCondition(preCond
);
451 void truncateLiterals(Env
& env
) {
452 if (!env
.region
|| env
.region
->empty() ||
453 env
.region
->blocks().back()->empty()) return;
455 // Don't finish a region with literal values or values that have a class
456 // related to the current context class. They produce valuable information
457 // for optimizations that's lost across region boundaries.
458 auto& lastBlock
= *env
.region
->blocks().back();
459 auto sk
= lastBlock
.start();
461 auto unit
= lastBlock
.unit();
462 for (int i
= 0, len
= lastBlock
.length(); i
< len
; ++i
, sk
.advance(unit
)) {
463 auto const op
= sk
.op();
464 if (!isLiteral(op
) && !isThisSelfOrParent(op
) && !isTypeAssert(op
)) {
465 if (i
== len
- 1) return;
469 // Don't truncate if we've decided we want to truncate the entire block.
470 // That'll mean we'll chop off the trailing N-1 opcodes, then in the next
471 // region we'll select N-1 opcodes and chop off N-2 opcodes, and so forth...
472 if (endSk
!= lastBlock
.start()) {
473 FTRACE(1, "selectTracelet truncating block after offset {}:\n{}\n",
474 endSk
.offset(), show(lastBlock
));
475 lastBlock
.truncateAfter(endSk
);
479 RegionDescPtr
form_region(Env
& env
) {
480 SCOPE_ASSERT_DETAIL("Tracelet Selector") {
481 return folly::sformat("Region:\n{}\n\nUnit:\n{}\n",
482 *env
.region
, show(env
.irgs
.irb
->unit()));
485 for (auto const& lt
: env
.ctx
.liveTypes
) {
488 irgen::assertTypeLocation(env
.irgs
, lt
.location
, t
);
489 env
.curBlock
->addPreCondition({lt
.location
, t
, DataTypeGeneric
});
491 irgen::checkType(env
.irgs
, lt
.location
, t
, env
.ctx
.bcOffset
,
492 true /* outerOnly */);
496 irgen::gen(env
.irgs
, EndGuards
);
498 for (bool firstInst
= true; true; firstInst
= false) {
499 assertx(env
.numBCInstrs
>= 0);
500 if (env
.numBCInstrs
== 0) {
501 FTRACE(1, "selectTracelet: breaking region due to size limit\n");
505 if (!firstInst
&& env
.sk
== env
.breakAt
) {
506 FTRACE(1, "selectTracelet: breaking region at breakAt: {}\n",
511 if (!prepareInstruction(env
)) break;
513 // Until the rest of the pipeline can handle it without regressing, try to
514 // not break tracelets in the middle of new minstr sequences. Note that
515 // this is just an optimization; we can generate correct code regardless of
516 // how the minstrs are chopped up.
517 if (!firstInst
&& isMemberBaseOp(env
.inst
.op())) {
518 env
.minstrStart
= env
.sk
;
519 } else if (isMemberFinalOp(env
.inst
.op())) {
520 env
.minstrStart
= SrcKey
{};
523 env
.curBlock
->setKnownFunc(env
.sk
, env
.inst
.funcd
);
525 if (traceThroughJmp(env
)) continue;
527 env
.inst
.interp
= env
.interp
.count(env
.sk
);
530 translateInstr(env
.irgs
, env
.inst
, true /* checkOuterTypeOnly */,
532 } catch (const FailedIRGen
& exn
) {
533 FTRACE(1, "ir generation for {} failed with {}\n",
534 env
.inst
.toString(), exn
.what());
536 !env
.interp
.count(env
.sk
),
537 "Double PUNT trying to translate {}\n", env
.inst
539 env
.interp
.insert(env
.sk
);
544 irgen::finishHHBC(env
.irgs
);
546 if (!instrAllowsFallThru(env
.inst
.op())) {
547 FTRACE(1, "selectTracelet: tracelet broken after instruction with no "
548 "fall-through {}\n", env
.inst
);
552 // We successfully translated the instruction, so update env.sk.
553 env
.sk
.advance(env
.curBlock
->unit());
555 auto const endsRegion
= env
.inst
.endsRegion
;
558 FTRACE(1, "selectTracelet: tracelet broken after {}\n", env
.inst
);
561 assertx(env
.sk
.func() == curFunc(env
));
564 auto const curIRBlock
= env
.irgs
.irb
->curBlock();
565 if (!irBlockReachable(env
, curIRBlock
)) {
567 "selectTracelet: tracelet broken due to unreachable code (block {})\n",
572 if (curIRBlock
->isExitNoThrow()) {
573 FTRACE(1, "selectTracelet: tracelet broken due to exiting IR instruction:"
574 "{}\n", curIRBlock
->back());
578 if (isFCallStar(env
.inst
.op())) env
.arStates
.back().pop();
581 // Return an empty region to restart region formation at env.minstrStart.
582 if (env
.minstrStart
.valid()) {
583 env
.breakAt
= env
.minstrStart
;
587 if (env
.region
&& !env
.region
->empty()) {
588 // Make sure we end the region before trying to print the IRUnit.
589 irgen::endRegion(env
.irgs
, env
.sk
);
592 kTraceletLevel
, env
.irgs
.irb
->unit(),
593 env
.inlining
? " after inlining tracelet formation "
594 : " after tracelet formation ",
596 env
.irgs
.irb
->guards()
599 recordDependencies(env
);
601 // Make sure that the IR unit contains a main exit corresponding
602 // to the last bytecode instruction in the region. Note that this
603 // check has to happen before the call to truncateLiterals()
604 // because that updates the region but not the IR unit.
605 if (!env
.region
->blocks().back()->empty()) {
606 auto lastSk
= env
.region
->lastSrcKey();
607 always_assert_flog(findMainExitBlock(env
.irgs
.irb
->unit(), lastSk
),
608 "No main exits found!");
611 truncateLiterals(env
);
614 return std::move(env
.region
);
617 ///////////////////////////////////////////////////////////////////////////////
620 RegionDescPtr
selectTracelet(const RegionContext
& ctx
, TransKind kind
,
621 int32_t maxBCInstrs
, bool inlining
/* = false */) {
622 Timer
_t(Timer::selectTracelet
);
625 RegionDescPtr region
;
628 FTRACE(1, "selectTracelet: starting with maxBCInstrs = {}\n", maxBCInstrs
);
631 Env env
{ctx
, kind
, interp
, breakAt
, maxBCInstrs
, inlining
};
632 region
= form_region(env
);
636 if (region
->empty() || region
->blocks().front()->length() == 0) {
637 FTRACE(1, "selectTracelet giving up after {} tries\n", tries
);
638 return RegionDescPtr
{ nullptr };
641 if (region
->blocks().back()->length() == 0) {
642 // If the final block is empty because it would've only contained
643 // instructions producing literal values, kill it.
644 region
->deleteBlock(region
->blocks().back()->id());
647 if (RuntimeOption::EvalRegionRelaxGuards
) {
648 FTRACE(1, "selectTracelet: before optimizeGuards:\n{}\n",
650 optimizeGuards(*region
, kind
== TransKind::Profile
);
653 FTRACE(1, "selectTracelet returning, {}, {} tries:\n{}\n",
654 inlining
? "inlining" : "not inlining", tries
, show(*region
));