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/util/trace.h"
20 #include "hphp/runtime/vm/jit/annotation.h"
21 #include "hphp/runtime/vm/jit/guard-relaxation.h"
22 #include "hphp/runtime/vm/jit/hhbc-translator.h"
23 #include "hphp/runtime/vm/jit/ir-translator.h"
24 #include "hphp/runtime/vm/jit/normalized-instruction.h"
25 #include "hphp/runtime/vm/jit/print.h"
26 #include "hphp/runtime/vm/jit/region-selection.h"
27 #include "hphp/runtime/vm/jit/timer.h"
28 #include "hphp/runtime/vm/jit/tracelet.h"
29 #include "hphp/runtime/vm/jit/translator.h"
31 namespace HPHP
{ namespace JIT
{
34 TRACE_SET_MOD(region
);
36 typedef hphp_hash_set
<SrcKey
, SrcKey::Hasher
> InterpSet
;
39 struct RegionDescIter
: public RegionIter
{
40 explicit RegionDescIter(const RegionDesc
& region
)
41 : m_blocks(region
.blocks
)
42 , m_blockIter(region
.blocks
.begin())
43 , m_sk(m_blockIter
== m_blocks
.end() ? SrcKey() : (*m_blockIter
)->start())
46 bool finished() const { return m_blockIter
== m_blocks
.end(); }
55 assert(m_sk
.func() == (*m_blockIter
)->func());
57 if (m_sk
== (*m_blockIter
)->last()) {
59 if (!finished()) m_sk
= (*m_blockIter
)->start();
66 const std::vector
<RegionDesc::BlockPtr
>& m_blocks
;
67 std::vector
<RegionDesc::BlockPtr
>::const_iterator m_blockIter
;
72 RegionFormer(const RegionContext
& ctx
, InterpSet
& interp
, int inlineDepth
,
78 const RegionContext
& m_ctx
;
81 const SrcKey m_startSk
;
82 NormalizedInstruction m_inst
;
83 RegionDescPtr m_region
;
84 RegionDesc::Block
* m_curBlock
;
86 IRTranslator m_irTrans
;
88 smart::vector
<ActRecState
> m_arStates
;
90 const int m_inlineDepth
;
91 const bool m_profiling
;
93 const Func
* curFunc() const;
94 const Unit
* curUnit() const;
95 Offset
curSpOffset() const;
97 int inliningDepth() const;
99 bool prepareInstruction();
100 void addInstruction();
101 bool consumeInput(int i
, const InputInfo
& ii
);
103 void recordDependencies();
104 void truncateLiterals();
107 RegionFormer::RegionFormer(const RegionContext
& ctx
, InterpSet
& interp
,
108 int inlineDepth
, bool profiling
)
111 , m_sk(ctx
.func
, ctx
.bcOffset
, ctx
.resumed
)
113 , m_region(std::make_shared
<RegionDesc
>())
114 , m_curBlock(m_region
->addBlock(ctx
.func
, m_sk
.resumed(), m_sk
.offset(), 0,
116 , m_blockFinished(false)
117 , m_irTrans(ctx
.bcOffset
, ctx
.spOffset
, ctx
.resumed
, ctx
.func
)
118 , m_ht(m_irTrans
.hhbcTrans())
120 , m_inlineDepth(inlineDepth
)
121 , m_profiling(profiling
)
125 const Func
* RegionFormer::curFunc() const {
126 return m_ht
.curFunc();
129 const Unit
* RegionFormer::curUnit() const {
130 return m_ht
.curUnit();
133 Offset
RegionFormer::curSpOffset() const {
134 return m_ht
.spOffset();
137 bool RegionFormer::resumed() const {
138 return m_ht
.resumed();
141 int RegionFormer::inliningDepth() const {
142 return m_inlineDepth
+ m_ht
.inliningDepth();
145 RegionDescPtr
RegionFormer::go() {
146 uint32_t numJmps
= 0;
148 for (auto const& lt
: m_ctx
.liveTypes
) {
150 if (t
<= Type::Cls
) {
151 m_ht
.assertTypeStack(lt
.location
.stackOffset(), t
);
152 m_curBlock
->addPredicted(m_sk
, RegionDesc::TypePred
{lt
.location
, t
});
154 m_ht
.guardTypeLocation(lt
.location
, t
, true /* outerOnly */);
159 if (!prepareInstruction()) break;
161 // Instead of translating a Jmp, go to its destination.
162 if (!m_profiling
&& isUnconditionalJmp(m_inst
.op()) &&
163 m_inst
.imm
[0].u_BA
> 0 && numJmps
< Translator::MaxJmpsTracedThrough
) {
164 // Include the Jmp in the region and continue to its destination.
166 m_sk
.setOffset(m_sk
.offset() + m_inst
.imm
[0].u_BA
);
167 m_blockFinished
= true;
172 m_curBlock
->setKnownFunc(m_sk
, m_inst
.funcd
);
174 m_inst
.interp
= m_interp
.count(m_sk
);
175 auto const doPrediction
=
176 m_profiling
? false : outputIsPredicted(m_inst
);
179 // If m_inst is an FCall and the callee is suitable for inlining, we can
180 // translate the callee and potentially use its return type to extend the
183 auto callee
= m_inst
.funcd
;
184 FTRACE(1, "\nselectTracelet starting inlined call from {} to "
185 "{} with stack:\n{}\n", curFunc()->fullName()->data(),
186 callee
->fullName()->data(), m_ht
.showStack());
187 auto returnSk
= m_inst
.nextSk();
188 auto returnFuncOff
= returnSk
.offset() - curFunc()->base();
190 m_arStates
.back().pop();
191 m_arStates
.emplace_back();
192 m_curBlock
->setInlinedCallee(callee
);
193 m_ht
.beginInlining(m_inst
.imm
[0].u_IVA
, callee
, returnFuncOff
,
194 doPrediction
? m_inst
.outPred
: Type::Gen
);
196 m_sk
= m_ht
.curSrcKey();
197 m_blockFinished
= true;
201 auto const inlineReturn
= m_ht
.isInlining() && isRet(m_inst
.op());
203 m_irTrans
.translateInstr(m_inst
);
204 } catch (const FailedIRGen
& exn
) {
205 FTRACE(1, "ir generation for {} failed with {}\n",
206 m_inst
.toString(), exn
.what());
207 always_assert(!m_interp
.count(m_sk
));
208 m_interp
.insert(m_sk
);
213 // We successfully translated the instruction, so update m_sk.
214 m_sk
.advance(m_curBlock
->unit());
217 // If we just translated an inlined RetC, grab the updated SrcKey from
218 // m_ht and clean up.
219 m_sk
= m_ht
.curSrcKey().advanced(curUnit());
220 m_arStates
.pop_back();
221 m_blockFinished
= true;
223 } else if (m_inst
.breaksTracelet
||
224 (m_profiling
&& instrBreaksProfileBB(&m_inst
))) {
225 FTRACE(1, "selectTracelet: tracelet broken after {}\n", m_inst
);
228 assert(m_sk
.func() == m_ht
.curFunc());
231 if (isFCallStar(m_inst
.op())) m_arStates
.back().pop();
233 // Since the current instruction is over, advance HhbcTranslator's sk
234 // before emitting the prediction (if any).
235 if (doPrediction
&& m_inst
.outPred
< m_ht
.topType(0, DataTypeGeneric
)) {
236 m_ht
.setBcOff(m_sk
.offset(), false);
237 m_ht
.checkTypeStack(0, m_inst
.outPred
, m_sk
.offset());
241 printUnit(2, m_ht
.unit(), " after tracelet formation ",
242 nullptr, nullptr, m_ht
.irBuilder().guards());
244 if (m_region
&& !m_region
->blocks
.empty()) {
248 return folly::format("Tried to end region while inlining:\n{}",
252 m_ht
.end(m_sk
.offset());
253 recordDependencies();
257 return std::move(m_region
);
261 * Populate most fields of the NormalizedInstruction, assuming its sk
262 * has already been set. Returns false iff the region should be
263 * truncated before inst's SrcKey.
265 bool RegionFormer::prepareInstruction() {
266 m_inst
.~NormalizedInstruction();
267 new (&m_inst
) NormalizedInstruction();
268 m_inst
.source
= m_sk
;
269 m_inst
.m_unit
= curUnit();
270 m_inst
.breaksTracelet
= opcodeBreaksBB(m_inst
.op()) ||
271 (dontGuardAnyInputs(m_inst
.op()) &&
272 opcodeChangesPC(m_inst
.op()));
273 m_inst
.changesPC
= opcodeChangesPC(m_inst
.op());
274 m_inst
.funcd
= m_arStates
.back().knownFunc();
275 populateImmediates(m_inst
);
276 m_ht
.setBcOff(m_sk
.offset(), false);
278 InputInfos inputInfos
;
279 getInputs(m_startSk
, m_inst
, inputInfos
, m_curBlock
->func(), [&](int i
) {
280 return m_ht
.irBuilder().localType(i
, DataTypeGeneric
);
283 // Read types for all the inputs and apply MetaData.
284 auto newDynLoc
= [&](const InputInfo
& ii
) {
285 auto dl
= m_inst
.newDynLoc(ii
.loc
, m_ht
.rttFromLocation(ii
.loc
));
286 FTRACE(2, "rttFromLocation: {} -> {}\n",
287 ii
.loc
.pretty(), dl
->rtt
.pretty());
291 for (auto const& ii
: inputInfos
) m_inst
.inputs
.push_back(newDynLoc(ii
));
293 // This may not be necessary, but for now it's preserving
294 // side-effects that the call to readMetaData used to have.
295 if (isAlwaysNop(m_inst
.op())) {
299 // This reads valueClass from the inputs so it used to need to
300 // happen after readMetaData. But now readMetaData is gone ...
301 if (inliningDepth() == 0) annotate(&m_inst
);
303 // Check all the inputs for unknown values.
304 assert(inputInfos
.size() == m_inst
.inputs
.size());
305 for (unsigned i
= 0; i
< inputInfos
.size(); ++i
) {
306 if (!consumeInput(i
, inputInfos
[i
])) return false;
309 if (!m_inst
.noOp
&& inputInfos
.needsRefCheck
) {
310 // Reffiness guards are always at the beginning of the trace for now, so
311 // calculate the delta from the original sp to the ar.
312 auto argNum
= m_inst
.imm
[0].u_IVA
;
313 size_t entryArDelta
= instrSpToArDelta((Op
*)m_inst
.pc()) -
314 (m_ht
.spOffset() - m_ctx
.spOffset
);
316 m_inst
.preppedByRef
= m_arStates
.back().checkByRef(argNum
, entryArDelta
,
318 } catch (const UnknownInputExc
& exn
) {
319 // We don't have a guess for the current ActRec.
320 FTRACE(1, "selectTracelet: don't have reffiness guess for {}\n",
325 m_curBlock
->setParamByRef(m_inst
.source
, m_inst
.preppedByRef
);
330 if (isFPush(m_inst
.op())) m_arStates
.back().pushFunc(m_inst
);
336 * Add the current instruction to the region.
338 void RegionFormer::addInstruction() {
339 if (m_blockFinished
) {
340 FTRACE(2, "selectTracelet adding new block at {} after:\n{}\n",
341 showShort(m_sk
), show(*m_curBlock
));
342 RegionDesc::Block
* newCurBlock
= m_region
->addBlock(curFunc(),
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();
355 bool RegionFormer::tryInline() {
356 if (!RuntimeOption::RepoAuthoritative
||
357 (m_inst
.op() != Op::FCall
&& m_inst
.op() != Op::FCallD
)) {
361 auto refuse
= [this](const std::string
& str
) {
362 FTRACE(2, "selectTracelet not inlining {}: {}\n",
363 m_inst
.toString(), str
);
367 if (inliningDepth() >= RuntimeOption::EvalHHIRInliningMaxDepth
) {
368 return refuse("inlining level would be too deep");
371 auto callee
= m_inst
.funcd
;
372 if (!callee
|| callee
->isCPPBuiltin()) {
373 return refuse("don't know callee or callee is builtin");
376 if (callee
== curFunc()) {
377 return refuse("call is recursive");
380 if (callee
->hasVariadicCaptureParam()) {
381 // FIXME: this doesn't have to remove inlining
382 return refuse("callee has a variadic capture");
385 if (m_inst
.imm
[0].u_IVA
!= callee
->numParams()) {
386 return refuse("numArgs doesn't match numParams of callee");
389 // For analysis purposes, we require that the FPush* instruction is in the
391 auto fpi
= curFunc()->findFPI(m_sk
.offset());
392 const SrcKey pushSk
{curFunc(), fpi
->m_fpushOff
, resumed()};
394 auto& blocks
= m_region
->blocks
;
395 for (unsigned i
= 0; i
< blocks
.size(); ++i
) {
396 if (blocks
[i
]->contains(pushSk
)) {
401 if (pushBlock
== -1) {
402 return refuse("FPush* is not in the current region");
405 // Calls invalidate all live SSATmps, so don't allow any in the fpi region
406 auto findFCall
= [&] {
407 for (unsigned i
= pushBlock
; i
< blocks
.size(); ++i
) {
408 auto& block
= *blocks
[i
];
409 auto sk
= i
== pushBlock
? pushSk
.advanced() : block
.start();
410 while (sk
<= block
.last()) {
411 if (sk
== m_sk
) return false;
414 if (isFCallStar(op
) || op
== Op::FCallBuiltin
) return true;
421 return refuse("fpi region contains another call");
424 switch (pushSk
.op()) {
425 case OpFPushClsMethodD
:
426 if (callee
->mayHaveThis()) return refuse("callee may have this pointer");
429 case OpFPushObjMethodD
:
435 return refuse(folly::format("unsupported push op {}",
436 opcodeToName(pushSk
.op())).str());
439 // Make sure the FPushOp wasn't interpreted.
440 auto spillFrame
= findSpillFrame(m_ht
.irBuilder().sp());
442 return refuse("couldn't find SpillFrame for FPushOp");
445 // Set up the region context, mapping stack slots in the caller to locals in
449 ctx
.bcOffset
= callee
->base();
450 ctx
.spOffset
= callee
->numSlotsInFrame();
452 for (int i
= 0; i
< callee
->numParams(); ++i
) {
453 // DataTypeGeneric is used because we're just passing the locals into the
454 // callee. It's up to the callee to constraint further if needed.
455 auto type
= m_ht
.topType(i
, DataTypeGeneric
);
456 uint32_t paramIdx
= callee
->numParams() - 1 - i
;
457 typedef RegionDesc::Location Location
;
458 ctx
.liveTypes
.push_back({Location::Local
{paramIdx
}, type
});
461 FTRACE(1, "selectTracelet analyzing callee {} with context:\n{}",
462 callee
->fullName()->data(), show(ctx
));
463 auto region
= selectTracelet(ctx
, inliningDepth() + 1, m_profiling
);
465 return refuse("failed to select region in callee");
468 RegionDescIter
iter(*region
);
469 if (!shouldIRInline(curFunc(), callee
, iter
)) {
470 return refuse("shouldIRInline failed");
475 void RegionFormer::truncateLiterals() {
476 if (!m_region
|| m_region
->blocks
.empty() ||
477 m_region
->blocks
.back()->empty()) return;
479 // Don't finish a region with literal values or values that have a class
480 // related to the current context class. They produce valuable information
481 // for optimizations that's lost across region boundaries.
482 auto& lastBlock
= *m_region
->blocks
.back();
483 auto sk
= lastBlock
.start();
485 auto unit
= lastBlock
.unit();
486 for (int i
= 0, len
= lastBlock
.length(); i
< len
; ++i
, sk
.advance(unit
)) {
487 auto const op
= sk
.op();
488 if (!isLiteral(op
) && !isThisSelfOrParent(op
)) {
489 if (i
== len
- 1) return;
493 FTRACE(1, "selectTracelet truncating block after offset {}:\n{}\n",
494 endSk
.offset(), show(lastBlock
));
495 lastBlock
.truncateAfter(endSk
);
499 * Check if the current type for the location in ii is specific enough for what
500 * the current opcode wants. If not, return false.
502 bool RegionFormer::consumeInput(int i
, const InputInfo
& ii
) {
503 auto& rtt
= m_inst
.inputs
[i
]->rtt
;
504 if (ii
.dontGuard
|| !rtt
.isValue()) return true;
506 if (m_profiling
&& rtt
.isRef() &&
507 (m_region
->blocks
.size() > 1 || !m_region
->blocks
[0]->empty())) {
508 // We don't want side exits when profiling, so only allow instructions that
509 // consume refs at the beginning of the region.
513 if (!ii
.dontBreak
&& !Type(rtt
).isKnownDataType()) {
514 // Trying to consume a value without a precise enough type.
515 FTRACE(1, "selectTracelet: {} tried to consume {}\n",
516 m_inst
.toString(), m_inst
.inputs
[i
]->pretty());
520 if (!rtt
.isRef() || m_inst
.ignoreInnerType
|| ii
.dontGuardInner
) {
524 if (!Type(rtt
.innerType()).isKnownDataType()) {
525 // Trying to consume a boxed value without a guess for the inner type.
526 FTRACE(1, "selectTracelet: {} tried to consume ref {}\n",
527 m_inst
.toString(), m_inst
.inputs
[i
]->pretty());
535 * Records any type/reffiness predictions we depend on in the region. Guards
536 * for locals and stack cells that are not used will be eliminated by the call
539 void RegionFormer::recordDependencies() {
540 // Record the incrementally constructed reffiness predictions.
541 assert(!m_region
->blocks
.empty());
542 auto& frontBlock
= *m_region
->blocks
.front();
543 for (auto const& dep
: m_refDeps
.m_arMap
) {
544 frontBlock
.addReffinessPred(m_startSk
, {dep
.second
.m_mask
,
549 // Relax guards and record the ones that survived.
550 auto& firstBlock
= *m_region
->blocks
.front();
551 auto blockStart
= firstBlock
.start();
552 auto& unit
= m_ht
.unit();
553 auto const doRelax
= RuntimeOption::EvalHHIRRelaxGuards
;
554 bool changed
= false;
556 Timer
_t(Timer::selectTracelet_relaxGuards
);
557 changed
= relaxGuards(unit
, *m_ht
.irBuilder().guards(), m_profiling
);
560 visitGuards(unit
, [&](const RegionDesc::Location
& loc
, Type type
) {
561 if (type
<= Type::Cls
) return;
562 RegionDesc::TypePred pred
{loc
, type
};
563 FTRACE(1, "selectTracelet adding guard {}\n", show(pred
));
564 firstBlock
.addPredicted(blockStart
, pred
);
567 printUnit(3, unit
, " after guard relaxation ",
568 nullptr, nullptr, m_ht
.irBuilder().guards());
575 * Region selector that attempts to form the longest possible region using the
576 * given context. The region will be broken before the first instruction that
577 * attempts to consume an input with an insufficiently precise type, or after
578 * most control flow instructions.
580 * May return a null region if the given RegionContext doesn't have
581 * enough information to translate at least one instruction.
583 RegionDescPtr
selectTracelet(const RegionContext
& ctx
, int inlineDepth
,
585 Timer
_t(Timer::selectTracelet
);
587 RegionDescPtr region
;
590 while (!(region
= RegionFormer(ctx
, interp
, inlineDepth
, profiling
).go())) {
594 if (region
->blocks
.size() == 0 || region
->blocks
.front()->length() == 0) {
595 FTRACE(1, "selectTracelet giving up after {} tries\n", tries
);
596 return RegionDescPtr
{ nullptr };
599 FTRACE(1, "selectTracelet returning, inlineDepth {}, {} tries:\n{}\n",
600 inlineDepth
, tries
, show(*region
));
601 if (region
->blocks
.back()->length() == 0) {
602 // If the final block is empty because it would've only contained
603 // instructions producing literal values, kill it.
604 region
->blocks
.pop_back();