2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 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/guard-relaxation.h"
21 #include "hphp/runtime/vm/jit/inlining-decider.h"
22 #include "hphp/runtime/vm/jit/mc-generator.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"
36 #include "hphp/runtime/vm/jit/irgen.h"
38 // TODO(#5710324): it seems a little odd that region-tracelet is not part of
39 // irgen:: but needs access to this. Probably we don't have the right abstraction
40 // boundaries. We'll resolve this somehow later.
41 #include "hphp/runtime/vm/jit/irgen-internal.h"
43 namespace HPHP
{ namespace jit
{
45 TRACE_SET_MOD(region
);
47 typedef hphp_hash_set
<SrcKey
, SrcKey::Hasher
> InterpSet
;
50 ///////////////////////////////////////////////////////////////////////////////
53 RegionFormer(const RegionContext
& ctx
,
61 const RegionContext
& m_ctx
;
64 const SrcKey m_startSk
;
65 NormalizedInstruction m_inst
;
66 RegionDescPtr m_region
;
67 RegionDesc::Block
* m_curBlock
;
70 jit::vector
<ActRecState
> m_arStates
;
73 uint32_t m_numBCInstrs
{0};
74 uint32_t m_pendingInlinedInstrs
{0};
76 InliningDecider
& m_inl
;
77 const bool m_profiling
;
79 const Func
* curFunc() const;
80 const Unit
* curUnit() const;
81 Offset
curSpOffset() const;
84 bool prepareInstruction();
85 void addInstruction();
86 bool consumeInput(int i
, const InputInfo
& ii
);
87 bool traceThroughJmp();
88 bool tryInline(uint32_t& instrSize
);
89 void recordDependencies();
90 void truncateLiterals();
93 RegionFormer::RegionFormer(const RegionContext
& ctx
,
99 , m_sk(ctx
.func
, ctx
.bcOffset
, ctx
.resumed
)
101 , m_region(std::make_shared
<RegionDesc
>())
102 , m_curBlock(m_region
->addBlock(m_sk
, 0, ctx
.spOffset
))
103 , m_blockFinished(false)
104 , m_hts(// TODO(#5703534): this is using a different TransContext than actual
105 // translation will use.
106 TransContext
{ kInvalidTransID
,
115 , m_profiling(profiling
)
118 const Func
* RegionFormer::curFunc() const {
119 return irgen::curFunc(m_hts
);
122 const Unit
* RegionFormer::curUnit() const {
123 return irgen::curUnit(m_hts
);
126 Offset
RegionFormer::curSpOffset() const {
127 return irgen::spOffset(m_hts
);
130 bool RegionFormer::resumed() const {
131 return irgen::resumed(m_hts
);
134 RegionDescPtr
RegionFormer::go() {
135 for (auto const& lt
: m_ctx
.liveTypes
) {
137 if (t
<= Type::Cls
) {
138 irgen::assertTypeLocation(m_hts
, lt
.location
, t
);
139 m_curBlock
->addPredicted(m_sk
, RegionDesc::TypePred
{lt
.location
, t
});
141 irgen::guardTypeLocation(m_hts
, lt
.location
, t
, true /* outerOnly */);
146 assert(m_numBCInstrs
<= RuntimeOption::EvalJitMaxRegionInstrs
);
147 if (m_numBCInstrs
== RuntimeOption::EvalJitMaxRegionInstrs
) {
148 FTRACE(1, "selectTracelet: breaking region due to size limit ({})\n",
153 if (!prepareInstruction()) break;
155 if (traceThroughJmp()) continue;
157 m_curBlock
->setKnownFunc(m_sk
, m_inst
.funcd
);
159 m_inst
.interp
= m_interp
.count(m_sk
);
160 auto const doPrediction
=
161 m_profiling
? false : outputIsPredicted(m_inst
);
163 uint32_t calleeInstrSize
;
164 if (tryInline(calleeInstrSize
)) {
165 // If m_inst is an FCall and the callee is suitable for inlining, we can
166 // translate the callee and potentially use its return type to extend the
169 auto callee
= m_inst
.funcd
;
170 FTRACE(1, "\nselectTracelet starting inlined call from {} to "
171 "{} with stack:\n{}\n", curFunc()->fullName()->data(),
172 callee
->fullName()->data(), show(m_hts
));
173 auto returnSk
= m_inst
.nextSk();
174 auto returnFuncOff
= returnSk
.offset() - curFunc()->base();
176 m_arStates
.back().pop();
177 m_arStates
.emplace_back();
178 m_curBlock
->setInlinedCallee(callee
);
179 irgen::beginInlining(m_hts
, m_inst
.imm
[0].u_IVA
, callee
, returnFuncOff
,
180 doPrediction
? m_inst
.outPred
: Type::Gen
);
182 m_sk
= irgen::curSrcKey(m_hts
);
183 m_blockFinished
= true;
184 m_pendingInlinedInstrs
+= calleeInstrSize
;
188 auto const inlineReturn
= irgen::isInlining(m_hts
) &&
189 (isRet(m_inst
.op()) || m_inst
.op() == OpNativeImpl
);
191 translateInstr(m_hts
, m_inst
);
192 } catch (const FailedIRGen
& exn
) {
193 FTRACE(1, "ir generation for {} failed with {}\n",
194 m_inst
.toString(), exn
.what());
195 always_assert(!m_interp
.count(m_sk
));
196 m_interp
.insert(m_sk
);
201 // If we just translated a return from an inlined call, grab the updated
202 // SrcKey from m_ht and clean up.
204 m_inl
.registerEndInlining(m_sk
.func());
205 m_sk
= irgen::curSrcKey(m_hts
).advanced(curUnit());
206 m_arStates
.pop_back();
207 m_blockFinished
= true;
211 // We successfully translated the instruction, so update m_sk.
212 m_sk
.advance(m_curBlock
->unit());
214 auto const endsRegion
= m_inst
.endsRegion
;
217 FTRACE(1, "selectTracelet: tracelet broken after {}\n", m_inst
);
220 assert(m_sk
.func() == curFunc());
223 if (isFCallStar(m_inst
.op())) m_arStates
.back().pop();
225 // Since the current instruction is over, advance sk before emitting the
226 // prediction (if any).
228 // TODO(#5710339): would be nice to remove the following check
229 irgen::publicTopType(m_hts
, 0).maybe(m_inst
.outPred
)) {
230 irgen::updateBCOff(m_hts
, &m_inst
, m_sk
.offset(), false);
231 irgen::checkTypeStack(m_hts
, 0, m_inst
.outPred
, m_sk
.offset());
235 // If we failed while trying to inline, trigger retry without inlining.
236 if (m_region
&& !m_region
->empty() && irgen::isInlining(m_hts
)) {
237 // We can recover from this situation just fine, but it's more often
238 // than not indicative of a real bug somewhere else in the system.
239 // TODO: 5515310 investigate whether legit bugs cause this.
240 FTRACE(1, "selectTracelet: Failed while inlining:\n{}\n{}",
241 show(*m_region
), m_hts
.irb
->unit());
246 if (m_region
&& !m_region
->empty()) {
247 // Make sure we end the region before trying to print the IRUnit.
248 irgen::endRegion(m_hts
, m_sk
.offset());
251 kTraceletLevel
, m_hts
.irb
->unit(),
252 m_inl
.depth() || m_inl
.disabled() ? " after inlining tracelet formation "
253 : " after tracelet formation ",
258 recordDependencies();
262 return std::move(m_region
);
266 * Populate most fields of the NormalizedInstruction, assuming its sk
267 * has already been set. Returns false iff the region should be
268 * truncated before inst's SrcKey.
270 bool RegionFormer::prepareInstruction() {
271 m_inst
.~NormalizedInstruction();
272 new (&m_inst
) NormalizedInstruction(m_sk
, curUnit());
273 auto const breaksBB
=
274 (m_profiling
&& instrBreaksProfileBB(&m_inst
)) ||
275 (mcg
->useLLVM() ? opcodeChangesPC(m_inst
.op())
276 : opcodeBreaksBB(m_inst
.op()));
277 m_inst
.endsRegion
= breaksBB
||
278 (dontGuardAnyInputs(m_inst
.op()) && opcodeChangesPC(m_inst
.op()));
279 m_inst
.funcd
= m_arStates
.back().knownFunc();
280 irgen::updateBCOff(m_hts
, &m_inst
, m_sk
.offset(), false);
282 auto const inputInfos
= getInputs(m_startSk
, m_inst
);
284 // Read types for all the inputs and apply MetaData.
285 auto newDynLoc
= [&](const InputInfo
& ii
) {
286 auto dl
= m_inst
.newDynLoc(
288 irgen::predictedTypeFromLocation(m_hts
, ii
.loc
)
290 FTRACE(2, "predictedTypeFromLocation: {} -> {}\n",
291 ii
.loc
.pretty(), dl
->rtt
);
295 for (auto const& ii
: inputInfos
) m_inst
.inputs
.push_back(newDynLoc(ii
));
297 // This reads valueClass from the inputs so it used to need to
298 // happen after readMetaData. But now readMetaData is gone ...
301 // Check all the inputs for unknown values.
302 assert(inputInfos
.size() == m_inst
.inputs
.size());
303 for (unsigned i
= 0; i
< inputInfos
.size(); ++i
) {
304 if (!consumeInput(i
, inputInfos
[i
])) {
305 FTRACE(2, "Stopping tracelet consuming {} input {}\n",
306 opcodeToName(m_inst
.op()), i
);
311 if (inputInfos
.needsRefCheck
) {
312 // Reffiness guards are always at the beginning of the trace for now, so
313 // calculate the delta from the original sp to the ar.
314 auto argNum
= m_inst
.imm
[0].u_IVA
;
315 size_t entryArDelta
= instrSpToArDelta((Op
*)m_inst
.pc()) -
316 (irgen::spOffset(m_hts
) - m_ctx
.spOffset
);
318 m_inst
.preppedByRef
= m_arStates
.back().checkByRef(argNum
, entryArDelta
,
320 } catch (const UnknownInputExc
& exn
) {
321 // We don't have a guess for the current ActRec.
322 FTRACE(1, "selectTracelet: don't have reffiness guess for {}\n",
327 m_curBlock
->setParamByRef(m_inst
.source
, m_inst
.preppedByRef
);
332 if (isFPush(m_inst
.op())) m_arStates
.back().pushFunc(m_inst
);
338 * Add the current instruction to the region.
340 void RegionFormer::addInstruction() {
341 if (m_blockFinished
) {
342 FTRACE(2, "selectTracelet adding new block at {} after:\n{}\n",
343 showShort(m_sk
), show(*m_curBlock
));
344 always_assert(m_sk
.func() == curFunc());
345 RegionDesc::Block
* newCurBlock
= m_region
->addBlock(m_sk
, 0, curSpOffset());
346 m_region
->addArc(m_curBlock
->id(), newCurBlock
->id());
347 m_curBlock
= newCurBlock
;
348 m_blockFinished
= false;
351 FTRACE(2, "selectTracelet adding instruction {}\n", m_inst
.toString());
352 m_curBlock
->addInstruction();
354 if (irgen::isInlining(m_hts
)) m_pendingInlinedInstrs
--;
357 bool RegionFormer::traceThroughJmp() {
358 // This flag means "if we are currently inlining, or we are generating a
359 // tracelet for inlining analysis". The latter is the case when inlining is
360 // disabled (because our caller wants to peek at our region without us
361 // inlining ourselves).
362 bool inlining
= m_inl
.depth() || m_inl
.disabled();
364 // We only trace through unconditional jumps and conditional jumps with const
365 // inputs while inlining.
366 if (!isUnconditionalJmp(m_inst
.op()) &&
367 !(inlining
&& isConditionalJmp(m_inst
.op()) &&
368 irgen::publicTopType(m_hts
, 0).isConst())) {
372 // Normally we want to keep profiling translations to basic blocks, but if
373 // we're inlining we want to analyze as much of the callee as possible.
374 if (m_profiling
&& !inlining
) return false;
376 // Don't trace through too many jumps, unless we're inlining. We want to make
377 // sure we don't break a tracelet in the middle of an inlined call; if the
378 // inlined callee becomes too big that's caught in shouldIRInline.
379 if (m_numJmps
== Translator::MaxJmpsTracedThrough
&& !inlining
) {
383 auto offset
= m_inst
.imm
[0].u_BA
;
384 // Only trace through backwards jumps if it's a JmpNS and we're
385 // inlining. This is to get DV funclets.
386 if (offset
<= 0 && (m_inst
.op() != OpJmpNS
|| !inlining
)) {
390 // Ok we're good. For unconditional jumps, just set m_sk to the dest. For
391 // known conditional jumps we have to consume the const value on the top of
392 // the stack and figure out which branch to go to.
393 if (isUnconditionalJmp(m_inst
.op())) {
394 m_sk
.setOffset(m_sk
.offset() + offset
);
396 auto value
= irgen::popC(m_hts
);
398 value
->variantVal().toBoolean() == (m_inst
.op() == OpJmpNZ
);
399 FTRACE(2, "Tracing through {}taken Jmp(N)Z on constant {}\n",
400 taken
? "" : "not ", *value
->inst());
402 m_sk
.setOffset(taken
? m_sk
.offset() + offset
403 : m_sk
.advanced().offset());
407 m_blockFinished
= true;
411 bool RegionFormer::tryInline(uint32_t& instrSize
) {
412 assert(m_inst
.source
== m_sk
);
413 assert(m_inst
.func() == curFunc());
414 assert(m_sk
.resumed() == resumed());
418 if (!m_inl
.canInlineAt(m_inst
.source
, m_inst
.funcd
, *m_region
)) {
422 auto refuse
= [this](const std::string
& str
) {
423 FTRACE(2, "selectTracelet not inlining {}: {}\n",
424 m_inst
.toString(), str
);
428 auto callee
= m_inst
.funcd
;
430 // Make sure the FPushOp wasn't interpreted.
431 auto spillFrame
= findSpillFrame(m_hts
.irb
->sp());
433 return refuse("couldn't find SpillFrame for FPushOp");
436 auto numArgs
= m_inst
.imm
[0].u_IVA
;
437 auto numParams
= callee
->numParams();
439 // Set up the region context, mapping stack slots in the caller to locals in
443 ctx
.bcOffset
= callee
->getEntryForNumArgs(numArgs
);
444 ctx
.spOffset
= callee
->numSlotsInFrame();
446 for (int i
= 0; i
< numArgs
; ++i
) {
447 auto type
= irgen::publicTopType(m_hts
, i
);
448 uint32_t paramIdx
= numArgs
- 1 - i
;
449 ctx
.liveTypes
.push_back({RegionDesc::Location::Local
{paramIdx
}, type
});
452 for (unsigned i
= numArgs
; i
< numParams
; ++i
) {
453 // These locals will be populated by DV init funclets but they'll start
455 ctx
.liveTypes
.push_back({RegionDesc::Location::Local
{i
}, Type::Uninit
});
458 FTRACE(1, "selectTracelet analyzing callee {} with context:\n{}",
459 callee
->fullName()->data(), show(ctx
));
460 auto region
= selectTracelet(ctx
, m_profiling
, false /* noinline */);
462 return refuse("failed to select region in callee");
465 instrSize
= region
->instrSize();
466 auto newInstrSize
= instrSize
+ m_numBCInstrs
+ m_pendingInlinedInstrs
;
467 if (newInstrSize
> RuntimeOption::EvalJitMaxRegionInstrs
) {
468 return refuse("new region would be too large");
470 if (!m_inl
.shouldInline(callee
, *region
)) {
471 return refuse("shouldIRInline failed");
476 void RegionFormer::truncateLiterals() {
477 if (!m_region
|| m_region
->empty() ||
478 m_region
->blocks().back()->empty()) return;
480 // Don't finish a region with literal values or values that have a class
481 // related to the current context class. They produce valuable information
482 // for optimizations that's lost across region boundaries.
483 auto& lastBlock
= *m_region
->blocks().back();
484 auto sk
= lastBlock
.start();
486 auto unit
= lastBlock
.unit();
487 for (int i
= 0, len
= lastBlock
.length(); i
< len
; ++i
, sk
.advance(unit
)) {
488 auto const op
= sk
.op();
489 if (!isLiteral(op
) && !isThisSelfOrParent(op
) && !isTypeAssert(op
)) {
490 if (i
== len
- 1) return;
494 FTRACE(1, "selectTracelet truncating block after offset {}:\n{}\n",
495 endSk
.offset(), show(lastBlock
));
496 lastBlock
.truncateAfter(endSk
);
500 * Check if the current type for the location in ii is specific enough for what
501 * the current opcode wants. If not, return false.
503 bool RegionFormer::consumeInput(int i
, const InputInfo
& ii
) {
504 auto& rtt
= m_inst
.inputs
[i
]->rtt
;
505 if (ii
.dontGuard
) return true;
507 if (m_profiling
&& rtt
.isBoxed() &&
508 (m_region
->blocks().size() > 1 || !m_region
->entry()->empty())) {
509 // We don't want side exits when profiling, so only allow instructions that
510 // consume refs at the beginning of the region.
514 if (!ii
.dontBreak
&& !rtt
.isKnownDataType()) {
515 // Trying to consume a value without a precise enough type.
516 FTRACE(1, "selectTracelet: {} tried to consume {}\n",
517 m_inst
.toString(), m_inst
.inputs
[i
]->pretty());
521 if (!rtt
.isBoxed() || m_inst
.ignoreInnerType
|| ii
.dontGuardInner
) {
525 if (!rtt
.innerType().isKnownDataType()) {
526 // Trying to consume a boxed value without a guess for the inner type.
527 FTRACE(1, "selectTracelet: {} tried to consume ref {}\n",
528 m_inst
.toString(), m_inst
.inputs
[i
]->pretty());
536 * Records any type/reffiness predictions we depend on in the region. Guards
537 * for locals and stack cells that are not used will be eliminated by the call
540 void RegionFormer::recordDependencies() {
541 // Record the incrementally constructed reffiness predictions.
542 assert(!m_region
->empty());
543 auto& frontBlock
= *m_region
->blocks().front();
544 for (auto const& dep
: m_refDeps
.m_arMap
) {
545 frontBlock
.addReffinessPred(m_startSk
, {dep
.second
.m_mask
,
550 // Relax guards and record the ones that survived.
551 auto& firstBlock
= *m_region
->blocks().front();
552 auto blockStart
= firstBlock
.start();
553 auto& unit
= m_hts
.unit
;
554 auto const doRelax
= RuntimeOption::EvalHHIRRelaxGuards
;
555 bool changed
= false;
557 Timer
_t(Timer::selectTracelet_relaxGuards
);
558 // The IR is going to be discarded immediately, so skip reflowing
559 // the types in relaxGuards to save JIT time.
560 RelaxGuardsFlags flags
= m_profiling
? RelaxSimple
: RelaxNormal
;
561 changed
= relaxGuards(unit
, *m_hts
.irb
->guards(), flags
);
564 auto guardMap
= std::map
<RegionDesc::Location
,Type
>{};
565 visitGuards(unit
, [&](const RegionDesc::Location
& loc
, Type type
) {
566 if (type
<= Type::Cls
) return;
567 auto inret
= guardMap
.insert(std::make_pair(loc
, type
));
568 if (inret
.second
) return;
569 auto& oldTy
= inret
.first
->second
;
570 if (oldTy
== Type::Gen
) {
571 // This is the case that we see an inner type prediction for a GuardLoc
572 // that got relaxed to Gen.
578 for (auto& kv
: guardMap
) {
579 if (kv
.second
== Type::Gen
) {
580 // Guard was relaxed to Gen---don't record it.
583 auto const pred
= RegionDesc::TypePred
{ kv
.first
, kv
.second
};
584 FTRACE(1, "selectTracelet adding guard {}\n", show(pred
));
585 firstBlock
.addPredicted(blockStart
, pred
);
589 printUnit(3, unit
, " after guard relaxation ", nullptr,
590 m_hts
.irb
->guards());
595 ///////////////////////////////////////////////////////////////////////////////
598 RegionDescPtr
selectTracelet(const RegionContext
& ctx
, bool profiling
,
599 bool allowInlining
/* = true */) {
600 Timer
_t(Timer::selectTracelet
);
602 RegionDescPtr region
;
605 InliningDecider
inl(ctx
.func
);
606 if (!allowInlining
) inl
.disable();
608 while (!(region
= RegionFormer(ctx
, interp
, inl
, profiling
).go())) {
613 if (region
->empty() || region
->blocks().front()->length() == 0) {
614 FTRACE(1, "selectTracelet giving up after {} tries\n", tries
);
615 return RegionDescPtr
{ nullptr };
618 FTRACE(1, "selectTracelet returning, inlining {}, {} tries:\n{}\n",
619 allowInlining
? "allowed" : "disallowed", tries
, show(*region
));
620 if (region
->blocks().back()->length() == 0) {
621 // If the final block is empty because it would've only contained
622 // instructions producing literal values, kill it.
623 region
->deleteBlock(region
->blocks().back()->id());