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/ir-builder.h"
21 #include <folly/ScopeGuard.h>
23 #include "hphp/util/trace.h"
24 #include "hphp/runtime/vm/jit/ir-unit.h"
25 #include "hphp/runtime/vm/jit/guard-relaxation.h"
26 #include "hphp/runtime/vm/jit/mutation.h"
27 #include "hphp/runtime/vm/jit/timer.h"
28 #include "hphp/runtime/vm/jit/translator.h"
29 #include "hphp/runtime/vm/jit/print.h"
30 #include "hphp/runtime/vm/jit/punt.h"
31 #include "hphp/runtime/base/rds.h"
32 #include "hphp/runtime/vm/jit/analysis.h"
33 #include "hphp/util/assertions.h"
35 namespace HPHP
{ namespace jit
{
42 //////////////////////////////////////////////////////////////////////
45 const typename
M::mapped_type
& get_required(const M
& m
,
46 typename
M::key_type key
) {
47 auto it
= m
.find(key
);
48 always_assert(it
!= m
.end());
52 SSATmp
* fwdGuardSource(IRInstruction
* inst
) {
53 if (inst
->is(AssertType
, CheckType
)) return inst
->src(0);
55 assert(inst
->is(AssertLoc
, CheckLoc
, AssertStk
, CheckStk
));
60 //////////////////////////////////////////////////////////////////////
64 IRBuilder::IRBuilder(IRUnit
& unit
, BCMarker initMarker
)
66 , m_initialMarker(initMarker
)
67 , m_curMarker(initMarker
)
69 , m_curBlock(m_unit
.entry())
70 , m_constrainGuards(shouldHHIRRelaxGuards())
72 m_state
.setBuilding();
73 if (RuntimeOption::EvalHHIRGenOpts
) {
74 m_enableCse
= false; // CSE is disabled at gen time.
75 m_enableSimplification
= RuntimeOption::EvalHHIRSimplification
;
80 * Returns whether or not the given value might have its type relaxed by guard
81 * relaxation. If tmp is null, only conditions that apply to all values are
84 bool IRBuilder::typeMightRelax(SSATmp
* tmp
/* = nullptr */) const {
85 return shouldConstrainGuards() && jit::typeMightRelax(tmp
);
88 void IRBuilder::appendInstruction(IRInstruction
* inst
) {
89 FTRACE(1, " append {}\n", inst
->toString());
91 if (shouldConstrainGuards()) {
92 // If we're constraining guards, some instructions need certain information
93 // to be recorded in side tables.
94 if (inst
->is(AssertLoc
, CheckLoc
, LdLoc
)) {
95 auto const locId
= inst
->extra
<LocalId
>()->locId
;
96 m_constraints
.typeSrcs
[inst
] = localTypeSources(locId
);
97 if (inst
->is(AssertLoc
, CheckLoc
)) {
98 m_constraints
.prevTypes
[inst
] = localType(locId
, DataTypeGeneric
);
101 if (inst
->is(AssertStk
, CheckStk
, LdStk
)) {
102 auto const offset
= inst
->extra
<StackOffset
>()->offset
;
103 m_constraints
.typeSrcs
[inst
] = stackTypeSources(offset
);
104 if (inst
->is(AssertStk
, CheckStk
)) {
105 m_constraints
.prevTypes
[inst
] = stackType(offset
, DataTypeGeneric
);
109 // And a LdRef or CheckRefInner automatically constrains the value to be a
110 // boxed cell, specifically.
111 if (inst
->is(LdRef
, CheckRefInner
)) {
112 constrainValue(inst
->src(0), DataTypeSpecific
);
115 // In psuedomains we have to pre-constrain local guards, because we don't
116 // ever actually generate code that will constrain them otherwise.
117 // (Because of the LdLocPseudoMain stuff.)
118 if (inst
->marker().func()->isPseudoMain() &&
119 inst
->is(GuardLoc
, CheckLoc
)) {
120 constrainGuard(inst
, DataTypeSpecific
);
124 auto where
= m_curBlock
->end();
126 // If the block isn't empty, check if we need to create a new block.
127 if (where
!= m_curBlock
->begin()) {
130 auto& prev
= *prevIt
;
132 if (prev
.isBlockEnd()) {
133 assert(where
== m_curBlock
->end());
135 auto oldBlock
= m_curBlock
;
137 // First make the inst's next block, so we can save state to it in
139 m_curBlock
= m_unit
.defBlock();
140 if (!prev
.isTerminal()) {
141 // New block is reachable from old block so link it.
142 prev
.setNext(m_curBlock
);
143 m_curBlock
->setHint(prev
.block()->hint());
146 m_state
.finishBlock(oldBlock
);
148 m_state
.startBlock(m_curBlock
, inst
->marker());
149 where
= m_curBlock
->begin();
151 FTRACE(2, "lazily adding B{}\n", m_curBlock
->id());
156 assert(IMPLIES(inst
->isBlockEnd(), where
== m_curBlock
->end()) &&
157 "Can't insert a BlockEnd instruction in the middle of a block");
158 if (do_assert
&& where
!= m_curBlock
->begin()) {
159 UNUSED
auto prevIt
= where
;
161 assert(!prevIt
->isBlockEnd() &&
162 "Can't append an instruction after a BlockEnd instruction");
165 assert(inst
->marker().valid());
166 if (!inst
->is(Nop
, DefConst
)) {
167 where
= m_curBlock
->insert(where
, inst
);
171 m_state
.update(inst
);
175 void IRBuilder::appendBlock(Block
* block
) {
176 m_state
.finishBlock(m_curBlock
);
178 FTRACE(2, "appending B{}\n", block
->id());
179 // Load up the state for the new block.
180 m_state
.startBlock(block
, m_curMarker
);
184 void IRBuilder::setGuardFailBlock(Block
* block
) {
185 m_guardFailBlock
= block
;
188 void IRBuilder::resetGuardFailBlock() {
189 m_guardFailBlock
= nullptr;
192 Block
* IRBuilder::guardFailBlock() const {
193 return m_guardFailBlock
;
196 //////////////////////////////////////////////////////////////////////
198 SSATmp
* IRBuilder::preOptimizeCheckTypeOp(IRInstruction
* inst
, Type oldType
) {
199 auto const typeParam
= inst
->typeParam();
201 if (!oldType
.maybe(typeParam
)) {
202 /* This check will always fail. It's probably due to an incorrect
203 * prediction. Generate a Jmp and return the src. The fact that the type
204 * will be slightly off is ok because all the code after the Jmp is
206 gen(Jmp
, inst
->taken());
207 return fwdGuardSource(inst
);
210 auto const newType
= oldType
& inst
->typeParam();
212 if (oldType
<= newType
) {
213 /* The type of the src is the same or more refined than type, so the guard
215 return fwdGuardSource(inst
);
221 SSATmp
* IRBuilder::preOptimizeCheckType(IRInstruction
* inst
) {
222 return preOptimizeCheckTypeOp(inst
, inst
->src(0)->type());
225 SSATmp
* IRBuilder::preOptimizeCheckStk(IRInstruction
* inst
) {
226 auto const offset
= inst
->extra
<CheckStk
>()->offset
;
228 if (auto const prevValue
= stackValue(offset
, DataTypeGeneric
)) {
229 gen(CheckType
, inst
->typeParam(), inst
->taken(), prevValue
);
230 inst
->convertToNop();
234 return preOptimizeCheckTypeOp(
236 stackType(offset
, DataTypeGeneric
)
240 SSATmp
* IRBuilder::preOptimizeCheckLoc(IRInstruction
* inst
) {
241 auto const locId
= inst
->extra
<CheckLoc
>()->locId
;
243 if (auto const prevValue
= localValue(locId
, DataTypeGeneric
)) {
244 gen(CheckType
, inst
->typeParam(), inst
->taken(), prevValue
);
245 inst
->convertToNop();
249 return preOptimizeCheckTypeOp(
251 localType(locId
, DataTypeGeneric
)
255 SSATmp
* IRBuilder::preOptimizeHintLocInner(IRInstruction
* inst
) {
256 auto const locId
= inst
->extra
<HintLocInner
>()->locId
;
257 if (!(localType(locId
, DataTypeGeneric
) <= Type::BoxedCell
) ||
258 predictedInnerType(locId
).box() <= inst
->typeParam()) {
259 inst
->convertToNop();
265 SSATmp
* IRBuilder::preOptimizeAssertTypeOp(IRInstruction
* inst
,
268 const IRInstruction
* typeSrc
) {
269 ITRACE(3, "preOptimizeAssertTypeOp({}, {}, {}, {})\n",
271 oldVal
? oldVal
->toString() : "nullptr",
272 typeSrc
? typeSrc
->toString() : "nullptr");
273 auto const typeParam
= inst
->typeParam();
275 if (!oldType
.maybe(typeParam
)) {
276 // If both types are boxed this is ok and even expected as a means to
277 // update the hint for the inner type.
278 if (oldType
.isBoxed() && typeParam
.isBoxed()) return nullptr;
280 // We got external information (probably from static analysis) that
281 // conflicts with what we've built up so far. There's no reasonable way to
282 // continue here: we can't properly fatal the request because we can't make
283 // a catch trace or SpillStack without HhbcTranslator, we can't punt on
284 // just this instruction because we might not be in the initial translation
285 // phase, and we can't just plow on forward since we'll probably generate
286 // malformed IR. Since this case is very rare, just punt on the whole trace
287 // so it gets interpreted.
288 TRACE_PUNT("Invalid AssertTypeOp");
291 // Asserting in these situations doesn't add any information.
292 if (typeParam
== Type::Cls
&& oldType
<= Type::Cls
) return inst
->src(0);
293 if (typeParam
== Type::Gen
&& oldType
<= Type::Gen
) return inst
->src(0);
295 auto const newType
= oldType
& typeParam
;
297 if (oldType
<= newType
) {
298 // oldType is at least as good as the new type. Eliminate this
299 // AssertTypeOp, but only if the src type won't relax, or the source value
300 // is another assert that's good enough. We do this to avoid eliminating
301 // apparently redundant assert opcodes that may become useful after prior
302 // guards are relaxed.
303 if (!typeMightRelax(oldVal
) ||
304 (typeSrc
&& typeSrc
->is(AssertType
, AssertLoc
, AssertStk
) &&
305 typeSrc
->typeParam() <= inst
->typeParam())) {
306 return fwdGuardSource(inst
);
309 if (oldType
< newType
) {
310 // This can happen because of limitations in how Type::operator& (used in
311 // refinedType()) handles specialized types: sometimes it returns a Type
312 // that's wider than it could be. It shouldn't affect correctness but it
313 // can cause us to miss out on some perf.
314 ITRACE(1, "Suboptimal AssertTypeOp: refineType({}, {}) -> {} in {}\n",
315 oldType
, typeParam
, newType
, *inst
);
317 // We don't currently support intersecting RepoAuthType::Arrays
318 // (t4473238), so we might be here because oldType and typeParam have
319 // different RATArrays. If that's the case and typeParam provides no
320 // other useful information we can unconditionally eliminate this
321 // instruction: RATArrays never come from guards so we can't miss out on
322 // anything by doing so.
323 if (oldType
< Type::Arr
&&
324 oldType
.arrSpec().type() &&
325 typeParam
< Type::Arr
&&
326 typeParam
.arrSpec().type() &&
327 !typeParam
.arrSpec().kind()) {
328 return fwdGuardSource(inst
);
336 SSATmp
* IRBuilder::preOptimizeAssertType(IRInstruction
* inst
) {
337 auto const src
= inst
->src(0);
338 return preOptimizeAssertTypeOp(inst
, src
->type(), src
, src
->inst());
341 static const IRInstruction
* singleTypeSrcInst(const TypeSourceSet
& typeSrcs
) {
342 if (typeSrcs
.size() == 1) {
343 auto typeSrc
= *typeSrcs
.begin();
344 return typeSrc
.isValue() ? typeSrc
.value
->inst() :
345 typeSrc
.isGuard() ? typeSrc
.guard
:
351 // TODO(#5868904): almost the same as AssertLoc now; combine
352 SSATmp
* IRBuilder::preOptimizeAssertStk(IRInstruction
* inst
) {
353 auto const offset
= inst
->extra
<AssertStk
>()->offset
;
355 if (auto const prevValue
= stackValue(offset
, DataTypeGeneric
)) {
356 gen(AssertType
, inst
->typeParam(), prevValue
);
357 inst
->convertToNop();
361 // If the value has a single type-source instruction, pass it along to
362 // preOptimizeAssertTypeOp, which may be able to use it to optimize away the
364 auto const typeSrcInst
= singleTypeSrcInst(stackTypeSources(offset
));
366 return preOptimizeAssertTypeOp(
368 stackType(offset
, DataTypeGeneric
),
369 stackValue(offset
, DataTypeGeneric
),
374 SSATmp
* IRBuilder::preOptimizeAssertLoc(IRInstruction
* inst
) {
375 auto const locId
= inst
->extra
<AssertLoc
>()->locId
;
377 if (auto const prevValue
= localValue(locId
, DataTypeGeneric
)) {
378 gen(AssertType
, inst
->typeParam(), prevValue
);
379 inst
->convertToNop();
383 // If the local has a single type-source instruction, pass it along
384 // to preOptimizeAssertTypeOp, which may be able to use it to
385 // optimize away the AssertLoc.
386 auto const typeSrcInst
= singleTypeSrcInst(localTypeSources(locId
));
388 return preOptimizeAssertTypeOp(
390 localType(locId
, DataTypeGeneric
),
391 localValue(locId
, DataTypeGeneric
),
396 SSATmp
* IRBuilder::preOptimizeCheckCtxThis(IRInstruction
* inst
) {
397 if (m_state
.thisAvailable()) inst
->convertToNop();
401 SSATmp
* IRBuilder::preOptimizeLdCtx(IRInstruction
* inst
) {
402 auto const fpInst
= inst
->src(0)->inst();
404 // Change LdCtx in static functions to LdCctx, or if we're inlining try to
405 // fish out a constant context.
406 auto const func
= inst
->marker().func();
407 if (func
->isStatic()) {
408 if (fpInst
->is(DefInlineFP
)) {
409 auto const ctx
= fpInst
->extra
<DefInlineFP
>()->ctx
;
410 if (ctx
->isConst(Type::Cls
)) {
411 inst
->convertToNop();
412 return m_unit
.cns(ConstCctx::cctx(ctx
->clsVal()));
416 // ActRec->m_cls of a static function is always a valid class pointer with
417 // the bottom bit set
418 auto const src
= inst
->src(0);
419 inst
->convertToNop();
420 return gen(LdCctx
, src
);
423 if (fpInst
->is(DefInlineFP
)) {
424 // TODO(#5623596): this optimization required for correctness in refcount
426 // check that we haven't nuked the SSATmp
427 if (!m_state
.frameMaySpanCall()) {
428 auto const ctx
= fpInst
->extra
<DefInlineFP
>()->ctx
;
429 if (ctx
->isA(Type::Obj
)) return ctx
;
435 SSATmp
* IRBuilder::preOptimizeDecRefThis(IRInstruction
* inst
) {
437 * If $this is available, convert to an instruction sequence that doesn't
438 * need to test $this is available. (Hopefully we CSE the load, too.)
440 if (thisAvailable()) {
441 auto const ctx
= gen(LdCtx
, m_state
.fp());
442 auto const thiss
= gen(CastCtxThis
, ctx
);
444 inst
->convertToNop();
450 SSATmp
* IRBuilder::preOptimizeLdLoc(IRInstruction
* inst
) {
451 auto const locId
= inst
->extra
<LdLoc
>()->locId
;
452 if (auto tmp
= localValue(locId
, DataTypeGeneric
)) return tmp
;
454 auto const type
= localType(locId
, DataTypeGeneric
);
456 // The types may not be compatible in the presence of unreachable code.
457 // Unreachable code elimination will take care of it later.
458 if (!type
.maybe(inst
->typeParam())) {
459 inst
->setTypeParam(Type::Bottom
);
462 // If FrameStateMgr's type isn't as good as the type param, we're missing
463 // information in the IR.
464 assert(inst
->typeParam() >= type
);
465 inst
->setTypeParam(std::min(type
, inst
->typeParam()));
467 if (typeMightRelax()) return nullptr;
468 if (inst
->typeParam().isConst()) {
469 return m_unit
.cns(inst
->typeParam());
475 SSATmp
* IRBuilder::preOptimizeStLoc(IRInstruction
* inst
) {
476 // Guard relaxation might change the current local type, so don't try to
477 // change to StLocNT until after relaxation happens.
478 if (typeMightRelax()) return nullptr;
480 auto locId
= inst
->extra
<StLoc
>()->locId
;
481 auto const curType
= localType(locId
, DataTypeGeneric
);
482 auto const newType
= inst
->src(1)->type();
485 * There's no need to store the type if it's going to be the same
486 * KindOfFoo. We'll still have to store string types because we
487 * aren't specific about storing KindOfStaticString
488 * vs. KindOfString, and a Type::Null might mean KindOfUninit or
491 auto const bothBoxed
= curType
.isBoxed() && newType
.isBoxed();
492 auto const sameUnboxed
= [&] {
493 auto avoidable
= { Type::Uninit
,
502 for (auto t
: avoidable
) {
503 if (curType
<= t
&& newType
<= t
) return true;
507 if (bothBoxed
|| sameUnboxed()) {
508 inst
->setOpcode(StLocNT
);
514 SSATmp
* IRBuilder::preOptimizeCastStk(IRInstruction
* inst
) {
515 auto const curType
= stackType(
516 inst
->extra
<CastStk
>()->offset
,
519 auto const curVal
= stackValue(
520 inst
->extra
<CastStk
>()->offset
,
523 if (typeMightRelax(curVal
)) return nullptr;
524 if (inst
->typeParam() == Type::NullableObj
&& curType
<= Type::Null
) {
525 // If we're casting Null to NullableObj, we still need to call
526 // tvCastToNullableObjectInPlace. See comment there and t3879280 for
530 if (curType
<= inst
->typeParam()) {
531 inst
->convertToNop();
537 SSATmp
* IRBuilder::preOptimizeCoerceStk(IRInstruction
* inst
) {
538 auto const curType
= stackType(
539 inst
->extra
<CoerceStk
>()->offset
,
542 auto const curVal
= stackValue(
543 inst
->extra
<CoerceStk
>()->offset
,
546 if (typeMightRelax(curVal
)) return nullptr;
547 if (curType
<= inst
->typeParam()) {
548 inst
->convertToNop();
554 SSATmp
* IRBuilder::preOptimizeLdStk(IRInstruction
* inst
) {
555 auto const offset
= inst
->extra
<LdStk
>()->offset
;
556 if (auto tmp
= stackValue(offset
, DataTypeGeneric
)) {
560 // The types may not be compatible in the presence of unreachable code.
561 // Don't try to optimize the code in this case, and just let
562 // unreachable code elimination take care of it later.
563 auto const type
= stackType(offset
, DataTypeGeneric
);
564 if (!type
.maybe(inst
->typeParam())) return nullptr;
565 inst
->setTypeParam(std::min(type
, inst
->typeParam()));
567 if (typeMightRelax()) return nullptr;
568 if (inst
->typeParam().isConst()) {
569 return m_unit
.cns(inst
->typeParam());
575 SSATmp
* IRBuilder::preOptimize(IRInstruction
* inst
) {
576 #define X(op) case op: return preOptimize##op(inst);
577 switch (inst
->op()) {
599 //////////////////////////////////////////////////////////////////////
602 * Performs simplification and CSE on the input instruction. If the input
603 * instruction has a dest, this will return an SSATmp that represents the same
604 * value as dst(0) of the input instruction. If the input instruction has no
605 * dest, this will return nullptr.
607 * The caller never needs to clone or append; all this has been done.
609 SSATmp
* IRBuilder::optimizeInst(IRInstruction
* inst
,
612 const folly::Optional
<IdomVector
>& idoms
) {
613 static DEBUG_ONLY __thread
int instNest
= 0;
614 if (debug
) ++instNest
;
615 SCOPE_EXIT
{ if (debug
) --instNest
; };
616 DEBUG_ONLY
auto indent
= [&] { return std::string(instNest
* 2, ' '); };
618 FTRACE(1, "optimize: {}\n", inst
->toString());
620 auto doCse
= [&] (IRInstruction
* cseInput
) -> SSATmp
* {
621 if (m_enableCse
&& cseInput
->canCSE()) {
622 if (auto const cseResult
= cseLookup(*cseInput
, srcBlock
, idoms
)) {
623 // Found a dominating instruction that can be used instead of input
624 FTRACE(1, " {}cse found: {}\n",
625 indent(), cseResult
->inst()->toString());
627 assert(!cseInput
->consumesReferences());
628 if (cseInput
->producesReference(0)) {
629 // Replace with an IncRef
630 FTRACE(1, " {}cse of refcount-producing instruction\n", indent());
631 gen(IncRef
, cseResult
);
639 auto cloneAndAppendOriginal
= [&] () -> SSATmp
* {
640 if (inst
->op() == Nop
) return nullptr;
641 if (auto cseResult
= doCse(inst
)) {
644 if (doClone
== CloneFlag::Yes
) {
645 inst
= m_unit
.cloneInstruction(inst
);
647 appendInstruction(inst
);
651 // Since some of these optimizations inspect tracked state, we don't
652 // perform any of them on non-main traces.
653 if (m_savedBlocks
.size() > 0) return cloneAndAppendOriginal();
655 // copy propagation on inst source operands
658 // First pass of IRBuilder optimizations try to replace an
659 // instruction based on tracked state before we do anything else.
660 // May mutate the IRInstruction in place (and return nullptr) or
661 // return an SSATmp*.
662 if (auto const preOpt
= preOptimize(inst
)) {
663 FTRACE(1, " {}preOptimize returned: {}\n",
664 indent(), preOpt
->inst()->toString());
667 if (inst
->op() == Nop
) return cloneAndAppendOriginal();
669 if (!m_enableSimplification
) {
670 return cloneAndAppendOriginal();
673 auto const simpResult
= simplify(m_unit
, inst
, shouldConstrainGuards());
675 // These are the possible outputs:
677 // ([], nullptr): no optimization possible. Use original inst.
679 // ([], non-nullptr): passing through a src. Don't CSE.
681 // ([X, ...], Y): throw away input instruction, append 'X, ...' (CSEing
682 // as we go), return Y.
684 if (!simpResult
.instrs
.empty()) {
685 // New instructions were generated. Append the new ones, filtering out Nops.
686 for (auto* newInst
: simpResult
.instrs
) {
687 assert(!newInst
->isTransient());
688 if (newInst
->op() == Nop
) continue;
690 auto cseResult
= doCse(newInst
);
692 appendInstruction(m_unit
.mov(newInst
->dst(), cseResult
,
695 appendInstruction(newInst
);
699 return simpResult
.dst
;
702 // No new instructions were generated. Either simplification didn't do
703 // anything, or we're using some other instruction's dst instead of our own.
705 if (simpResult
.dst
) {
706 // We're using some other instruction's output. Don't append anything, and
708 assert(simpResult
.dst
->inst() != inst
);
709 return simpResult
.dst
;
712 // No simplification happened.
713 return cloneAndAppendOriginal();
716 void IRBuilder::prepareForNextHHBC() {
718 syncedSpLevel() + m_state
.evalStack().size() - m_state
.stackDeficit() ==
721 m_exnStack
= ExnStackState
{
723 m_state
.syncedSpLevel(),
724 m_state
.stackDeficit(),
730 void IRBuilder::exceptionStackBoundary() {
732 * If this assert fires, we're trying to put things on the stack in a catch
733 * trace that the unwinder won't be able to see.
736 syncedSpLevel() + m_state
.evalStack().size() - m_state
.stackDeficit() ==
739 m_exnStack
.spOffset
= m_state
.spOffset();
740 m_exnStack
.syncedSpLevel
= m_state
.syncedSpLevel();
741 m_exnStack
.stackDeficit
= m_state
.stackDeficit();
742 m_exnStack
.evalStack
= m_state
.evalStack();
743 m_exnStack
.sp
= m_state
.sp();
747 * reoptimize() runs a trace through a second pass of IRBuilder
748 * optimizations, like this:
751 * move all blocks to a temporary list.
752 * compute immediate dominators.
753 * for each block in trace order:
754 * if we have a snapshot state for this block:
755 * clear cse entries that don't dominate this block.
756 * use snapshot state.
757 * move all instructions to a temporary list.
758 * for each instruction:
759 * optimizeWork - do CSE and simplify again
761 * append existing instruction and update state.
763 * if the instruction has a result, insert a mov from the
764 * simplified tmp to the original tmp and discard the instruction.
765 * if the last conditional branch was turned into a jump, remove the
766 * fall-through edge to the next block.
768 void IRBuilder::reoptimize() {
769 Timer
_t(Timer::optimize_reoptimize
);
770 FTRACE(5, "ReOptimize:vvvvvvvvvvvvvvvvvvvv\n");
771 SCOPE_EXIT
{ FTRACE(5, "ReOptimize:^^^^^^^^^^^^^^^^^^^^\n"); };
772 always_assert(m_savedBlocks
.empty());
774 if (splitCriticalEdges(m_unit
)) {
775 printUnit(6, m_unit
, "after splitting critical edges for reoptimize");
778 m_state
= FrameStateMgr
{m_initialMarker
};
780 m_enableCse
= RuntimeOption::EvalHHIRCse
;
781 m_enableSimplification
= RuntimeOption::EvalHHIRSimplification
;
782 if (!m_enableCse
&& !m_enableSimplification
) return;
783 setConstrainGuards(false);
785 // We need to use fixed-point for loops, otherwise legacy reoptimize will not
786 // handle the CFG's back-edges correctly.
787 auto const use_fixed_point
= RuntimeOption::EvalJitLoops
;
789 auto blocksIds
= rpoSortCfgWithIds(m_unit
);
790 auto const idoms
= findDominators(m_unit
, blocksIds
);
791 boost::dynamic_bitset
<> reachable(m_unit
.numBlocks());
792 reachable
.set(m_unit
.entry()->id());
794 if (use_fixed_point
) {
795 m_state
.computeFixedPoint(blocksIds
);
797 m_state
.setLegacyReoptimize();
800 for (auto block
: blocksIds
.blocks
) {
801 ITRACE(5, "reoptimize entering block: {}\n", block
->id());
804 // Skip block if it's unreachable.
805 if (!reachable
.test(block
->id())) continue;
807 if (use_fixed_point
) {
808 m_state
.loadBlock(block
);
810 m_state
.startBlock(block
, block
->front().marker());
814 auto nextBlock
= block
->next();
815 auto backMarker
= block
->back().marker();
816 auto instructions
= block
->moveInstrs();
817 assert(block
->empty());
818 while (!instructions
.empty()) {
819 auto* inst
= &instructions
.front();
820 instructions
.pop_front();
822 // optimizeWork below may create new instructions, and will use
823 // m_nextMarker to decide where they are. Use the marker from this
825 assert(inst
->marker().valid());
826 setCurMarker(inst
->marker());
828 auto const tmp
= optimizeInst(inst
, CloneFlag::No
, block
, idoms
);
829 SSATmp
* dst
= inst
->dst(0);
832 // The result of optimization has a different destination than the inst.
833 // Generate a mov(tmp->dst) to get result into dst.
834 assert(inst
->op() != DefLabel
);
835 assert(block
->empty() || !block
->back().isBlockEnd() || inst
->next());
836 auto src
= tmp
->inst()->is(Mov
) ? tmp
->inst()->src(0) : tmp
;
838 // If the last instruction is a guard, insert the mov on the
839 // fall-through edge (inst->next()).
840 auto nextBlk
= inst
->next();
841 nextBlk
->insert(nextBlk
->begin(),
842 m_unit
.mov(dst
, src
, inst
->marker()));
844 appendInstruction(m_unit
.mov(dst
, src
, inst
->marker()));
848 if (inst
->block() == nullptr && inst
->isBlockEnd()) {
849 // We're not re-adding the block-end instruction. Unset its edges.
850 inst
->setTaken(nullptr);
851 inst
->setNext(nullptr);
855 if (block
->empty() || !block
->back().isBlockEnd()) {
856 // Our block-end instruction was eliminated (most likely a Jmp* converted
857 // to a nop). Replace it with a jump to the next block.
858 appendInstruction(m_unit
.gen(Jmp
, backMarker
, nextBlock
));
860 assert(block
->back().isBlockEnd());
861 if (!block
->back().isTerminal() && !block
->next()) {
862 // We converted the block-end instruction to a different one.
863 // Set its next block appropriately.
864 block
->back().setNext(nextBlock
);
866 // Mark successor blocks as reachable.
867 if (block
->back().next()) reachable
.set(block
->back().next()->id());
868 if (block
->back().taken()) reachable
.set(block
->back().taken()->id());
869 if (!use_fixed_point
) {
870 m_state
.finishBlock(block
);
876 * Returns true iff a guard to constrain was found, and tc was more specific
877 * than the guard's existing constraint. Note that this doesn't necessarily
878 * mean that the guard was constrained: tc.weak might be true.
880 bool IRBuilder::constrainGuard(const IRInstruction
* inst
, TypeConstraint tc
) {
881 if (!shouldConstrainGuards()) return false;
883 auto& guard
= m_constraints
.guards
[inst
];
884 auto newTc
= applyConstraint(guard
, tc
);
885 ITRACE(2, "constrainGuard({}, {}): {} -> {}\n", *inst
, tc
, guard
, newTc
);
888 auto const changed
= guard
!= newTc
;
889 if (!tc
.weak
) guard
= newTc
;
895 * Trace back to the guard that provided the type of val, if
896 * any. Constrain it so its type will not be relaxed beyond the given
897 * DataTypeCategory. Returns true iff one or more guard instructions
900 bool IRBuilder::constrainValue(SSATmp
* const val
, TypeConstraint tc
) {
901 if (!shouldConstrainGuards() || tc
.empty()) return false;
904 ITRACE(1, "constrainValue(nullptr, {}), bailing\n", tc
);
908 ITRACE(1, "constrainValue({}, {})\n", *val
->inst(), tc
);
911 auto inst
= val
->inst();
912 if (inst
->is(LdLoc
, LdStk
)) {
913 // If the value's type source is non-null and not a FramePtr, it's a real
914 // value that was killed by a Call. The value won't be live but it's ok to
915 // use it to track down the guard.
917 always_assert_flog(m_constraints
.typeSrcs
.count(inst
),
918 "No typeSrcs found for {}", *inst
);
920 auto const typeSrcs
= get_required(m_constraints
.typeSrcs
, inst
);
922 bool changed
= false;
923 for (auto typeSrc
: typeSrcs
) {
924 if (typeSrc
.isGuard()) {
925 if (inst
->is(LdLoc
)) {
926 changed
= constrainSlot(inst
->extra
<LdLoc
>()->locId
, typeSrc
, tc
,
927 "constrainValueLoc") || changed
;
929 assert(inst
->is(LdStk
));
930 changed
= constrainSlot(inst
->extra
<LdStk
>()->offset
, typeSrc
, tc
,
931 "constrainValueStk") || changed
;
934 // Keep chasing down the source of val.
935 assert(typeSrc
.isValue());
936 changed
= constrainValue(typeSrc
.value
, tc
) || changed
;
942 if (inst
->is(AssertType
)) {
943 // Sometimes code in HhbcTranslator asks for a value with DataTypeSpecific
944 // but can tolerate a less specific value. If that happens, there's nothing
946 if (!typeFitsConstraint(val
->type(), tc
)) return false;
948 // If the immutable typeParam fits the constraint, we're done.
949 auto const typeParam
= inst
->typeParam();
950 if (typeFitsConstraint(typeParam
, tc
)) return false;
952 auto const newTc
= relaxConstraint(tc
, typeParam
, inst
->src(0)->type());
953 ITRACE(1, "tracing through {}, orig tc: {}, new tc: {}\n",
955 return constrainValue(inst
->src(0), newTc
);
958 if (inst
->is(CheckType
)) {
959 // Sometimes code in HhbcTranslator asks for a value with DataTypeSpecific
960 // but can tolerate a less specific value. If that happens, there's nothing
962 if (!typeFitsConstraint(val
->type(), tc
)) return false;
964 bool changed
= false;
965 auto const typeParam
= inst
->typeParam();
966 auto const srcType
= inst
->src(0)->type();
968 // Constrain the guard on the CheckType, but first relax the constraint
969 // based on what's known about srcType.
970 auto const guardTc
= relaxConstraint(tc
, srcType
, typeParam
);
971 changed
= constrainGuard(inst
, guardTc
) || changed
;
973 // Relax typeParam with its current constraint. This is used below to
974 // recursively relax the constraint on the source, if needed.
975 auto constraint
= applyConstraint(m_constraints
.guards
[inst
], guardTc
);
976 auto const knownType
= relaxType(typeParam
, constraint
);
978 if (!typeFitsConstraint(knownType
, tc
)) {
979 auto const newTc
= relaxConstraint(tc
, knownType
, srcType
);
980 ITRACE(1, "tracing through {}, orig tc: {}, new tc: {}\n",
982 changed
= constrainValue(inst
->src(0), newTc
) || changed
;
987 if (inst
->isPassthrough()) {
988 return constrainValue(inst
->getPassthroughValue(), tc
);
991 if (inst
->is(DefLabel
)) {
992 auto changed
= false;
994 for (; dst
< inst
->numDsts(); dst
++) {
995 if (val
== inst
->dst(dst
)) break;
997 assert(dst
!= inst
->numDsts());
998 for (auto& pred
: inst
->block()->preds()) {
999 assert(pred
.inst()->is(Jmp
));
1000 auto src
= pred
.inst()->src(dst
);
1001 changed
= constrainValue(src
, tc
) || changed
;
1006 // Any instructions not special cased above produce a new value, so
1007 // there's no guard for us to constrain.
1008 ITRACE(2, "value is new in this trace, bailing\n");
1012 bool IRBuilder::constrainLocal(uint32_t locId
,
1014 const std::string
& why
) {
1015 if (!shouldConstrainGuards() || tc
.empty()) return false;
1016 bool changed
= false;
1017 for (auto typeSrc
: localTypeSources(locId
)) {
1018 changed
= constrainSlot(locId
, typeSrc
, tc
, why
+ "Loc") || changed
;
1023 bool IRBuilder::constrainStack(int32_t offset
, TypeConstraint tc
) {
1024 if (!shouldConstrainGuards() || tc
.empty()) return false;
1025 auto changed
= false;
1026 for (auto typeSrc
: stackTypeSources(offset
)) {
1027 changed
= constrainSlot(offset
, typeSrc
, tc
, "Stk") || changed
;
1032 // Constrains a local or stack slot.
1033 bool IRBuilder::constrainSlot(int32_t idOrOffset
,
1036 const std::string
& why
) {
1037 if (!shouldConstrainGuards() || tc
.empty()) return false;
1039 ITRACE(1, "constrainSlot({}, {}, {}, {})\n",
1040 idOrOffset
, show(typeSrc
), tc
, why
);
1043 if (typeSrc
.isValue()) return constrainValue(typeSrc
.value
, tc
);
1045 assert(typeSrc
.isGuard());
1046 auto const guard
= typeSrc
.guard
;
1048 if (guard
->is(GuardLoc
, GuardStk
)) {
1049 ITRACE(2, "found guard to constrain\n");
1050 return constrainGuard(guard
, tc
);
1053 always_assert(guard
->is(AssertLoc
, CheckLoc
, AssertStk
, CheckStk
));
1055 // If the dest of the Assert/Check doesn't fit tc there's no point in
1057 auto prevType
= get_required(m_constraints
.prevTypes
, guard
);
1058 if (!typeFitsConstraint(prevType
& guard
->typeParam(), tc
)) {
1062 if (guard
->is(AssertLoc
, AssertStk
)) {
1063 // If the immutable typeParam fits the constraint, we're done.
1064 auto const typeParam
= guard
->typeParam();
1065 if (typeFitsConstraint(typeParam
, tc
)) return false;
1067 auto const newTc
= relaxConstraint(tc
, typeParam
, prevType
);
1068 ITRACE(1, "tracing through {}, orig tc: {}, new tc: {}\n",
1070 bool changed
= false;
1071 auto typeSrcs
= get_required(m_constraints
.typeSrcs
, guard
);
1072 for (auto typeSrc
: typeSrcs
) {
1073 changed
= constrainSlot(idOrOffset
, typeSrc
, newTc
, why
) || changed
;
1078 // guard is a CheckLoc or CheckStk.
1079 auto changed
= false;
1080 auto const typeParam
= guard
->typeParam();
1082 // Constrain the guard on the Check instruction, but first relax the
1083 // constraint based on what's known about prevType.
1084 auto const guardTc
= relaxConstraint(tc
, prevType
, typeParam
);
1085 changed
= constrainGuard(guard
, guardTc
) || changed
;
1087 // Relax typeParam with its current constraint. This is used below to
1088 // recursively relax the constraint on the source, if needed.
1089 auto constraint
= applyConstraint(m_constraints
.guards
[guard
], guardTc
);
1090 auto const knownType
= relaxType(typeParam
, constraint
);
1092 if (!typeFitsConstraint(knownType
, tc
)) {
1093 auto const newTc
= relaxConstraint(tc
, knownType
, prevType
);
1094 ITRACE(1, "tracing through {}, orig tc: {}, new tc: {}\n",
1096 auto typeSrcs
= get_required(m_constraints
.typeSrcs
, guard
);
1097 for (auto typeSrc
: typeSrcs
) {
1098 changed
= constrainSlot(idOrOffset
, typeSrc
, newTc
, why
) || changed
;
1104 Type
IRBuilder::localType(uint32_t id
, TypeConstraint tc
) {
1105 constrainLocal(id
, tc
, "localType");
1106 return m_state
.localType(id
);
1109 Type
IRBuilder::predictedInnerType(uint32_t id
) {
1110 auto const ty
= m_state
.predictedLocalType(id
);
1111 assert(ty
.isBoxed());
1112 return ldRefReturn(ty
.unbox());
1115 Type
IRBuilder::stackInnerTypePrediction(int32_t offset
) const {
1116 auto const ty
= m_state
.predictedStackType(offset
);
1117 assert(ty
.isBoxed());
1118 return ldRefReturn(ty
.unbox());
1121 Type
IRBuilder::predictedLocalType(uint32_t id
) {
1122 return m_state
.predictedLocalType(id
);
1125 SSATmp
* IRBuilder::localValue(uint32_t id
, TypeConstraint tc
) {
1126 constrainLocal(id
, tc
, "localValue");
1127 return m_state
.localValue(id
);
1130 SSATmp
* IRBuilder::stackValue(int32_t offset
, TypeConstraint tc
) {
1131 auto const val
= m_state
.stackValue(offset
);
1132 if (val
) constrainValue(val
, tc
);
1136 Type
IRBuilder::stackType(int32_t offset
, TypeConstraint tc
) {
1137 constrainStack(offset
, tc
);
1138 return m_state
.stackType(offset
);
1141 void IRBuilder::setCurMarker(BCMarker newMarker
) {
1142 if (newMarker
== m_curMarker
) return;
1143 FTRACE(2, "IRBuilder changing current marker from {} to {}\n",
1144 m_curMarker
.valid() ? m_curMarker
.show() : "<invalid>",
1146 assert(newMarker
.valid());
1147 m_curMarker
= newMarker
;
1150 bool IRBuilder::startBlock(Block
* block
, const BCMarker
& marker
,
1151 bool isLoopHeader
) {
1153 assert(m_savedBlocks
.empty()); // No bytecode control flow in exits.
1155 if (block
== m_curBlock
) return true;
1157 // Return false if we don't have a state for block. This can happen
1158 // when trying to start a region block that turned out to be unreachable.
1159 if (!m_state
.hasStateFor(block
)) return false;
1161 // There's no reason for us to be starting on the entry block when it's not
1162 // our current block.
1163 always_assert(!block
->isEntry());
1165 auto& lastInst
= m_curBlock
->back();
1166 always_assert(lastInst
.isBlockEnd());
1167 always_assert(lastInst
.isTerminal() || m_curBlock
->next() != nullptr);
1169 m_state
.finishBlock(m_curBlock
);
1172 m_state
.startBlock(m_curBlock
, marker
, isLoopHeader
);
1173 if (sp() == nullptr) gen(DefSP
, StackOffset
{spOffset()}, fp());
1174 always_assert(fp() != nullptr);
1176 FTRACE(2, "IRBuilder switching to block B{}: {}\n", block
->id(),
1181 Block
* IRBuilder::makeBlock(Offset offset
) {
1182 auto it
= m_offsetToBlockMap
.find(offset
);
1183 if (it
== m_offsetToBlockMap
.end()) {
1184 auto* block
= m_unit
.defBlock();
1185 m_offsetToBlockMap
.insert(std::make_pair(offset
, block
));
1191 void IRBuilder::resetOffsetMapping() {
1192 m_offsetToBlockMap
.clear();
1195 bool IRBuilder::hasBlock(Offset offset
) const {
1196 return m_offsetToBlockMap
.count(offset
);
1199 void IRBuilder::setBlock(Offset offset
, Block
* block
) {
1200 assert(!hasBlock(offset
));
1201 m_offsetToBlockMap
[offset
] = block
;
1204 void IRBuilder::pushBlock(BCMarker marker
, Block
* b
) {
1205 FTRACE(2, "IRBuilder saving {}@{} and using {}@{}\n",
1206 m_curBlock
, m_curMarker
.show(), b
, marker
.show());
1209 m_savedBlocks
.push_back(
1210 BlockState
{ m_curBlock
, m_curMarker
, m_exnStack
}
1212 m_state
.pauseBlock(m_curBlock
);
1213 m_state
.startBlock(b
, marker
);
1215 m_curMarker
= marker
;
1218 for (UNUSED
auto const& state
: m_savedBlocks
) {
1219 assert(state
.block
!= b
&&
1220 "Can't push a block that's already in the saved stack");
1225 void IRBuilder::popBlock() {
1226 assert(!m_savedBlocks
.empty());
1228 auto const& top
= m_savedBlocks
.back();
1229 FTRACE(2, "IRBuilder popping {}@{} to restore {}@{}\n",
1230 m_curBlock
, m_curMarker
.show(), top
.block
, top
.marker
.show());
1231 m_state
.finishBlock(m_curBlock
);
1232 m_state
.unpauseBlock(top
.block
);
1233 m_curBlock
= top
.block
;
1234 m_curMarker
= top
.marker
;
1235 m_exnStack
= top
.exnStack
;
1236 m_savedBlocks
.pop_back();
1239 //////////////////////////////////////////////////////////////////////
1241 CSEHash
& IRBuilder::cseHashTable(const IRInstruction
& inst
) {
1242 return inst
.is(DefConst
) ? m_unit
.constTable() : m_cseHash
;
1245 const CSEHash
& IRBuilder::cseHashTable(const IRInstruction
& inst
) const {
1246 return const_cast<IRBuilder
*>(this)->cseHashTable(inst
);
1249 SSATmp
* IRBuilder::cseLookup(const IRInstruction
& inst
,
1250 const Block
* srcBlock
,
1251 const folly::Optional
<IdomVector
>& idoms
) const {
1252 auto tmp
= cseHashTable(inst
).lookup(const_cast<IRInstruction
*>(&inst
));
1254 // During a reoptimize pass, we need to make sure that any values we want
1255 // to reuse for CSE are only reused in blocks dominated by the block that
1257 if (tmp
->inst()->is(DefConst
)) return tmp
;
1258 auto const dom
= findDefiningBlock(tmp
);
1259 if (!dom
|| !dominates(dom
, srcBlock
, *idoms
)) return nullptr;
1264 void IRBuilder::cseUpdate(const IRInstruction
& inst
) {
1265 switch (inst
.op()) {
1275 if (m_enableCse
&& inst
.canCSE()) {
1276 cseHashTable(inst
).insert(inst
.dst());
1278 if (inst
.killsSources()) {
1279 for (auto i
= uint32_t{0}; i
< inst
.numSrcs(); ++i
) {
1280 if (inst
.killsSource(i
) && inst
.src(i
)->inst()->canCSE()) {
1281 cseHashTable(inst
).erase(inst
.src(i
));
1287 //////////////////////////////////////////////////////////////////////