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); } } } //////////////////////////////////////////////////////////////////////////////// }