hphp/runtime/vm/jit/simplify.cpp (3,279 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/vm/jit/simplify.h"
#include "hphp/runtime/base/array-data-defs.h"
#include "hphp/runtime/base/array-init.h"
#include "hphp/runtime/base/bespoke-array.h"
#include "hphp/runtime/base/bespoke/struct-dict.h"
#include "hphp/runtime/base/comparisons.h"
#include "hphp/runtime/base/double-to-int64.h"
#include "hphp/runtime/base/repo-auth-type-array.h"
#include "hphp/runtime/base/tv-refcount.h"
#include "hphp/runtime/base/tv-type.h"
#include "hphp/runtime/base/type-structure-helpers.h"
#include "hphp/runtime/base/type-structure-helpers-defs.h"
#include "hphp/runtime/base/vanilla-dict-defs.h"
#include "hphp/runtime/base/vanilla-dict.h"
#include "hphp/runtime/base/vanilla-vec-defs.h"
#include "hphp/runtime/base/vanilla-vec.h"
#include "hphp/runtime/vm/hhbc.h"
#include "hphp/runtime/vm/jit/ir-opcode.h"
#include "hphp/runtime/vm/runtime.h"
#include "hphp/runtime/vm/jit/analysis.h"
#include "hphp/runtime/vm/jit/containers.h"
#include "hphp/runtime/vm/jit/ir-builder.h"
#include "hphp/runtime/vm/jit/irgen-internal.h"
#include "hphp/runtime/vm/jit/mutation.h"
#include "hphp/runtime/vm/jit/simple-propagation.h"
#include "hphp/runtime/vm/jit/timer.h"
#include "hphp/runtime/vm/jit/translator-runtime.h"
#include "hphp/runtime/vm/jit/type-array-elem.h"
#include "hphp/runtime/vm/jit/type.h"
#include "hphp/runtime/ext/hh/ext_hh.h"
#include "hphp/runtime/ext/asio/ext_static-wait-handle.h"
#include "hphp/runtime/ext/std/ext_std_closure.h"
#include "hphp/util/overflow.h"
#include "hphp/util/trace.h"
#include <limits>
#include <sstream>
#include <type_traits>
namespace HPHP::jit {
TRACE_SET_MOD(simplify);
//////////////////////////////////////////////////////////////////////
namespace {
//////////////////////////////////////////////////////////////////////
const StaticString s_isEmpty("isEmpty");
const StaticString s_count("count");
const StaticString s_1("1");
const StaticString s_invoke("__invoke");
const StaticString s_isFinished("isFinished");
const StaticString s_isSucceeded("isSucceeded");
const StaticString s_isFailed("isFailed");
const StaticString s_Awaitable("HH\\Awaitable");
//////////////////////////////////////////////////////////////////////
struct State {
explicit State(IRUnit& unit) : unit(unit) {}
IRUnit& unit;
// The current instruction being simplified is always at insts.top(). This
// has to be a stack instead of just a pointer because simplify is reentrant.
jit::stack<const IRInstruction*> insts;
jit::vector<IRInstruction*> newInsts;
};
//////////////////////////////////////////////////////////////////////
SSATmp* simplifyWork(State&, const IRInstruction*);
template<class... Args>
SSATmp* cns(State& env, Args&&... cns) {
return env.unit.cns(std::forward<Args>(cns)...);
}
template<class... Args>
SSATmp* gen(State& env, Opcode op, Args&&... args);
template<class... Args>
SSATmp* gen(State& env, Opcode op, BCContext bcctx, Args&&... args) {
return makeInstruction(
[&] (IRInstruction* inst) -> SSATmp* {
auto const prevNewCount = env.newInsts.size();
auto const newDest = simplifyWork(env, inst);
// If any simplification happened to this instruction, drop it. We have to
// check that nothing was added to newInsts because that's the only way
// we can tell simplification happened to a no-dest instruction.
if (newDest || env.newInsts.size() != prevNewCount) {
return newDest;
} else {
assertx(inst->isTransient());
if (!env.newInsts.empty() && env.newInsts.back()->is(Unreachable)) {
return cns(env, TBottom);
}
assertx(checkOperandTypes(inst));
inst = env.unit.clone(inst);
env.newInsts.push_back(inst);
// If the new instruction's result is unreachable (and it is not block
// ending), indicate this in the IR stream.
if (inst->isNextEdgeUnreachable() && !inst->isBlockEnd()) {
gen(env, Unreachable, ASSERT_REASON);
}
return inst->dst(0);
}
},
op,
bcctx,
std::forward<Args>(args)...
);
}
template<class... Args>
SSATmp* gen(State& env, Opcode op, Args&&... args) {
assertx(!env.insts.empty());
return gen(env, op, env.insts.top()->bcctx(), std::forward<Args>(args)...);
}
//////////////////////////////////////////////////////////////////////
DEBUG_ONLY bool validate(const State& env,
SSATmp* newDst,
const IRInstruction* origInst) {
// simplify() rules are not allowed to add new uses to SSATmps that aren't
// known to be available. All the sources to the original instruction must
// be available, and non-reference counted values reachable through the
// source chain are also always available. Anything else requires more
// complicated analysis than belongs in the simplifier right now.
auto known_available = [&] (SSATmp* src) -> bool {
if (!src->type().maybe(TCounted)) return true;
for (auto& oldSrc : origInst->srcs()) {
if (oldSrc == src) return true;
// Some instructions consume a counted SSATmp and produce a new SSATmp
// which supports the consumed location. If the result of one such
// instruction is available then the value whose count it supports must
// also be available. For now CreateSSWH is the only instruction of this
// form that we care about.
if (oldSrc->inst()->is(CreateSSWH) && oldSrc->inst()->src(0) == src) {
return true;
}
}
return false;
};
// Return early for the no-simplification case.
if (env.newInsts.empty() && !newDst) {
return true;
}
const IRInstruction* last = nullptr;
if (!env.newInsts.empty()) {
for (size_t i = 0, n = env.newInsts.size(); i < n; ++i) {
auto const newInst = env.newInsts[i];
for (auto& src : newInst->srcs()) {
always_assert_flog(
known_available(src),
"A simplification rule produced an instruction that used a value "
"that wasn't known to be available:\n"
" original inst: {}\n"
" new inst: {}\n"
" src: {}\n",
origInst->toString(),
newInst->toString(),
src->toString()
);
}
if (i == n - 1) {
last = newInst;
continue;
}
always_assert_flog(
!newInst->isBlockEnd(),
"Block-ending instruction produced in the middle of a simplified "
"instruction stream:\n"
" original inst: {}\n"
" new inst: {}\n",
origInst->toString(),
newInst->toString()
);
}
}
if (newDst) {
const bool available = known_available(newDst) ||
std::any_of(env.newInsts.begin(), env.newInsts.end(),
[&] (IRInstruction* inst) { return newDst == inst->dst(); });
always_assert_flog(
available,
"simplify() produced a new destination that wasn't known to be "
"available:\n"
" original inst: {}\n"
" new dst: {}\n",
origInst->toString(),
newDst->toString()
);
}
if (!last) return true;
auto assert_last = [&] (bool cond, const char* msg) {
always_assert_flog(
cond,
"{}:\n"
" original inst: {}\n"
" last new inst: {}\n",
msg,
origInst->toString(),
last->toString()
);
};
assert_last(
!origInst->naryDst(),
"Nontrivial simplification returned for instruction with NaryDest"
);
assert_last(
origInst->hasDst() == (newDst != nullptr),
"HasDest mismatch between input and output"
);
if (last->hasEdges()) {
assert_last(
origInst->hasEdges(),
"Instruction with edges produced for simplification of edge-free "
"instruction"
);
assert_last(
IMPLIES(last->next(), last->next() == origInst->next() ||
last->next() == origInst->taken()),
"Last instruction of simplified stream has next edge not reachable from "
"the input instruction"
);
assert_last(
IMPLIES(last->taken(), last->taken() == origInst->next() ||
last->taken() == origInst->taken()),
"Last instruction of simplified stream has taken edge not reachable "
"from the input instruction"
);
}
return true;
}
//////////////////////////////////////////////////////////////////////
/*
* Individual simplification routines return nullptr if they don't want to
* change anything, or they can call gen any number of times to produce a
* different IR sequence, returning the thing gen'd that should be used as the
* value of the simplified instruction sequence.
*/
SSATmp* mergeBranchDests(State& env, const IRInstruction* inst) {
// Replace a conditional branch with a Jmp if both branches go to the same
// block. Only work if the instruction does not have side effect.
// JmpZero/JmpNZero is handled separately.
assertx(inst->is(CheckTypeMem,
CheckLoc,
CheckStk,
CheckMBase,
CheckInit,
CheckInitMem,
CheckRDSInitialized,
CheckVecBounds,
CheckDictKeys,
CheckMissingKeyInArrLike,
CheckDictOffset,
CheckKeysetOffset));
if (inst->next() != nullptr && inst->next() == inst->taken()) {
return gen(env, Jmp, inst->next());
}
if (inst->taken() && inst->taken()->isUnreachable()) {
return gen(env, Nop);
}
if (inst->next() && inst->next()->isUnreachable()) {
assertx(inst->taken());
return gen(env, Jmp, inst->taken());
}
return nullptr;
}
SSATmp* simplifyEqFunc(State& env, const IRInstruction* inst) {
auto const src0 = inst->src(0);
auto const src1 = inst->src(1);
if (src0->hasConstVal() && src1->hasConstVal()) {
return cns(env, src0->funcVal() == src1->funcVal());
}
return nullptr;
}
SSATmp* simplifyLdFuncInOutBits(State& env, const IRInstruction* inst) {
auto const funcTmp = inst->src(0);
return funcTmp->hasConstVal(TFunc)
? cns(env, funcTmp->funcVal()->inOutBits())
: nullptr;
}
SSATmp* simplifyLdFuncNumParams(State& env, const IRInstruction* inst) {
auto const funcTmp = inst->src(0);
return funcTmp->hasConstVal(TFunc)
? cns(env, (funcTmp->funcVal()->numParams()))
: nullptr;
}
SSATmp* simplifyFuncHasAttr(State& env, const IRInstruction* inst) {
auto const funcTmp = inst->src(0);
return funcTmp->hasConstVal(TFunc)
? cns(env, !!(funcTmp->funcVal()->attrs() & inst->extra<AttrData>()->attr))
: nullptr;
}
SSATmp* simplifyClassHasAttr(State& env, const IRInstruction* inst) {
auto const clsTmp = inst->src(0);
return clsTmp->hasConstVal(TCls)
? cns(env, (clsTmp->clsVal()->attrs() & inst->extra<AttrData>()->attr))
: nullptr;
}
SSATmp* simplifyLdFuncRequiredCoeffects(State& env, const IRInstruction* inst) {
auto const funcTmp = inst->src(0);
return funcTmp->hasConstVal(TFunc)
? cns(env, (funcTmp->funcVal()->requiredCoeffects().value()))
: nullptr;
}
SSATmp* simplifyLookupClsCtxCns(State& env, const IRInstruction* inst) {
auto const clsTmp = inst->src(0);
auto const nameTmp = inst->src(1);
if (!clsTmp->hasConstVal(TCls) || !nameTmp->hasConstVal(TStr)) return nullptr;
auto const result = clsTmp->clsVal()->clsCtxCnsGet(nameTmp->strVal(), false);
if (!result) return nullptr; // we will raise warning/error
return cns(env, result->value());
}
SSATmp* simplifyLdResolvedTypeCns(State& env, const IRInstruction* inst) {
auto const clsTmp = inst->src(0);
auto const clsSpec = clsTmp->type().clsSpec();
if (!clsSpec) return nullptr;
auto const cls = clsSpec.cls();
auto const extra = inst->extra<LdResolvedTypeCns>();
assertx(cls->hasTypeConstant(extra->cnsName, true));
assertx(extra->slot < cls->numConstants());
auto const& typeCns = cls->constants()[extra->slot];
auto const& resolved = typeCns.preConst->resolvedTypeStructure();
if (clsSpec.exact()) {
if (typeCns.isAbstractAndUninit()) {
gen(env, Jmp, inst->taken());
return cns(env, TBottom);
}
if (resolved.isNull()) return nullptr;
return cns(env, resolved.get());
}
if (resolved.isNull()) return nullptr;
switch (typeCns.preConst->invariance()) {
case PreClass::Const::Invariance::None:
return nullptr;
case PreClass::Const::Invariance::Present:
case PreClass::Const::Invariance::ClassnamePresent:
return gen(env, LdResolvedTypeCnsNoCheck, *extra, clsTmp);
case PreClass::Const::Invariance::Same:
return cns(env, resolved.get());
}
always_assert(false);
}
SSATmp* simplifyLdResolvedTypeCnsNoCheck(State& env,
const IRInstruction* inst) {
auto const clsTmp = inst->src(0);
auto const clsSpec = clsTmp->type().clsSpec();
if (!clsSpec) return nullptr;
auto const cls = clsSpec.cls();
auto const extra = inst->extra<LdResolvedTypeCnsNoCheck>();
assertx(cls->hasTypeConstant(extra->cnsName, true));
assertx(extra->slot < cls->numConstants());
auto const& typeCns = cls->constants()[extra->slot];
auto const& resolved = typeCns.preConst->resolvedTypeStructure();
if (resolved.isNull()) return nullptr;
if (clsSpec.exact()) return cns(env, resolved.get());
if (typeCns.preConst->invariance() == PreClass::Const::Invariance::Same) {
return cns(env, resolved.get());
}
return nullptr;
}
SSATmp* simplifyLdResolvedTypeCnsClsName(State& env,
const IRInstruction* inst) {
auto const clsTmp = inst->src(0);
auto const clsSpec = clsTmp->type().clsSpec();
if (!clsSpec) return nullptr;
auto const cls = clsSpec.cls();
auto const extra = inst->extra<LdResolvedTypeCnsClsName>();
assertx(cls->hasTypeConstant(extra->cnsName, true));
assertx(extra->slot < cls->numConstants());
auto const& typeCns = cls->constants()[extra->slot];
auto const& resolved = typeCns.preConst->resolvedTypeStructure();
auto const classname = [&] {
auto const name = resolved->get(s_classname);
if (tvIsString(name)) return cns(env, name);
return cns(env, nullptr);
};
if (clsSpec.exact()) {
if (typeCns.isAbstractAndUninit()) return cns(env, nullptr);
if (resolved.isNull()) return nullptr;
return classname();
}
if (resolved.isNull()) return nullptr;
if (typeCns.preConst->invariance() == PreClass::Const::Invariance::Same) {
return classname();
}
return nullptr;
}
SSATmp* simplifyLdCls(State& env, const IRInstruction* inst) {
auto const str = inst->src(0);
auto const cls = inst->src(1);
if (str->inst()->is(LdClsName)) {
return str->inst()->src(0);
}
if (str->inst()->is(LdLazyClsName)) {
auto const lcls = str->inst()->src(0);
if (lcls->inst()->is(LdLazyCls)) {
return lcls->inst()->src(0);
}
}
if (str->hasConstVal() && (cls->hasConstVal(TCls) || cls->isA(TNullptr))) {
auto const sval = str->strVal();
auto const cval = cls->hasConstVal(TCls) ? cls->clsVal() : nullptr;
auto const result = Class::lookupUniqueInContext(sval, cval, nullptr);
if (result) return cns(env, result);
}
return nullptr;
}
SSATmp* simplifyLdClsMethod(State& env, const IRInstruction* inst) {
auto const clsTmp = inst->src(0);
auto const idxTmp = inst->src(1);
if (clsTmp->hasConstVal() && idxTmp->hasConstVal()) {
auto const cls = clsTmp->clsVal();
auto const idx = idxTmp->intVal();
if (idx < cls->numMethods()) {
return cns(env, cls->getMethod(idx));
}
}
return nullptr;
}
SSATmp* simplifyLdObjClass(State& env, const IRInstruction* inst) {
auto const ty = inst->src(0)->type();
if (!(ty < TObj)) return nullptr;
if (auto const exact = ty.clsSpec().exactCls()) return cns(env, exact);
return nullptr;
}
SSATmp* simplifyLdFrameCls(State& env, const IRInstruction* inst) {
auto const ty = inst->typeParam();
if (!(ty < TCls)) return nullptr;
if (auto const cls = ty.clsSpec().exactCls()) {
return cns(env, cls);
}
return nullptr;
}
SSATmp* simplifyLdObjInvoke(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (!src->hasConstVal(TCls)) return nullptr;
auto const meth = src->clsVal()->getCachedInvoke();
return meth == nullptr ? nullptr : cns(env, meth);
}
SSATmp* simplifyMov(State& /*env*/, const IRInstruction* inst) {
return inst->src(0);
}
SSATmp* simplifyAbsDbl(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) return cns(env, fabs(src->dblVal()));
return nullptr;
}
template<class Oper>
SSATmp* constImpl(State& env, SSATmp* src1, SSATmp* src2, Oper op) {
// don't canonicalize to the right, OP might not be commutative
if (!src1->hasConstVal() || !src2->hasConstVal()) return nullptr;
auto both = [&](Type ty) { return src1->type() <= ty && src2->type() <= ty; };
if (both(TBool)) return cns(env, op(src1->boolVal(), src2->boolVal()));
if (both(TInt)) return cns(env, op(src1->intVal(), src2->intVal()));
if (both(TDbl)) return cns(env, op(src1->dblVal(), src2->dblVal()));
return nullptr;
}
template<class Oper>
SSATmp* commutativeImpl(State& env,
SSATmp* src1,
SSATmp* src2,
Opcode opcode,
Oper op) {
if (auto simp = constImpl(env, src1, src2, op)) return simp;
// Canonicalize constants to the right.
if (src1->hasConstVal() && !src2->hasConstVal()) {
return gen(env, opcode, src2, src1);
}
// Only handle integer operations for now.
if (!src1->isA(TInt) || !src2->isA(TInt)) return nullptr;
auto const inst1 = src1->inst();
auto const inst2 = src2->inst();
if (inst1->op() == opcode && inst1->src(1)->hasConstVal()) {
// (X + C1) + C2 --> X + C3
if (src2->hasConstVal()) {
auto const right = op(inst1->src(1)->intVal(), src2->intVal());
return gen(env, opcode, inst1->src(0), cns(env, right));
}
// (X + C1) + (Y + C2) --> X + Y + C3
if (inst2->op() == opcode && inst2->src(1)->hasConstVal()) {
auto const right = op(inst1->src(1)->intVal(), inst2->src(1)->intVal());
auto const left = gen(env, opcode, inst1->src(0), inst2->src(0));
return gen(env, opcode, left, cns(env, right));
}
}
return nullptr;
}
/*
* Assumes that outop is commutative, don't use with subtract!
*
* Assumes that the values we're going to add new uses to are not reference
* counted. (I.e. this is a distributive FooInt opcode.)
*/
template <class OutOper, class InOper>
SSATmp* distributiveImpl(State& env, SSATmp* src1, SSATmp* src2, Opcode outcode,
Opcode incode, OutOper outop, InOper /*inop*/) {
if (auto simp = commutativeImpl(env, src1, src2, outcode, outop)) {
return simp;
}
auto const inst1 = src1->inst();
auto const inst2 = src2->inst();
auto const op1 = inst1->op();
auto const op2 = inst2->op();
// all combinations of X * Y + X * Z --> X * (Y + Z)
if (op1 == incode && op2 == incode) {
if (inst1->src(0) == inst2->src(0)) {
auto const fold = gen(env, outcode, inst1->src(1), inst2->src(1));
return gen(env, incode, inst1->src(0), fold);
}
if (inst1->src(0) == inst2->src(1)) {
auto const fold = gen(env, outcode, inst1->src(1), inst2->src(0));
return gen(env, incode, inst1->src(0), fold);
}
if (inst1->src(1) == inst2->src(0)) {
auto const fold = gen(env, outcode, inst1->src(0), inst2->src(1));
return gen(env, incode, inst1->src(1), fold);
}
if (inst1->src(1) == inst2->src(1)) {
auto const fold = gen(env, outcode, inst1->src(0), inst2->src(0));
return gen(env, incode, inst1->src(1), fold);
}
}
return nullptr;
}
SSATmp* simplifyAddInt(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
auto add = std::plus<uint64_t>();
auto mul = std::multiplies<uint64_t>();
if (auto simp = distributiveImpl(env, src1, src2, AddInt,
MulInt, add, mul)) {
return simp;
}
if (src2->hasConstVal()) {
int64_t src2Val = src2->intVal();
// X + 0 --> X
if (src2Val == 0) return src1;
// X + -C --> X - C
// Weird, but can show up as a result of other simplifications.
auto const min = std::numeric_limits<int64_t>::min();
if (src2Val < 0 && src2Val > min) {
return gen(env, SubInt, src1, cns(env, -src2Val));
}
}
// X + (0 - Y) --> X - Y
auto const inst2 = src2->inst();
if (inst2->op() == SubInt) {
auto const src = inst2->src(0);
if (src->hasConstVal() && src->intVal() == 0) {
return gen(env, SubInt, src1, inst2->src(1));
}
}
auto const inst1 = src1->inst();
// (X - C1) + ...
if (inst1->op() == SubInt && inst1->src(1)->hasConstVal()) {
auto const x = inst1->src(0);
auto const c1 = inst1->src(1);
// (X - C1) + C2 --> X + (C2 - C1)
if (src2->hasConstVal()) {
auto const rhs = gen(env, SubInt, cns(env, src2->intVal()), c1);
return gen(env, AddInt, x, rhs);
}
// (X - C1) + (Y +/- C2)
if ((inst2->op() == AddInt || inst2->op() == SubInt) &&
inst2->src(1)->hasConstVal()) {
auto const y = inst2->src(0);
auto const c2 = inst2->src(1);
auto const rhs = inst2->op() == SubInt ?
// (X - C1) + (Y - C2) --> X + Y + (-C1 - C2)
gen(env, SubInt, gen(env, SubInt, cns(env, 0), c1), c2) :
// (X - C1) + (Y + C2) --> X + Y + (C2 - C1)
gen(env, SubInt, c2, c1);
auto const lhs = gen(env, AddInt, x, y);
return gen(env, AddInt, lhs, rhs);
}
// (X - C1) + (Y + C2) --> X + Y + (C2 - C1)
if (inst2->op() == AddInt && inst2->src(1)->hasConstVal()) {
auto const y = inst2->src(0);
auto const c2 = inst2->src(1);
auto const lhs = gen(env, AddInt, x, y);
auto const rhs = gen(env, SubInt, c2, c1);
return gen(env, AddInt, lhs, rhs);
}
}
return nullptr;
}
SSATmp* simplifyAddIntO(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
if (src1->hasConstVal() && src2->hasConstVal()) {
auto const a = src1->intVal();
auto const b = src2->intVal();
if (add_overflow(a, b)) {
gen(env, Jmp, inst->taken());
return cns(env, TBottom);
}
return cns(env, a + b);
}
return nullptr;
}
SSATmp* simplifySubInt(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
auto const sub = std::minus<uint64_t>();
if (auto simp = constImpl(env, src1, src2, sub)) return simp;
// X - X --> 0
if (src1 == src2) return cns(env, 0);
if (src2->hasConstVal()) {
auto const src2Val = src2->intVal();
// X - 0 --> X
if (src2Val == 0) return src1;
// X - -C --> X + C
// Need to check for C == INT_MIN, otherwise we'd infinite loop as
// X + -C would send us back here.
auto const min = std::numeric_limits<int64_t>::min();
if (src2Val > min && src2Val < 0) {
return gen(env, AddInt, src1, cns(env, -src2Val));
}
}
// X - (0 - Y) --> X + Y
auto const inst2 = src2->inst();
if (inst2->op() == SubInt) {
auto const src = inst2->src(0);
if (src->hasConstVal(0)) return gen(env, AddInt, src1, inst2->src(1));
}
return nullptr;
}
SSATmp* simplifySubIntO(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
if (src1->hasConstVal() && src2->hasConstVal()) {
auto const a = src1->intVal();
auto const b = src2->intVal();
if (sub_overflow(a, b)) {
gen(env, Jmp, inst->taken());
return cns(env, TBottom);
}
return cns(env, a - b);
}
return nullptr;
}
SSATmp* simplifyMulInt(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
auto const mul = std::multiplies<uint64_t>();
if (auto simp = commutativeImpl(env, src1, src2, MulInt, mul)) return simp;
if (!src2->hasConstVal()) return nullptr;
auto const rhs = src2->intVal();
// X * (-1) --> -X
if (rhs == -1) return gen(env, SubInt, cns(env, 0), src1);
// X * 0 --> 0
if (rhs == 0) return cns(env, 0);
// X * 1 --> X
if (rhs == 1) return src1;
// X * 2 --> X + X
if (rhs == 2) return gen(env, AddInt, src1, src1);
auto isPowTwo = [](int64_t a) {
return a > 0 && folly::isPowTwo<uint64_t>(a);
};
auto log2 = [](int64_t a) {
assertx(a > 0);
return folly::findLastSet<uint64_t>(a) - 1;
};
// X * 2^C --> X << C
if (isPowTwo(rhs)) return gen(env, Shl, src1, cns(env, log2(rhs)));
// X * (2^C + 1) --> ((X << C) + X)
if (isPowTwo(rhs - 1)) {
auto const lhs = gen(env, Shl, src1, cns(env, log2(rhs - 1)));
return gen(env, AddInt, lhs, src1);
}
// X * (2^C - 1) --> ((X << C) - X)
if (isPowTwo(rhs + 1)) {
auto const lhs = gen(env, Shl, src1, cns(env, log2(rhs + 1)));
return gen(env, SubInt, lhs, src1);
}
return nullptr;
}
SSATmp* simplifyAddDbl(State& env, const IRInstruction* inst) {
return constImpl(env, inst->src(0), inst->src(1), std::plus<double>());
}
SSATmp* simplifySubDbl(State& env, const IRInstruction* inst) {
return constImpl(env, inst->src(0), inst->src(1), std::minus<double>());
}
SSATmp* simplifyMulDbl(State& env, const IRInstruction* inst) {
return constImpl(env, inst->src(0), inst->src(1), std::multiplies<double>());
}
SSATmp* simplifyMulIntO(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
if (src1->hasConstVal() && src2->hasConstVal()) {
auto const a = src1->intVal();
auto const b = src2->intVal();
if (mul_overflow(a, b)) {
gen(env, Jmp, inst->taken());
return cns(env, TBottom);
}
return cns(env, a * b);
}
return nullptr;
}
/*
Integer Modulo/Remainder Operator: a % b = a - a / b * b
*/
SSATmp* simplifyMod(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
if (!src2->hasConstVal()) return nullptr;
auto const src2Val = src2->intVal();
if (src2Val == 0) {
// Undefined behavior, so we might as well constant propagate whatever we
// want. If we're being asked to simplify this, it better be dynamically
// unreachable code.
// TODO we can do `return gen(env, Unreachable, ASSERT_REASON);` here
return cns(env, 0);
}
// X % 1 --> 0, X % -1 --> 0
if (src2Val == -1 || src2Val == 1) return cns(env, 0);
if (src1->hasConstVal()) return cns(env, src1->intVal() % src2Val);
// Optimization: x % 2^n == x & (2^n - 1)
if (folly::popcount(llabs(src2Val)) == 1) {
// long shft =
// static_cast<unsigned long>(x >> 63) >> (64 - __builtin_ctzll(y));
// ret ((x + shft) & (y - 1)) - shft;
auto const divisor = llabs(src2Val);
auto const trailingZeros = cns(env, 64 - __builtin_ctzll(divisor));
auto const shft = gen(env, Lshr,
gen(env, Shr, src1, cns(env, 63)), trailingZeros);
return gen(env, SubInt,
gen(env, AndInt,
gen(env, AddInt, src1, shft), cns(env, divisor - 1)), shft);
}
return nullptr;
}
SSATmp* simplifyDivDbl(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
if (!src2->hasConstVal()) return nullptr;
auto const src2Val = src2->dblVal();
if (src2Val == 0.0) {
// The branch emitted during irgen will deal with this
return nullptr;
}
// statically compute X / Y
return src1->hasConstVal() ? cns(env, src1->dblVal() / src2Val) : nullptr;
}
SSATmp* simplifyDivInt(State& env, const IRInstruction* inst) {
auto const dividend = inst->src(0);
auto const divisor = inst->src(1);
if (!divisor->hasConstVal()) return nullptr;
auto const divisorVal = divisor->intVal();
if (divisorVal == 0) {
// The branch emitted during irgen will deal with this
return nullptr;
}
if (!dividend->hasConstVal()) return nullptr;
auto const dividendVal = dividend->intVal();
if (dividendVal == LLONG_MIN || dividendVal % divisorVal) {
// This should be unreachable
return nullptr;
}
return cns(env, dividendVal / divisorVal);
}
SSATmp* simplifyAndInt(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
auto bit_and = [](int64_t a, int64_t b) { return a & b; };
auto bit_or = [](int64_t a, int64_t b) { return a | b; };
auto const simp = distributiveImpl(env, src1, src2, AndInt, OrInt,
bit_and, bit_or);
if (simp != nullptr) {
return simp;
}
// X & X --> X
if (src1 == src2) {
return src1;
}
if (src2->hasConstVal()) {
// X & 0 --> 0
if (src2->intVal() == 0) {
return cns(env, 0);
}
// X & (~0) --> X
if (src2->intVal() == ~0L) {
return src1;
}
}
return nullptr;
}
SSATmp* simplifyOrInt(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
auto bit_and = [](int64_t a, int64_t b) { return a & b; };
auto bit_or = [](int64_t a, int64_t b) { return a | b; };
auto const simp = distributiveImpl(env, src1, src2, OrInt, AndInt,
bit_or, bit_and);
if (simp != nullptr) {
return simp;
}
// X | X --> X
if (src1 == src2) {
return src1;
}
if (src2->hasConstVal()) {
// X | 0 --> X
if (src2->intVal() == 0) {
return src1;
}
// X | (~0) --> ~0
if (src2->intVal() == ~uint64_t(0)) {
return cns(env, ~uint64_t(0));
}
}
return nullptr;
}
SSATmp* simplifyXorInt(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
auto bitxor = [](int64_t a, int64_t b) { return a ^ b; };
if (auto simp = commutativeImpl(env, src1, src2, XorInt, bitxor)) {
return simp;
}
// X ^ X --> 0
if (src1 == src2) return cns(env, 0);
// X ^ 0 --> X
if (src2->hasConstVal(0)) return src1;
return nullptr;
}
SSATmp* xorTrueImpl(State& env, SSATmp* src) {
auto const inst = src->inst();
auto const op = inst->op();
// !(X cmp Y) --> X opposite_cmp Y
if (auto const negated = negateCmpOp(op)) {
if (opcodeMayRaise(op)) return nullptr;
auto const s0 = inst->src(0);
auto const s1 = inst->src(1);
// We can't add new uses to reference counted types without a more
// advanced availability analysis.
if (!s0->type().maybe(TCounted) && !s1->type().maybe(TCounted)) {
return gen(env, *negated, s0, s1);
}
return nullptr;
}
switch (op) {
// !!X --> X
case XorBool:
if (inst->src(1)->hasConstVal(true)) {
// This is safe to add a new use to because inst->src(0) is a bool.
assertx(inst->src(0)->isA(TBool));
return inst->src(0);
}
return nullptr;
case InstanceOfBitmask:
case NInstanceOfBitmask:
// This is safe because instanceofs don't take reference counted
// arguments.
assertx(!inst->src(0)->type().maybe(TCounted) &&
!inst->src(1)->type().maybe(TCounted));
return gen(
env,
(op == InstanceOfBitmask) ?
NInstanceOfBitmask :
InstanceOfBitmask,
inst->src(0),
inst->src(1)
);
default:
break;
}
return nullptr;
}
SSATmp* simplifyXorBool(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
// Both constants.
if (src1->hasConstVal() && src2->hasConstVal()) {
return cns(env, bool(src1->boolVal() ^ src2->boolVal()));
}
// Canonicalize constants to the right.
if (src1->hasConstVal() && !src2->hasConstVal()) {
return gen(env, XorBool, src2, src1);
}
// X^0 => X
if (src2->hasConstVal(false)) return src1;
// X^1 => simplify "not" logic
if (src2->hasConstVal(true)) return xorTrueImpl(env, src1);
// X^X => false
if (src1 == src2) return cns(env, false);
return nullptr;
}
template<class Oper>
SSATmp* shiftImpl(State& env, const IRInstruction* inst, Oper op) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
if (src1->hasConstVal()) {
if (src1->intVal() == 0) {
return cns(env, 0);
}
if (src2->hasConstVal()) {
return cns(env, op(src1->intVal(), src2->intVal() & 63));
}
}
if (src2->hasConstVal() && src2->intVal() == 0) {
return src1;
}
return nullptr;
}
SSATmp* simplifyShl(State& env, const IRInstruction* inst) {
return shiftImpl(env, inst,
[] (uint64_t a, int64_t b) -> int64_t {
return a << b;
});
}
SSATmp* simplifyLshr(State& env, const IRInstruction* inst) {
return shiftImpl(env, inst,
[] (uint64_t a, int64_t b) -> int64_t {
return a >> b;
});
}
SSATmp* simplifyShr(State& env, const IRInstruction* inst) {
return shiftImpl(env, inst,
[] (int64_t a, int64_t b) {
// avoid implementation defined behavior
// gcc optimizes this to a signed right shift.
return a >= 0 ? a >> b : -(-(a+1) >> b) - 1;
});
}
// This function isn't meant to perform the actual comparison at
// compile-time. Instead, it performs the matching comparison against a
// primitive type (usually bool).
template<class T, class U>
static bool cmpOp(Opcode opc, T a, U b) {
switch (opc) {
case GtBool:
case GtInt:
case GtDbl:
case GtStr:
case GtObj:
case GtArrLike:
case GtRes: return a > b;
case GteBool:
case GteInt:
case GteDbl:
case GteStr:
case GteObj:
case GteArrLike:
case GteRes: return a >= b;
case LtBool:
case LtInt:
case LtDbl:
case LtStr:
case LtObj:
case LtArrLike:
case LtRes: return a < b;
case LteBool:
case LteInt:
case LteDbl:
case LteStr:
case LteObj:
case LteArrLike:
case LteRes: return a <= b;
case SameStr:
case SameObj:
case SameArrLike:
case EqBool:
case EqInt:
case EqDbl:
case EqStr:
case EqObj:
case EqArrLike:
case EqRes: return a == b;
case NSameStr:
case NSameObj:
case NSameArrLike:
case NeqBool:
case NeqInt:
case NeqDbl:
case NeqStr:
case NeqObj:
case NeqArrLike:
case NeqRes: return a != b;
default:
always_assert(false);
}
}
SSATmp* cmpBoolImpl(State& env,
Opcode opc,
const IRInstruction* const inst,
SSATmp* left,
SSATmp* right) {
assertx(left->type() <= TBool);
assertx(right->type() <= TBool);
auto newInst = [&](Opcode op, SSATmp* src1, SSATmp* src2) {
return gen(env, op, inst ? inst->taken() : (Block*)nullptr, src1, src2);
};
// Identity optimization
if (left == right) {
return cns(env, cmpOp(opc, true, true));
}
if (left->hasConstVal()) {
// If both operands are constants, constant-fold them. Otherwise, move the
// constant over to the right.
if (right->hasConstVal()) {
return cns(env, cmpOp(opc, left->boolVal(), right->boolVal()));
} else {
auto const newOpc = [](Opcode opcode) {
switch (opcode) {
case GtBool: return LtBool;
case GteBool: return LteBool;
case LtBool: return GtBool;
case LteBool: return GteBool;
case EqBool: return EqBool;
case NeqBool: return NeqBool;
default: always_assert(false);
}
}(opc);
return newInst(newOpc, right, left);
}
}
if (right->hasConstVal()) {
bool b = right->boolVal();
// The result of the comparison might be independent of the truth
// value of the LHS. If so, then simplify.
if (cmpOp(opc, false, b) == cmpOp(opc, true, b)) {
return cns(env, cmpOp(opc, false, b));
}
// There are only two distinct booleans - false and true (0 and 1).
// From above, we know that (0 OP b) != (1 OP b).
// Hence exactly one of (0 OP b) and (1 OP b) is true.
// Hence there is exactly one boolean value of "left" that results in the
// overall expression being true.
// Hence we may check for equality with that boolean.
if (opc != EqBool) {
return newInst(EqBool, left, cns(env, !cmpOp(opc, false, b)));
}
// If we reach here, this is an equality comparison against a
// constant. Testing for equality with true simplifies to just the left
// operand, while equality with false is the negation of the left operand
// (equivalent to XORing with true).
return b ? left : newInst(XorBool, left, cns(env, true));
}
return nullptr;
}
SSATmp* cmpIntImpl(State& env,
Opcode opc,
const IRInstruction* const inst,
SSATmp* left,
SSATmp* right) {
assertx(left->type() <= TInt);
assertx(right->type() <= TInt);
auto newInst = [&](Opcode op, SSATmp* src1, SSATmp* src2) {
return gen(env, op, inst ? inst->taken() : (Block*)nullptr, src1, src2);
};
// Identity optimization
if (left == right) {
return cns(env, cmpOp(opc, true, true));
}
// Arithmetic optimization
if (opc == EqInt || opc == NeqInt) {
if (left->inst()->is(AddInt)) {
auto const add = left->inst();
if (add->src(0) == right) return gen(env, opc, cns(env, 0), add->src(1));
if (add->src(1) == right) return gen(env, opc, cns(env, 0), add->src(0));
}
if (right->inst()->is(AddInt)) {
auto const add = right->inst();
if (add->src(0) == left) return gen(env, opc, cns(env, 0), add->src(1));
if (add->src(1) == left) return gen(env, opc, cns(env, 0), add->src(0));
}
}
if (left->hasConstVal()) {
// If both operands are constants, constant-fold them. Otherwise, move the
// constant over to the right.
if (right->hasConstVal()) {
return cns(env, cmpOp(opc, left->intVal(), right->intVal()));
} else {
auto const newOpc = [](Opcode opcode) {
switch (opcode) {
case GtInt: return LtInt;
case GteInt: return LteInt;
case LtInt: return GtInt;
case LteInt: return GteInt;
case EqInt: return EqInt;
case NeqInt: return NeqInt;
default: always_assert(false);
}
}(opc);
return newInst(newOpc, right, left);
}
}
return nullptr;
}
SSATmp* cmpDblImpl(State& env,
Opcode opc,
const IRInstruction* const inst,
SSATmp* left,
SSATmp* right) {
assertx(left->type() <= TDbl);
assertx(right->type() <= TDbl);
// Identity optimization is not safe because Nan's compare unequal.
if (left->hasConstVal() && right->hasConstVal()) {
return cns(env, cmpOp(opc, left->dblVal(), right->dblVal()));
}
return nullptr;
}
SSATmp* cmpStrImpl(State& env,
Opcode opc,
const IRInstruction* const inst,
SSATmp* left,
SSATmp* right) {
assertx(left->type() <= TStr);
assertx(right->type() <= TStr);
auto newInst = [&](Opcode op, SSATmp* src1, SSATmp* src2) {
return gen(env, op, inst ? inst->taken() : (Block*)nullptr, src1, src2);
};
// Identity optimization
if (left == right) {
return cns(env, cmpOp(opc, true, true));
}
if (left->hasConstVal()) {
// If both operands are constants, constant-fold them. Otherwise, move the
// constant over to the right.
if (right->hasConstVal()) {
if (opc == SameStr || opc == NSameStr) {
return cns(
env,
cmpOp(opc, left->strVal()->same(right->strVal()), true)
);
} else if (opc == EqStr || opc == NeqStr) {
// if we're going to be converting these to num and then comparing an
// int and a float, block this fold because it needs to trigger a notice
int64_t lval; double dval;
auto ret1 = left->strVal()->isNumericWithVal(lval, dval, 0);
auto ret2 = right->strVal()->isNumericWithVal(lval, dval, 0);
if (ret1 == ret2 || ret1 == KindOfNull || ret2 == KindOfNull) {
return cns(
env,
cmpOp(opc, left->strVal()->equal(right->strVal()), true)
);
}
} else{
return cns(
env,
cmpOp(opc, left->strVal()->compare(right->strVal()), 0)
);
}
} else {
auto const newOpc = [](Opcode opcode) {
switch (opcode) {
case GtStr: return LtStr;
case GteStr: return LteStr;
case LtStr: return GtStr;
case LteStr: return GteStr;
case EqStr: return EqStr;
case NeqStr: return NeqStr;
case SameStr: return SameStr;
case NSameStr: return NSameStr;
default: always_assert(false);
}
}(opc);
return newInst(newOpc, right, left);
}
}
// Comparisons against the empty string can be optimized to checks on the
// string length.
if (right->hasConstVal() && right->strVal()->empty()) {
const auto op =
opc == EqStr || opc == SameStr || opc == LteStr ? EqInt : NeqInt;
switch (opc) {
case EqStr:
case NeqStr: return gen(env, op, gen(env, LdStrLen, left), cns(env, 0));
case SameStr:
case LteStr:
case NSameStr:
case GtStr: return newInst(op, gen(env, LdStrLen, left), cns(env, 0));
case LtStr: return cns(env, false);
case GteStr: return cns(env, true);
default: always_assert(false);
}
}
return nullptr;
}
SSATmp* cmpObjImpl(State& env, Opcode opc, const IRInstruction* const /*inst*/,
SSATmp* left, SSATmp* right) {
assertx(left->type() <= TObj);
assertx(right->type() <= TObj);
// Identity optimization. Object comparisons can produce arbitrary
// side-effects, so we can only eliminate the comparison if its checking for
// sameness.
if ((opc == SameObj || opc == NSameObj) && left == right) {
return cns(env, cmpOp(opc, true, true));
}
return nullptr;
}
SSATmp*
cmpKeysetImpl(State& env, Opcode opc, SSATmp* left, SSATmp* right) {
assertx(left->type() <= TKeyset);
assertx(right->type() <= TKeyset);
// Unlike other array types, keyset comparisons can never throw or re-enter
// (because they can only store integers and strings). Therefore we can fully
// simplify equality comparisons if both arrays are constants.
if (left->hasConstVal() && right->hasConstVal()) {
auto const leftVal = left->keysetVal();
auto const rightVal = right->keysetVal();
switch (opc) {
case EqArrLike:
return cns(env, VanillaKeyset::Equal(leftVal, rightVal));
case SameArrLike:
return cns(env, VanillaKeyset::Same(leftVal, rightVal));
case NeqArrLike:
return cns(env, VanillaKeyset::NotEqual(leftVal, rightVal));
case NSameArrLike:
return cns(env, VanillaKeyset::NotSame(leftVal, rightVal));
default:
break;
}
}
// Even if not a constant, we can apply an identity simplification as long as
// we're doing an equality comparison.
if ((opc == SameArrLike || opc == NSameArrLike ||
opc == EqArrLike || opc == NeqArrLike)
&& left == right) {
return cns(env, cmpOp(opc, true, true));
}
return nullptr;
}
SSATmp* cmpArrLikeImpl(State& env, Opcode opc,
const IRInstruction* const /*inst*/,
SSATmp* left, SSATmp* right) {
assertx(left->type() <= TArrLike);
assertx(right->type() <= TArrLike);
if (left->type() <= TKeyset && right->type() <= TKeyset) {
return cmpKeysetImpl(env, opc, left, right);
}
// Identity optimization. Array comparisons can produce arbitrary
// side-effects, so we can only eliminate the comparison if its checking for
// sameness.
if ((opc == SameArrLike || opc == NSameArrLike) && left == right) {
return cns(env, cmpOp(opc, true, true));
}
return nullptr;
}
SSATmp* cmpResImpl(State& env, Opcode opc, const IRInstruction* const /*inst*/,
SSATmp* left, SSATmp* right) {
assertx(left->type() <= TRes);
assertx(right->type() <= TRes);
// Identity optimization.
if (left == right) {
return cns(env, cmpOp(opc, true, true));
}
return nullptr;
}
#define X(name, type) \
SSATmp* simplify##name(State& env, const IRInstruction* i) { \
return cmp##type##Impl(env, i->op(), i, i->src(0), i->src(1)); \
}
X(GtBool, Bool)
X(GteBool, Bool)
X(LtBool, Bool)
X(LteBool, Bool)
X(EqBool, Bool)
X(NeqBool, Bool)
X(GtInt, Int)
X(GteInt, Int)
X(LtInt, Int)
X(LteInt, Int)
X(EqInt, Int)
X(NeqInt, Int)
X(GtDbl, Dbl)
X(GteDbl, Dbl)
X(LtDbl, Dbl)
X(LteDbl, Dbl)
X(EqDbl, Dbl)
X(NeqDbl, Dbl)
X(GtStr, Str)
X(GteStr, Str)
X(LtStr, Str)
X(LteStr, Str)
X(EqStr, Str)
X(NeqStr, Str)
X(SameStr, Str)
X(NSameStr, Str)
X(GtObj, Obj)
X(GteObj, Obj)
X(LtObj, Obj)
X(LteObj, Obj)
X(EqObj, Obj)
X(NeqObj, Obj)
X(SameObj, Obj)
X(NSameObj, Obj)
X(GtArrLike, ArrLike)
X(GteArrLike, ArrLike)
X(LtArrLike, ArrLike)
X(LteArrLike, ArrLike)
X(EqArrLike, ArrLike)
X(NeqArrLike, ArrLike)
X(SameArrLike, ArrLike)
X(NSameArrLike, ArrLike)
X(GtRes, Res)
X(GteRes, Res)
X(LtRes, Res)
X(LteRes, Res)
X(EqRes, Res)
X(NeqRes, Res)
#undef X
SSATmp* simplifyEqCls(State& env, const IRInstruction* inst) {
auto const left = inst->src(0);
auto const right = inst->src(1);
if (left->hasConstVal() && right->hasConstVal()) {
return cns(env, left->clsVal() == right->clsVal());
}
return nullptr;
}
SSATmp* simplifyEqLazyCls(State& env, const IRInstruction* inst) {
auto const left = inst->src(0);
auto const right = inst->src(1);
if (left->hasConstVal() && right->hasConstVal()) {
return cns(env, left->lclsVal().name() == right->lclsVal().name());
}
return nullptr;
}
SSATmp* simplifyEqStrPtr(State& env, const IRInstruction* inst) {
auto const left = inst->src(0);
auto const right = inst->src(1);
if (left == right) return cns(env, true);
if (left->hasConstVal() && right->hasConstVal()) {
return cns(env, left->strVal() == right->strVal());
}
return nullptr;
}
SSATmp* simplifyEqArrayDataPtr(State& env, const IRInstruction* inst) {
auto const left = inst->src(0);
auto const right = inst->src(1);
if (left == right) return cns(env, true);
if (!left->type().maybe(right->type())) return cns(env, false);
if (left->hasConstVal() && right->hasConstVal()) {
return cns(env, left->arrLikeVal() == right->arrLikeVal());
}
return nullptr;
}
SSATmp* simplifyCmpBool(State& env, const IRInstruction* inst) {
auto const left = inst->src(0);
auto const right = inst->src(1);
assertx(left->type() <= TBool);
assertx(right->type() <= TBool);
if (left->hasConstVal() && right->hasConstVal()) {
return cns(env, HPHP::compare(left->boolVal(), right->boolVal()));
}
return nullptr;
}
SSATmp* simplifyCmpInt(State& env, const IRInstruction* inst) {
auto const left = inst->src(0);
auto const right = inst->src(1);
assertx(left->type() <= TInt);
assertx(right->type() <= TInt);
if (left->hasConstVal() && right->hasConstVal()) {
return cns(env, HPHP::compare(left->intVal(), right->intVal()));
}
return nullptr;
}
SSATmp* simplifyCmpDbl(State& env, const IRInstruction* inst) {
auto const left = inst->src(0);
auto const right = inst->src(1);
assertx(left->type() <= TDbl);
assertx(right->type() <= TDbl);
if (left->hasConstVal() && right->hasConstVal()) {
return cns(env, HPHP::compare(left->dblVal(), right->dblVal()));
}
return nullptr;
}
SSATmp* simplifyCmpStr(State& env, const IRInstruction* inst) {
auto const left = inst->src(0);
auto const right = inst->src(1);
assertx(left->type() <= TStr);
assertx(right->type() <= TStr);
auto newInst = [&](Opcode op, SSATmp* src1, SSATmp* src2) {
return gen(env, op, inst ? inst->taken() : (Block*)nullptr, src1, src2);
};
if (left->hasConstVal()) {
if (right->hasConstVal()) {
return cns(env, HPHP::compare(left->strVal(), right->strVal()));
} else if (left->strVal()->empty()) {
// Comparisons against the empty string can be optimized to a comparison
// on the string length.
return newInst(CmpInt, cns(env, 0), gen(env, LdStrLen, right));
}
} else if (right->hasConstVal() && right->strVal()->empty()) {
return newInst(CmpInt, gen(env, LdStrLen, left), cns(env, 0));
}
return nullptr;
}
SSATmp* simplifyCmpRes(State& env, const IRInstruction* inst) {
auto const left = inst->src(0);
auto const right = inst->src(1);
assertx(left->type() <= TRes);
assertx(right->type() <= TRes);
return (left == right) ? cns(env, 0) : nullptr;
}
SSATmp* simplifyCGetPropQ(State& env, const IRInstruction* inst) {
if (inst->src(0)->isA(TNull)) return cns(env, TInitNull);
return nullptr;
}
SSATmp* instanceOfImpl(State& env, SSATmp* ssatmp1, ClassSpec spec2) {
assertx(ssatmp1->type() <= TCls);
if (!spec2) return nullptr;
auto const cls2 = spec2.cls();
ClassSpec spec1 = ssatmp1->type().clsSpec();
if (spec2.exact() && spec1 && spec1.cls()->classof(cls2)) {
return cns(env, true);
}
// If spec2 is exact and we have an instance bit for it, use
// InstanceOfBitmask.
const bool useInstanceBits = InstanceBits::initted() ||
env.unit.context().kind == TransKind::Optimize;
if (spec2.exact() && useInstanceBits) {
InstanceBits::init();
if (InstanceBits::lookup(cls2->name()) != 0) {
return gen(env, InstanceOfBitmask, ssatmp1, cns(env, cls2->name()));
}
}
// If spec2 is an interface and we've assigned it a vtable slot, use that.
if (spec2.exact() && isInterface(cls2) && RO::RepoAuthoritative) {
auto const slot = cls2->preClass()->ifaceVtableSlot();
if (slot != kInvalidSlot) {
auto const data = InstanceOfIfaceVtableData{spec2.cls(), true};
return gen(env, InstanceOfIfaceVtable, data, ssatmp1);
}
}
if (!spec1) return nullptr;
auto const cls1 = spec1.cls();
if (cls1->classof(cls2)) {
assertx(!spec2.exact()); // the exact case is handled above
return nullptr;
}
if (isInterface(cls1)) return nullptr;
if (spec1.exact()) return cns(env, false);
// At this point cls1 is not a cls2, and its not exact, so:
//
// - If cls2 is an interface, a descendent of cls1 could implement
// that interface
// - If cls2 is a descendent of cls1, then (clearly) a descendent
// of cls1 could be a cls2
// - Otherwise no descendent of cls1 can be a cls2 or any class
// that isa cls2
if (!isInterface(cls2) && !cls2->classof(cls1)) {
return cns(env, false);
}
return nullptr;
}
SSATmp* simplifyInstanceOf(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
if (src2->isA(TNullptr)) {
return cns(env, false);
}
auto const spec2 = src2->type().clsSpec();
if (auto const cls = spec2.exactCls()) {
if (isNormalClass(cls) && (cls->attrs() & AttrUnique)) {
return gen(env, ExtendsClass, ExtendsClassData{ cls }, src1);
}
if (isInterface(cls) && (cls->attrs() & AttrUnique)) {
return gen(env, InstanceOfIface, src1, cns(env, cls->name()));
}
}
return instanceOfImpl(env, src1, src2->type().clsSpec());
}
SSATmp* simplifyExtendsClass(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const cls2 = inst->extra<ExtendsClassData>()->cls;
assertx(cls2 && isNormalClass(cls2));
auto const spec2 = ClassSpec{cls2, ClassSpec::ExactTag{}};
return instanceOfImpl(env, src1, spec2);
}
SSATmp* simplifyInstanceOfBitmask(State& env, const IRInstruction* inst) {
auto const cls = inst->src(0);
auto const name = inst->src(1);
if (!name->hasConstVal(TStr)) return nullptr;
auto const bit = InstanceBits::lookup(name->strVal());
always_assert(bit && "cgInstanceOfBitmask had no bitmask");
if (cls->type().clsSpec() &&
cls->type().clsSpec().cls()->checkInstanceBit(bit)) {
return cns(env, true);
}
if (!cls->hasConstVal(TCls)) return nullptr;
return cns(env, false);
}
SSATmp* simplifyNInstanceOfBitmask(State& env, const IRInstruction* inst) {
auto const cls = inst->src(0);
auto const name = inst->src(1);
if (!name->hasConstVal(TStr)) return nullptr;
auto const bit = InstanceBits::lookup(name->strVal());
always_assert(bit && "cgNInstanceOfBitmask had no bitmask");
if (cls->type().clsSpec() &&
cls->type().clsSpec().cls()->checkInstanceBit(bit)) {
return cns(env, false);
}
if (!cls->hasConstVal(TCls)) return nullptr;
return cns(env, true);
}
SSATmp* simplifyInstanceOfIface(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
auto const cls2 = Class::lookupUniqueInContext(
src2->strVal(), inst->ctx(), nullptr);
assertx(cls2 && isInterface(cls2));
auto const spec2 = ClassSpec{cls2, ClassSpec::ExactTag{}};
return instanceOfImpl(env, src1, spec2);
}
SSATmp* simplifyInstanceOfIfaceVtable(State& env, const IRInstruction* inst) {
if (!inst->extra<InstanceOfIfaceVtable>()->canOptimize) return nullptr;
auto const cls = inst->src(0);
auto const iface = inst->extra<InstanceOfIfaceVtable>()->cls;
if (cls->type().clsSpec() &&
cls->type().clsSpec().cls()->classof(iface)) {
return cns(env, true);
}
const bool useInstanceBits = InstanceBits::initted() ||
env.unit.context().kind == TransKind::Optimize;
if (useInstanceBits) {
InstanceBits::init();
auto const ifaceName = iface->name();
if (InstanceBits::lookup(ifaceName) != 0) {
return gen(env, InstanceOfBitmask, cls, cns(env, ifaceName));
}
}
return nullptr;
}
SSATmp* isTypeImpl(State& env, const IRInstruction* inst, const Type& srcType) {
assertx(inst->is(IsNType, IsType));
auto const trueSense = inst->is(IsType);
auto const type = inst->typeParam();
// Testing for StaticStr will make you miss out on CountedStr, and vice versa,
// and similarly for arrays. PHP treats both types of string the same, so if
// the distinction matters to you here, be careful.
assertx(IMPLIES(type <= TStr, type == TStr));
assertx(IMPLIES(type <= TVec, type == TVec));
assertx(IMPLIES(type <= TDict, type == TDict));
assertx(IMPLIES(type <= TKeyset, type == TKeyset));
// The types are disjoint; the result must be false.
if (!srcType.maybe(type)) {
return cns(env, !trueSense);
}
// The src type is a subtype of the tested type; the result must be true.
if (srcType <= type) {
return cns(env, trueSense);
}
// At this point, either the tested type is a subtype of the src type, or they
// are non-disjoint but neither is a subtype of the other. We can't simplify
// this away.
return nullptr;
}
SSATmp* simplifyIsType(State& env, const IRInstruction* i) {
auto const src = i->src(0);
return isTypeImpl(env, i, src->type());
}
SSATmp* simplifyIsNType(State& env, const IRInstruction* i) {
auto const src = i->src(0);
return isTypeImpl(env, i, src->type());
}
SSATmp* simplifyMethodExists(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
auto const clsSpec = src1->type().clsSpec();
if (clsSpec.cls() != nullptr && src2->hasConstVal(TStr)) {
// If we don't have an exact type, then we can't say for sure the class
// doesn't have the method.
auto const result = clsSpec.cls()->lookupMethod(src2->strVal()) != nullptr;
return (clsSpec.exact() || result) ? cns(env, result) : nullptr;
}
return nullptr;
}
static auto concat_litstrs(State& env, SSATmp* s1, SSATmp* s2) {
auto const str1 = const_cast<StringData*>(s1->strVal());
auto const str2 = const_cast<StringData*>(s2->strVal());
auto const sval = String::attach(concat_ss(str1, str2));
return cns(env, makeStaticString(sval.get()));
}
SSATmp* simplifyConcatStrStr(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
if (src1->hasConstVal(TStaticStr) &&
src2->hasConstVal(TStaticStr)) {
return concat_litstrs(env, src1, src2);
}
// ConcatStrStr consumes a reference to src1 and produces a reference, so
// anything we replace it with must do the same thing.
if (src1->hasConstVal(staticEmptyString())) {
// Produce a reference on src2.
gen(env, IncRef, src2);
return src2;
}
if (src2->hasConstVal(staticEmptyString())) {
// Forward the reference on src1 from input to output.
return src1;
}
return nullptr;
}
SSATmp* simplifyConcatStr3(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
auto const src3 = inst->src(2);
if (src2->hasConstVal(TStaticStr)) {
if (src1->hasConstVal(TStaticStr)) {
return gen(env, ConcatStrStr, inst->taken(),
concat_litstrs(env, src1, src2), src3);
}
if (src3->hasConstVal(TStaticStr)) {
return gen(env, ConcatStrStr, inst->taken(),
src1, concat_litstrs(env, src2, src3));
}
if (src2->hasConstVal(staticEmptyString())) {
return gen(env, ConcatStrStr, inst->taken(), src1, src3);
}
}
// ConcatStr3 consumes a reference to src1 and produces a reference, so
// anything we replace it with must do the same thing.
if (src1->hasConstVal(staticEmptyString())) {
// Compensate for ConcatStrStr consuming src2.
gen(env, IncRef, src2);
return gen(env, ConcatStrStr, inst->taken(), src2, src3);
}
if (src3->hasConstVal(staticEmptyString())) {
// ConcatStrStr also consumes a reference to src1
return gen(env, ConcatStrStr, inst->taken(), src1, src2);
}
return nullptr;
}
SSATmp* simplifyConcatStr4(State& env, const IRInstruction* inst) {
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
auto const src3 = inst->src(2);
auto const src4 = inst->src(3);
if (src2->hasConstVal(TStaticStr)) {
if (src1->hasConstVal(TStaticStr)) {
return gen(env, ConcatStr3, inst->taken(),
concat_litstrs(env, src1, src2), src3, src4);
}
if (src3->hasConstVal(TStaticStr)) {
return gen(env, ConcatStr3, inst->taken(),
src1, concat_litstrs(env, src2, src3), src4);
}
if (src2->hasConstVal(staticEmptyString())) {
return gen(env, ConcatStr3, inst->taken(), src1, src3, src4);
}
}
if (src3->hasConstVal(TStaticStr)) {
if (src4->hasConstVal(TStaticStr)) {
return gen(env, ConcatStr3, inst->taken(),
src1, src2, concat_litstrs(env, src3, src4));
}
if (src3->hasConstVal(staticEmptyString())) {
return gen(env, ConcatStr3, inst->taken(), src1, src2, src4);
}
}
// ConcatStr4 consumes a reference to src1 and produces a reference, so
// anything we replace it with must do the same thing.
if (src1->hasConstVal(staticEmptyString())) {
// Compensate for ConcatStrStr consuming src2.
gen(env, IncRef, src2);
return gen(env, ConcatStr3, inst->taken(), src2, src3, src4);
}
if (src4->hasConstVal(staticEmptyString())) {
// ConcatStr3 also consumes a reference to src1
return gen(env, ConcatStr3, inst->taken(), src1, src2, src3);
}
return nullptr;
}
SSATmp* simplifyConcatIntStr(State& env, const IRInstruction* inst) {
auto const lhs = inst->src(0);
auto const rhs = inst->src(1);
if (lhs->hasConstVal()) {
auto const lhsStr =
cns(env, makeStaticString(folly::to<std::string>(lhs->intVal())));
return gen(env, ConcatStrStr, inst->taken(), lhsStr, rhs);
}
if (rhs->hasConstVal(staticEmptyString())) {
return gen(env, ConvIntToStr, lhs);
}
return nullptr;
}
SSATmp* simplifyConcatStrInt(State& env, const IRInstruction* inst) {
auto const lhs = inst->src(0);
auto const rhs = inst->src(1);
if (rhs->hasConstVal()) {
auto const rhsStr =
cns(env, makeStaticString(folly::to<std::string>(rhs->intVal())));
return gen(env, ConcatStrStr, inst->taken(), lhs, rhsStr);
}
if (lhs->hasConstVal(staticEmptyString())) {
return gen(env, ConvIntToStr, rhs);
}
return nullptr;
}
namespace {
template <typename C>
SSATmp* arrayLikeConvImpl(State& env, const IRInstruction* inst, C convert) {
auto const src = inst->src(0);
if (!src->hasConstVal()) return nullptr;
auto const before = src->arrLikeVal();
auto converted = convert(const_cast<ArrayData*>(before));
if (!converted) return nullptr;
ArrayData::GetScalarArray(&converted);
return cns(env, converted);
}
}
SSATmp* simplifyConvArrLikeToVec(State& env, const IRInstruction* inst) {
return arrayLikeConvImpl(
env, inst,
[&](ArrayData* a) { return a->toVec(true); }
);
}
SSATmp* simplifyConvArrLikeToDict(State& env, const IRInstruction* inst) {
return arrayLikeConvImpl(
env, inst,
[&](ArrayData* a) { return a->toDict(true); }
);
}
SSATmp* simplifyConvArrLikeToKeyset(State& env, const IRInstruction* inst) {
return arrayLikeConvImpl(
env, inst,
[&](ArrayData* a) {
// We need to check if the array contains values suitable for keyset
// before attempting the conversion. Otherwise, toKeyset() might re-enter
// which we can't do from the simplifier.
bool keylike = true;
IterateV(
a,
[&](TypedValue v) {
if (!isIntType(v.m_type) && !isStringType(v.m_type)) {
keylike = false;
return true;
}
return false;
}
);
return keylike ? a->toKeyset(true) : nullptr;
}
);
}
SSATmp* simplifyConvDblToBool(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) {
bool const bval = src->dblVal();
return cns(env, bval);
}
return nullptr;
}
SSATmp* simplifyConvIntToBool(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) {
bool const bval = src->intVal();
return cns(env, bval);
}
return nullptr;
}
SSATmp* simplifyConvStrToBool(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) {
// only the strings "", and "0" convert to false, all other strings
// are converted to true
auto const str = src->strVal();
return cns(env, !str->empty() && !str->isZero());
}
return nullptr;
}
SSATmp* simplifyConvBoolToDbl(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) {
double const dval = src->boolVal();
return cns(env, dval);
}
return nullptr;
}
SSATmp* simplifyConvIntToDbl(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) {
double const dval = src->intVal();
return cns(env, dval);
}
if (src->inst()->is(ConvBoolToInt)) {
// This is safe, because the bool src is not reference counted.
return gen(env, ConvBoolToDbl, src->inst()->src(0));
}
return nullptr;
}
SSATmp* simplifyConvStrToDbl(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
return src->hasConstVal() ? cns(env, src->strVal()->toDouble()) : nullptr;
}
SSATmp* simplifyConvBoolToInt(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) return cns(env, static_cast<int>(src->boolVal()));
return nullptr;
}
SSATmp* simplifyConvDblToInt(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) return cns(env, double_to_int64(src->dblVal()));
return nullptr;
}
SSATmp* simplifyConvStrToInt(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
return src->hasConstVal() ? cns(env, src->strVal()->toInt64()) : nullptr;
}
SSATmp* simplifyConvDblToStr(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) {
auto const dblStr = String::attach(buildStringData(src->dblVal()));
return cns(env, makeStaticString(dblStr));
}
return nullptr;
}
SSATmp* simplifyConvIntToStr(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) {
return cns(env,
makeStaticString(folly::to<std::string>(src->intVal()))
);
}
return nullptr;
}
namespace {
const StaticString
s_msgArrToStr("Array to string conversion"),
s_msgVecToStr("Vec to string conversion"),
s_msgDictToStr("Dict to string conversion"),
s_msgKeysetToStr("Keyset to string conversion"),
s_msgClsMethToStr("Cannot convert clsmeth to string"),
s_msgClsMethToInt("Cannot convert clsmeth to int"),
s_msgClsMethToIntImpl("Implicit clsmeth to int conversion"),
s_msgClsMethToDbl("Cannot convert clsmeth to double"),
s_msgClsMethToDblImpl("Implicit clsmeth to double conversion");
}
SSATmp* simplifyConvTVToBool(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
auto const srcType = src->type();
if (srcType <= TBool) return src;
if (srcType <= TNull) return cns(env, false);
if (srcType <= TVec) {
auto const length = gen(env, CountVec, src);
return gen(env, NeqInt, length, cns(env, 0));
}
if (srcType <= TDict) {
auto const length = gen(env, CountDict, src);
return gen(env, NeqInt, length, cns(env, 0));
}
if (srcType <= TKeyset) {
auto const length = gen(env, CountKeyset, src);
return gen(env, NeqInt, length, cns(env, 0));
}
if (srcType <= TDbl) return gen(env, ConvDblToBool, src);
if (srcType <= TInt) return gen(env, ConvIntToBool, src);
if (srcType <= TStr) return gen(env, ConvStrToBool, src);
if (srcType <= TObj) {
if (auto const cls = srcType.clsSpec().cls()) {
// We need to exclude interfaces like ConstSet. For now, just
// skip anything that's an interface or extension.
if (!(cls->attrs() & AttrInterface)) {
if (!cls->instanceCtor()) {
return cns(env, true);
}
}
}
return gen(env, ConvObjToBool, inst->taken(), src);
}
if (srcType <= TRes) return cns(env, true);
if (srcType <= TFunc) return cns(env, true);
if (srcType <= TClsMeth) return cns(env, true);
return nullptr;
}
// return true if throws
bool handleConvNoticeLevel(
State& env,
Block* trace,
const ConvNoticeData* notice_data,
const char* const from,
const char* const to) {
// do this check here because if notice level is none, reason may be nullptr
if (notice_data->level == ConvNoticeLevel::None) return false;
assertx(notice_data->reason != nullptr);
const auto str = cns(env, makeStaticString(folly::sformat(
"Implicit {} to {} conversion for {}", from, to, notice_data->reason)));
switch(notice_data->level) {
case ConvNoticeLevel::Throw:
gen(env, ThrowInvalidOperation, trace, str);
return true;
case ConvNoticeLevel::Log:
gen(env, RaiseNotice, trace, str);
// FALLTHROUGH
case ConvNoticeLevel::None:
return false;
}
not_reached();
}
SSATmp* simplifyConvTVToStr(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
auto const srcType = src->type();
auto const catchTrace = inst->taken();
auto const notice_data = inst->extra<ConvNoticeData>();
if (srcType <= TBool) {
const auto tmp = gen(
env,
Select,
src,
cns(env, s_1.get()),
cns(env, staticEmptyString())
);
auto throws = handleConvNoticeLevel(
env, catchTrace, notice_data, "bool", "string");
return throws ? cns(env, TBottom) : tmp ;
}
if (srcType <= TNull) {
auto throws = handleConvNoticeLevel(
env, catchTrace, notice_data, "null", "string");
return throws ? cns(env, TBottom) : cns(env, staticEmptyString());
}
if (srcType <= TVec) {
gen(env, ThrowInvalidOperation, catchTrace, cns(env, s_msgVecToStr.get()));
return cns(env, TBottom);
}
if (srcType <= TDict) {
gen(env, ThrowInvalidOperation, catchTrace, cns(env, s_msgDictToStr.get()));
return cns(env, TBottom);
}
if (srcType <= TKeyset) {
auto const message = cns(env, s_msgKeysetToStr.get());
gen(env, ThrowInvalidOperation, catchTrace, message);
return cns(env, TBottom);
}
if (srcType <= TDbl) {
const auto tmp = gen(env, ConvDblToStr, src);
auto throws = handleConvNoticeLevel(
env, catchTrace, notice_data, "double", "string");
return throws ? cns(env, TBottom) : tmp;
}
if (srcType <= TInt) return gen(env, ConvIntToStr, src);
if (srcType <= TStr) {
gen(env, IncRef, src);
return src;
}
if (srcType <= TObj) return gen(env, ConvObjToStr, catchTrace, src);
if (srcType <= TClsMeth) {
auto const message = cns(env, s_msgClsMethToStr.get());
gen(env, ThrowInvalidOperation, catchTrace, message);
return cns(env, TBottom);
}
return nullptr;
}
SSATmp* simplifyConvTVToInt(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
auto const srcType = src->type();
auto const catchTrace = inst->taken();
if (srcType <= TInt) return src;
if (srcType <= TNull) return cns(env, 0);
auto genFromArray = [&](Opcode op) {
return gen(env, Select, gen(env, op, src), cns(env, 1), cns(env, 0));
};
if (srcType <= TVec) return genFromArray(CountVec);
if (srcType <= TDict) return genFromArray(CountDict);
if (srcType <= TKeyset) return genFromArray(CountKeyset);
if (srcType <= TBool) return gen(env, ConvBoolToInt, src);
if (srcType <= TDbl) return gen(env, ConvDblToInt, src);
if (srcType <= TStr) return gen(env, ConvStrToInt, src);
if (srcType <= TObj) return gen(env, ConvObjToInt, catchTrace, src);
if (srcType <= TRes) return gen(env, ConvResToInt, src);
if (srcType <= TClsMeth) {
gen(env, ThrowInvalidOperation, catchTrace,
cns(env, s_msgClsMethToInt.get()));
return cns(env, TBottom);
}
return nullptr;
}
SSATmp* simplifyConvTVToDbl(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
auto const srcType = src->type();
auto const catchTrace = inst->taken();
if (srcType <= TDbl) return src;
if (srcType <= TNull) return cns(env, 0.0);
if (srcType <= TVec) {
auto const length = gen(env, CountVec, src);
return gen(env, ConvBoolToDbl, gen(env, ConvIntToBool, length));
}
if (srcType <= TDict) {
auto const length = gen(env, CountDict, src);
return gen(env, ConvBoolToDbl, gen(env, ConvIntToBool, length));
}
if (srcType <= TKeyset) {
auto const length = gen(env, CountKeyset, src);
return gen(env, ConvBoolToDbl, gen(env, ConvIntToBool, length));
}
if (srcType <= TBool) return gen(env, ConvBoolToDbl, src);
if (srcType <= TInt) return gen(env, ConvIntToDbl, src);
if (srcType <= TStr) return gen(env, ConvStrToDbl, src);
if (srcType <= TObj) return gen(env, ConvObjToDbl, inst->taken(), src);
if (srcType <= TRes) return gen(env, ConvResToDbl, src);
if (srcType <= TClsMeth) {
gen(env, ThrowInvalidOperation, catchTrace,
cns(env, s_msgClsMethToDbl.get()));
return cns(env, TBottom);
}
return nullptr;
}
SSATmp* simplifyConvObjToBool(State& env, const IRInstruction* inst) {
auto const ty = inst->src(0)->type();
if (ty < TObj &&
ty.clsSpec().cls() &&
ty.clsSpec().cls()->isCollectionClass()) {
if (RuntimeOption::EvalNoticeOnCollectionToBool) return nullptr;
return gen(env, ColIsNEmpty, inst->src(0));
}
return nullptr;
}
SSATmp* simplifyDblAsBits(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) {
union {
int64_t i;
double d;
};
d = src->dblVal();
return cns(env, i);
}
return nullptr;
}
SSATmp* roundImpl(State& env, const IRInstruction* inst, double (*op)(double)) {
auto const src = inst->src(0);
if (src->hasConstVal()) {
return cns(env, op(src->dblVal()));
}
auto const srcInst = src->inst();
if (srcInst->op() == ConvIntToDbl || srcInst->op() == ConvBoolToDbl) {
return src;
}
return nullptr;
}
SSATmp* simplifyFloor(State& env, const IRInstruction* inst) {
return roundImpl(env, inst, floor);
}
SSATmp* simplifyCeil(State& env, const IRInstruction* inst) {
return roundImpl(env, inst, ceil);
}
SSATmp* simplifyCheckInit(State& env, const IRInstruction* inst) {
auto const srcType = inst->src(0)->type();
assertx(!srcType.maybe(TMem));
assertx(inst->taken());
if (!srcType.maybe(TUninit)) return gen(env, Nop);
return mergeBranchDests(env, inst);
}
SSATmp* simplifyCheckInitMem(State& env, const IRInstruction* inst) {
return mergeBranchDests(env, inst);
}
SSATmp* simplifyCheckRDSInitialized(State& env, const IRInstruction* inst) {
auto const handle = inst->extra<CheckRDSInitialized>()->handle;
if (!rds::isNormalHandle(handle)) return gen(env, Jmp, inst->next());
return mergeBranchDests(env, inst);
}
SSATmp* simplifyMarkRDSInitialized(State& env, const IRInstruction* inst) {
auto const handle = inst->extra<MarkRDSInitialized>()->handle;
if (!rds::isNormalHandle(handle)) return gen(env, Nop);
return nullptr;
}
SSATmp* simplifyMarkRDSAccess(State& env, const IRInstruction* inst) {
auto const handle = inst->extra<MarkRDSAccess>()->handle;
auto const profile = rds::profileForHandle(handle);
if (profile == rds::kUninitHandle) return gen(env, Nop);
return nullptr;
}
SSATmp* simplifyInitObjProps(State& env, const IRInstruction* inst) {
auto const cls = inst->extra<InitObjProps>()->cls;
if (cls->numDeclProperties() == 0 && !cls->hasMemoSlots()) {
return gen(env, Nop);
}
return nullptr;
}
SSATmp* simplifyInitObjMemoSlots(State& env, const IRInstruction* inst) {
auto const cls = inst->extra<InitObjMemoSlots>()->cls;
if (!cls->hasMemoSlots()) {
return gen(env, Nop);
}
return nullptr;
}
SSATmp* simplifyCheckType(State& env, const IRInstruction* inst) {
auto const typeParam = inst->typeParam();
auto const srcType = inst->src(0)->type();
if (!srcType.maybe(typeParam) || inst->next() == inst->taken() ||
(inst->next() && inst->next()->isUnreachable())) {
/*
* Convert the check into a Jmp. The dest of the CheckType (which would've
* been Bottom) is now never going to be defined, so we return a Bottom.
*/
gen(env, Jmp, inst->taken());
return cns(env, TBottom);
}
auto const newType = srcType & typeParam;
if (srcType <= newType) {
// The type of the src is the same or more refined than type, so the guard
// is unnecessary.
return inst->src(0);
}
if (inst->taken() && inst->taken()->isUnreachable()) {
return gen(env, AssertType, newType, inst->src(0));
}
return nullptr;
}
SSATmp* simplifyCheckTypeMem(State& env, const IRInstruction* inst) {
if (inst->typeParam() == TBottom) {
return gen(env, Jmp, inst->taken());
}
return mergeBranchDests(env, inst);
}
SSATmp* simplifyAssertType(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
auto const newType = src->type() & inst->typeParam();
if (newType == TBottom) {
return cns(env, TBottom);
}
return src->isA(newType) ? src : nullptr;
}
SSATmp* simplifyCheckLoc(State& env, const IRInstruction* inst) {
return mergeBranchDests(env, inst);
}
SSATmp* simplifyCheckStk(State& env, const IRInstruction* inst) {
return mergeBranchDests(env, inst);
}
SSATmp* simplifyCheckMBase(State& env, const IRInstruction* inst) {
return mergeBranchDests(env, inst);
}
SSATmp* simplifyCheckNonNull(State& env, const IRInstruction* inst) {
auto const type = inst->src(0)->type();
if (type <= TNullptr || inst->next() == inst->taken() ||
(inst->next() && inst->next()->isUnreachable())) {
gen(env, Jmp, inst->taken());
return cns(env, TBottom);
}
if (inst->taken() && inst->taken()->isUnreachable()) {
return gen(env, AssertType, type - TNullptr, inst->src(0));
}
if (!type.maybe(TNullptr)) return inst->src(0);
return nullptr;
}
SSATmp* simplifyDefLabel(State& env, const IRInstruction* inst) {
if (inst->numDsts() == 0) {
return gen(env, Nop);
}
return nullptr;
}
SSATmp* decRefImpl(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (noop_decref || !src->type().maybe(TCounted)) {
return gen(env, Nop);
}
return nullptr;
}
SSATmp* simplifyDecRef(State& env, const IRInstruction* inst) {
return decRefImpl(env, inst);
}
SSATmp* simplifyDecRefNZ(State& env, const IRInstruction* inst) {
return decRefImpl(env, inst);
}
SSATmp* simplifyIncRef(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (!src->type().maybe(TCounted)) {
return gen(env, Nop);
}
return nullptr;
}
SSATmp* condJmpImpl(State& env, const IRInstruction* inst) {
assertx(inst->is(JmpZero, JmpNZero));
// Both ways go to the same block.
if (inst->taken() == inst->next()) {
assertx(inst->taken() != nullptr);
return gen(env, Jmp, inst->taken());
}
if (inst->taken() && inst->taken()->isUnreachable()) {
return gen(env, Nop);
}
if (inst->next() && inst->next()->isUnreachable()) {
return gen(env, Jmp, inst->taken());
}
auto const src = inst->src(0);
auto const srcInst = src->inst();
// Constant propagate.
if (src->hasConstVal()) {
bool val = src->isA(TBool) ? src->boolVal()
: static_cast<bool>(src->intVal());
if (val == inst->is(JmpNZero)) {
// always taken
assertx(inst->taken());
return gen(env, Jmp, inst->taken());
}
// Never taken. Since simplify() is also run when building the IR,
// inst->next() could be nullptr at this moment.
return gen(env, Nop);
}
auto absorb = [&](){
return gen(env, inst->op(), inst->taken(), srcInst->src(0));
};
auto absorbOpp = [&](){
return gen(env,
inst->op() == JmpZero ? JmpNZero : JmpZero,
inst->taken(),
srcInst->src(0)
);
};
// Absorb negations.
if (srcInst->is(XorBool) && srcInst->src(1)->hasConstVal(true)) {
return absorbOpp();
}
// Absorb ConvIntToBool.
if (srcInst->is(ConvIntToBool)) {
return absorb();
}
if (srcInst->is(ConvBoolToInt)) {
return absorb();
}
// Absorb boolean comparisons.
if (srcInst->is(EqBool) && srcInst->src(1)->hasConstVal()) {
return srcInst->src(1)->boolVal() ? absorb() : absorbOpp();
}
if (srcInst->is(NeqBool) && srcInst->src(1)->hasConstVal()) {
return srcInst->src(1)->boolVal() ? absorbOpp() : absorb();
}
// Absorb integer comparisons against constant zero.
if (srcInst->is(EqInt) && srcInst->src(1)->hasConstVal(0)) {
return absorbOpp();
}
if (srcInst->is(NeqInt) && srcInst->src(1)->hasConstVal(0)) {
return absorb();
}
return nullptr;
}
SSATmp* simplifyJmpZero(State& env, const IRInstruction* i) {
return condJmpImpl(env, i);
}
SSATmp* simplifyJmpNZero(State& env, const IRInstruction* i) {
return condJmpImpl(env, i);
}
SSATmp* simplifyJmp(State& env, const IRInstruction* inst) {
assertx(inst->taken());
if (inst->taken()->isUnreachable()) {
return gen(env, Unreachable, *inst->taken()->back().extra<Unreachable>());
}
return nullptr;
}
SSATmp* simplifySelect(State& env, const IRInstruction* inst) {
auto const cond = inst->src(0);
auto const tval = inst->src(1);
auto const fval = inst->src(2);
// Simplifications based on condition:
if (cond->hasConstVal()) {
auto const cval = cond->isA(TBool)
? cond->boolVal()
: static_cast<bool>(cond->intVal());
return cval ? tval : fval;
}
// The condition isn't statically known, but could be computed from a
// different operation we can absorb.
auto const condInst = cond->inst();
auto const absorb = [&]{
return gen(env, Select, condInst->src(0), tval, fval);
};
auto const absorbOpp = [&]{
return gen(env, Select, condInst->src(0), fval, tval);
};
// Conversions between int and bool can't change which value is selected.
if (condInst->is(ConvIntToBool)) return absorb();
if (condInst->is(ConvBoolToInt)) return absorb();
// Condition is negated, so check the pre-negated condition with flipped
// values.
if (condInst->is(XorBool) && condInst->src(1)->hasConstVal(true)) {
return absorbOpp();
}
// Condition comes from comparisons against true/false or 0, which is
// equivalent to selecting on original value (possibly with values flipped).
if (condInst->is(EqBool) && condInst->src(1)->hasConstVal()) {
return condInst->src(1)->boolVal() ? absorb() : absorbOpp();
}
if (condInst->is(NeqBool) && condInst->src(1)->hasConstVal()) {
return condInst->src(1)->boolVal() ? absorbOpp() : absorb();
}
if (condInst->is(EqInt) && condInst->src(1)->hasConstVal(0)) {
return absorbOpp();
}
if (condInst->is(NeqInt) && condInst->src(1)->hasConstVal(0)) {
return absorb();
}
// Condition comes from Select C, X, 0 or Select C, 0, X (where X != 0), which
// is equivalent to selecting on C directly.
if (condInst->is(Select)) {
auto const condInstT = condInst->src(1);
auto const condInstF = condInst->src(2);
if (condInstT->hasConstVal(TInt) &&
condInstT->intVal() != 0 &&
condInstF->hasConstVal(0)) {
return absorb();
}
if (condInstT->hasConstVal(0) &&
condInstF->hasConstVal(TInt) &&
condInstF->intVal() != 0) {
return absorbOpp();
}
}
// Simplifications based on known value choices:
// If the two values are the same tmp, or if they're both constants with the
// same value, no need to do a select, as we'll always get the same value.
if (tval == fval) return tval;
if ((tval->type() == fval->type()) &&
(tval->type().hasConstVal() ||
tval->type().subtypeOfAny(TUninit, TInitNull, TNullptr))) {
return tval;
}
// If either value isn't satisfiable, assume it comes from unreachable code.
if (tval->isA(TBottom)) return fval;
if (fval->isA(TBottom)) return tval;
// If the values are true and false (and vice-versa), then its equal to the
// value of the condition itself (or inverted).
if (tval->hasConstVal(true) && fval->hasConstVal(false)) {
if (cond->isA(TBool)) return cond;
if (cond->isA(TInt)) return gen(env, NeqInt, cond, cns(env, 0));
}
if (tval->hasConstVal(false) && fval->hasConstVal(true)) {
if (cond->isA(TBool)) return gen(env, XorBool, cond, cns(env, true));
if (cond->isA(TInt)) return gen(env, EqInt, cond, cns(env, 0));
}
// Select C, 0, 1 or Select C, 1, 0 is the same as a bool -> int conversion.
if (tval->hasConstVal(1) && fval->hasConstVal(0) && cond->isA(TBool)) {
return gen(env, ConvBoolToInt, cond);
}
if (tval->hasConstVal(0) && fval->hasConstVal(1) && cond->isA(TBool)) {
return gen(env, ConvBoolToInt, gen(env, XorBool, cond, cns(env, true)));
}
return nullptr;
}
SSATmp* simplifyAssertNonNull(State& /*env*/, const IRInstruction* inst) {
if (!inst->src(0)->type().maybe(TNullptr)) {
return inst->src(0);
}
return nullptr;
}
SSATmp* simplifyCheckVecBounds(State& env, const IRInstruction* inst) {
auto const array = inst->src(0);
auto const idx = inst->src(1);
auto const idxVal = idx->hasConstVal()
? Optional<int64_t>(idx->intVal())
: std::nullopt;
switch (vecBoundsStaticCheck(array->type(), idxVal)) {
case VecBounds::In: return gen(env, Nop);
case VecBounds::Out: return gen(env, Jmp, inst->taken());
case VecBounds::Unknown: break;
}
return mergeBranchDests(env, inst);
}
SSATmp* simplifyReserveVecNewElem(State& env, const IRInstruction* inst) {
auto const base = inst->src(0);
if (base->type() <= TPersistentVec) {
return cns(env, TBottom);
}
return nullptr;
}
namespace {
SSATmp* arrGetKImpl(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
if (!inst->src(2)->hasConstVal(TInt)) return nullptr;
auto const pos = inst->src(2)->intVal();
assertx(validPos(ssize_t(pos)));
if (!arr->hasConstVal()) return nullptr;
auto const mixed = VanillaDict::as(arr->arrLikeVal());
auto const tv = mixed->getArrayElmPtr(pos);
// The array doesn't contain a valid element at that offset. Since this
// instruction should be guarded by a check, this (should be) unreachable.
if (!tv) {
return cns(env, TBottom);
}
assertx(tvIsPlausible(*tv));
return cns(env, *tv);
}
template <typename I, typename S, typename F>
SSATmp* hackArrQueryImpl(State& /*env*/, const IRInstruction* inst, I getInt,
S getStr, F finish) {
auto const arr = inst->src(0);
auto const key = inst->src(1);
if (!arr->hasConstVal()) return nullptr;
if (!key->hasConstVal(TInt) && !key->hasConstVal(TStr)) return nullptr;
auto const value = key->hasConstVal(TInt)
? getInt(arr, key->intVal())
: getStr(arr, key->strVal());
return finish(value);
}
template <typename I, typename S>
SSATmp* hackArrGetImpl(State& env, const IRInstruction* inst,
I getInt, S getStr) {
return hackArrQueryImpl(
env, inst,
getInt, getStr,
[&] (TypedValue tv) {
if (tv.is_init()) return cns(env, tv);
gen(env, ThrowOutOfBounds, inst->taken(), inst->src(0), inst->src(1));
return cns(env, TBottom);
}
);
}
template <typename I, typename S>
SSATmp* hackArrGetQuietImpl(State& env, const IRInstruction* inst,
I getInt, S getStr) {
return hackArrQueryImpl(
env, inst,
getInt, getStr,
[&] (TypedValue tv) {
return tv.is_init() ? cns(env, tv) : cns(env, TInitNull);
}
);
}
template <typename I, typename S>
SSATmp* hackArrIssetImpl(State& env, const IRInstruction* inst,
I getInt, S getStr) {
return hackArrQueryImpl(
env, inst,
getInt, getStr,
[&] (TypedValue tv) { return cns(env, !tvIsNull(tv)); }
);
}
template <typename I, typename S>
SSATmp* hackArrIdxImpl(State& env, const IRInstruction* inst,
I getInt, S getStr) {
return hackArrQueryImpl(
env, inst,
getInt, getStr,
[&] (TypedValue tv) { return tv.is_init() ? cns(env, tv) : inst->src(2); }
);
}
template <typename I, typename S>
SSATmp* hackArrAKExistsImpl(State& env, const IRInstruction* inst,
I getInt, S getStr) {
return hackArrQueryImpl(
env, inst,
getInt, getStr,
[&] (TypedValue tv) { return cns(env, tv.is_init()); }
);
}
}
#define X(Name, Action) \
SSATmp* simplify##Name(State& env, const IRInstruction* inst) { \
return hackArr##Action##Impl( \
env, inst, \
[](SSATmp* a, int64_t k) { \
return VanillaDict::NvGetInt(a->arrLikeVal(), k); \
}, \
[](SSATmp* a, const StringData* k) { \
return VanillaDict::NvGetStr(a->arrLikeVal(), k); \
} \
); \
}
X(DictGet, Get)
X(DictGetQuiet, GetQuiet)
X(DictIsset, Isset)
X(DictIdx, Idx)
X(AKExistsDict, AKExists)
#undef X
#define X(Name, Action) \
SSATmp* simplify##Name(State& env, const IRInstruction* inst) { \
return hackArr##Action##Impl( \
env, inst, \
[](SSATmp* a, int64_t k) { \
return VanillaKeyset::NvGetInt(a->keysetVal(), k); \
}, \
[](SSATmp* a, const StringData* k) { \
return VanillaKeyset::NvGetStr(a->keysetVal(), k); \
} \
); \
}
X(KeysetGet, Get)
X(KeysetGetQuiet, GetQuiet)
X(KeysetIsset, Isset)
X(KeysetIdx, Idx)
X(AKExistsKeyset, AKExists)
#undef X
SSATmp* simplifyDictGetK(State& env, const IRInstruction* inst) {
return arrGetKImpl(env, inst);
}
SSATmp* simplifyKeysetGetK(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
if (!inst->src(2)->hasConstVal(TInt)) return nullptr;
auto const pos = inst->src(2)->intVal();
assertx(validPos(ssize_t(pos)));
if (!arr->hasConstVal()) return nullptr;
auto const set = VanillaKeyset::asSet(arr->keysetVal());
auto const tv = set->tvOfPos(pos);
// The array doesn't contain a valid element at that offset. Since this
// instruction should be guarded by a check, this (should be) unreachable.
if (!tv) {
return cns(env, TBottom);
}
assertx(tvIsPlausible(*tv));
assertx(isStringType(tv->m_type) || isIntType(tv->m_type));
return cns(env, *tv);
}
SSATmp* simplifyGetDictPtrIter(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const idx = inst->src(1);
if (!arr->hasConstVal(TArrLike)) return nullptr;
if (!idx->hasConstVal(TInt)) return nullptr;
auto const ad = VanillaDict::as(arr->arrLikeVal());
auto const elm = ad->data() + idx->intVal();
return cns(env, Type::cns(elm, outputType(inst)));
}
SSATmp* simplifyCheckDictKeys(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (!src->hasConstVal()) return mergeBranchDests(env, inst);
auto const arr = src->arrLikeVal();
auto match = true;
IterateKV(arr, [&](TypedValue key, TypedValue val){
match &= Type::cns(key) <= inst->typeParam();
return !match;
});
return match ? gen(env, Nop) : gen(env, Jmp, inst->taken());
}
SSATmp* simplifyCheckMissingKeyInArrLike(State& env,
const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const key = inst->src(1);
if (!arr->hasConstVal(TArrLike) || !key->hasConstVal(TStaticStr)) {
return mergeBranchDests(env, inst);
}
auto const ad = arr->arrLikeVal();
auto const sd = key->strVal();
if (ad->hasStrKeyTable() && !ad->missingKeySideTable().mayContain(sd)) {
return gen(env, Nop);
}
return gen(env, Jmp, inst->taken());
}
SSATmp* simplifyCheckOffsetImpl(State& env, const IRInstruction* inst) {
assertx(inst->is(CheckDictOffset, CheckKeysetOffset));
auto const arr = inst->src(0);
auto const key = inst->src(1);
auto const& extra = inst->extra<IndexData>();
assertx(validPos(ssize_t(extra->index)));
auto const keyType =
arrLikePosType(arr->type(), Type::cns(extra->index), true, inst->ctx());
assertx(keyType <= (TInt | TStr));
assertx(key->isA(TInt | TStr));
if (keyType <= TBottom) return gen(env, Jmp, inst->taken());
if (keyType <= TInt) {
if (!key->type().maybe(TInt)) return gen(env, Jmp, inst->taken());
}
if (keyType <= TStr) {
if (!key->type().maybe(TStr)) return gen(env, Jmp, inst->taken());
}
if (key->isA(TInt) && keyType.hasConstVal(TInt)) {
auto const idx = keyType.intVal();
auto const cmp = gen(env, EqInt, key, cns(env, idx));
return gen(env, JmpZero, inst->taken(), cmp);
}
if (key->isA(TStr) && keyType.hasConstVal(TStr)) {
auto const str = keyType.strVal();
auto const cmp = gen(env, EqStrPtr, key, cns(env, str));
return gen(env, JmpZero, inst->taken(), cmp);
}
return mergeBranchDests(env, inst);
}
SSATmp* simplifyCheckDictOffset(State& env, const IRInstruction* inst) {
return simplifyCheckOffsetImpl(env, inst);
}
SSATmp* simplifyCheckKeysetOffset(State& env, const IRInstruction* inst) {
return simplifyCheckOffsetImpl(env, inst);
}
SSATmp* simplifyCheckArrayCOW(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
if (arr->isA(TPersistentArrLike)) {
gen(env, Jmp, inst->taken());
return cns(env, TBottom);
}
return nullptr;
}
SSATmp* simplifyCount(State& env, const IRInstruction* inst) {
auto const val = inst->src(0);
auto const ty = val->type();
if (ty <= TNull) return cns(env, 0);
auto const oneTy = TBool | TInt | TDbl | TStr | TRes;
if (ty <= oneTy) return cns(env, 1);
if (ty <= TVec) return gen(env, CountVec, val);
if (ty <= TDict) return gen(env, CountDict, val);
if (ty <= TKeyset) return gen(env, CountKeyset, val);
if (ty < TObj) {
auto const cls = ty.clsSpec().cls();
if (cls != nullptr && cls->isCollectionClass()) {
return gen(env, CountCollection, val);
}
}
return nullptr;
}
SSATmp* simplifyCountHelper(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal(TArrLike)) return cns(env, src->arrLikeVal()->size());
auto const layout = src->type().arrSpec().layout();
if (auto const num = layout.numElements()) return cns(env, *num);
auto const at = src->type().arrSpec().type();
if (!at) return nullptr;
using A = RepoAuthType::Array;
switch (at->tag()) {
case A::Tag::Tuple:
if (at->emptiness() == A::Empty::No) return cns(env, at->size());
break;
case A::Tag::Packed:
break;
}
return nullptr;
}
#define X(Name) \
SSATmp* simplify##Name(State& env, const IRInstruction* inst) { \
return simplifyCountHelper(env, inst); \
}
X(CountVec)
X(CountDict)
X(CountKeyset)
#undef X
// Simplify generic bespoke getters, either based on the DataType (often we
// can make simplifications for all varrays and vecs) or specific layout.
SSATmp* simplifyBespokeGet(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const key = inst->src(1);
assertx(key->type().subtypeOfAny(TInt, TStr));
if (arr->hasConstVal()) {
auto const ad = arr->arrLikeVal();
if (ad->empty()) return cns(env, TUninit);
if (key->hasConstVal()) {
auto const tv = key->isA(TInt) ? ad->get(key->intVal())
: ad->get(key->strVal());
return cns(env, tv);
}
}
if (arr->isA(TVec)) {
if (key->isA(TStr)) return cns(env, TUninit);
if (arr->type().arrSpec().monotype() &&
inst->extra<BespokeGet>()->state == BespokeGetData::KeyState::Present) {
return gen(env, LdMonotypeVecElem, arr, key);
}
}
return nullptr;
}
SSATmp* simplifyBespokeGetThrow(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const key = inst->src(1);
assertx(key->type().subtypeOfAny(TInt, TStr));
if (arr->hasConstVal()) {
auto const ad = arr->arrLikeVal();
if (ad->empty()) {
gen(env, ThrowOutOfBounds, inst->taken(), arr, key);
return cns(env, TBottom);
}
if (key->hasConstVal()) {
auto const tv = key->isA(TInt) ? ad->get(key->intVal())
: ad->get(key->strVal());
if (tv.is_init()) {
return cns(env, tv);
} else {
gen(env, ThrowOutOfBounds, inst->taken(), arr, key);
return cns(env, TBottom);
}
}
}
if (arr->type().arrSpec().is_struct() && key->isA(TInt)) {
gen(env, ThrowOutOfBounds, inst->taken(), arr, key);
return cns(env, TBottom);
}
return nullptr;
}
SSATmp* simplifyStructDictSlot(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const key = inst->src(1);
assertx(arr->type().arrSpec().is_struct());
auto const& layout = arr->type().arrSpec().layout();
if (key->hasConstVal(TStr) && layout.bespokeLayout()->isConcrete()) {
auto const& slayout = bespoke::StructLayout::As(layout.bespokeLayout());
auto const slot = slayout->keySlot(key->strVal());
if (slot == kInvalidSlot) {
gen(env, Jmp, inst->taken());
return cns(env, TBottom);
}
return cns(env, slot);
}
return nullptr;
}
SSATmp* simplifyBespokeUnset(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const key = inst->src(1);
// If the key is definitely missing, we can skip the remove.
auto const missing = [&]{
if (arr->isA(TVec) && key->isA(TStr)) return true;
auto const type = arrLikeElemType(arr->type(), key->type(), inst->ctx());
return type.first == TBottom;
}();
if (missing) return arr;
if (arr->type().arrSpec().is_struct() && key->hasConstVal(TStr)) {
return gen(env, StructDictUnset, arr, key);
}
return nullptr;
}
SSATmp* simplifyBespokeIterFirstPos(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
if (arr->hasConstVal()) {
auto const pos = arr->type().arrLikeVal()->iter_begin();
return cns(env, pos);
}
if (arr->isA(TVec)) {
return cns(env, 0);
}
return nullptr;
}
SSATmp* simplifyBespokeIterLastPos(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
if (arr->hasConstVal()) {
auto const pos = arr->type().arrLikeVal()->iter_last();
return cns(env, pos);
}
if (arr->isA(TVec)) {
auto const size = gen(env, CountVec, arr);
return gen(env, SubInt, size, cns(env, 1));
}
return nullptr;
}
SSATmp* simplifyBespokeIterEnd(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const spec = arr->type().arrSpec();
if (arr->hasConstVal()) {
auto const pos = arr->type().arrLikeVal()->iter_end();
return cns(env, pos);
}
if (arr->isA(TVec)) {
return gen(env, CountVec, arr);
}
if (arr->isA(TDict) && spec.monotype()) {
auto const size = gen(env, CountDict, arr);
auto const tombstones = gen(env, LdMonotypeDictTombstones, arr);
return gen(env, AddInt, size, tombstones);
}
if (arr->isA(TDict) && spec.is_struct()) {
return gen(env, CountDict, arr);
}
return nullptr;
}
SSATmp* simplifyBespokeIterGetKey(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const pos = inst->src(1);
if (arr->hasConstVal() && pos->hasConstVal()) {
auto const idx = pos->intVal();
auto const ad = arr->type().arrLikeVal();
if (idx < 0 || idx >= ad->size()) return cns(env, TBottom);
auto const key = arr->type().arrLikeVal()->nvGetKey(pos->intVal());
return cns(env, key);
}
if (arr->isA(TVec)) return pos;
if (arr->isA(TDict) && arr->type().arrSpec().monotype()) {
return gen(env, LdMonotypeDictKey, arr, pos);
}
return nullptr;
}
SSATmp* simplifyBespokeIterGetVal(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const pos = inst->src(1);
if (arr->hasConstVal() && pos->hasConstVal()) {
auto const idx = pos->intVal();
auto const ad = arr->type().arrLikeVal();
if (idx < 0 || idx >= ad->size()) return cns(env, TBottom);
auto const val = arr->type().arrLikeVal()->nvGetVal(pos->intVal());
return cns(env, val);
}
if (arr->isA(TVec)) {
auto const data = BespokeGetData { BespokeGetData::KeyState::Present };
return gen(env, BespokeGet, data, arr, pos);
}
if (arr->isA(TDict) && arr->type().arrSpec().monotype()) {
return gen(env, LdMonotypeDictVal, arr, pos);
}
return nullptr;
}
// Simplify layout-specific bespoke helpers.
SSATmp* simplifyLdMonotypeDictTombstones(
State& env, const IRInstruction* inst) {
auto const type = inst->src(0)->type();
if (type.hasConstVal()) {
auto const arr = type.arrLikeVal();
return cns(env, arr->iter_end() - arr->size());
}
return nullptr;
}
SSATmp* simplifyLdMonotypeDictKey(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const pos = inst->src(1);
if (arr->hasConstVal() && pos->hasConstVal()) {
auto const tv = arr->arrLikeVal()->nvGetKey(pos->intVal());
return tv.is_init() ? cns(env, tv) : nullptr;
}
return nullptr;
}
SSATmp* simplifyLdMonotypeDictVal(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const pos = inst->src(1);
if (arr->hasConstVal() && pos->hasConstVal()) {
auto const tv = arr->arrLikeVal()->nvGetVal(pos->intVal());
return tv.is_init() ? cns(env, tv) : nullptr;
}
return nullptr;
}
SSATmp* simplifyLdMonotypeVecElem(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
auto const key = inst->src(1);
if (arr->hasConstVal() && key->hasConstVal()) {
auto const tv = arr->arrLikeVal()->get(key->intVal());
return tv.is_init() ? cns(env, tv) : nullptr;
}
return nullptr;
}
SSATmp* simplifyLdClsName(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
return src->hasConstVal(TCls) ? cns(env, src->clsVal()->name()) : nullptr;
}
SSATmp* simplifyLdLazyCls(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
return src->hasConstVal(TCls) ?
cns(env, LazyClassData::create(src->clsVal()->name())) : nullptr;
}
SSATmp* simplifyLdLazyClsName(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
return src->hasConstVal(TLazyCls) ?
cns(env, src->lclsVal().name()) : nullptr;
}
SSATmp* simplifyLookupClsRDS(State& env, const IRInstruction* inst) {
auto const str = inst->src(0);
if (str->inst()->is(LdClsName, LdLazyCls)) {
return str->inst()->src(0);
}
if (str->hasConstVal()) {
auto const sval = str->strVal();
auto const result = Class::lookupUniqueInContext(sval, nullptr, nullptr);
if (result) return cns(env, result);
}
return nullptr;
}
SSATmp* simplifyLookupSPropSlot(State& env, const IRInstruction* inst) {
auto const cls = inst->src(0);
auto const name = inst->src(1);
if (!cls->hasConstVal(TCls) || !name->hasConstVal(TStr)) return nullptr;
return cns(env, cls->clsVal()->lookupSProp(name->strVal()));
}
SSATmp* simplifyLdStrLen(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
return src->hasConstVal(TStr) ? cns(env, src->strVal()->size()) : nullptr;
}
SSATmp* simplifyLdVecElem(State& env, const IRInstruction* inst) {
auto const src0 = inst->src(0);
auto const src1 = inst->src(1);
if (src0->hasConstVal() && src1->hasConstVal(TInt)) {
auto const arr = src0->arrLikeVal();
auto const idx = src1->intVal();
assertx(arr->isVanillaVec());
auto const tv = VanillaVec::NvGetInt(arr, idx);
return tv.is_init() ? cns(env, tv) : nullptr;
}
return nullptr;
}
template <class F>
SSATmp* simplifyByClass(State& /*env*/, const SSATmp* src, F f) {
if (!src->isA(TObj)) return nullptr;
if (auto const spec = src->type().clsSpec()) {
return f(spec.cls(), spec.exact());
}
return nullptr;
}
SSATmp* simplifyCallBuiltin(State& env, const IRInstruction* inst) {
if (inst->numSrcs() != 3) return nullptr;
auto const thiz = inst->src(2);
return simplifyByClass(
env, thiz,
[&](const Class* cls, bool) -> SSATmp* {
auto const callee = inst->extra<CallBuiltin>()->callee;
if (cls->isCollectionClass()) {
if (callee->name()->isame(s_isEmpty.get())) {
FTRACE(3, "simplifying collection: {}\n", callee->name()->data());
return gen(env, ColIsEmpty, thiz);
}
if (callee->name()->isame(s_count.get())) {
FTRACE(3, "simplifying collection: {}\n", callee->name()->data());
return gen(env, CountCollection, thiz);
}
return nullptr;
}
if (cls->classof(c_Awaitable::classof())) {
auto const genState = [&] (Opcode op, int64_t whstate) -> SSATmp* {
// these methods all spring from the base class
assertx(callee->cls()->name()->isame(s_Awaitable.get()));
auto const state = gen(env, LdWHState, thiz);
return gen(env, op, state, cns(env, whstate));
};
auto const methName = callee->name();
if (methName->isame(s_isFinished.get())) {
return genState(LteInt, int64_t{c_Awaitable::STATE_FAILED});
}
if (methName->isame(s_isSucceeded.get())) {
return genState(EqInt, int64_t{c_Awaitable::STATE_SUCCEEDED});
}
if (methName->isame(s_isFailed.get())) {
return genState(EqInt, int64_t{c_Awaitable::STATE_FAILED});
}
}
return nullptr;
});
}
SSATmp* simplifyIsWaitHandle(State& env, const IRInstruction* inst) {
return simplifyByClass(
env, inst->src(0),
[&](const Class* cls, bool) -> SSATmp* {
if (cls->classof(c_Awaitable::classof())) return cns(env, true);
if (!isInterface(cls) &&
!c_Awaitable::classof()->classof(cls)) {
return cns(env, false);
}
return nullptr;
});
}
SSATmp* simplifyLdWHState(State& env, const IRInstruction* inst) {
auto const wh = canonical(inst->src(0));
if (wh->inst()->is(CreateSSWH)) {
return cns(env, int64_t{c_Awaitable::STATE_SUCCEEDED});
}
return nullptr;
}
SSATmp* simplifyLdWHResult(State& env, const IRInstruction* inst) {
auto const wh = canonical(inst->src(0));
if (wh->inst()->is(CreateSSWH)) {
return wh->inst()->src(0);
}
return nullptr;
}
SSATmp* simplifyLdWHNotDone(State& env, const IRInstruction* inst) {
return simplifyByClass(
env, inst->src(0),
[&](const Class* cls, bool) -> SSATmp* {
if (cls->classof(c_StaticWaitHandle::classof())) return cns(env, 0);
return nullptr;
});
}
SSATmp* simplifyIsCol(State& env, const IRInstruction* inst) {
return simplifyByClass(
env, inst->src(0),
[&](const Class* cls, bool) -> SSATmp* {
if (cls->isCollectionClass()) return cns(env, true);
if (!isInterface(cls)) return cns(env, false);
return nullptr;
});
}
SSATmp* simplifyIsLegacyArrLike(State& env, const IRInstruction* inst) {
auto const arr = inst->src(0);
if (arr->hasConstVal()) {
return cns(env, arr->arrLikeVal()->isLegacyArray());
}
return nullptr;
}
SSATmp* simplifyHasToString(State& env, const IRInstruction* inst) {
return simplifyByClass(
env, inst->src(0),
[&](const Class* cls, bool exact) -> SSATmp* {
if (cls->getToString() != nullptr) return cns(env, true);
if (exact) return cns(env, false);
return nullptr;
});
}
SSATmp* simplifyHasReifiedGenerics(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal()) {
return cns(env, src->funcVal()->hasReifiedGenerics());
}
return nullptr;
}
SSATmp* simplifyChrInt(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal(TInt)) {
auto const str = makeStaticString(char(src->intVal() & 255));
return cns(env, str);
}
return nullptr;
}
SSATmp* simplifyOrdStr(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (src->hasConstVal(TStr)) {
// a static string is passed in, resolve with a constant.
unsigned char first = src->strVal()->data()[0];
return cns(env, int64_t{first});
}
if (src->inst()->is(ChrInt)) {
return gen(env, AndInt, src->inst()->src(0), cns(env, 255));
}
return nullptr;
}
SSATmp* simplifyJmpSwitchDest(State& env, const IRInstruction* inst) {
auto const index = inst->src(0);
if (!index->hasConstVal(TInt)) return nullptr;
auto const indexVal = index->intVal();
auto const sp = inst->src(1);
auto const fp = inst->src(2);
auto const& extra = *inst->extra<JmpSwitchDest>();
if (indexVal < 0 || indexVal >= extra.cases) {
// Instruction is unreachable.
return gen(env, Unreachable, ASSERT_REASON);
}
auto const newExtra = ReqBindJmpData {
extra.targets[indexVal],
extra.spOffBCFromStackBase,
extra.spOffBCFromIRSP
};
return gen(env, ReqBindJmp, newExtra, sp, fp);
}
SSATmp* simplifyCheckRange(State& env, const IRInstruction* inst) {
auto const val = inst->src(0);
auto const limit = inst->src(1);
// CheckRange returns (0 <= val < limit).
if (val && val->hasConstVal(TInt)) {
if (val->intVal() < 0) return cns(env, false);
if (limit && limit->hasConstVal(TInt)) {
return cns(env, val->intVal() < limit->intVal());
}
}
if (limit && limit->hasConstVal(TInt) && limit->intVal() <= 0) {
return cns(env, false);
}
return nullptr;
}
SSATmp* simplifyGetMemoKeyScalar(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
// Note: this all uses the fully generic memo key scheme. If we used a more
// specific scheme, then the GetMemoKey op wouldn't have been emitted.
if (src->hasConstVal()) {
try {
ThrowAllErrorsSetter taes;
auto const key =
HHVM_FN(serialize_memoize_param)(*src->variantVal().asTypedValue());
SCOPE_EXIT { tvDecRefGen(key); };
assertx(tvIsPlausible(key));
if (tvIsString(&key)) {
return cns(env, makeStaticString(key.m_data.pstr));
} else {
assertx(key.m_type == KindOfInt64);
return cns(env, key.m_data.num);
}
} catch (...) {
}
}
if (src->isA(TInt)) return src;
if (src->isA(TBool)) {
return gen(
env,
Select,
src,
cns(env, s_trueMemoKey.get()),
cns(env, s_falseMemoKey.get())
);
}
if (src->isA(TNull)) return cns(env, s_nullMemoKey.get());
return nullptr;
}
SSATmp* simplifyGetMemoKey(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (auto ret = simplifyGetMemoKeyScalar(env, inst)) return ret;
if (src->isA(TUncounted|TStr) &&
!RuntimeOption::EvalClassMemoNotices) {
return gen(env, GetMemoKeyScalar, src);
}
return nullptr;
}
SSATmp* simplifyStrictlyIntegerConv(State& env, const IRInstruction* inst) {
auto const src = inst->src(0);
if (!src->hasConstVal()) return nullptr;
int64_t n;
if (src->strVal()->isStrictlyInteger(n)) return cns(env, n);
gen(env, IncRef, src);
return src;
}
SSATmp* simplifyRaiseErrorOnInvalidIsAsExpressionType(
State& env,
const IRInstruction* inst
) {
auto const ts = inst->src(0);
if (!ts->hasConstVal(TDict)) {
return nullptr;
}
auto const tsVal = ts->arrLikeVal();
if (errorOnIsAsExpressionInvalidTypes(ArrNR(tsVal), true)) {
return nullptr;
}
return ts;
}
SSATmp* simplifyCheckClsMethFunc(State& env, const IRInstruction* inst) {
if (!inst->src(0)->hasConstVal(TFunc)) return nullptr;
auto const func = inst->src(0)->funcVal();
if (func->isStaticInPrologue() && !func->isAbstract()) {
return gen(env, Nop);
}
return nullptr;
}
SSATmp* simplifyNewClsMeth(State& env, const IRInstruction* inst) {
auto const cls = inst->src(0);
auto const func = inst->src(1);
if (!cls->hasConstVal() || !func->hasConstVal()) return nullptr;
auto const clsmeth = ClsMethDataRef::create(
const_cast<Class*>(cls->clsVal()),
const_cast<Func*>(func->funcVal()));
return cns(env, clsmeth);
}
SSATmp* simplifyLdClsFromClsMeth(State& env, const IRInstruction* inst) {
auto const clsmeth = inst->src(0);
if (!clsmeth->hasConstVal()) return nullptr;
return cns(env, clsmeth->clsmethVal()->getCls());
}
SSATmp* simplifyLdFuncFromClsMeth(State& env, const IRInstruction* inst) {
auto const clsmeth = inst->src(0);
if (!clsmeth->hasConstVal()) return nullptr;
return cns(env, clsmeth->clsmethVal()->getFunc());
}
SSATmp* simplifyStructDictTypeBoundCheck(State& env,
const IRInstruction* inst) {
auto const val = inst->src(0);
auto const arr = inst->src(1);
auto const slot = inst->src(2);
auto const& layout = arr->type().arrSpec().layout();
assertx(layout.is_struct());
if (!layout.bespokeLayout()->isConcrete()) return nullptr;
auto const slayout = bespoke::StructLayout::As(layout.bespokeLayout());
if (slot->hasConstVal()) {
if (slot->intVal() >= slayout->numFields()) return cns(env, TBottom);
return gen(
env,
CheckType,
slayout->getTypeBound(slot->intVal()),
inst->taken(),
val
);
} else {
auto const type = slayout->getUnionTypeBound();
if (!type.isKnownDataType()) return nullptr;
return gen(
env,
CheckType,
type,
inst->taken(),
val
);
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////
SSATmp* simplifyWork(State& env, const IRInstruction* inst) {
env.insts.push(inst);
SCOPE_EXIT {
assertx(env.insts.top() == inst);
env.insts.pop();
};
auto res = [&] () -> SSATmp* {
switch (inst->op()) {
#define X(x) case x: return simplify##x(env, inst);
X(Shl)
X(Shr)
X(Lshr)
X(AbsDbl)
X(AssertNonNull)
X(CallBuiltin)
X(Ceil)
X(CheckInit)
X(CheckInitMem)
X(CheckRDSInitialized)
X(MarkRDSInitialized)
X(MarkRDSAccess)
X(CheckLoc)
X(CheckMBase)
X(CheckStk)
X(CheckType)
X(CheckTypeMem)
X(AssertType)
X(CheckNonNull)
X(CheckVecBounds)
X(ReserveVecNewElem)
X(ConcatStrStr)
X(ConcatStr3)
X(ConcatStr4)
X(ConcatIntStr)
X(ConcatStrInt)
X(ConvBoolToDbl)
X(ConvBoolToInt)
X(ConvTVToBool)
X(ConvTVToDbl)
X(ConvTVToInt)
X(ConvTVToStr)
X(ConvDblToBool)
X(ConvDblToInt)
X(ConvDblToStr)
X(ConvIntToBool)
X(ConvIntToDbl)
X(ConvIntToStr)
X(ConvObjToBool)
X(ConvStrToBool)
X(ConvStrToDbl)
X(ConvStrToInt)
X(ConvArrLikeToVec)
X(ConvArrLikeToDict)
X(ConvArrLikeToKeyset)
X(DblAsBits)
X(Count)
X(CountVec)
X(CountDict)
X(CountKeyset)
X(DecRef)
X(DecRefNZ)
X(DefLabel)
X(DivDbl)
X(DivInt)
X(EqFunc)
X(ExtendsClass)
X(InstanceOfBitmask)
X(NInstanceOfBitmask)
X(Floor)
X(IncRef)
X(InitObjProps)
X(InitObjMemoSlots)
X(InstanceOf)
X(InstanceOfIface)
X(InstanceOfIfaceVtable)
X(IsNType)
X(IsType)
X(IsLegacyArrLike)
X(IsWaitHandle)
X(IsCol)
X(HasToString)
X(HasReifiedGenerics)
X(LdCls)
X(LdClsName)
X(LdLazyCls)
X(LdLazyClsName)
X(LdWHResult)
X(LdWHState)
X(LdWHNotDone)
X(LookupClsRDS)
X(LookupSPropSlot)
X(LdClsMethod)
X(LdStrLen)
X(BespokeGet)
X(BespokeUnset)
X(BespokeGetThrow)
X(StructDictSlot)
X(BespokeIterFirstPos)
X(BespokeIterLastPos)
X(BespokeIterEnd)
X(BespokeIterGetKey)
X(BespokeIterGetVal)
X(LdMonotypeDictTombstones)
X(LdMonotypeDictKey)
X(LdMonotypeDictVal)
X(LdMonotypeVecElem)
X(LdVecElem)
X(MethodExists)
X(LdFuncInOutBits)
X(LdFuncNumParams)
X(FuncHasAttr)
X(ClassHasAttr)
X(LdFuncRequiredCoeffects)
X(LookupClsCtxCns)
X(LdObjClass)
X(LdObjInvoke)
X(Mov)
X(Jmp)
X(JmpZero)
X(JmpNZero)
X(Select)
X(OrInt)
X(AddInt)
X(SubInt)
X(MulInt)
X(AddDbl)
X(SubDbl)
X(MulDbl)
X(Mod)
X(AndInt)
X(XorInt)
X(XorBool)
X(AddIntO)
X(SubIntO)
X(MulIntO)
X(GtBool)
X(GteBool)
X(LtBool)
X(LteBool)
X(EqBool)
X(NeqBool)
X(CmpBool)
X(GtInt)
X(GteInt)
X(LtInt)
X(LteInt)
X(EqInt)
X(NeqInt)
X(CmpInt)
X(GtDbl)
X(GteDbl)
X(LtDbl)
X(LteDbl)
X(EqDbl)
X(NeqDbl)
X(CmpDbl)
X(GtStr)
X(GteStr)
X(LtStr)
X(LteStr)
X(EqStr)
X(NeqStr)
X(SameStr)
X(NSameStr)
X(CmpStr)
X(GtObj)
X(GteObj)
X(LtObj)
X(LteObj)
X(EqObj)
X(NeqObj)
X(SameObj)
X(NSameObj)
X(GtArrLike)
X(GteArrLike)
X(LtArrLike)
X(LteArrLike)
X(EqArrLike)
X(NeqArrLike)
X(SameArrLike)
X(NSameArrLike)
X(GtRes)
X(GteRes)
X(LtRes)
X(LteRes)
X(EqRes)
X(NeqRes)
X(CmpRes)
X(EqCls)
X(EqLazyCls)
X(EqStrPtr)
X(EqArrayDataPtr)
X(DictGet)
X(DictGetQuiet)
X(DictGetK)
X(KeysetGet)
X(KeysetGetQuiet)
X(KeysetGetK)
X(GetDictPtrIter)
X(CheckDictKeys)
X(CheckDictOffset)
X(CheckKeysetOffset)
X(CheckArrayCOW)
X(CheckMissingKeyInArrLike)
X(DictIsset)
X(KeysetIsset)
X(DictIdx)
X(AKExistsDict)
X(KeysetIdx)
X(AKExistsKeyset)
X(OrdStr)
X(ChrInt)
X(JmpSwitchDest)
X(CheckRange)
X(GetMemoKey)
X(GetMemoKeyScalar)
X(StrictlyIntegerConv)
X(RaiseErrorOnInvalidIsAsExpressionType)
X(LdFrameCls)
X(NewClsMeth)
X(CheckClsMethFunc)
X(LdClsFromClsMeth)
X(LdFuncFromClsMeth)
X(LdResolvedTypeCns)
X(LdResolvedTypeCnsNoCheck)
X(LdResolvedTypeCnsClsName)
X(CGetPropQ)
X(StructDictTypeBoundCheck)
#undef X
default: break;
}
return nullptr;
}();
// If the new instruction list is empty, we are either returning a known
// value (res != nullptr), or leaving the instruction unchanged (res ==
// nullptr). We should mark the instruction as unreachable if the known value
// is Bottom. Cases where the new final instruction is a block-ending
// instruction are handled by consumers.
if (inst->hasDst() && !inst->naryDst() && env.newInsts.empty()) {
if (res && res->type() == TBottom) {
// The instruction has been simplified away, leaving only a bottom
// constant. Mark it as unreachable.
gen(env, Unreachable, ASSERT_REASON);
} else if (!res && outputType(inst) == TBottom && !inst->isBlockEnd()) {
// The instruction is passing through unchanged, but is known to have a
// bottom result. Replace it with the proper unreachable annotations.
res = cns(env, TBottom);
gen(env, Unreachable, ASSERT_REASON);
}
}
return res;
}
Block* unreachableBlock(IRUnit& unit, BCContext ctx) {
auto const unreachableBlock = unit.defBlock(1, Block::Hint::Unused);
makeInstruction(
[&] (IRInstruction* inst) -> void {
unreachableBlock->push_back(unit.clone(inst));
},
Unreachable,
ctx,
ASSERT_REASON
);
return unreachableBlock;
}
//////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////
SimplifyResult simplify(IRUnit& unit, const IRInstruction* origInst) {
auto env = State { unit };
auto const newDst = simplifyWork(env, origInst);
assertx(validate(env, newDst, origInst));
return SimplifyResult { std::move(env.newInsts), newDst };
}
void simplifyInPlace(IRUnit& unit, IRInstruction* origInst) {
assertx(!origInst->isTransient());
copyProp(origInst);
constProp(unit, origInst);
retypeDests(origInst, &unit);
auto res = simplify(unit, origInst);
// No simplification occurred; nothing to do.
if (res.instrs.empty() && !res.dst) {
// If we have narrowed the result of a block end instruction to a bottom
// type, its next block must be unreachable.
if (origInst->isNextEdgeUnreachable() && origInst->isBlockEnd()) {
origInst->setNext(unreachableBlock(unit, origInst->bcctx()));
}
return;
}
FTRACE(1, "simplifying: {}\n", origInst->toString());
if (origInst->isBlockEnd()) {
auto const next = origInst->block()->next();
if (res.instrs.empty() || !res.instrs.back()->isBlockEnd()) {
// Our block-end instruction was eliminated (most likely a Jmp* converted
// to a Nop). Replace it with a Jmp to the next block.
res.instrs.push_back(unit.gen(Jmp, origInst->bcctx(), next));
}
auto const last = res.instrs.back();
assertx(last->isBlockEnd());
if (!last->isTerminal() && !last->next()) {
// We converted the block-end instruction to a different one. Set its
// next block appropriately.
last->setNext(next);
}
}
size_t out_size = 0;
bool need_mov = res.dst;
IRInstruction* last = nullptr;
for (auto const inst : res.instrs) {
if (inst->is(Nop)) continue;
++out_size;
last = inst;
if (res.dst && res.dst == inst->dst()) {
// One of the new instructions produced the new dst. Since we're going
// to drop `origInst', just use origInst->dst() instead.
inst->setDst(origInst->dst());
inst->dst()->setInstruction(inst);
need_mov = false;
}
}
auto const block = origInst->block();
auto pos = ++block->iteratorTo(origInst);
if (last != nullptr && last->isTerminal()) {
// Delete remaining instructions in the block if a terminal is created.
// This can happen, e.g., 'Unreachable' may be created when the block is
// unreachable.
while (pos != block->end()) pos = block->erase(pos);
}
// If the last instruction is block-ending and produces a Bottom result, its
// next block should be unreachable.
if (last != nullptr && last->isNextEdgeUnreachable() && last->isBlockEnd()) {
last->setNext(unreachableBlock(unit, last->bcctx()));
}
if (need_mov) {
/*
* In `killed_edge_defining' we have the case that an instruction defining
* a temp on an edge (like CheckType) determined it can never define that
* tmp. In this situation we just Nop out the instruction and leave the
* old tmp dangling. The reason this is ok is that one of the following
* two things are happening:
*
* o The old next() block is becoming unreachable. It's ok not to make
* a new definition of this tmp, because the code running simplify is
* going to have to track unreachable blocks and avoid looking at
* them. It will also have to remove unreachable blocks when it's
* finished to maintain IR invariants (e.g. through DCE::Minimal),
* which will mean the uses of the no-longer-defined tmp will go away.
*
* o The old next() block is still reachable (e.g. if we're removing a
* CheckType because it had next == taken). But in this case, the
* next() edge must have been a critical edge, and therefore nothing
* could have any use of the old destination of the CheckType, or the
* program would already not have been in SSA, because it was only
* defined in blocks dominated by the next edge.
*/
auto const killed_edge_defining = res.dst->type() <= TBottom &&
origInst->isBlockEnd();
if (killed_edge_defining) {
origInst->convertToNop();
} else {
unit.replace(origInst, Mov, res.dst);
// Force the existing dst type to match that of `res.dst'.
origInst->dst()->setType(res.dst->type());
}
} else {
if (out_size == 1) {
assertx(origInst->dst() == last->dst());
FTRACE(1, " {}\n", last->toString());
// We only have a single instruction, so just become it.
origInst->become(unit, last);
// Make sure to reset our dst's inst pointer, if we have one. (It may
// have been set to `last'.
if (origInst->dst()) {
origInst->dst()->setInstruction(origInst);
}
// And we also need to kill `last', to update preds.
last->convertToNop();
return;
}
origInst->convertToNop();
}
FTRACE(1, " {}\n", origInst->toString());
for (auto const inst : res.instrs) {
if (inst->is(Nop)) continue;
block->insert(pos, inst);
FTRACE(1, " {}\n", inst->toString());
}
}
////////////////////////////////////////////////////////////////////////////////
void simplifyPass(IRUnit& unit) {
Timer timer{Timer::optimize_simplify, unit.logEntry().get_pointer()};
auto reachable = boost::dynamic_bitset<>(unit.numBlocks());
{
jit::stack<Block*> worklist;
worklist.push(unit.entry());
while (!worklist.empty()) {
auto const block = worklist.top();
worklist.pop();
if (reachable.test(block->id())) continue;
reachable.set(block->id());
if (!block->isUnreachable()) {
for (auto& inst : *block) simplifyInPlace(unit, &inst);
}
if (auto const b = block->next()) {
if (!b->isUnreachable()) worklist.push(b);
}
if (auto const b = block->taken()) {
if (!b->isUnreachable()) worklist.push(b);
}
}
}
auto const markUnreachable = [&] (Block* block) {
// Any code that's postdominated by Unreachable is also unreachable, so
// erase everything else in this block.
if (block->back().hasDst()) block->back().setDst(nullptr);
unit.replace(&block->back(), Unreachable, ASSERT_REASON);
for (auto it = block->skipHeader(), end = block->backIter(); it != end;) {
auto toErase = it;
++it;
block->erase(toErase);
}
};
// We may have introduced new unreachable blocks.
if (unit.numBlocks() > reachable.size()) reachable.resize(unit.numBlocks());
auto const poBlocks = poSortCfg(unit);
// Collapse unreachable blocks
std::deque<Block*> workQ(poBlocks.cbegin(), poBlocks.cend());
while (!workQ.empty()) {
auto const block = workQ.front();
workQ.pop_front();
if (!reachable.test(block->id())) continue;
auto& inst = block->back();
if (inst.hasEdges() &&
(!inst.next() || !reachable.test(inst.next()->id())) &&
(!inst.taken() || !reachable.test(inst.taken()->id()))) {
auto const& preds = block->preds();
for (auto const& pred : preds) workQ.push_back(pred.from());
markUnreachable(block);
}
}
}
////////////////////////////////////////////////////////////////////////////////
}