Various llvm backend bugfixes
[hiphop-php.git] / hphp / runtime / vm / jit / region-tracelet.cpp
blob52f5bd88cd05e9842377156b76ab6a887a888037
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
33 #include <algorithm>
34 #include <vector>
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;
49 namespace {
50 ///////////////////////////////////////////////////////////////////////////////
52 struct RegionFormer {
53 RegionFormer(const RegionContext& ctx,
54 InterpSet& interp,
55 InliningDecider& inl,
56 bool profiling);
58 RegionDescPtr go();
60 private:
61 const RegionContext& m_ctx;
62 InterpSet& m_interp;
63 SrcKey m_sk;
64 const SrcKey m_startSk;
65 NormalizedInstruction m_inst;
66 RegionDescPtr m_region;
67 RegionDesc::Block* m_curBlock;
68 bool m_blockFinished;
69 HTS m_hts;
70 jit::vector<ActRecState> m_arStates;
71 RefDeps m_refDeps;
72 uint32_t m_numJmps;
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;
82 bool resumed() 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,
94 InterpSet& interp,
95 InliningDecider& inl,
96 bool profiling)
97 : m_ctx(ctx)
98 , m_interp(interp)
99 , m_sk(ctx.func, ctx.bcOffset, ctx.resumed)
100 , m_startSk(m_sk)
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,
107 ctx.bcOffset,
108 ctx.spOffset,
109 ctx.resumed,
110 ctx.func,
111 nullptr })
112 , m_arStates(1)
113 , m_numJmps(0)
114 , m_inl(inl)
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) {
136 auto t = lt.type;
137 if (t <= Type::Cls) {
138 irgen::assertTypeLocation(m_hts, lt.location, t);
139 m_curBlock->addPredicted(m_sk, RegionDesc::TypePred{lt.location, t});
140 } else {
141 irgen::guardTypeLocation(m_hts, lt.location, t, true /* outerOnly */);
145 while (true) {
146 assert(m_numBCInstrs <= RuntimeOption::EvalJitMaxRegionInstrs);
147 if (m_numBCInstrs == RuntimeOption::EvalJitMaxRegionInstrs) {
148 FTRACE(1, "selectTracelet: breaking region due to size limit ({})\n",
149 m_numBCInstrs);
150 break;
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
167 // tracelet.
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;
185 continue;
188 auto const inlineReturn = irgen::isInlining(m_hts) &&
189 (isRet(m_inst.op()) || m_inst.op() == OpNativeImpl);
190 try {
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);
197 m_region.reset();
198 break;
201 // If we just translated a return from an inlined call, grab the updated
202 // SrcKey from m_ht and clean up.
203 if (inlineReturn) {
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;
208 continue;
211 // We successfully translated the instruction, so update m_sk.
212 m_sk.advance(m_curBlock->unit());
214 auto const endsRegion = m_inst.endsRegion;
216 if (endsRegion) {
217 FTRACE(1, "selectTracelet: tracelet broken after {}\n", m_inst);
218 break;
219 } else {
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).
227 if (doPrediction &&
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());
242 m_inl.disable();
243 m_region.reset();
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());
250 printUnit(
251 kTraceletLevel, m_hts.irb->unit(),
252 m_inl.depth() || m_inl.disabled() ? " after inlining tracelet formation "
253 : " after tracelet formation ",
254 nullptr,
255 m_hts.irb->guards()
258 recordDependencies();
259 truncateLiterals();
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(
287 ii.loc,
288 irgen::predictedTypeFromLocation(m_hts, ii.loc)
290 FTRACE(2, "predictedTypeFromLocation: {} -> {}\n",
291 ii.loc.pretty(), dl->rtt);
292 return dl;
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 ...
299 annotate(&m_inst);
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);
307 return false;
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);
317 try {
318 m_inst.preppedByRef = m_arStates.back().checkByRef(argNum, entryArDelta,
319 &m_refDeps);
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",
323 m_inst.toString());
324 return false;
326 addInstruction();
327 m_curBlock->setParamByRef(m_inst.source, m_inst.preppedByRef);
328 } else {
329 addInstruction();
332 if (isFPush(m_inst.op())) m_arStates.back().pushFunc(m_inst);
334 return true;
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();
353 m_numBCInstrs++;
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())) {
369 return false;
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) {
380 return false;
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)) {
387 return false;
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);
395 } else {
396 auto value = irgen::popC(m_hts);
397 auto taken =
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());
406 m_numJmps++;
407 m_blockFinished = true;
408 return 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());
416 instrSize = 0;
418 if (!m_inl.canInlineAt(m_inst.source, m_inst.funcd, *m_region)) {
419 return false;
422 auto refuse = [this](const std::string& str) {
423 FTRACE(2, "selectTracelet not inlining {}: {}\n",
424 m_inst.toString(), str);
425 return false;
428 auto callee = m_inst.funcd;
430 // Make sure the FPushOp wasn't interpreted.
431 auto spillFrame = findSpillFrame(m_hts.irb->sp());
432 if (!spillFrame) {
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
440 // the callee.
441 RegionContext ctx;
442 ctx.func = callee;
443 ctx.bcOffset = callee->getEntryForNumArgs(numArgs);
444 ctx.spOffset = callee->numSlotsInFrame();
445 ctx.resumed = false;
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
454 // out as Uninit.
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 */);
461 if (!region) {
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");
473 return true;
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();
485 auto endSk = sk;
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;
491 endSk = sk;
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.
511 return false;
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());
518 return false;
521 if (!rtt.isBoxed() || m_inst.ignoreInnerType || ii.dontGuardInner) {
522 return true;
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());
529 return false;
532 return true;
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
538 * to relaxGuards.
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,
546 dep.second.m_vals,
547 dep.first});
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;
556 if (doRelax) {
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.
573 return;
575 oldTy &= type;
578 for (auto& kv : guardMap) {
579 if (kv.second == Type::Gen) {
580 // Guard was relaxed to Gen---don't record it.
581 continue;
583 auto const pred = RegionDesc::TypePred { kv.first, kv.second };
584 FTRACE(1, "selectTracelet adding guard {}\n", show(pred));
585 firstBlock.addPredicted(blockStart, pred);
588 if (changed) {
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);
601 InterpSet interp;
602 RegionDescPtr region;
603 uint32_t tries = 1;
605 InliningDecider inl(ctx.func);
606 if (!allowInlining) inl.disable();
608 while (!(region = RegionFormer(ctx, interp, inl, profiling).go())) {
609 ++tries;
610 inl.resetState();
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());
625 return region;