Clean up irgen.h a bit
[hiphop-php.git] / hphp / runtime / vm / jit / region-tracelet.cpp
blobcdc936958dc1e3f71b3d744c29cc0905a48fc075
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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>
34 #include <algorithm>
35 #include <vector>
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;
50 namespace {
52 ///////////////////////////////////////////////////////////////////////////////
54 struct Env {
55 Env(const RegionContext& ctx,
56 TransKind kind,
57 InterpSet& interp,
58 SrcKey& breakAt,
59 int32_t maxBCInstrs,
60 bool inlining)
61 : ctx(ctx)
62 , interp(interp)
63 , breakAt(breakAt)
64 , sk(ctx.func, ctx.bcOffset, ctx.resumed)
65 , startSk(sk)
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})
72 , irgs(unit)
73 , arStates(1)
74 , numJmps(0)
75 , numBCInstrs(maxBCInstrs)
76 , profiling(kind == TransKind::Profile)
77 , inlining(inlining)
80 const RegionContext& ctx;
81 InterpSet& interp;
82 SrcKey& breakAt;
83 SrcKey sk;
84 const SrcKey startSk;
85 NormalizedInstruction inst;
86 RegionDescPtr region;
87 RegionDesc::Block* curBlock;
88 bool blockFinished;
89 IRUnit unit;
90 IRGS irgs;
91 jit::vector<ActRecState> arStates;
92 RefDeps refDeps;
93 uint32_t numJmps;
94 int32_t numBCInstrs;
95 SrcKey minstrStart;
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;
102 const bool inlining;
104 private:
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())) {
128 result = true;
129 break;
132 env.irReachableBlocks[blockId] = result;
133 return 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.
148 return false;
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());
155 return false;
158 if (!(type <= TBoxedCell) || env.inst.ignoreInnerType || ii.dontGuardInner) {
159 return true;
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());
166 return false;
169 return true;
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();
188 env.numBCInstrs--;
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) {
200 return false;
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 ...
216 annotate(&env.inst);
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);
224 return false;
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
233 // different place.
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();
239 try {
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());
246 return false;
248 addInstruction(env);
249 env.curBlock->setParamByRef(env.inst.source, env.inst.preppedByRef);
250 } else {
251 addInstruction(env);
254 if (isFPush(env.inst.op())) env.arStates.back().pushFunc(env.inst);
256 return true;
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())) {
265 return false;
268 // We want to keep profiling translations to basic blocks, inlining shouldn't
269 // happen in profiling translations
270 if (env.profiling) {
271 assert(!env.inlining);
272 return false;
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) {
279 return false;
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)) {
286 return false;
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);
294 } else {
295 auto value = irgen::popC(env.irgs);
296 auto taken =
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());
305 env.numJmps++;
306 env.blockFinished = true;
307 return true;
310 bool isLiteral(Op op) {
311 switch (op) {
312 case OpNull:
313 case OpNullUninit:
314 case OpTrue:
315 case OpFalse:
316 case OpInt:
317 case OpDouble:
318 case OpString:
319 case OpArray:
320 return true;
322 default:
323 return false;
327 bool isThisSelfOrParent(Op op) {
328 switch (op) {
329 case OpThis:
330 case OpSelf:
331 case OpParent:
332 return true;
334 default:
335 return false;
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.
343 template<typename F>
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) {
349 switch (inst.op()) {
350 case EndGuards:
351 return;
352 case HintLocInner:
353 case CheckLoc:
354 func(&inst,
355 L::Local{inst.extra<LocalId>()->locId},
356 inst.typeParam(),
357 inst.is(HintLocInner));
358 break;
359 case HintStkInner:
360 case CheckStk:
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());
371 func(&inst,
372 L::Stack{offsetFromFP},
373 inst.typeParam(),
374 inst.is(HintStkInner));
375 break;
377 default: break;
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,
392 dep.first});
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));
410 if (inret.second) {
411 if (!hint) {
412 catMap[loc] = folly::get_default(guards, guard).category;
414 return;
416 auto& oldTy = inret.first->second;
417 oldTy &= type;
418 if (!hint) {
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 {
433 hint_it->first,
434 hint_it->second
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).
442 continue;
444 auto const preCond = RegionDesc::GuardedLocation { kv.first, kv.second,
445 catMap[kv.first] };
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();
460 auto endSk = sk;
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;
466 endSk = sk;
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) {
486 auto t = lt.type;
487 if (t <= TCls) {
488 irgen::assertTypeLocation(env.irgs, lt.location, t);
489 env.curBlock->addPreCondition({lt.location, t, DataTypeGeneric});
490 } else {
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");
502 break;
505 if (!firstInst && env.sk == env.breakAt) {
506 FTRACE(1, "selectTracelet: breaking region at breakAt: {}\n",
507 show(env.sk));
508 break;
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);
529 try {
530 translateInstr(env.irgs, env.inst, true /* checkOuterTypeOnly */,
531 firstInst);
532 } catch (const FailedIRGen& exn) {
533 FTRACE(1, "ir generation for {} failed with {}\n",
534 env.inst.toString(), exn.what());
535 always_assert_flog(
536 !env.interp.count(env.sk),
537 "Double PUNT trying to translate {}\n", env.inst
539 env.interp.insert(env.sk);
540 env.region.reset();
541 break;
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);
549 break;
552 // We successfully translated the instruction, so update env.sk.
553 env.sk.advance(env.curBlock->unit());
555 auto const endsRegion = env.inst.endsRegion;
557 if (endsRegion) {
558 FTRACE(1, "selectTracelet: tracelet broken after {}\n", env.inst);
559 break;
560 } else {
561 assertx(env.sk.func() == curFunc(env));
564 auto const curIRBlock = env.irgs.irb->curBlock();
565 if (!irBlockReachable(env, curIRBlock)) {
566 FTRACE(1,
567 "selectTracelet: tracelet broken due to unreachable code (block {})\n",
568 curIRBlock->id());
569 break;
572 if (curIRBlock->isExitNoThrow()) {
573 FTRACE(1, "selectTracelet: tracelet broken due to exiting IR instruction:"
574 "{}\n", curIRBlock->back());
575 break;
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;
584 env.region.reset();
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);
591 printUnit(
592 kTraceletLevel, env.irgs.irb->unit(),
593 env.inlining ? " after inlining tracelet formation "
594 : " after tracelet formation ",
595 nullptr,
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);
623 InterpSet interp;
624 SrcKey breakAt;
625 RegionDescPtr region;
626 uint32_t tries = 0;
628 FTRACE(1, "selectTracelet: starting with maxBCInstrs = {}\n", maxBCInstrs);
630 do {
631 Env env{ctx, kind, interp, breakAt, maxBCInstrs, inlining};
632 region = form_region(env);
633 ++tries;
634 } while (!region);
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",
649 show(*region));
650 optimizeGuards(*region, kind == TransKind::Profile);
653 FTRACE(1, "selectTracelet returning, {}, {} tries:\n{}\n",
654 inlining ? "inlining" : "not inlining", tries, show(*region));
655 return region;