2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2015 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/simplify.h"
21 #include <type_traits>
23 #include "hphp/util/overflow.h"
24 #include "hphp/util/trace.h"
26 #include "hphp/runtime/base/array-data-defs.h"
27 #include "hphp/runtime/base/comparisons.h"
28 #include "hphp/runtime/base/repo-auth-type-array.h"
29 #include "hphp/runtime/base/type-conversions.h"
30 #include "hphp/runtime/ext/collections/ext_collections-idl.h"
31 #include "hphp/runtime/vm/hhbc.h"
32 #include "hphp/runtime/vm/jit/analysis.h"
33 #include "hphp/runtime/vm/jit/containers.h"
34 #include "hphp/runtime/vm/jit/guard-relaxation.h"
35 #include "hphp/runtime/vm/jit/ir-builder.h"
36 #include "hphp/runtime/vm/jit/minstr-effects.h"
37 #include "hphp/runtime/vm/jit/punt.h"
38 #include "hphp/runtime/vm/jit/type.h"
39 #include "hphp/runtime/vm/runtime.h"
41 namespace HPHP
{ namespace jit
{
43 TRACE_SET_MOD(simplify
);
45 //////////////////////////////////////////////////////////////////////
49 //////////////////////////////////////////////////////////////////////
51 const StaticString
s_Array("Array");
52 const StaticString
s_isEmpty("isEmpty");
53 const StaticString
s_count("count");
54 const StaticString
s_1("1");
55 const StaticString
s_empty("");
56 const StaticString
s_invoke("__invoke");
57 const StaticString
s_isFinished("isFinished");
58 const StaticString
s_isSucceeded("isSucceeded");
59 const StaticString
s_isFailed("isFailed");
60 const StaticString
s_WaitHandle("HH\\WaitHandle");
62 //////////////////////////////////////////////////////////////////////
66 const bool typesMightRelax
;
68 // The current instruction being simplified is always at insts.top(). This
69 // has to be a stack instead of just a pointer because simplify is reentrant.
70 jit::stack
<const IRInstruction
*> insts
;
71 jit::vector
<IRInstruction
*> newInsts
;
74 //////////////////////////////////////////////////////////////////////
76 SSATmp
* simplifyWork(State
&, const IRInstruction
*);
78 bool mightRelax(State
& env
, SSATmp
* tmp
) {
79 if (!env
.typesMightRelax
) return false;
80 return jit::typeMightRelax(tmp
);
83 template<class... Args
>
84 SSATmp
* cns(State
& env
, Args
&&... cns
) {
85 return env
.unit
.cns(std::forward
<Args
>(cns
)...);
88 template<class... Args
>
89 SSATmp
* gen(State
& env
, Opcode op
, BCMarker marker
, Args
&&... args
) {
90 return makeInstruction(
91 [&] (IRInstruction
* inst
) -> SSATmp
* {
92 auto prevNewCount
= env
.newInsts
.size();
93 auto newDest
= simplifyWork(env
, inst
);
95 // If any simplification happened to this instruction, drop it. We have to
96 // check that nothing was added to newInsts because that's the only way
97 // we can tell simplification happened to a no-dest instruction.
98 if (newDest
|| env
.newInsts
.size() != prevNewCount
) {
101 assertx(inst
->isTransient());
102 inst
= env
.unit
.clone(inst
);
103 env
.newInsts
.push_back(inst
);
110 std::forward
<Args
>(args
)...
114 template<class... Args
>
115 SSATmp
* gen(State
& env
, Opcode op
, Args
&&... args
) {
116 assertx(!env
.insts
.empty());
117 return gen(env
, op
, env
.insts
.top()->marker(), std::forward
<Args
>(args
)...);
120 bool arrayKindNeedsVsize(const ArrayData::ArrayKind kind
) {
122 case ArrayData::kPackedKind
:
123 case ArrayData::kStructKind
:
124 case ArrayData::kMixedKind
:
125 case ArrayData::kEmptyKind
:
126 case ArrayData::kApcKind
:
133 //////////////////////////////////////////////////////////////////////
136 * Simplifier rules are not allowed to add new uses to SSATmps that aren't
137 * known to be available. All the sources to the original instruction must
138 * be available, and non-reference counted values reachable through the
139 * source chain are also always available. Anything else requires more
140 * complicated analysis than belongs in the simplifier right now.
142 DEBUG_ONLY
bool validate(const State
& env
,
144 const IRInstruction
* origInst
) {
145 auto known_available
= [&] (SSATmp
* src
) -> bool {
146 if (!src
->type().maybe(TCounted
)) return true;
147 for (auto& oldSrc
: origInst
->srcs()) {
148 if (oldSrc
== src
) return true;
153 // Return early for the no-simplification case.
154 if (env
.newInsts
.empty() && !newDst
) {
158 const IRInstruction
* last
= nullptr;
160 if (!env
.newInsts
.empty()) {
161 for (size_t i
= 0, n
= env
.newInsts
.size(); i
< n
; ++i
) {
162 auto newInst
= env
.newInsts
[i
];
164 for (auto& src
: newInst
->srcs()) {
166 known_available(src
),
167 "A simplification rule produced an instruction that used a value "
168 "that wasn't known to be available:\n"
169 " original inst: {}\n"
172 origInst
->toString(),
184 !newInst
->isBlockEnd(),
185 "Block-ending instruction produced in the middle of a simplified "
186 "instruction stream:\n"
187 " original inst: {}\n"
189 origInst
->toString(),
196 const bool available
= known_available(newDst
) ||
197 std::any_of(env
.newInsts
.begin(), env
.newInsts
.end(),
198 [&] (IRInstruction
* inst
) { return newDst
== inst
->dst(); });
202 "Simplifier produced a new destination that wasn't known to be "
204 " original inst: {}\n"
206 origInst
->toString(),
211 if (!last
) return true;
213 auto assert_last
= [&] (bool cond
, const char* msg
) {
217 " original inst: {}\n"
218 " last new inst: {}\n",
220 origInst
->toString(),
226 !origInst
->naryDst(),
227 "Nontrivial simplification returned for instruction with NaryDest"
231 origInst
->hasDst() == (bool)newDst
,
232 "HasDest mismatch between input and output"
236 IMPLIES(last
->isBlockEnd(), origInst
->isBlockEnd()),
237 "Block-end instruction produced for simplification of non-block-end "
241 if (last
->hasEdges()) {
243 origInst
->hasEdges(),
244 "Instruction with edges produced for simplification of edge-free "
249 IMPLIES(last
->next(), last
->next() == origInst
->next() ||
250 last
->next() == origInst
->taken()),
251 "Last instruction of simplified stream has next edge not reachable from "
252 "the input instruction"
256 IMPLIES(last
->taken(), last
->taken() == origInst
->next() ||
257 last
->taken() == origInst
->taken()),
258 "Last instruction of simplified stream has taken edge not reachable "
259 "from the input instruction"
266 //////////////////////////////////////////////////////////////////////
269 * Individual simplification routines return nullptr if they don't want to
270 * change anything, or they can call gen any number of times to produce a
271 * different IR sequence, returning the thing gen'd that should be used as the
272 * value of the simplified instruction sequence.
275 SSATmp
* mergeBranchDests(State
& env
, const IRInstruction
* inst
) {
276 // Replace a conditional branch with a Jmp if both branches go to the same
277 // block. Only work if the instruction does not have side effect.
278 // JmpZero/JmpNZero is handled separately.
279 assertx(inst
->is(CheckTypeMem
,
286 CheckPackedArrayBounds
,
290 if (inst
->next() != nullptr && inst
->next() == inst
->taken()) {
291 return gen(env
, Jmp
, inst
->next());
296 SSATmp
* simplifyCheckCtxThis(State
& env
, const IRInstruction
* inst
) {
297 auto const func
= inst
->marker().func();
298 auto const srcTy
= inst
->src(0)->type();
299 if (srcTy
<= TObj
) return gen(env
, Nop
);
300 if (!func
->mayHaveThis() || !srcTy
.maybe(TObj
)) {
301 return gen(env
, Jmp
, inst
->taken());
303 return mergeBranchDests(env
, inst
);
306 SSATmp
* simplifyCastCtxThis(State
& env
, const IRInstruction
* inst
) {
307 if (inst
->src(0)->type() <= TObj
) return inst
->src(0);
311 SSATmp
* simplifyLdClsCtx(State
& env
, const IRInstruction
* inst
) {
312 SSATmp
* ctx
= inst
->src(0);
313 if (ctx
->hasConstVal(TCctx
)) {
314 return cns(env
, ctx
->cctxVal().cls());
316 Type ctxType
= ctx
->type();
317 if (ctxType
<= TObj
) {
318 // this pointer... load its class ptr
319 return gen(env
, LdObjClass
, ctx
);
321 if (ctxType
<= TCctx
) {
322 return gen(env
, LdClsCctx
, ctx
);
327 SSATmp
* simplifyLdObjClass(State
& env
, const IRInstruction
* inst
) {
328 auto const ty
= inst
->src(0)->type();
330 if (mightRelax(env
, inst
->src(0)) || !(ty
< TObj
)) return nullptr;
332 if (auto const exact
= ty
.clsSpec().exactCls()) return cns(env
, exact
);
336 SSATmp
* simplifyLdObjInvoke(State
& env
, const IRInstruction
* inst
) {
337 auto const src
= inst
->src(0);
338 if (!src
->hasConstVal()) return nullptr;
340 auto const cls
= src
->clsVal();
341 if (!rds::isPersistentHandle(cls
->classHandle())) return nullptr;
343 auto const meth
= cls
->getCachedInvoke();
344 return meth
== nullptr ? nullptr : cns(env
, meth
);
347 SSATmp
* simplifyGetCtxFwdCall(State
& env
, const IRInstruction
* inst
) {
348 auto const srcCtx
= inst
->src(0);
349 if (srcCtx
->isA(TCctx
)) return srcCtx
;
353 SSATmp
* simplifyConvClsToCctx(State
& env
, const IRInstruction
* inst
) {
354 auto* srcInst
= inst
->src(0)->inst();
355 if (srcInst
->is(LdClsCctx
)) return srcInst
->src(0);
359 SSATmp
* simplifyMov(State
& env
, const IRInstruction
* inst
) {
363 SSATmp
* simplifyAbsDbl(State
& env
, const IRInstruction
* inst
) {
364 auto const src
= inst
->src(0);
365 if (src
->hasConstVal()) return cns(env
, fabs(src
->dblVal()));
370 SSATmp
* constImpl(State
& env
, SSATmp
* src1
, SSATmp
* src2
, Oper op
) {
371 // don't canonicalize to the right, OP might not be commutative
372 if (!src1
->hasConstVal() || !src2
->hasConstVal()) return nullptr;
374 auto both
= [&](Type ty
) { return src1
->type() <= ty
&& src2
->type() <= ty
; };
376 if (both(TBool
)) return cns(env
, op(src1
->boolVal(), src2
->boolVal()));
377 if (both(TInt
)) return cns(env
, op(src1
->intVal(), src2
->intVal()));
378 if (both(TDbl
)) return cns(env
, op(src1
->dblVal(), src2
->dblVal()));
383 SSATmp
* commutativeImpl(State
& env
,
388 if (auto simp
= constImpl(env
, src1
, src2
, op
)) return simp
;
390 // Canonicalize constants to the right.
391 if (src1
->hasConstVal() && src2
->type().hasConstVal()) {
392 return gen(env
, opcode
, src2
, src1
);
395 // Only handle integer operations for now.
396 if (!src1
->isA(TInt
) || !src2
->isA(TInt
)) return nullptr;
398 auto const inst1
= src1
->inst();
399 auto const inst2
= src2
->inst();
400 if (inst1
->op() == opcode
&& inst1
->src(1)->hasConstVal()) {
401 // (X + C1) + C2 --> X + C3
402 if (src2
->hasConstVal()) {
403 int64_t right
= inst1
->src(1)->intVal();
404 right
= op(right
, src2
->intVal());
405 return gen(env
, opcode
, inst1
->src(0), cns(env
, right
));
407 // (X + C1) + (Y + C2) --> X + Y + C3
408 if (inst2
->op() == opcode
&& inst2
->src(1)->hasConstVal()) {
409 int64_t right
= inst1
->src(1)->intVal();
410 right
= op(right
, inst2
->src(1)->intVal());
411 SSATmp
* left
= gen(env
, opcode
, inst1
->src(0), inst2
->src(0));
412 return gen(env
, opcode
, left
, cns(env
, right
));
419 * Assumes that outop is commutative, don't use with subtract!
421 * Assumes that the values we're going to add new uses to are not reference
422 * counted. (I.e. this is a distributive FooInt opcode.)
424 template<class OutOper
, class InOper
>
425 SSATmp
* distributiveImpl(State
& env
,
432 if (auto simp
= commutativeImpl(env
, src1
, src2
, outcode
, outop
)) {
436 auto const inst1
= src1
->inst();
437 auto const inst2
= src2
->inst();
438 auto const op1
= inst1
->op();
439 auto const op2
= inst2
->op();
440 // all combinations of X * Y + X * Z --> X * (Y + Z)
441 if (op1
== incode
&& op2
== incode
) {
442 if (inst1
->src(0) == inst2
->src(0)) {
443 SSATmp
* fold
= gen(env
, outcode
, inst1
->src(1), inst2
->src(1));
444 return gen(env
, incode
, inst1
->src(0), fold
);
446 if (inst1
->src(0) == inst2
->src(1)) {
447 SSATmp
* fold
= gen(env
, outcode
, inst1
->src(1), inst2
->src(0));
448 return gen(env
, incode
, inst1
->src(0), fold
);
450 if (inst1
->src(1) == inst2
->src(0)) {
451 SSATmp
* fold
= gen(env
, outcode
, inst1
->src(0), inst2
->src(1));
452 return gen(env
, incode
, inst1
->src(1), fold
);
454 if (inst1
->src(1) == inst2
->src(1)) {
455 SSATmp
* fold
= gen(env
, outcode
, inst1
->src(0), inst2
->src(0));
456 return gen(env
, incode
, inst1
->src(1), fold
);
462 SSATmp
* simplifyAddInt(State
& env
, const IRInstruction
* inst
) {
463 auto const src1
= inst
->src(0);
464 auto const src2
= inst
->src(1);
466 auto add
= std::plus
<int64_t>();
467 auto mul
= std::multiplies
<int64_t>();
468 if (auto simp
= distributiveImpl(env
, src1
, src2
, AddInt
,
472 if (src2
->hasConstVal()) {
473 int64_t src2Val
= src2
->intVal();
475 if (src2Val
== 0) return src1
;
478 // Weird, but can show up as a result of other simplifications. Don't need
479 // to check for C == INT_MIN, simplifySubInt already checks.
480 if (src2Val
< 0) return gen(env
, SubInt
, src1
, cns(env
, -src2Val
));
482 // X + (0 - Y) --> X - Y
483 auto inst2
= src2
->inst();
484 if (inst2
->op() == SubInt
) {
485 SSATmp
* src
= inst2
->src(0);
486 if (src
->hasConstVal() && src
->intVal() == 0) {
487 return gen(env
, SubInt
, src1
, inst2
->src(1));
490 auto inst1
= src1
->inst();
493 if (inst1
->op() == SubInt
&& inst1
->src(1)->hasConstVal()) {
494 auto x
= inst1
->src(0);
495 auto c1
= inst1
->src(1);
497 // (X - C1) + C2 --> X + (C2 - C1)
498 if (src2
->hasConstVal()) {
499 auto rhs
= gen(env
, SubInt
, cns(env
, src2
->intVal()), c1
);
500 return gen(env
, AddInt
, x
, rhs
);
503 // (X - C1) + (Y +/- C2)
504 if ((inst2
->op() == AddInt
|| inst2
->op() == SubInt
) &&
505 inst2
->src(1)->hasConstVal()) {
506 auto y
= inst2
->src(0);
507 auto c2
= inst2
->src(1);
508 SSATmp
* rhs
= nullptr;
509 if (inst2
->op() == SubInt
) {
510 // (X - C1) + (Y - C2) --> X + Y + (-C1 - C2)
511 rhs
= gen(env
, SubInt
, gen(env
, SubInt
, cns(env
, 0), c1
), c2
);
513 // (X - C1) + (Y + C2) --> X + Y + (C2 - C1)
514 rhs
= gen(env
, SubInt
, c2
, c1
);
516 auto lhs
= gen(env
, AddInt
, x
, y
);
517 return gen(env
, AddInt
, lhs
, rhs
);
519 // (X - C1) + (Y + C2) --> X + Y + (C2 - C1)
520 if (inst2
->op() == AddInt
&& inst2
->src(1)->hasConstVal()) {
521 auto y
= inst2
->src(0);
522 auto c2
= inst2
->src(1);
524 auto lhs
= gen(env
, AddInt
, x
, y
);
525 auto rhs
= gen(env
, SubInt
, c2
, c1
);
526 return gen(env
, AddInt
, lhs
, rhs
);
533 SSATmp
* simplifyAddIntO(State
& env
, const IRInstruction
* inst
) {
534 auto const src1
= inst
->src(0);
535 auto const src2
= inst
->src(1);
536 if (src1
->hasConstVal() && src2
->hasConstVal()) {
537 int64_t a
= src1
->intVal();
538 int64_t b
= src2
->intVal();
539 return add_overflow(a
, b
)
540 ? cns(env
, double(a
) + double(b
))
546 SSATmp
* simplifySubInt(State
& env
, const IRInstruction
* inst
) {
547 auto const src1
= inst
->src(0);
548 auto const src2
= inst
->src(1);
550 auto sub
= std::minus
<int64_t>();
551 if (auto simp
= constImpl(env
, src1
, src2
, sub
)) return simp
;
554 if (src1
== src2
) return cns(env
, 0);
556 if (src2
->hasConstVal()) {
557 int64_t src2Val
= src2
->intVal();
559 if (src2Val
== 0) return src1
;
562 // Need to check for C == INT_MIN, otherwise we'd infinite loop as
563 // X + -C would send us back here.
564 auto const min
= std::numeric_limits
<int64_t>::min();
565 if (src2Val
> min
&& src2Val
< 0) {
566 return gen(env
, AddInt
, src1
, cns(env
, -src2Val
));
569 // X - (0 - Y) --> X + Y
570 auto inst2
= src2
->inst();
571 if (inst2
->op() == SubInt
) {
572 SSATmp
* src
= inst2
->src(0);
573 if (src
->hasConstVal(0)) return gen(env
, AddInt
, src1
, inst2
->src(1));
578 SSATmp
* simplifySubIntO(State
& env
, const IRInstruction
* inst
) {
579 auto const src1
= inst
->src(0);
580 auto const src2
= inst
->src(1);
581 if (src1
->hasConstVal() && src2
->hasConstVal()) {
582 int64_t a
= src1
->intVal();
583 int64_t b
= src2
->intVal();
584 return sub_overflow(a
, b
)
585 ? cns(env
, double(a
) - double(b
))
591 SSATmp
* simplifyMulInt(State
& env
, const IRInstruction
* inst
) {
592 auto const src1
= inst
->src(0);
593 auto const src2
= inst
->src(1);
595 auto mul
= std::multiplies
<int64_t>();
596 if (auto simp
= commutativeImpl(env
, src1
, src2
, MulInt
, mul
)) return simp
;
598 if (!src2
->hasConstVal()) return nullptr;
600 int64_t rhs
= src2
->intVal();
603 if (rhs
== -1) return gen(env
, SubInt
, cns(env
, 0), src1
);
605 if (rhs
== 0) return cns(env
, 0);
607 if (rhs
== 1) return src1
;
609 if (rhs
== 2) return gen(env
, AddInt
, src1
, src1
);
611 auto isPowTwo
= [](int64_t a
) {
612 return a
> 0 && folly::isPowTwo
<uint64_t>(a
);
614 auto log2
= [](int64_t a
) {
616 return folly::findLastSet
<uint64_t>(a
) - 1;
619 // X * 2^C --> X << C
620 if (isPowTwo(rhs
)) return gen(env
, Shl
, src1
, cns(env
, log2(rhs
)));
622 // X * (2^C + 1) --> ((X << C) + X)
623 if (isPowTwo(rhs
- 1)) {
624 auto lhs
= gen(env
, Shl
, src1
, cns(env
, log2(rhs
- 1)));
625 return gen(env
, AddInt
, lhs
, src1
);
627 // X * (2^C - 1) --> ((X << C) - X)
628 if (isPowTwo(rhs
+ 1)) {
629 auto lhs
= gen(env
, Shl
, src1
, cns(env
, log2(rhs
+ 1)));
630 return gen(env
, SubInt
, lhs
, src1
);
636 SSATmp
* simplifyAddDbl(State
& env
, const IRInstruction
* inst
) {
637 return constImpl(env
, inst
->src(0), inst
->src(1), std::plus
<double>());
640 SSATmp
* simplifySubDbl(State
& env
, const IRInstruction
* inst
) {
641 return constImpl(env
, inst
->src(0), inst
->src(1), std::minus
<double>());
644 SSATmp
* simplifyMulDbl(State
& env
, const IRInstruction
* inst
) {
645 return constImpl(env
, inst
->src(0), inst
->src(1), std::multiplies
<double>());
648 SSATmp
* simplifyMulIntO(State
& env
, const IRInstruction
* inst
) {
649 auto const src1
= inst
->src(0);
650 auto const src2
= inst
->src(1);
651 if (src1
->hasConstVal() && src2
->hasConstVal()) {
652 int64_t a
= src1
->intVal();
653 int64_t b
= src2
->intVal();
654 return mul_overflow(a
, b
)
655 ? cns(env
, double(a
) * double(b
))
661 SSATmp
* simplifyMod(State
& env
, const IRInstruction
* inst
) {
662 auto const src1
= inst
->src(0);
663 auto const src2
= inst
->src(1);
665 if (!src2
->hasConstVal()) return nullptr;
667 auto const src2Val
= src2
->intVal();
668 if (src2Val
== 0 || src2Val
== -1) {
669 // Undefined behavior, so we might as well constant propagate whatever we
670 // want. If we're being asked to simplify this, it better be dynamically
675 if (src1
->hasConstVal()) return cns(env
, src1
->intVal() % src2Val
);
677 if (src2Val
== 1) return cns(env
, 0);
682 SSATmp
* simplifyDivDbl(State
& env
, const IRInstruction
* inst
) {
683 auto src1
= inst
->src(0);
684 auto src2
= inst
->src(1);
686 if (!src2
->hasConstVal()) return nullptr;
688 // not supporting integers (#2570625)
689 double src2Val
= src2
->dblVal();
691 // X / 0 -> bool(false)
692 if (src2Val
== 0.0) {
693 // Ideally we'd generate a RaiseWarning and return false here, but we need
694 // a catch trace for that and we can't make a catch trace here.
698 // statically compute X / Y
699 return src1
->hasConstVal() ? cns(env
, src1
->dblVal() / src2Val
) : nullptr;
702 SSATmp
* simplifyAndInt(State
& env
, const IRInstruction
* inst
) {
703 auto const src1
= inst
->src(0);
704 auto const src2
= inst
->src(1);
705 auto bit_and
= [](int64_t a
, int64_t b
) { return a
& b
; };
706 auto bit_or
= [](int64_t a
, int64_t b
) { return a
| b
; };
707 auto simp
= distributiveImpl(env
, src1
, src2
, AndInt
, OrInt
,
709 if (simp
!= nullptr) {
716 if (src2
->hasConstVal()) {
718 if (src2
->intVal() == 0) {
722 if (src2
->intVal() == ~0L) {
729 SSATmp
* simplifyOrInt(State
& env
, const IRInstruction
* inst
) {
730 auto const src1
= inst
->src(0);
731 auto const src2
= inst
->src(1);
733 auto bit_and
= [](int64_t a
, int64_t b
) { return a
& b
; };
734 auto bit_or
= [](int64_t a
, int64_t b
) { return a
| b
; };
735 auto simp
= distributiveImpl(env
, src1
, src2
, OrInt
, AndInt
,
737 if (simp
!= nullptr) {
744 if (src2
->hasConstVal()) {
746 if (src2
->intVal() == 0) {
750 if (src2
->intVal() == ~uint64_t(0)) {
751 return cns(env
, ~uint64_t(0));
757 SSATmp
* simplifyXorInt(State
& env
, const IRInstruction
* inst
) {
758 auto const src1
= inst
->src(0);
759 auto const src2
= inst
->src(1);
760 auto bitxor
= [](int64_t a
, int64_t b
) { return a
^ b
; };
761 if (auto simp
= commutativeImpl(env
, src1
, src2
, XorInt
, bitxor
)) {
765 if (src1
== src2
) return cns(env
, 0);
767 if (src2
->hasConstVal(0)) return src1
;
771 SSATmp
* xorTrueImpl(State
& env
, SSATmp
* src
) {
772 auto const inst
= src
->inst();
773 auto const op
= inst
->op();
775 // !(X cmp Y) --> X opposite_cmp Y
776 if (auto negated
= negateCmpOp(op
)) {
777 auto const s0
= inst
->src(0);
778 auto const s1
= inst
->src(1);
779 // We can't add new uses to reference counted types without a more
780 // advanced availability analysis.
781 if (!s0
->type().maybe(TCounted
) && !s1
->type().maybe(TCounted
)) {
782 return gen(env
, *negated
, s0
, s1
);
790 if (inst
->src(1)->hasConstVal(true)) {
791 // This is safe to add a new use to because inst->src(0) is a bool.
792 assertx(inst
->src(0)->isA(TBool
));
796 case InstanceOfBitmask
:
797 case NInstanceOfBitmask
:
798 // This is safe because instanceofs don't take reference counted
800 assertx(!inst
->src(0)->type().maybe(TCounted
) &&
801 !inst
->src(1)->type().maybe(TCounted
));
804 (op
== InstanceOfBitmask
) ?
817 SSATmp
* simplifyXorBool(State
& env
, const IRInstruction
* inst
) {
818 auto const src1
= inst
->src(0);
819 auto const src2
= inst
->src(1);
822 if (src1
->hasConstVal() && src2
->hasConstVal()) {
823 return cns(env
, bool(src1
->boolVal() ^ src2
->boolVal()));
826 // Canonicalize constants to the right.
827 if (src1
->hasConstVal() && !src2
->hasConstVal()) {
828 return gen(env
, XorBool
, src2
, src1
);
832 if (src2
->hasConstVal(false)) return src1
;
834 // X^1 => simplify "not" logic
835 if (src2
->hasConstVal(true)) return xorTrueImpl(env
, src1
);
840 SSATmp
* shiftImpl(State
& env
, const IRInstruction
* inst
, Oper op
) {
841 auto const src1
= inst
->src(0);
842 auto const src2
= inst
->src(1);
844 if (src1
->hasConstVal()) {
845 if (src1
->intVal() == 0) {
849 if (src2
->hasConstVal()) {
850 return cns(env
, op(src1
->intVal(), src2
->intVal()));
854 if (src2
->hasConstVal() && src2
->intVal() == 0) {
861 SSATmp
* simplifyShl(State
& env
, const IRInstruction
* inst
) {
862 return shiftImpl(env
, inst
, [] (int64_t a
, int64_t b
) { return a
<< b
; });
865 SSATmp
* simplifyShr(State
& env
, const IRInstruction
* inst
) {
866 return shiftImpl(env
, inst
, [] (int64_t a
, int64_t b
) { return a
>> b
; });
869 // This function isn't meant to perform the actual comparison at
870 // compile-time. Instead, it performs the matching comparison against a
871 // primitive type (usually bool).
872 template<class T
, class U
>
873 static bool cmpOp(Opcode opc
, T a
, U b
) {
881 case GtRes
: return a
> b
;
888 case GteRes
: return a
>= b
;
895 case LtRes
: return a
< b
;
902 case LteRes
: return a
<= b
;
912 case EqRes
: return a
== b
;
922 case NeqRes
: return a
!= b
;
924 always_assert(false);
928 SSATmp
* cmpBoolImpl(State
& env
,
930 const IRInstruction
* const inst
,
933 assertx(left
->type() <= TBool
);
934 assertx(right
->type() <= TBool
);
936 auto newInst
= [&](Opcode op
, SSATmp
* src1
, SSATmp
* src2
) {
937 return gen(env
, op
, inst
? inst
->taken() : (Block
*)nullptr, src1
, src2
);
940 // Identity optimization
942 return cns(env
, cmpOp(opc
, true, true));
945 if (left
->hasConstVal()) {
946 // If both operands are constants, constant-fold them. Otherwise, move the
947 // constant over to the right.
948 if (right
->hasConstVal()) {
949 return cns(env
, cmpOp(opc
, left
->boolVal(), right
->boolVal()));
951 auto newOpc
= [](Opcode opc
) {
953 case GtBool
: return LtBool
;
954 case GteBool
: return LteBool
;
955 case LtBool
: return GtBool
;
956 case LteBool
: return GteBool
;
957 case EqBool
: return EqBool
;
958 case NeqBool
: return NeqBool
;
959 default: always_assert(false);
962 return newInst(newOpc
, right
, left
);
966 if (right
->hasConstVal()) {
967 bool b
= right
->boolVal();
969 // The result of the comparison might be independent of the truth
970 // value of the LHS. If so, then simplify.
971 if (cmpOp(opc
, false, b
) == cmpOp(opc
, true, b
)) {
972 return cns(env
, cmpOp(opc
, false, b
));
975 // There are only two distinct booleans - false and true (0 and 1).
976 // From above, we know that (0 OP b) != (1 OP b).
977 // Hence exactly one of (0 OP b) and (1 OP b) is true.
978 // Hence there is exactly one boolean value of "left" that results in the
979 // overall expression being true.
980 // Hence we may check for equality with that boolean.
982 return newInst(EqBool
, left
, cns(env
, !cmpOp(opc
, false, b
)));
985 // If we reach here, this is an equality comparison against a
986 // constant. Testing for equality with true simplifies to just the left
987 // operand, while equality with false is the negation of the left operand
988 // (equivalent to XORing with true).
989 return b
? left
: newInst(XorBool
, left
, cns(env
, true));
995 SSATmp
* cmpIntImpl(State
& env
,
997 const IRInstruction
* const inst
,
1000 assertx(left
->type() <= TInt
);
1001 assertx(right
->type() <= TInt
);
1003 auto newInst
= [&](Opcode op
, SSATmp
* src1
, SSATmp
* src2
) {
1004 return gen(env
, op
, inst
? inst
->taken() : (Block
*)nullptr, src1
, src2
);
1007 // Identity optimization
1008 if (left
== right
) {
1009 return cns(env
, cmpOp(opc
, true, true));
1012 if (left
->hasConstVal()) {
1013 // If both operands are constants, constant-fold them. Otherwise, move the
1014 // constant over to the right.
1015 if (right
->hasConstVal()) {
1016 return cns(env
, cmpOp(opc
, left
->intVal(), right
->intVal()));
1018 auto newOpc
= [](Opcode opc
) {
1020 case GtInt
: return LtInt
;
1021 case GteInt
: return LteInt
;
1022 case LtInt
: return GtInt
;
1023 case LteInt
: return GteInt
;
1024 case EqInt
: return EqInt
;
1025 case NeqInt
: return NeqInt
;
1026 default: always_assert(false);
1029 return newInst(newOpc
, right
, left
);
1036 SSATmp
* cmpStrImpl(State
& env
,
1038 const IRInstruction
* const inst
,
1041 assertx(left
->type() <= TStr
);
1042 assertx(right
->type() <= TStr
);
1044 auto newInst
= [&](Opcode op
, SSATmp
* src1
, SSATmp
* src2
) {
1045 return gen(env
, op
, inst
? inst
->taken() : (Block
*)nullptr, src1
, src2
);
1048 // Identity optimization
1049 if (left
== right
) {
1050 return cns(env
, cmpOp(opc
, true, true));
1053 if (left
->hasConstVal()) {
1054 // If both operands are constants, constant-fold them. Otherwise, move the
1055 // constant over to the right.
1056 if (right
->hasConstVal()) {
1057 if (opc
== SameStr
|| opc
== NSameStr
) {
1060 cmpOp(opc
, left
->strVal()->same(right
->strVal()), true)
1065 cmpOp(opc
, left
->strVal()->compare(right
->strVal()), 0)
1069 auto newOpc
= [](Opcode opc
) {
1071 case GtStr
: return LtStr
;
1072 case GteStr
: return LteStr
;
1073 case LtStr
: return GtStr
;
1074 case LteStr
: return GteStr
;
1075 case EqStr
: return EqStr
;
1076 case NeqStr
: return NeqStr
;
1077 case SameStr
: return SameStr
;
1078 case NSameStr
: return NSameStr
;
1079 default: always_assert(false);
1082 return newInst(newOpc
, right
, left
);
1086 // Comparisons against the empty string can be optimized to checks on the
1088 if (right
->hasConstVal() && right
->strVal()->empty()) {
1092 case LteStr
: return newInst(EqInt
, gen(env
, LdStrLen
, left
), cns(env
, 0));
1095 case GtStr
: return newInst(NeqInt
, gen(env
, LdStrLen
, left
), cns(env
, 0));
1096 case LtStr
: return cns(env
, false);
1097 case GteStr
: return cns(env
, true);
1098 default: always_assert(false);
1105 SSATmp
* cmpStrIntImpl(State
& env
,
1107 const IRInstruction
* const inst
,
1110 assertx(left
->type() <= TStr
);
1111 assertx(right
->type() <= TInt
);
1113 auto newInst
= [&](Opcode op
, SSATmp
* src1
, SSATmp
* src2
) {
1114 return gen(env
, op
, inst
? inst
->taken() : (Block
*)nullptr, src1
, src2
);
1117 // If the string operand has a constant value, convert it to the appropriate
1118 // numeric and lower to a numeric comparison.
1119 if (left
->hasConstVal()) {
1123 left
->strVal()->isNumericWithVal(si
, sd
, true /* allow errors */);
1124 if (type
== KindOfDouble
) {
1125 auto dblOpc
= [](Opcode opc
) {
1127 case GtStrInt
: return GtDbl
;
1128 case GteStrInt
: return GteDbl
;
1129 case LtStrInt
: return LtDbl
;
1130 case LteStrInt
: return LteDbl
;
1131 case EqStrInt
: return EqDbl
;
1132 case NeqStrInt
: return NeqDbl
;
1133 default: always_assert(false);
1139 gen(env
, ConvIntToDbl
, right
)
1142 auto intOpc
= [](Opcode opc
) {
1144 case GtStrInt
: return GtInt
;
1145 case GteStrInt
: return GteInt
;
1146 case LtStrInt
: return LtInt
;
1147 case LteStrInt
: return LteInt
;
1148 case EqStrInt
: return EqInt
;
1149 case NeqStrInt
: return NeqInt
;
1150 default: always_assert(false);
1155 cns(env
, type
== KindOfNull
? (int64_t)0 : si
),
1164 SSATmp
* cmpObjImpl(State
& env
,
1166 const IRInstruction
* const inst
,
1169 assertx(left
->type() <= TObj
);
1170 assertx(right
->type() <= TObj
);
1172 // Identity optimization. Object comparisons can produce arbitrary
1173 // side-effects, so we can only eliminate the comparison if its checking for
1175 if ((opc
== SameObj
|| opc
== NSameObj
) && left
== right
) {
1176 return cns(env
, cmpOp(opc
, true, true));
1182 SSATmp
* cmpArrImpl(State
& env
,
1184 const IRInstruction
* const inst
,
1187 assertx(left
->type() <= TArr
);
1188 assertx(right
->type() <= TArr
);
1190 // Identity optimization. Array comparisons can produce arbitrary
1191 // side-effects, so we can only eliminate the comparison if its checking for
1193 if ((opc
== SameArr
|| opc
== NSameArr
) && left
== right
) {
1194 return cns(env
, cmpOp(opc
, true, true));
1201 SSATmp
* cmpResImpl(State
& env
,
1203 const IRInstruction
* const inst
,
1206 assertx(left
->type() <= TRes
);
1207 assertx(right
->type() <= TRes
);
1209 // Identity optimization.
1210 if (left
== right
) {
1211 return cns(env
, cmpOp(opc
, true, true));
1217 #define X(name, type) \
1218 SSATmp* simplify##name(State& env, const IRInstruction* i) { \
1219 return cmp##type##Impl(env, i->op(), i, i->src(0), i->src(1)); \
1246 X(GteStrInt
, StrInt
)
1248 X(LteStrInt
, StrInt
)
1250 X(NeqStrInt
, StrInt
)
1279 SSATmp
* simplifyCmpBool(State
& env
, const IRInstruction
* inst
) {
1280 auto const left
= inst
->src(0);
1281 auto const right
= inst
->src(1);
1282 assertx(left
->type() <= TBool
);
1283 assertx(right
->type() <= TBool
);
1284 if (left
->hasConstVal() && right
->hasConstVal()) {
1285 return cns(env
, HPHP::compare(left
->boolVal(), right
->boolVal()));
1290 SSATmp
* simplifyCmpInt(State
& env
, const IRInstruction
* inst
) {
1291 auto const left
= inst
->src(0);
1292 auto const right
= inst
->src(1);
1293 assertx(left
->type() <= TInt
);
1294 assertx(right
->type() <= TInt
);
1295 if (left
->hasConstVal() && right
->hasConstVal()) {
1296 return cns(env
, HPHP::compare(left
->intVal(), right
->intVal()));
1301 SSATmp
* simplifyCmpStr(State
& env
, const IRInstruction
* inst
) {
1302 auto const left
= inst
->src(0);
1303 auto const right
= inst
->src(1);
1305 assertx(left
->type() <= TStr
);
1306 assertx(right
->type() <= TStr
);
1308 auto newInst
= [&](Opcode op
, SSATmp
* src1
, SSATmp
* src2
) {
1309 return gen(env
, op
, inst
? inst
->taken() : (Block
*)nullptr, src1
, src2
);
1312 if (left
->hasConstVal()) {
1313 if (right
->hasConstVal()) {
1314 return cns(env
, HPHP::compare(left
->strVal(), right
->strVal()));
1315 } else if (left
->strVal()->empty()) {
1316 // Comparisons against the empty string can be optimized to a comparison
1317 // on the string length.
1318 return newInst(CmpInt
, cns(env
, 0), gen(env
, LdStrLen
, right
));
1320 } else if (right
->hasConstVal() && right
->strVal()->empty()) {
1321 return newInst(CmpInt
, gen(env
, LdStrLen
, left
), cns(env
, 0));
1327 SSATmp
* simplifyCmpStrInt(State
& env
, const IRInstruction
* inst
) {
1328 auto const left
= inst
->src(0);
1329 auto const right
= inst
->src(1);
1331 assertx(left
->type() <= TStr
);
1332 assertx(right
->type() <= TInt
);
1334 auto newInst
= [&](Opcode op
, SSATmp
* src1
, SSATmp
* src2
) {
1335 return gen(env
, op
, inst
? inst
->taken() : (Block
*)nullptr, src1
, src2
);
1338 // If the string operand has a constant value, convert it to the appropriate
1339 // numeric and lower to a numeric comparison.
1340 if (left
->hasConstVal()) {
1344 left
->strVal()->isNumericWithVal(si
, sd
, true /* allow errors */);
1345 if (type
== KindOfDouble
) {
1349 gen(env
, ConvIntToDbl
, right
)
1354 cns(env
, type
== KindOfNull
? (int64_t)0 : si
),
1363 SSATmp
* simplifyCmpRes(State
& env
, const IRInstruction
* inst
) {
1364 auto const left
= inst
->src(0);
1365 auto const right
= inst
->src(1);
1366 assertx(left
->type() <= TRes
);
1367 assertx(right
->type() <= TRes
);
1368 return (left
== right
) ? cns(env
, 0) : nullptr;
1371 SSATmp
* isTypeImpl(State
& env
, const IRInstruction
* inst
) {
1372 bool const trueSense
= inst
->op() == IsType
;
1373 auto const type
= inst
->typeParam();
1374 auto const src
= inst
->src(0);
1375 auto const srcType
= src
->type();
1377 // If mightRelax(src) returns true, we can't generally depend on the src's
1378 // type. However, we always constrain the input to this opcode with at least
1379 // DataTypeSpecific, so we only have to skip the optimization if the
1380 // typeParam is specialized.
1381 if (mightRelax(env
, src
) && type
.isSpecialized()) return nullptr;
1383 // Testing for StaticStr will make you miss out on CountedStr, and vice versa,
1384 // and similarly for arrays. PHP treats both types of string the same, so if
1385 // the distinction matters to you here, be careful.
1386 assertx(IMPLIES(type
<= TStr
, type
== TStr
));
1387 assertx(IMPLIES(type
<= TArr
, type
== TArr
));
1389 // The types are disjoint; the result must be false.
1390 if (!srcType
.maybe(type
)) {
1391 return cns(env
, !trueSense
);
1394 // The src type is a subtype of the tested type; the result must be true.
1395 if (srcType
<= type
) {
1396 return cns(env
, trueSense
);
1399 // At this point, either the tested type is a subtype of the src type, or they
1400 // are non-disjoint but neither is a subtype of the other. We can't simplify
1405 SSATmp
* simplifyInstanceOf(State
& env
, const IRInstruction
* inst
) {
1406 auto const src1
= inst
->src(0);
1407 auto const src2
= inst
->src(1);
1409 if (src2
->isA(TNullptr
)) return cns(env
, false);
1411 if (!src1
->hasConstVal() || !src2
->hasConstVal()) return nullptr;
1413 return cns(env
, src1
->clsVal()->classof(src2
->clsVal()));
1416 SSATmp
* simplifyExtendsClass(State
& env
, const IRInstruction
* i
) {
1417 return simplifyInstanceOf(env
, i
);
1420 SSATmp
* simplifyInstanceOfIface(State
& env
, const IRInstruction
* inst
) {
1421 auto const src1
= inst
->src(0);
1422 auto const src2
= inst
->src(1);
1424 if (!src1
->hasConstVal() || !src2
->hasConstVal()) return nullptr;
1426 return cns(env
, src1
->clsVal()->ifaceofDirect(src2
->strVal()));
1429 SSATmp
* simplifyIsType(State
& env
, const IRInstruction
* i
) {
1430 return isTypeImpl(env
, i
);
1433 SSATmp
* simplifyIsNType(State
& env
, const IRInstruction
* i
) {
1434 return isTypeImpl(env
, i
);
1437 SSATmp
* simplifyIsScalarType(State
& env
, const IRInstruction
* inst
) {
1438 auto const src
= inst
->src(0);
1439 if (src
->type().isKnownDataType()) {
1440 return cns(env
, src
->isA(TInt
| TDbl
| TStr
| TBool
));
1445 SSATmp
* simplifyConcatStrStr(State
& env
, const IRInstruction
* inst
) {
1446 auto const src1
= inst
->src(0);
1447 auto const src2
= inst
->src(1);
1448 if (src1
->hasConstVal(TStaticStr
) &&
1449 src2
->hasConstVal(TStaticStr
)) {
1450 auto const str1
= const_cast<StringData
*>(src1
->strVal());
1451 auto const str2
= const_cast<StringData
*>(src2
->strVal());
1452 auto const sval
= String::attach(concat_ss(str1
, str2
));
1453 return cns(env
, makeStaticString(sval
.get()));
1459 SSATmp
* convToArrImpl(State
& env
, const IRInstruction
* inst
) {
1460 auto const src
= inst
->src(0);
1461 if (src
->hasConstVal()) {
1462 Array arr
= Array::Create(src
->variantVal());
1463 return cns(env
, ArrayData::GetScalarArray(arr
.get()));
1468 SSATmp
* simplifyConvBoolToArr(State
& env
, const IRInstruction
* inst
) {
1469 return convToArrImpl(env
, inst
);
1472 SSATmp
* simplifyConvIntToArr(State
& env
, const IRInstruction
* inst
) {
1473 return convToArrImpl(env
, inst
);
1476 SSATmp
* simplifyConvDblToArr(State
& env
, const IRInstruction
* inst
) {
1477 return convToArrImpl(env
, inst
);
1480 SSATmp
* simplifyConvStrToArr(State
& env
, const IRInstruction
* inst
) {
1481 return convToArrImpl(env
, inst
);
1484 SSATmp
* simplifyConvArrToBool(State
& env
, const IRInstruction
* inst
) {
1485 auto const src
= inst
->src(0);
1486 auto const kind
= src
->type().arrSpec().kind();
1487 if (src
->isA(TStaticArr
) || (kind
&& !arrayKindNeedsVsize(*kind
))) {
1488 return gen(env
, ConvIntToBool
, gen(env
, CountArrayFast
, src
));
1493 SSATmp
* simplifyConvDblToBool(State
& env
, const IRInstruction
* inst
) {
1494 auto const src
= inst
->src(0);
1495 if (src
->hasConstVal()) {
1496 bool const bval
= src
->dblVal();
1497 return cns(env
, bval
);
1502 SSATmp
* simplifyConvIntToBool(State
& env
, const IRInstruction
* inst
) {
1503 auto const src
= inst
->src(0);
1504 if (src
->hasConstVal()) {
1505 bool const bval
= src
->intVal();
1506 return cns(env
, bval
);
1511 SSATmp
* simplifyConvStrToBool(State
& env
, const IRInstruction
* inst
) {
1512 auto const src
= inst
->src(0);
1513 if (src
->hasConstVal()) {
1514 // only the strings "", and "0" convert to false, all other strings
1515 // are converted to true
1516 auto const str
= src
->strVal();
1517 return cns(env
, !str
->empty() && !str
->isZero());
1522 SSATmp
* simplifyConvArrToDbl(State
& env
, const IRInstruction
* inst
) {
1523 auto const src
= inst
->src(0);
1524 if (src
->hasConstVal()) {
1525 if (src
->arrVal()->empty()) {
1526 return cns(env
, 0.0);
1532 SSATmp
* simplifyConvBoolToDbl(State
& env
, const IRInstruction
* inst
) {
1533 auto const src
= inst
->src(0);
1534 if (src
->hasConstVal()) {
1535 double const dval
= src
->boolVal();
1536 return cns(env
, dval
);
1541 SSATmp
* simplifyConvIntToDbl(State
& env
, const IRInstruction
* inst
) {
1542 auto const src
= inst
->src(0);
1543 if (src
->hasConstVal()) {
1544 double const dval
= src
->intVal();
1545 return cns(env
, dval
);
1547 if (src
->inst()->is(ConvBoolToInt
)) {
1548 // This is safe, because the bool src is not reference counted.
1549 return gen(env
, ConvBoolToDbl
, src
->inst()->src(0));
1554 SSATmp
* simplifyConvStrToDbl(State
& env
, const IRInstruction
* inst
) {
1555 auto const src
= inst
->src(0);
1556 return src
->hasConstVal() ? cns(env
, src
->strVal()->toDouble()) : nullptr;
1559 SSATmp
* simplifyConvArrToInt(State
& env
, const IRInstruction
* inst
) {
1560 auto const src
= inst
->src(0);
1561 if (src
->hasConstVal()) {
1562 if (src
->arrVal()->empty()) return cns(env
, 0);
1568 SSATmp
* simplifyConvBoolToInt(State
& env
, const IRInstruction
* inst
) {
1569 auto const src
= inst
->src(0);
1570 if (src
->hasConstVal()) return cns(env
, static_cast<int>(src
->boolVal()));
1574 SSATmp
* simplifyConvDblToInt(State
& env
, const IRInstruction
* inst
) {
1575 auto const src
= inst
->src(0);
1576 if (src
->hasConstVal()) return cns(env
, toInt64(src
->dblVal()));
1580 SSATmp
* simplifyConvStrToInt(State
& env
, const IRInstruction
* inst
) {
1581 auto const src
= inst
->src(0);
1582 return src
->hasConstVal() ? cns(env
, src
->strVal()->toInt64()) : nullptr;
1585 SSATmp
* simplifyConvBoolToStr(State
& env
, const IRInstruction
* inst
) {
1586 auto const src
= inst
->src(0);
1587 if (src
->hasConstVal()) {
1588 if (src
->boolVal()) return cns(env
, s_1
.get());
1589 return cns(env
, s_empty
.get());
1594 SSATmp
* simplifyConvDblToStr(State
& env
, const IRInstruction
* inst
) {
1595 auto const src
= inst
->src(0);
1596 if (src
->hasConstVal()) {
1597 auto dblStr
= String::attach(buildStringData(src
->dblVal()));
1598 return cns(env
, makeStaticString(dblStr
));
1603 SSATmp
* simplifyConvIntToStr(State
& env
, const IRInstruction
* inst
) {
1604 auto const src
= inst
->src(0);
1605 if (src
->hasConstVal()) {
1607 makeStaticString(folly::to
<std::string
>(src
->intVal()))
1613 SSATmp
* simplifyConvCellToBool(State
& env
, const IRInstruction
* inst
) {
1614 auto const src
= inst
->src(0);
1615 auto const srcType
= src
->type();
1617 if (srcType
<= TBool
) return src
;
1618 if (srcType
<= TNull
) return cns(env
, false);
1619 if (srcType
<= TArr
) return gen(env
, ConvArrToBool
, src
);
1620 if (srcType
<= TDbl
) return gen(env
, ConvDblToBool
, src
);
1621 if (srcType
<= TInt
) return gen(env
, ConvIntToBool
, src
);
1622 if (srcType
<= TStr
) return gen(env
, ConvStrToBool
, src
);
1623 if (srcType
<= TObj
) {
1624 if (auto cls
= srcType
.clsSpec().cls()) {
1625 // We need to exclude interfaces like ConstSet. For now, just
1626 // skip anything that's an interface.
1627 if (!(cls
->attrs() & AttrInterface
)) {
1628 // t3429711 we should test cls->m_ODAttr
1629 // here, but currently it doesnt have all
1631 if (!cls
->instanceCtor()) {
1632 return cns(env
, true);
1636 return gen(env
, ConvObjToBool
, src
);
1638 if (srcType
<= TRes
) return cns(env
, true);
1643 SSATmp
* simplifyConvCellToStr(State
& env
, const IRInstruction
* inst
) {
1644 auto const src
= inst
->src(0);
1645 auto const srcType
= src
->type();
1646 auto const catchTrace
= inst
->taken();
1648 if (srcType
<= TBool
) return gen(env
, ConvBoolToStr
, src
);
1649 if (srcType
<= TNull
) return cns(env
, s_empty
.get());
1650 if (srcType
<= TArr
) {
1651 gen(env
, RaiseNotice
, catchTrace
,
1652 cns(env
, makeStaticString("Array to string conversion")));
1653 return cns(env
, s_Array
.get());
1655 if (srcType
<= TDbl
) return gen(env
, ConvDblToStr
, src
);
1656 if (srcType
<= TInt
) return gen(env
, ConvIntToStr
, src
);
1657 if (srcType
<= TStr
) {
1658 gen(env
, IncRef
, src
);
1661 if (srcType
<= TObj
) return gen(env
, ConvObjToStr
, catchTrace
, src
);
1662 if (srcType
<= TRes
) return gen(env
, ConvResToStr
, catchTrace
, src
);
1667 SSATmp
* simplifyConvCellToInt(State
& env
, const IRInstruction
* inst
) {
1668 auto const src
= inst
->src(0);
1669 auto const srcType
= src
->type();
1671 if (srcType
<= TInt
) return src
;
1672 if (srcType
<= TNull
) return cns(env
, 0);
1673 if (srcType
<= TArr
) return gen(env
, ConvArrToInt
, src
);
1674 if (srcType
<= TBool
) return gen(env
, ConvBoolToInt
, src
);
1675 if (srcType
<= TDbl
) return gen(env
, ConvDblToInt
, src
);
1676 if (srcType
<= TStr
) return gen(env
, ConvStrToInt
, src
);
1677 if (srcType
<= TObj
) return gen(env
, ConvObjToInt
, inst
->taken(), src
);
1678 if (srcType
<= TRes
) return gen(env
, ConvResToInt
, src
);
1683 SSATmp
* simplifyConvCellToDbl(State
& env
, const IRInstruction
* inst
) {
1684 auto const src
= inst
->src(0);
1685 auto const srcType
= src
->type();
1687 if (srcType
<= TDbl
) return src
;
1688 if (srcType
<= TNull
) return cns(env
, 0.0);
1689 if (srcType
<= TArr
) return gen(env
, ConvArrToDbl
, src
);
1690 if (srcType
<= TBool
) return gen(env
, ConvBoolToDbl
, src
);
1691 if (srcType
<= TInt
) return gen(env
, ConvIntToDbl
, src
);
1692 if (srcType
<= TStr
) return gen(env
, ConvStrToDbl
, src
);
1693 if (srcType
<= TObj
) return gen(env
, ConvObjToDbl
, inst
->taken(), src
);
1694 if (srcType
<= TRes
) return gen(env
, ConvResToDbl
, src
);
1699 SSATmp
* simplifyConvObjToBool(State
& env
, const IRInstruction
* inst
) {
1700 auto const ty
= inst
->src(0)->type();
1702 if (!typeMightRelax(inst
->src(0)) &&
1704 ty
.clsSpec().cls() &&
1705 ty
.clsSpec().cls()->isCollectionClass()) {
1706 return gen(env
, ColIsNEmpty
, inst
->src(0));
1711 SSATmp
* simplifyConvCellToObj(State
& env
, const IRInstruction
* inst
) {
1712 if (inst
->src(0)->isA(TObj
)) return inst
->src(0);
1716 SSATmp
* simplifyCoerceCellToBool(State
& env
, const IRInstruction
* inst
) {
1717 auto const src
= inst
->src(0);
1718 auto const srcType
= src
->type();
1720 if (srcType
.subtypeOfAny(TBool
, TNull
, TDbl
,
1722 return gen(env
, ConvCellToBool
, src
);
1725 // We actually know that any other type will fail causing us to side exit
1726 // but there's no easy way to optimize for that
1731 SSATmp
* simplifyCoerceCellToInt(State
& env
, const IRInstruction
* inst
) {
1732 auto const src
= inst
->src(0);
1733 auto const srcType
= src
->type();
1735 if (srcType
.subtypeOfAny(TInt
, TBool
, TNull
, TDbl
,
1737 return gen(env
, ConvCellToInt
, inst
->taken(), src
);
1740 if (srcType
<= TStr
) return gen(env
, CoerceStrToInt
, inst
->taken(),
1741 *inst
->extra
<CoerceCellToInt
>(), src
);
1743 // We actually know that any other type will fail causing us to side exit
1744 // but there's no easy way to optimize for that
1749 SSATmp
* simplifyCoerceCellToDbl(State
& env
, const IRInstruction
* inst
) {
1750 auto const src
= inst
->src(0);
1751 auto const srcType
= src
->type();
1753 if (srcType
.subtypeOfAny(TInt
, TBool
, TNull
, TDbl
,
1755 return gen(env
, ConvCellToDbl
, inst
->taken(), src
);
1758 if (srcType
<= TStr
) return gen(env
, CoerceStrToDbl
, inst
->taken(),
1759 *inst
->extra
<CoerceCellToDbl
>(), src
);
1761 // We actually know that any other type will fail causing us to side exit
1762 // but there's no easy way to optimize for that
1767 SSATmp
* roundImpl(State
& env
, const IRInstruction
* inst
, double (*op
)(double)) {
1768 auto const src
= inst
->src(0);
1770 if (src
->hasConstVal()) {
1771 return cns(env
, op(src
->dblVal()));
1774 auto srcInst
= src
->inst();
1775 if (srcInst
->op() == ConvIntToDbl
|| srcInst
->op() == ConvBoolToDbl
) {
1782 SSATmp
* simplifyFloor(State
& env
, const IRInstruction
* inst
) {
1783 return roundImpl(env
, inst
, floor
);
1786 SSATmp
* simplifyCeil(State
& env
, const IRInstruction
* inst
) {
1787 return roundImpl(env
, inst
, ceil
);
1790 SSATmp
* simplifyUnboxPtr(State
& env
, const IRInstruction
* inst
) {
1791 if (inst
->src(0)->isA(TPtrToCell
)) {
1792 return inst
->src(0);
1797 SSATmp
* simplifyBoxPtr(State
& env
, const IRInstruction
* inst
) {
1798 if (inst
->src(0)->isA(TPtrToBoxedCell
)) {
1799 return inst
->src(0);
1804 SSATmp
* simplifyCheckInit(State
& env
, const IRInstruction
* inst
) {
1805 auto const srcType
= inst
->src(0)->type();
1806 assertx(!srcType
.maybe(TPtrToGen
));
1807 assertx(inst
->taken());
1808 if (!srcType
.maybe(TUninit
)) return gen(env
, Nop
);
1809 return mergeBranchDests(env
, inst
);
1812 SSATmp
* simplifyCheckInitMem(State
& env
, const IRInstruction
* inst
) {
1813 return mergeBranchDests(env
, inst
);
1816 SSATmp
* simplifyCheckInitProps(State
& env
, const IRInstruction
* inst
) {
1817 return mergeBranchDests(env
, inst
);
1820 SSATmp
* simplifyCheckInitSProps(State
& env
, const IRInstruction
* inst
) {
1821 return mergeBranchDests(env
, inst
);
1824 SSATmp
* simplifyInitObjProps(State
& env
, const IRInstruction
* inst
) {
1825 auto const cls
= inst
->extra
<InitObjProps
>()->cls
;
1826 if (cls
->getODAttrs() == 0 && cls
->numDeclProperties() == 0) {
1827 return gen(env
, Nop
);
1832 SSATmp
* simplifyCheckType(State
& env
, const IRInstruction
* inst
) {
1833 auto const typeParam
= inst
->typeParam();
1834 auto const srcType
= inst
->src(0)->type();
1836 if (!srcType
.maybe(typeParam
) || inst
->next() == inst
->taken()) {
1838 * Convert the check into a Jmp. The dest of the CheckType (which would've
1839 * been Bottom) is now never going to be defined, so we return a Bottom.
1841 gen(env
, Jmp
, inst
->taken());
1842 return cns(env
, TBottom
);
1845 auto const newType
= srcType
& typeParam
;
1846 if (srcType
<= newType
) {
1847 // The type of the src is the same or more refined than type, so the guard
1849 return inst
->src(0);
1855 SSATmp
* simplifyCheckTypeMem(State
& env
, const IRInstruction
* inst
) {
1856 if (inst
->next() == inst
->taken() ||
1857 inst
->typeParam() == TBottom
) {
1858 return gen(env
, Jmp
, inst
->taken());
1864 SSATmp
* simplifyAssertType(State
& env
, const IRInstruction
* inst
) {
1865 auto const src
= inst
->src(0);
1867 return canSimplifyAssertType(inst
, src
->type(), mightRelax(env
, src
))
1872 SSATmp
* simplifyCheckLoc(State
& env
, const IRInstruction
* inst
) {
1873 return mergeBranchDests(env
, inst
);
1876 SSATmp
* simplifyCheckStk(State
& env
, const IRInstruction
* inst
) {
1877 return mergeBranchDests(env
, inst
);
1880 SSATmp
* simplifyCheckStaticLocInit(State
& env
, const IRInstruction
* inst
) {
1881 return mergeBranchDests(env
, inst
);
1884 SSATmp
* simplifyCheckRefInner(State
& env
, const IRInstruction
* inst
) {
1885 // Ref inner cells are at worst InitCell, so don't bother checking for that.
1886 if (TInitCell
<= inst
->typeParam()) {
1887 return gen(env
, Nop
);
1889 return mergeBranchDests(env
, inst
);
1892 SSATmp
* simplifyDefLabel(State
& env
, const IRInstruction
* inst
) {
1893 if (inst
->numDsts() == 0) {
1894 return gen(env
, Nop
);
1899 SSATmp
* decRefImpl(State
& env
, const IRInstruction
* inst
) {
1900 auto const src
= inst
->src(0);
1901 if (!mightRelax(env
, src
) && !src
->type().maybe(TCounted
)) {
1902 return gen(env
, Nop
);
1907 SSATmp
* simplifyDecRef(State
& env
, const IRInstruction
* inst
) {
1908 return decRefImpl(env
, inst
);
1911 SSATmp
* simplifyDecRefNZ(State
& env
, const IRInstruction
* inst
) {
1912 return decRefImpl(env
, inst
);
1915 SSATmp
* simplifyIncRef(State
& env
, const IRInstruction
* inst
) {
1916 auto const src
= inst
->src(0);
1917 if (!mightRelax(env
, src
) && !src
->type().maybe(TCounted
)) {
1918 return gen(env
, Nop
);
1923 SSATmp
* simplifyIncRefCtx(State
& env
, const IRInstruction
* inst
) {
1924 auto const ctx
= inst
->src(0);
1925 if (ctx
->isA(TObj
)) {
1926 return gen(env
, IncRef
, ctx
);
1927 } else if (!mightRelax(env
, ctx
) && !ctx
->type().maybe(TCounted
)) {
1928 return gen(env
, Nop
);
1934 SSATmp
* condJmpImpl(State
& env
, const IRInstruction
* inst
) {
1935 assertx(inst
->is(JmpZero
, JmpNZero
));
1936 // Both ways go to the same block.
1937 if (inst
->taken() == inst
->next()) {
1938 assertx(inst
->taken() != nullptr);
1939 return gen(env
, Jmp
, inst
->taken());
1942 auto const src
= inst
->src(0);
1943 auto const srcInst
= src
->inst();
1945 // Constant propagate.
1946 if (src
->hasConstVal()) {
1947 bool val
= src
->isA(TBool
) ? src
->boolVal()
1948 : static_cast<bool>(src
->intVal());
1949 if (val
== inst
->is(JmpNZero
)) {
1951 assertx(inst
->taken());
1952 return gen(env
, Jmp
, inst
->taken());
1954 // Never taken. Since simplify() is also run when building the IR,
1955 // inst->next() could be nullptr at this moment.
1956 return gen(env
, Nop
);
1959 auto absorb
= [&](){
1960 return gen(env
, inst
->op(), inst
->taken(), srcInst
->src(0));
1962 auto absorbOpp
= [&](){
1964 inst
->op() == JmpZero
? JmpNZero
: JmpZero
,
1970 // Absorb negations.
1971 if (srcInst
->is(XorBool
) && srcInst
->src(1)->hasConstVal(true)) {
1975 // Absorb ConvIntToBool.
1976 if (srcInst
->is(ConvIntToBool
)) {
1980 // Absorb boolean comparisons.
1981 if (srcInst
->is(EqBool
) && srcInst
->src(1)->hasConstVal()) {
1982 return srcInst
->src(1)->boolVal() ? absorb() : absorbOpp();
1984 if (srcInst
->is(NeqBool
) && srcInst
->src(1)->hasConstVal()) {
1985 return srcInst
->src(1)->boolVal() ? absorbOpp() : absorb();
1988 // Absorb integer comparisons against constant zero.
1989 if (srcInst
->is(EqInt
) && srcInst
->src(1)->hasConstVal(0)) {
1992 if (srcInst
->is(NeqInt
) && srcInst
->src(1)->hasConstVal(0)) {
1999 SSATmp
* simplifyJmpZero(State
& env
, const IRInstruction
* i
) {
2000 return condJmpImpl(env
, i
);
2003 SSATmp
* simplifyJmpNZero(State
& env
, const IRInstruction
* i
) {
2004 return condJmpImpl(env
, i
);
2008 SSATmp
* simplifyAssertNonNull(State
& env
, const IRInstruction
* inst
) {
2009 if (!inst
->src(0)->type().maybe(TNullptr
)) {
2010 return inst
->src(0);
2015 SSATmp
* simplifyCheckPackedArrayBounds(State
& env
, const IRInstruction
* inst
) {
2016 auto const array
= inst
->src(0);
2017 auto const idx
= inst
->src(1);
2018 if (!idx
->hasConstVal()) return mergeBranchDests(env
, inst
);
2020 auto const idxVal
= idx
->intVal();
2021 switch (packedArrayBoundsStaticCheck(array
->type(), idxVal
)) {
2022 case PackedBounds::In
: return gen(env
, Nop
);
2023 case PackedBounds::Out
: return gen(env
, Jmp
, inst
->taken());
2024 case PackedBounds::Unknown
: break;
2027 return mergeBranchDests(env
, inst
);
2030 SSATmp
* arrIntKeyImpl(State
& env
, const IRInstruction
* inst
) {
2031 auto const arr
= inst
->src(0);
2032 auto const idx
= inst
->src(1);
2033 assertx(arr
->hasConstVal(TArr
));
2034 if (!idx
->hasConstVal()) return nullptr;
2035 auto const value
= arr
->arrVal()->nvGet(idx
->intVal());
2036 return value
? cns(env
, *value
) : nullptr;
2039 SSATmp
* arrStrKeyImpl(State
& env
, const IRInstruction
* inst
) {
2040 auto const arr
= inst
->src(0);
2041 auto const idx
= inst
->src(1);
2042 assertx(arr
->hasConstVal(TArr
));
2043 if (!idx
->hasConstVal()) return nullptr;
2044 auto const value
= [&] {
2046 if (idx
->strVal()->isStrictlyInteger(val
)) {
2047 return arr
->arrVal()->nvGet(val
);
2049 return arr
->arrVal()->nvGet(idx
->strVal());
2051 return value
? cns(env
, *value
) : nullptr;
2054 SSATmp
* simplifyArrayGet(State
& env
, const IRInstruction
* inst
) {
2055 if (inst
->src(0)->hasConstVal()) {
2056 if (inst
->src(1)->type() <= TInt
) return arrIntKeyImpl(env
, inst
);
2057 if (inst
->src(1)->type() <= TStr
) return arrStrKeyImpl(env
, inst
);
2062 SSATmp
* simplifyCount(State
& env
, const IRInstruction
* inst
) {
2063 auto const val
= inst
->src(0);
2064 auto const ty
= val
->type();
2066 if (ty
<= TNull
) return cns(env
, 0);
2068 auto const oneTy
= TBool
| TInt
| TDbl
| TStr
| TRes
;
2069 if (ty
<= oneTy
) return cns(env
, 1);
2071 if (ty
<= TArr
) return gen(env
, CountArray
, val
);
2074 auto const cls
= ty
.clsSpec().cls();
2075 if (!mightRelax(env
, val
) && cls
!= nullptr && cls
->isCollectionClass()) {
2076 return gen(env
, CountCollection
, val
);
2082 SSATmp
* simplifyCountArrayFast(State
& env
, const IRInstruction
* inst
) {
2083 auto const src
= inst
->src(0);
2084 if (inst
->src(0)->hasConstVal(TArr
)) return cns(env
, src
->arrVal()->size());
2085 auto const arrSpec
= src
->type().arrSpec();
2086 auto const at
= arrSpec
.type();
2087 if (!at
) return nullptr;
2088 using A
= RepoAuthType::Array
;
2089 switch (at
->tag()) {
2090 case A::Tag::Packed
:
2091 if (at
->emptiness() == A::Empty::No
) {
2092 return cns(env
, at
->size());
2095 case A::Tag::PackedN
:
2101 SSATmp
* simplifyCountArray(State
& env
, const IRInstruction
* inst
) {
2102 auto const src
= inst
->src(0);
2103 auto const ty
= src
->type();
2105 if (src
->hasConstVal()) return cns(env
, src
->arrVal()->size());
2107 auto const kind
= ty
.arrSpec().kind();
2109 if (kind
&& !mightRelax(env
, src
) && !arrayKindNeedsVsize(*kind
))
2110 return gen(env
, CountArrayFast
, src
);
2115 SSATmp
* simplifyLdClsName(State
& env
, const IRInstruction
* inst
) {
2116 auto const src
= inst
->src(0);
2117 return src
->hasConstVal(TCls
) ? cns(env
, src
->clsVal()->name()) : nullptr;
2120 SSATmp
* simplifyLdStrLen(State
& env
, const IRInstruction
* inst
) {
2121 auto const src
= inst
->src(0);
2122 return src
->hasConstVal(TStr
) ? cns(env
, src
->strVal()->size()) : nullptr;
2125 SSATmp
* simplifyCallBuiltin(State
& env
, const IRInstruction
* inst
) {
2126 auto const callee
= inst
->extra
<CallBuiltin
>()->callee
;
2127 auto const args
= inst
->srcs();
2130 bool const arg2IsCollection
= args
.size() == 3 &&
2131 args
[2]->isA(TObj
) &&
2132 args
[2]->type().clsSpec() &&
2133 args
[2]->type().clsSpec().cls()->isCollectionClass() &&
2134 !mightRelax(env
, args
[2]);
2136 if (arg2IsCollection
) {
2137 if (callee
->name()->isame(s_isEmpty
.get())) {
2138 FTRACE(3, "simplifying collection: {}\n", callee
->name()->data());
2139 return gen(env
, ColIsEmpty
, args
[2]);
2141 if (callee
->name()->isame(s_count
.get())) {
2142 FTRACE(3, "simplifying collection: {}\n", callee
->name()->data());
2143 return gen(env
, CountCollection
, args
[2]);
2147 bool const arg2IsWaitHandle
= !arg2IsCollection
&&
2149 args
[2]->isA(TObj
) &&
2150 args
[2]->type().clsSpec() &&
2151 args
[2]->type().clsSpec().cls()->classof(c_WaitHandle::classof()) &&
2152 !mightRelax(env
, args
[2]);
2154 if (arg2IsWaitHandle
) {
2155 const auto genState
= [&] (Opcode op
, int64_t whstate
) -> SSATmp
* {
2156 // these methods all spring from the base class
2157 assert(callee
->cls()->name()->isame(s_WaitHandle
.get()));
2158 const auto state
= gen(env
, LdWHState
, args
[2]);
2159 return gen(env
, op
, state
, cns(env
, whstate
));
2161 const auto methName
= callee
->name();
2162 if (methName
->isame(s_isFinished
.get())) {
2163 return genState(LteInt
, int64_t{c_WaitHandle::STATE_FAILED
});
2165 if (methName
->isame(s_isSucceeded
.get())) {
2166 return genState(EqInt
, int64_t{c_WaitHandle::STATE_SUCCEEDED
});
2168 if (methName
->isame(s_isFailed
.get())) {
2169 return genState(EqInt
, int64_t{c_WaitHandle::STATE_FAILED
});
2176 SSATmp
* simplifyIsWaitHandle(State
& env
, const IRInstruction
* inst
) {
2177 if (mightRelax(env
, inst
->src(0))) return nullptr;
2179 bool baseIsWaitHandle
= inst
->src(0)->isA(TObj
) &&
2180 inst
->src(0)->type().clsSpec() &&
2181 inst
->src(0)->type().clsSpec().cls()->classof(c_WaitHandle::classof());
2182 if (baseIsWaitHandle
) {
2183 return cns(env
, true);
2188 SSATmp
* simplifyIsCol(State
& env
, const IRInstruction
* inst
) {
2189 auto const ty
= inst
->src(0)->type();
2191 if (!typeMightRelax(inst
->src(0)) &&
2193 ty
.clsSpec().cls()) {
2194 return cns(env
, ty
.clsSpec().cls()->isCollectionClass());
2199 SSATmp
* simplifyHasToString(State
& env
, const IRInstruction
* inst
) {
2200 auto const src
= inst
->src(0);
2202 if (!mightRelax(env
, src
) &&
2204 src
->type().clsSpec()) {
2207 src
->type().clsSpec().cls()->getToString() != nullptr
2213 SSATmp
* simplifyOrdStr(State
& env
, const IRInstruction
* inst
) {
2214 const auto src
= inst
->src(0);
2215 if (src
->hasConstVal(TStr
)) {
2216 // a static string is passed in, resolve with a constant.
2217 unsigned char first
= src
->strVal()->data()[0];
2218 return cns(env
, int64_t{first
});
2223 SSATmp
* ldImpl(State
& env
, const IRInstruction
* inst
) {
2224 if (env
.typesMightRelax
) return nullptr;
2226 auto const t
= inst
->typeParam();
2228 return t
.hasConstVal() ||
2229 t
.subtypeOfAny(TUninit
, TInitNull
, TNullptr
)
2234 SSATmp
* simplifyLdLoc(State
& env
, const IRInstruction
* inst
) {
2235 return ldImpl(env
, inst
);
2238 SSATmp
* simplifyLdStk(State
& env
, const IRInstruction
* inst
) {
2239 return ldImpl(env
, inst
);
2242 SSATmp
* simplifyJmpSwitchDest(State
& env
, const IRInstruction
* inst
) {
2243 auto const index
= inst
->src(0);
2244 if (!index
->hasConstVal(TInt
)) return nullptr;
2246 auto indexVal
= index
->intVal();
2247 auto const sp
= inst
->src(1);
2248 auto const fp
= inst
->src(2);
2249 auto const& extra
= *inst
->extra
<JmpSwitchDest
>();
2251 if (indexVal
< 0 || indexVal
>= extra
.cases
) {
2252 // Instruction is unreachable.
2253 return gen(env
, Halt
);
2256 auto const newExtra
= ReqBindJmpData
{extra
.targets
[indexVal
], extra
.invSPOff
,
2257 extra
.irSPOff
, TransFlags
{}};
2258 return gen(env
, ReqBindJmp
, newExtra
, sp
, fp
);
2261 SSATmp
* simplifyCheckRange(State
& env
, const IRInstruction
* inst
) {
2262 auto val
= inst
->src(0);
2263 auto limit
= inst
->src(1);
2265 // CheckRange returns (0 <= val < limit).
2266 if (val
->hasConstVal(TInt
)) {
2267 if (val
->intVal() < 0) return cns(env
, false);
2269 if (limit
->hasConstVal(TInt
)) {
2270 return cns(env
, val
->intVal() < limit
->intVal());
2274 if (limit
->hasConstVal(TInt
) && limit
->intVal() <= 0) return cns(env
, false);
2279 //////////////////////////////////////////////////////////////////////
2281 SSATmp
* simplifyWork(State
& env
, const IRInstruction
* inst
) {
2282 env
.insts
.push(inst
);
2284 assertx(env
.insts
.top() == inst
);
2288 #define X(x) case x: return simplify##x(env, inst);
2289 switch (inst
->op()) {
2304 X(CheckStaticLocInit
)
2308 X(CheckPackedArrayBounds
)
2450 //////////////////////////////////////////////////////////////////////
2454 //////////////////////////////////////////////////////////////////////
2456 bool canSimplifyAssertType(const IRInstruction
* inst
,
2458 bool srcMightRelax
) {
2459 assert(inst
->is(AssertType
, AssertLoc
, AssertStk
));
2461 auto const typeParam
= inst
->typeParam();
2463 if (!srcType
.maybe(typeParam
)) {
2464 // If both types are boxed, this is okay and even expected as a means to
2465 // update the hint for the inner type.
2466 if (srcType
<= TBoxedCell
&&
2467 typeParam
<= TBoxedCell
) {
2471 // We got external information (probably from static analysis) that
2472 // conflicts with what we've built up so far. There's no reasonable way to
2473 // continue here: we can't properly fatal the request because we can't make
2474 // a catch trace or SpillStack without IRGS, and we can't punt on
2475 // just this instruction because we might not be in the initial translation
2476 // phase, and we can't just plow on forward since we'll probably generate
2477 // malformed IR. Since this case is very rare, just punt on the whole
2478 // trace so it gets interpreted.
2479 TRACE_PUNT("Invalid AssertTypeOp");
2482 // Asserting in these situations doesn't add any information.
2483 if (typeParam
== TCls
&& srcType
<= TCls
) return true;
2484 if (typeParam
== TGen
&& srcType
<= TGen
) return true;
2486 auto const newType
= srcType
& typeParam
;
2488 if (srcType
<= newType
) {
2489 // The src type is at least as good as the new type. Eliminate this
2490 // AssertType if the src type won't relax. We do this to avoid eliminating
2491 // apparently redundant assert opcodes that may become useful after prior
2492 // guards are relaxed.
2493 if (!srcMightRelax
) return true;
2495 if (srcType
< newType
) {
2496 // This can happen because of limitations in how Toperator& handles
2497 // specialized types: sometimes it returns a Type that's wider than it
2498 // needs to be. It shouldn't affect correctness but it can cause us to
2499 // miss out on some perf.
2500 FTRACE_MOD(Trace::hhir
, 1,
2501 "Suboptimal AssertTypeOp: refineType({}, {}) -> {} in {}\n",
2502 srcType
, typeParam
, newType
, *inst
);
2504 // We don't currently support intersecting RepoAuthType::Arrays
2505 // (t4473238), so we might be here because srcType and typeParam have
2506 // different RATArrays. If that's the case, and if typeParam provides no
2507 // other useful information, we can unconditionally eliminate this
2508 // instruction: RATArrays never come from guards so we can't miss out on
2509 // anything by doing so.
2510 if (srcType
< TArr
&&
2511 srcType
.arrSpec().type() &&
2513 typeParam
.arrSpec().type() &&
2514 !typeParam
.arrSpec().kind()) {
2523 //////////////////////////////////////////////////////////////////////
2525 SimplifyResult
simplify(IRUnit
& unit
,
2526 const IRInstruction
* origInst
,
2527 bool typesMightRelax
) {
2528 auto env
= State
{ unit
, typesMightRelax
};
2529 auto const newDst
= simplifyWork(env
, origInst
);
2531 assertx(validate(env
, newDst
, origInst
));
2533 return SimplifyResult
{ std::move(env
.newInsts
), newDst
};
2536 void simplify(IRUnit
& unit
, IRInstruction
* origInst
) {
2537 assertx(!origInst
->isTransient());
2540 auto res
= simplify(unit
, origInst
, false);
2542 // No simplification occurred; nothing to do.
2543 if (res
.instrs
.empty() && !res
.dst
) return;
2545 FTRACE(1, "simplifying: {}\n", origInst
->toString());
2547 if (origInst
->isBlockEnd()) {
2548 auto const next
= origInst
->block()->next();
2550 if (res
.instrs
.empty() || !res
.instrs
.back()->isBlockEnd()) {
2551 // Our block-end instruction was eliminated (most likely a Jmp* converted
2552 // to a Nop). Replace it with a Jmp to the next block.
2553 res
.instrs
.push_back(unit
.gen(Jmp
, origInst
->marker(), next
));
2556 auto last
= res
.instrs
.back();
2557 assertx(last
->isBlockEnd());
2559 if (!last
->isTerminal() && !last
->next()) {
2560 // We converted the block-end instruction to a different one. Set its
2561 // next block appropriately.
2562 last
->setNext(next
);
2566 size_t out_size
= 0;
2567 bool need_mov
= res
.dst
;
2568 IRInstruction
* last
= nullptr;
2570 for (auto inst
: res
.instrs
) {
2571 if (inst
->is(Nop
)) continue;
2576 if (res
.dst
&& res
.dst
== inst
->dst()) {
2577 // One of the new instructions produced the new dst. Since we're going
2578 // to drop `origInst', just use origInst->dst() instead.
2579 inst
->setDst(origInst
->dst());
2580 inst
->dst()->setInstruction(inst
);
2585 auto const block
= origInst
->block();
2586 auto const pos
= ++block
->iteratorTo(origInst
);
2590 * In `killed_edge_defining' we have the case that an instruction defining
2591 * a temp on an edge (like CheckType) determined it can never define that
2592 * tmp. In this situation we just Nop out the instruction and leave the
2593 * old tmp dangling. The reason this is ok is that one of the following
2594 * two things are happening:
2596 * o The old next() block is becoming unreachable. It's ok not to make
2597 * a new definition of this tmp, because the code running simplify is
2598 * going to have to track unreachable blocks and avoid looking at
2599 * them. It will also have to remove unreachable blocks when it's
2600 * finished to maintain IR invariants (e.g. through DCE::Minimal),
2601 * which will mean the uses of the no-longer-defined tmp will go away.
2603 * o The old next() block is still reachable (e.g. if we're removing a
2604 * CheckType because it had next == taken). But in this case, the
2605 * next() edge must have been a critical edge, and therefore nothing
2606 * could have any use of the old destination of the CheckType, or the
2607 * program would already not have been in SSA, because it was only
2608 * defined in blocks dominated by the next edge.
2610 auto const killed_edge_defining
= res
.dst
->type() <= TBottom
&&
2611 origInst
->isBlockEnd();
2612 if (killed_edge_defining
) {
2613 origInst
->convertToNop();
2615 unit
.replace(origInst
, Mov
, res
.dst
);
2616 // Force the existing dst type to match that of `res.dst'.
2617 origInst
->dst()->setType(res
.dst
->type());
2620 if (out_size
== 1) {
2621 assertx(origInst
->dst() == last
->dst());
2622 FTRACE(1, " {}\n", last
->toString());
2624 // We only have a single instruction, so just become it.
2625 origInst
->become(unit
, last
);
2627 // Make sure to reset our dst's inst pointer, if we have one. (It may
2628 // have been set to `last'.
2629 if (origInst
->dst()) {
2630 origInst
->dst()->setInstruction(origInst
);
2633 // And we also need to kill `last', to update preds.
2634 last
->convertToNop();
2638 origInst
->convertToNop();
2641 FTRACE(1, " {}\n", origInst
->toString());
2643 for (auto const inst
: res
.instrs
) {
2644 if (inst
->is(Nop
)) continue;
2645 block
->insert(pos
, inst
);
2646 FTRACE(1, " {}\n", inst
->toString());
2650 //////////////////////////////////////////////////////////////////////
2652 void simplifyPass(IRUnit
& unit
) {
2653 auto reachable
= boost::dynamic_bitset
<>(unit
.numBlocks());
2654 reachable
.set(unit
.entry()->id());
2656 for (auto block
: rpoSortCfg(unit
)) {
2657 if (!reachable
.test(block
->id())) continue;
2659 for (auto& inst
: *block
) simplify(unit
, &inst
);
2661 if (auto const b
= block
->next()) reachable
.set(b
->id());
2662 if (auto const b
= block
->taken()) reachable
.set(b
->id());
2666 //////////////////////////////////////////////////////////////////////
2668 void copyProp(IRInstruction
* inst
) {
2669 for (uint32_t i
= 0; i
< inst
->numSrcs(); i
++) {
2670 auto tmp
= inst
->src(i
);
2671 auto srcInst
= tmp
->inst();
2673 if (srcInst
->is(Mov
)) {
2674 inst
->setSrc(i
, srcInst
->src(0));
2677 // We're assuming that all of our src instructions have already been
2679 assertx(!inst
->src(i
)->inst()->is(Mov
));
2683 PackedBounds
packedArrayBoundsStaticCheck(Type arrayType
, int64_t idxVal
) {
2684 if (idxVal
< 0 || idxVal
> PackedArray::MaxSize
) return PackedBounds::Out
;
2686 if (arrayType
.hasConstVal()) {
2687 return idxVal
< arrayType
.arrVal()->size()
2689 : PackedBounds::Out
;
2692 auto const at
= arrayType
.arrSpec().type();
2693 if (!at
) return PackedBounds::Unknown
;
2695 using A
= RepoAuthType::Array
;
2696 switch (at
->tag()) {
2697 case A::Tag::Packed
:
2698 if (idxVal
< at
->size() && at
->emptiness() == A::Empty::No
) {
2699 return PackedBounds::In
;
2702 case A::Tag::PackedN
:
2703 if (idxVal
== 0 && at
->emptiness() == A::Empty::No
) {
2704 return PackedBounds::In
;
2707 return PackedBounds::Unknown
;
2710 Type
packedArrayElemType(SSATmp
* arr
, SSATmp
* idx
) {
2711 assertx(arr
->isA(TArr
) &&
2712 arr
->type().arrSpec().kind() == ArrayData::kPackedKind
&&
2715 if (arr
->hasConstVal() && idx
->hasConstVal()) {
2716 auto const idxVal
= idx
->intVal();
2717 if (idxVal
>= 0 && idxVal
< arr
->arrVal()->size()) {
2718 return Type(arr
->arrVal()->nvGet(idxVal
)->m_type
);
2723 Type t
= arr
->isA(TStaticArr
) ? TInitCell
: TGen
;
2725 auto const at
= arr
->type().arrSpec().type();
2728 switch (at
->tag()) {
2729 case RepoAuthType::Array::Tag::Packed
:
2731 if (idx
->hasConstVal(TInt
)) {
2732 auto const idxVal
= idx
->intVal();
2733 if (idxVal
>= 0 && idxVal
< at
->size()) {
2734 return typeFromRAT(at
->packedElem(idxVal
)) & t
;
2738 Type elemType
= TBottom
;
2739 for (uint32_t i
= 0; i
< at
->size(); ++i
) {
2740 elemType
|= typeFromRAT(at
->packedElem(i
));
2742 return elemType
& t
;
2744 case RepoAuthType::Array::Tag::PackedN
:
2745 return typeFromRAT(at
->elemType()) & t
;
2750 //////////////////////////////////////////////////////////////////////