Codemod asserts to assertxs in the runtime
[hiphop-php.git] / hphp / runtime / vm / jit / region-tracelet.cpp
blobda8cec2efb98e8f98122e18ca5ab79bb27b9f7ee
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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>
35 #include <algorithm>
36 #include <vector>
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;
51 namespace {
53 ///////////////////////////////////////////////////////////////////////////////
55 constexpr int MaxJmpsTracedThrough = 5;
57 struct Env {
58 Env(const RegionContext& ctx,
59 TransKind kind,
60 InterpSet& interp,
61 SrcKey& breakAt,
62 int32_t maxBCInstrs,
63 bool inlining)
64 : ctx(ctx)
65 , interp(interp)
66 , breakAt(breakAt)
67 , sk{ctx.func, ctx.bcOffset, ctx.resumeMode, ctx.hasThis}
68 , startSk(sk)
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})
75 , irgs(unit, nullptr)
76 , arStates(1)
77 , numJmps(0)
78 , numBCInstrs(maxBCInstrs)
79 , profiling(kind == TransKind::Profile)
80 , inlining(inlining)
82 if (RuntimeOption::EvalRegionRelaxGuards) {
83 irgs.irb->enableConstrainGuards();
87 const RegionContext& ctx;
88 InterpSet& interp;
89 SrcKey& breakAt;
90 SrcKey sk;
91 const SrcKey startSk;
92 NormalizedInstruction inst;
93 RegionDescPtr region;
94 RegionDesc::Block* curBlock;
95 bool blockFinished;
96 IRUnit unit;
97 irgen::IRGS irgs;
98 jit::vector<ActRecState> arStates;
99 RefDeps refDeps;
100 uint32_t numJmps;
101 int32_t numBCInstrs;
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;
108 const bool inlining;
110 private:
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())) {
134 result = true;
135 break;
138 env.irReachableBlocks[blockId] = result;
139 return 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 &&
151 * FIXME: T21872803:
152 * This check is only intended for profiling translations. We enabled it
153 * for live translations to avoid a bug tracking type dependences for
154 * boxed values. */
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.
159 return false;
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());
166 return false;
169 if (!(type <= TBoxedCell) ||
170 env.inst.ignoreInnerType ||
171 input.dontGuardInner) {
172 return true;
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));
179 return false;
182 return true;
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();
201 env.numBCInstrs--;
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) {
213 return false;
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 ...
227 annotate(&env.inst);
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));
234 return false;
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
243 // different place.
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();
251 try {
252 env.inst.preppedByRef =
253 env.arStates.back().checkByRef(argNum, entryArDelta, &env.refDeps,
254 env.ctx);
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());
259 return false;
261 addInstruction(env);
262 env.curBlock->setParamByRef(env.inst.source, env.inst.preppedByRef);
263 } else {
264 addInstruction(env);
267 if (isFPush(env.inst.op())) env.arStates.back().pushFunc(env.inst);
269 return true;
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())) {
278 return false;
281 // We want to keep profiling translations to basic blocks, inlining shouldn't
282 // happen in profiling translations
283 if (env.profiling) {
284 assertx(!env.inlining);
285 return false;
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) {
292 return false;
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)) {
299 return false;
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);
307 } else {
308 auto value = irgen::popC(env.irgs);
309 auto taken =
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());
318 env.numJmps++;
319 env.blockFinished = true;
320 return true;
323 bool isLiteral(Op op) {
324 switch (op) {
325 case OpNull:
326 case OpNullUninit:
327 case OpTrue:
328 case OpFalse:
329 case OpInt:
330 case OpDouble:
331 case OpString:
332 case OpArray:
333 case OpDict:
334 case OpVec:
335 case OpKeyset:
336 return true;
338 default:
339 return false;
343 bool isThisSelfOrParent(Op op) {
344 switch (op) {
345 case OpThis:
346 case OpSelf:
347 case OpParent:
348 return true;
350 default:
351 return false;
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.
359 template<typename F>
360 void visitGuards(IRUnit& unit, F func) {
361 auto blocks = rpoSortCfg(unit);
363 for (auto const block : blocks) {
364 for (auto const& inst : *block) {
365 switch (inst.op()) {
366 case EndGuards:
367 return;
368 case HintLocInner:
369 case CheckLoc:
370 func(&inst,
371 Location::Local{inst.extra<LocalId>()->locId},
372 inst.typeParam(),
373 inst.is(HintLocInner));
374 break;
375 case HintStkInner:
376 case CheckStk: {
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;
383 func(&inst,
384 Location::Stack{irSPRel.to<FPInvOffset>(irSPOff)},
385 inst.typeParam(),
386 inst.is(HintStkInner));
387 break;
389 case HintMBaseInner:
390 case CheckMBase:
391 func(&inst, Location::MBase{}, inst.typeParam(),
392 inst.is(HintMBaseInner));
393 break;
394 default: break;
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,
409 dep.first});
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,
422 const Location& loc,
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));
434 if (ret.second) {
435 FTRACE(1, "selectTracelet saving prediction for PseudoMain {}\n",
436 show(RegionDesc::TypedLocation {loc, type}));
437 } else {
438 auto& oldTy = ret.first->second;
439 oldTy &= type;
442 if (inret.second) {
443 if (!hint) {
444 catMap[loc] = folly::get_default(guards, guard).category;
446 return;
448 auto& oldTy = inret.first->second;
449 oldTy &= type;
450 if (!hint) {
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).
471 continue;
473 auto const preCond = RegionDesc::GuardedLocation {
474 kv.first, kv.second,
475 catMap[kv.first]
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();
497 auto endSk = sk;
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;
503 endSk = sk;
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) {
525 auto t = lt.type;
526 assertx(t <= TGen);
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");
538 break;
541 if (!firstInst && env.sk == env.breakAt) {
542 FTRACE(1, "selectTracelet: breaking region at breakAt: {}\n",
543 show(env.sk));
544 break;
547 // Break translation if there's already a translation starting at the
548 // current SrcKey.
549 if (!firstInst) {
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",
553 show(env.sk));
554 break;
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);
566 try {
567 translateInstr(env.irgs, env.inst, true /* checkOuterTypeOnly */,
568 firstInst);
569 } catch (const FailedIRGen& exn) {
570 FTRACE(1, "ir generation for {} failed with {}\n",
571 env.inst.toString(), exn.what());
572 always_assert_flog(
573 !env.interp.count(env.sk),
574 "Double PUNT trying to translate {}\n", env.inst
576 env.interp.insert(env.sk);
577 env.region.reset();
578 break;
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);
586 break;
589 // We successfully translated the instruction, so update env.sk.
590 env.sk.advance(env.curBlock->unit());
592 auto const endsRegion = env.inst.endsRegion;
594 if (endsRegion) {
595 FTRACE(1, "selectTracelet: tracelet broken after {}\n", env.inst);
596 break;
597 } else {
598 assertx(env.sk.func() == curFunc(env));
601 auto const curIRBlock = env.irgs.irb->curBlock();
602 if (!irBlockReachable(env, curIRBlock)) {
603 FTRACE(1,
604 "selectTracelet: tracelet broken due "
605 "to unreachable code (block {})\n",
606 curIRBlock->id());
607 break;
610 if (curIRBlock->isExitNoThrow()) {
611 FTRACE(1, "selectTracelet: tracelet broken due to exiting IR instruction:"
612 "{}\n", curIRBlock->back());
613 break;
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",
619 numGuards);
620 break;
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);
630 printUnit(
631 kTraceletLevel, env.irgs.irb->unit(),
632 env.inlining ? " after inlining tracelet formation "
633 : " after tracelet formation ",
634 nullptr,
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
653 * failed AssertRAT).
655 return !mainExit->back().is(Unreachable);
656 }();
658 if (truncate) {
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);
672 InterpSet interp;
673 SrcKey breakAt;
674 RegionDescPtr region;
675 uint32_t tries = 0;
677 FTRACE(1, "selectTracelet: starting with maxBCInstrs = {}\n", maxBCInstrs);
679 do {
680 Env env{ctx, kind, interp, breakAt, maxBCInstrs, inlining};
681 region = form_region(env);
682 ++tries;
683 } while (!region);
685 if (region->empty() || region->blocks().front()->length() == 0) {
686 FTRACE(1, "selectTracelet giving up after {} tries\n", tries);
687 return nullptr;
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",
698 show(*region));
699 optimizeGuards(*region, kind == TransKind::Profile);
702 FTRACE(1, "selectTracelet returning, {}, {} tries:\n{}\n",
703 inlining ? "inlining" : "not inlining", tries, show(*region));
704 return region;