d0e49bb0f0
OpMod wasn't doing any constant folding or optimization, so this might give it a little boost.
1805 linhas
59 KiB
C++
1805 linhas
59 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 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/simplifier.h"
|
|
|
|
#include <sstream>
|
|
#include <type_traits>
|
|
|
|
#include "hphp/runtime/base/memory/smart_containers.h"
|
|
#include "hphp/runtime/base/type_conversions.h"
|
|
#include "hphp/runtime/vm/jit/tracebuilder.h"
|
|
#include "hphp/runtime/vm/runtime.h"
|
|
|
|
namespace HPHP {
|
|
namespace JIT {
|
|
|
|
TRACE_SET_MOD(hhir);
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
StackValueInfo getStackValue(SSATmp* sp, uint32_t index) {
|
|
assert(sp->isA(Type::StkPtr));
|
|
IRInstruction* inst = sp->inst();
|
|
|
|
switch (inst->op()) {
|
|
case DefSP:
|
|
return StackValueInfo();
|
|
|
|
case ReDefGeneratorSP:
|
|
case StashGeneratorSP:
|
|
return getStackValue(inst->src(0), index);
|
|
|
|
case ReDefSP:
|
|
return getStackValue(inst->src(1), index);
|
|
|
|
case ExceptionBarrier:
|
|
return getStackValue(inst->src(0), index);
|
|
|
|
case SideExitGuardStk:
|
|
always_assert(0 && "simplifier is not tested for running after jumpopts");
|
|
|
|
case AssertStk:
|
|
// fallthrough
|
|
case CastStk:
|
|
// fallthrough
|
|
case CoerceStk:
|
|
// fallthrough
|
|
case CheckStk:
|
|
// fallthrough
|
|
case GuardStk:
|
|
// We don't have a value, but we may know the type due to guarding
|
|
// on it.
|
|
if (inst->extra<StackOffset>()->offset == index) {
|
|
return StackValueInfo { inst->typeParam() };
|
|
}
|
|
return getStackValue(inst->src(0), index);
|
|
|
|
case AssertStkVal:
|
|
if (inst->extra<StackOffset>()->offset == index) {
|
|
return StackValueInfo { inst->src(1) };
|
|
}
|
|
return getStackValue(inst->src(0), index);
|
|
|
|
case CallArray: {
|
|
if (index == 0) {
|
|
// return value from call
|
|
return StackValueInfo { nullptr };
|
|
}
|
|
auto info =
|
|
getStackValue(inst->src(0),
|
|
// Pushes a return value, pops an ActRec and args Array
|
|
index -
|
|
(1 /* pushed */ - kNumActRecCells + 1 /* popped */));
|
|
info.spansCall = true;
|
|
return info;
|
|
}
|
|
|
|
case Call: {
|
|
if (index == 0) {
|
|
// return value from call
|
|
return StackValueInfo { nullptr };
|
|
}
|
|
auto info =
|
|
getStackValue(inst->src(0),
|
|
index -
|
|
(1 /* pushed */ - kNumActRecCells /* popped */));
|
|
info.spansCall = true;
|
|
return info;
|
|
}
|
|
|
|
case SpillStack: {
|
|
int64_t numPushed = 0;
|
|
int32_t numSpillSrcs = inst->numSrcs() - 2;
|
|
|
|
for (int i = 0; i < numSpillSrcs; ++i) {
|
|
SSATmp* tmp = inst->src(i + 2);
|
|
if (index == numPushed) {
|
|
if (tmp->inst()->op() == IncRef) {
|
|
tmp = tmp->inst()->src(0);
|
|
}
|
|
if (!tmp->type().equals(Type::None)) {
|
|
return StackValueInfo { tmp };
|
|
}
|
|
}
|
|
++numPushed;
|
|
}
|
|
|
|
// This is not one of the values pushed onto the stack by this
|
|
// spillstack instruction, so continue searching.
|
|
SSATmp* prevSp = inst->src(0);
|
|
int64_t numPopped = inst->src(1)->getValInt();
|
|
return getStackValue(prevSp,
|
|
// pop values pushed by spillstack
|
|
index - (numPushed - numPopped));
|
|
}
|
|
|
|
case InterpOne: {
|
|
SSATmp* prevSp = inst->src(1);
|
|
auto const& extra = *inst->extra<InterpOne>();
|
|
int64_t spAdjustment = extra.cellsPopped - extra.cellsPushed;
|
|
Type resultType = inst->typeParam();
|
|
if (index == 0 && !resultType.equals(Type::None)) {
|
|
return StackValueInfo { resultType };
|
|
}
|
|
|
|
// If the index we're looking for is a cell pushed by the InterpOne (other
|
|
// than top of stack), we know nothing about its type.
|
|
if (index < extra.cellsPushed) return StackValueInfo{ nullptr };
|
|
|
|
return getStackValue(prevSp, index + spAdjustment);
|
|
}
|
|
|
|
case SpillFrame:
|
|
case CufIterSpillFrame:
|
|
return getStackValue(inst->src(0),
|
|
// pushes an ActRec
|
|
index - kNumActRecCells);
|
|
|
|
default:
|
|
{
|
|
// Assume it's a vector instruction. This will assert in
|
|
// vectorBaseIdx if not.
|
|
auto const base = inst->src(vectorBaseIdx(inst));
|
|
assert(base->inst()->op() == LdStackAddr);
|
|
if (base->inst()->extra<LdStackAddr>()->offset == index) {
|
|
VectorEffects ve(inst);
|
|
assert(ve.baseTypeChanged || ve.baseValChanged);
|
|
return StackValueInfo { ve.baseType.derefIfPtr() };
|
|
}
|
|
return getStackValue(base->inst()->src(0), index);
|
|
}
|
|
}
|
|
|
|
// Should not get here!
|
|
not_reached();
|
|
}
|
|
|
|
smart::vector<SSATmp*> collectStackValues(SSATmp* sp, uint32_t stackDepth) {
|
|
smart::vector<SSATmp*> ret;
|
|
ret.reserve(stackDepth);
|
|
for (uint32_t i = 0; i < stackDepth; ++i) {
|
|
auto const value = getStackValue(sp, i).value;
|
|
if (value) {
|
|
ret.push_back(value);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
static void copyPropSrc(IRInstruction* inst, int index) {
|
|
auto tmp = inst->src(index);
|
|
auto srcInst = tmp->inst();
|
|
|
|
switch (srcInst->op()) {
|
|
case Mov:
|
|
inst->setSrc(index, srcInst->src(0));
|
|
break;
|
|
|
|
case IncRef:
|
|
if (!isRefCounted(srcInst->src(0))) {
|
|
srcInst->setOpcode(Mov);
|
|
inst->setSrc(index, srcInst->src(0));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
void copyProp(IRInstruction* inst) {
|
|
for (uint32_t i = 0; i < inst->numSrcs(); i++) {
|
|
copyPropSrc(inst, i);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Checks if property propName of class clsTmp, called from context class ctx,
|
|
* can be accessed via the static property cache.
|
|
* Right now, this returns true for two cases:
|
|
* (a) the property is accessed from within the class containing it
|
|
* (b) the property belongs to a persistent class and it's accessible from ctx
|
|
*/
|
|
bool canUseSPropCache(SSATmp* clsTmp,
|
|
const StringData* propName,
|
|
const Class* ctx) {
|
|
if (propName == nullptr) return false;
|
|
|
|
const StringData* clsName = findClassName(clsTmp);
|
|
if (ctx) {
|
|
const StringData* ctxName = ctx->preClass()->name();;
|
|
if (clsName && ctxName && clsName->isame(ctxName)) return true;
|
|
}
|
|
|
|
if (!clsTmp->isConst()) return false;
|
|
|
|
const Class* cls = clsTmp->getValClass();
|
|
|
|
if (!Transl::TargetCache::classIsPersistent(cls)) return false;
|
|
|
|
// If the class requires initialization, it might not have been
|
|
// initialized yet. getSProp() below will trigger initialization,
|
|
// but that's only valid to do earlier if it doesn't require any
|
|
// property initializer ([sp]init methods).
|
|
if (cls->hasInitMethods()) return false;
|
|
|
|
bool visible, accessible;
|
|
cls->getSProp(const_cast<Class*>(ctx), propName, visible, accessible);
|
|
|
|
return visible && accessible;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
template<class... Args> SSATmp* Simplifier::cns(Args&&... cns) {
|
|
return m_tb->cns(std::forward<Args>(cns)...);
|
|
}
|
|
|
|
template<class... Args> SSATmp* Simplifier::gen(Opcode op, Args&&... args) {
|
|
assert(!m_insts.empty());
|
|
return m_tb->gen(op, m_insts.top()->marker(), std::forward<Args>(args)...);
|
|
}
|
|
|
|
template<class... Args> SSATmp* Simplifier::gen(Opcode op, BCMarker marker,
|
|
Args&&... args) {
|
|
return m_tb->gen(op, marker, std::forward<Args>(args)...);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
SSATmp* Simplifier::simplify(IRInstruction* inst) {
|
|
m_insts.push(inst);
|
|
SCOPE_EXIT {
|
|
assert(m_insts.top() == inst);
|
|
m_insts.pop();
|
|
};
|
|
|
|
SSATmp* src1 = inst->src(0);
|
|
SSATmp* src2 = inst->src(1);
|
|
|
|
Opcode opc = inst->op();
|
|
switch (opc) {
|
|
case OpAdd: return simplifyAdd(src1, src2);
|
|
case OpSub: return simplifySub(src1, src2);
|
|
case OpMul: return simplifyMul(src1, src2);
|
|
case OpMod: return simplifyMod(src1, src2);
|
|
case OpBitAnd: return simplifyBitAnd(src1, src2);
|
|
case OpBitOr: return simplifyBitOr(src1, src2);
|
|
case OpBitXor: return simplifyBitXor(src1, src2);
|
|
case OpLogicXor: return simplifyLogicXor(src1, src2);
|
|
|
|
case OpGt:
|
|
case OpGte:
|
|
case OpLt:
|
|
case OpLte:
|
|
case OpEq:
|
|
case OpNeq:
|
|
case OpSame:
|
|
case OpNSame:
|
|
return simplifyCmp(opc, inst, src1, src2);
|
|
|
|
case Concat: return simplifyConcat(src1, src2);
|
|
case Mov: return simplifyMov(src1);
|
|
case OpNot: return simplifyNot(src1);
|
|
case LdClsPropAddr: return simplifyLdClsPropAddr(inst);
|
|
case ConvBoolToArr: return simplifyConvToArr(inst);
|
|
case ConvDblToArr: return simplifyConvToArr(inst);
|
|
case ConvIntToArr: return simplifyConvToArr(inst);
|
|
case ConvStrToArr: return simplifyConvToArr(inst);
|
|
case ConvArrToBool: return simplifyConvArrToBool(inst);
|
|
case ConvDblToBool: return simplifyConvDblToBool(inst);
|
|
case ConvIntToBool: return simplifyConvIntToBool(inst);
|
|
case ConvStrToBool: return simplifyConvStrToBool(inst);
|
|
case ConvArrToDbl: return simplifyConvArrToDbl(inst);
|
|
case ConvBoolToDbl: return simplifyConvBoolToDbl(inst);
|
|
case ConvIntToDbl: return simplifyConvIntToDbl(inst);
|
|
case ConvStrToDbl: return simplifyConvStrToDbl(inst);
|
|
case ConvArrToInt: return simplifyConvArrToInt(inst);
|
|
case ConvBoolToInt: return simplifyConvBoolToInt(inst);
|
|
case ConvDblToInt: return simplifyConvDblToInt(inst);
|
|
case ConvStrToInt: return simplifyConvStrToInt(inst);
|
|
case ConvBoolToStr: return simplifyConvBoolToStr(inst);
|
|
case ConvDblToStr: return simplifyConvDblToStr(inst);
|
|
case ConvIntToStr: return simplifyConvIntToStr(inst);
|
|
case ConvCellToBool:return simplifyConvCellToBool(inst);
|
|
case ConvCellToInt: return simplifyConvCellToInt(inst);
|
|
case ConvCellToDbl: return simplifyConvCellToDbl(inst);
|
|
case Unbox: return simplifyUnbox(inst);
|
|
case UnboxPtr: return simplifyUnboxPtr(inst);
|
|
case IsType:
|
|
case IsNType: return simplifyIsType(inst);
|
|
case CheckInit:
|
|
case CheckInitMem: return simplifyCheckInit(inst);
|
|
|
|
case JmpZero:
|
|
case JmpNZero:
|
|
return simplifyCondJmp(inst);
|
|
|
|
case JmpGt:
|
|
case JmpGte:
|
|
case JmpLt:
|
|
case JmpLte:
|
|
case JmpEq:
|
|
case JmpNeq:
|
|
case JmpSame:
|
|
case JmpNSame:
|
|
return simplifyQueryJmp(inst);
|
|
|
|
case JmpIsType:
|
|
case JmpIsNType:
|
|
return simplifyJmpIsType(inst);
|
|
|
|
case PrintStr:
|
|
case PrintInt:
|
|
case PrintBool: return simplifyPrint(inst);
|
|
case DecRef:
|
|
case DecRefNZOrBranch:
|
|
case DecRefNZ: return simplifyDecRef(inst);
|
|
case IncRef: return simplifyIncRef(inst);
|
|
case CheckType:
|
|
case AssertType: return simplifyCheckType(inst);
|
|
case CheckStk: return simplifyCheckStk(inst);
|
|
case AssertNonNull:return simplifyAssertNonNull(inst);
|
|
|
|
case LdCls: return simplifyLdCls(inst);
|
|
case LdThis: return simplifyLdThis(inst);
|
|
case LdCtx: return simplifyLdCtx(inst);
|
|
case LdClsCtx: return simplifyLdClsCtx(inst);
|
|
case GetCtxFwdCall:return simplifyGetCtxFwdCall(inst);
|
|
|
|
case SpillStack: return simplifySpillStack(inst);
|
|
case Call: return simplifyCall(inst);
|
|
case CastStk: return simplifyCastStk(inst);
|
|
case CoerceStk: return simplifyCoerceStk(inst);
|
|
case AssertStk: return simplifyAssertStk(inst);
|
|
case LdStack: return simplifyLdStack(inst);
|
|
case LdStackAddr: return simplifyLdStackAddr(inst);
|
|
case DecRefStack: return simplifyDecRefStack(inst);
|
|
case DecRefLoc: return simplifyDecRefLoc(inst);
|
|
case LdLoc: return simplifyLdLoc(inst);
|
|
case StRef: return simplifyStRef(inst);
|
|
|
|
case ExitOnVarEnv: return simplifyExitOnVarEnv(inst);
|
|
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifySpillStack(IRInstruction* inst) {
|
|
auto const sp = inst->src(0);
|
|
auto const spDeficit = inst->src(1)->getValInt();
|
|
auto spillVals = inst->srcs().subpiece(2);
|
|
auto const numSpillSrcs = spillVals.size();
|
|
auto const spillCells = spillValueCells(inst);
|
|
int64_t adjustment = spDeficit - spillCells;
|
|
|
|
// If there's nothing to spill, and no stack adjustment, we don't
|
|
// need the instruction; the old stack is still accurate.
|
|
if (!numSpillSrcs && spDeficit == 0) return sp;
|
|
|
|
// If our value came from a LdStack on the same sp and offset,
|
|
// we don't need to spill it.
|
|
for (uint32_t i = 0, cellOff = 0; i < numSpillSrcs; i++) {
|
|
const int64_t offset = cellOff + adjustment;
|
|
auto* srcInst = spillVals[i]->inst();
|
|
if (srcInst->op() == LdStack && srcInst->src(0) == sp &&
|
|
srcInst->extra<LdStack>()->offset == offset) {
|
|
spillVals[i] = m_tb->genDefNone();
|
|
}
|
|
cellOff++;
|
|
}
|
|
|
|
// Note: although the instruction might have been modified above, we still
|
|
// need to return nullptr so that it gets cloned later if it's stack-allocated
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyCall(IRInstruction* inst) {
|
|
auto spillVals = inst->srcs().subpiece(3);
|
|
auto const spillStack = inst->src(0)->inst();
|
|
if (spillStack->op() != SpillStack) {
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* sp = spillStack->src(0);
|
|
int baseOffset = spillStack->src(1)->getValInt() -
|
|
spillValueCells(spillStack);
|
|
auto const numSpillSrcs = spillVals.size();
|
|
for (int32_t i = 0; i < numSpillSrcs; i++) {
|
|
const int64_t offset = -(i + 1) + baseOffset;
|
|
assert(spillVals[i]->type() != Type::ActRec);
|
|
IRInstruction* srcInst = spillVals[i]->inst();
|
|
// If our value came from a LdStack on the same sp and offset,
|
|
// we don't need to spill it.
|
|
if (srcInst->op() == LdStack && srcInst->src(0) == sp &&
|
|
srcInst->extra<LdStack>()->offset == offset) {
|
|
spillVals[i] = m_tb->genDefNone();
|
|
}
|
|
}
|
|
|
|
// Note: although the instruction might have been modified above, we still
|
|
// need to return nullptr so that it gets cloned later if it's stack-allocated
|
|
return nullptr;
|
|
}
|
|
|
|
// We never inline functions that could have a VarEnv, so an
|
|
// ExitOnVarEnv that has a frame based on DefInlineFP can be removed.
|
|
SSATmp* Simplifier::simplifyExitOnVarEnv(IRInstruction* inst) {
|
|
auto const frameInst = inst->src(0)->inst();
|
|
if (frameInst->op() == DefInlineFP) {
|
|
inst->convertToNop();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyLdCtx(IRInstruction* inst) {
|
|
const Func* func = inst->src(1)->getValFunc();
|
|
if (func->isStatic()) {
|
|
// ActRec->m_cls of a static function is always a valid class pointer with
|
|
// the bottom bit set
|
|
return gen(LdCctx, inst->src(0));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyLdClsCtx(IRInstruction* inst) {
|
|
SSATmp* ctx = inst->src(0);
|
|
Type ctxType = ctx->type();
|
|
if (ctxType.equals(Type::Obj)) {
|
|
// this pointer... load its class ptr
|
|
return gen(LdObjClass, ctx);
|
|
}
|
|
if (ctxType.equals(Type::Cctx)) {
|
|
return gen(LdClsCctx, ctx);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyGetCtxFwdCall(IRInstruction* inst) {
|
|
SSATmp* srcCtx = inst->src(0);
|
|
if (srcCtx->isA(Type::Cctx)) {
|
|
return srcCtx;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyLdCls(IRInstruction* inst) {
|
|
SSATmp* clsName = inst->src(0);
|
|
if (clsName->isConst()) {
|
|
const Class* cls = Unit::lookupClass(clsName->getValStr());
|
|
if (cls) {
|
|
if (Transl::TargetCache::isPersistentHandle(cls->m_cachedOffset)) {
|
|
// the class is always defined
|
|
return cns(cls);
|
|
}
|
|
const Class* ctx = inst->src(1)->getValClass();
|
|
if (ctx && ctx->classof(cls)) {
|
|
// the class of the current function being compiled is the
|
|
// same as or derived from cls, so cls must be defined and
|
|
// cannot change the next time we execute this same code
|
|
return cns(cls);
|
|
}
|
|
}
|
|
return gen(LdClsCached, clsName);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyCheckType(IRInstruction* inst) {
|
|
Type type = inst->typeParam();
|
|
SSATmp* src = inst->src(0);
|
|
Type srcType = src->type();
|
|
|
|
if (srcType.subtypeOf(type)) {
|
|
/*
|
|
* The type of the src is the same or more refined than type, so the
|
|
* guard is unnecessary.
|
|
*/
|
|
return src;
|
|
}
|
|
if (type.strictSubtypeOf(srcType)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (type.equals(Type::Str) && srcType.maybe(Type::Str)) {
|
|
/*
|
|
* If we're guarding against Str and srcType has StaticStr or CountedStr
|
|
* in it, refine the output type. This can happen when we have a
|
|
* KindOfString guard from Translator but internally we know a more
|
|
* specific subtype of Str.
|
|
*/
|
|
FTRACE(1, "CheckType: refining {} to {}\n", srcType.toString(),
|
|
type.toString());
|
|
inst->setTypeParam(type & srcType);
|
|
return nullptr;
|
|
}
|
|
|
|
/*
|
|
* We got a predicted type that is wrong -- it's incompatible with
|
|
* the tracked type. So throw the prediction away, since it would
|
|
* always fail.
|
|
*/
|
|
FTRACE(1, "WARNING: CheckType: removed incorrect prediction that {} is {}\n",
|
|
srcType.toString(), type.toString());
|
|
return src;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyCheckStk(IRInstruction* inst) {
|
|
auto type = inst->typeParam();
|
|
auto sp = inst->src(0);
|
|
auto offset = inst->extra<CheckStk>()->offset;
|
|
|
|
auto stkVal = getStackValue(sp, offset);
|
|
if (stkVal.knownType.equals(Type::None)) return nullptr;
|
|
|
|
if (stkVal.knownType.subtypeOf(type)) {
|
|
return sp;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyQueryJmp(IRInstruction* inst) {
|
|
SSATmp* src1 = inst->src(0);
|
|
SSATmp* src2 = inst->src(1);
|
|
Opcode opc = inst->op();
|
|
// reuse the logic in simplifyCmp.
|
|
SSATmp* newCmp = simplifyCmp(queryJmpToQueryOp(opc), nullptr, src1, src2);
|
|
if (!newCmp) return nullptr;
|
|
|
|
SSATmp* newQueryJmp = makeInstruction(
|
|
[=] (IRInstruction* condJmp) -> SSATmp* {
|
|
SSATmp* newCondJmp = simplifyCondJmp(condJmp);
|
|
if (newCondJmp) return newCondJmp;
|
|
if (condJmp->op() == Nop) {
|
|
// simplifyCondJmp folded the branch into a nop
|
|
inst->convertToNop();
|
|
}
|
|
// Couldn't fold condJmp or combine it with newCmp
|
|
return nullptr;
|
|
},
|
|
JmpNZero,
|
|
inst->marker(),
|
|
inst->taken(),
|
|
newCmp);
|
|
if (!newQueryJmp) return nullptr;
|
|
return newQueryJmp;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyMov(SSATmp* src) {
|
|
return src;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyNot(SSATmp* src) {
|
|
if (src->isConst()) {
|
|
return cns(!src->getValBool());
|
|
}
|
|
|
|
IRInstruction* inst = src->inst();
|
|
Opcode op = inst->op();
|
|
|
|
switch (op) {
|
|
// !!X --> X
|
|
case OpNot:
|
|
return inst->src(0);
|
|
|
|
// !(X cmp Y) --> X opposite_cmp Y
|
|
case OpLt:
|
|
case OpLte:
|
|
case OpGt:
|
|
case OpGte:
|
|
case OpEq:
|
|
case OpNeq:
|
|
case OpSame:
|
|
case OpNSame:
|
|
// Not for Dbl: (x < NaN) != !(x >= NaN)
|
|
if (!inst->src(0)->isA(Type::Dbl) &&
|
|
!inst->src(1)->isA(Type::Dbl)) {
|
|
return gen(negateQueryOp(op), inst->src(0), inst->src(1));
|
|
}
|
|
break;
|
|
|
|
case InstanceOfBitmask:
|
|
case NInstanceOfBitmask:
|
|
// TODO: combine this with the above check and use isQueryOp or
|
|
// add an isNegatable.
|
|
return gen(
|
|
negateQueryOp(op),
|
|
std::make_pair(inst->numSrcs(), inst->srcs().begin())
|
|
);
|
|
return nullptr;
|
|
// TODO !(X | non_zero) --> 0
|
|
default: (void)op;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
#define SIMPLIFY_CONST(OP) do { \
|
|
/* don't canonicalize to the right, OP might not be commutative */ \
|
|
if (src1->isConst() && src2->isConst()) { \
|
|
if (src1->type().isNull()) { \
|
|
/* Null op Null */ \
|
|
if (src2->type().isNull()) { \
|
|
return cns(int64_t(0 OP 0)); \
|
|
} \
|
|
/* Null op ConstInt */ \
|
|
if (src2->type() == Type::Int) { \
|
|
return cns(int64_t(0 OP src2->getValInt())); \
|
|
} \
|
|
/* Null op ConstBool */ \
|
|
if (src2->type() == Type::Bool) { \
|
|
return cns(int64_t(0 OP src2->getValBool())); \
|
|
} \
|
|
/* Null op StaticStr */ \
|
|
if (src2->type() == Type::StaticStr) { \
|
|
const StringData* str = src2->getValStr(); \
|
|
if (str->isInteger()) { \
|
|
return cns(int64_t(0 OP str->toInt64())); \
|
|
} \
|
|
return cns(int64_t(0 OP 0)); \
|
|
} \
|
|
} \
|
|
if (src1->type() == Type::Int) { \
|
|
/* ConstInt op Null */ \
|
|
if (src2->type().isNull()) { \
|
|
return cns(int64_t(src1->getValInt()) OP 0); \
|
|
} \
|
|
/* ConstInt op ConstInt */ \
|
|
if (src2->type() == Type::Int) { \
|
|
return cns(int64_t(src1->getValInt() OP \
|
|
src2->getValInt())); \
|
|
} \
|
|
/* ConstInt op ConstBool */ \
|
|
if (src2->type() == Type::Bool) { \
|
|
return cns(int64_t(src1->getValInt() OP \
|
|
int(src2->getValBool()))); \
|
|
} \
|
|
/* ConstInt op StaticStr */ \
|
|
if (src2->type() == Type::StaticStr) { \
|
|
const StringData* str = src2->getValStr(); \
|
|
if (str->isInteger()) { \
|
|
return cns(int64_t(src1->getValInt() OP str->toInt64())); \
|
|
} \
|
|
return cns(int64_t(src1->getValInt() OP 0)); \
|
|
} \
|
|
} \
|
|
if (src1->type() == Type::Bool) { \
|
|
/* ConstBool op Null */ \
|
|
if (src2->type().isNull()) { \
|
|
return cns(int64_t(src1->getValBool() OP 0)); \
|
|
} \
|
|
/* ConstBool op ConstInt */ \
|
|
if (src2->type() == Type::Int) { \
|
|
return cns(int64_t(int(src1->getValBool()) OP \
|
|
src2->getValInt())); \
|
|
} \
|
|
/* ConstBool op ConstBool */ \
|
|
if (src2->type() == Type::Bool) { \
|
|
return cns(int64_t(src1->getValBool() OP \
|
|
src2->getValBool())); \
|
|
} \
|
|
/* ConstBool op StaticStr */ \
|
|
if (src2->type() == Type::StaticStr) { \
|
|
const StringData* str = src2->getValStr(); \
|
|
if (str->isInteger()) { \
|
|
return cns(int64_t(int(src1->getValBool()) OP str->toInt64())); \
|
|
} \
|
|
return cns(int64_t(int(src1->getValBool()) OP 0)); \
|
|
} \
|
|
} \
|
|
if (src1->type() == Type::StaticStr) { \
|
|
const StringData* str = src1->getValStr(); \
|
|
int64_t strInt = 0; \
|
|
if (str->isInteger()) { \
|
|
strInt = str->toInt64(); \
|
|
} \
|
|
/* StaticStr op Null */ \
|
|
if (src2->type().isNull()) { \
|
|
return cns(int64_t(strInt OP 0)); \
|
|
} \
|
|
/* StaticStr op ConstInt */ \
|
|
if (src2->type() == Type::Int) { \
|
|
return cns(int64_t(strInt OP src2->getValInt())); \
|
|
} \
|
|
/* StaticStr op ConstBool */ \
|
|
if (src2->type() == Type::Bool) { \
|
|
return cns(int64_t(strInt OP int(src2->getValBool()))); \
|
|
} \
|
|
/* StaticStr op StaticStr */ \
|
|
if (src2->type() == Type::StaticStr) { \
|
|
const StringData* str2 = src2->getValStr(); \
|
|
if (str2->isInteger()) { \
|
|
return cns(int64_t(strInt OP str2->toInt64())); \
|
|
} \
|
|
return cns(int64_t(strInt OP 0)); \
|
|
} \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
#define SIMPLIFY_COMMUTATIVE(OP, NAME) do { \
|
|
SIMPLIFY_CONST(OP); \
|
|
if (src1->isConst() && !src2->isConst()) { \
|
|
return gen(Op##NAME, src2, src1); \
|
|
} \
|
|
if (src1->isA(Type::Int) && src2->isA(Type::Int)) { \
|
|
IRInstruction* inst1 = src1->inst(); \
|
|
IRInstruction* inst2 = src2->inst(); \
|
|
if (inst1->op() == Op##NAME && inst1->src(1)->isConst()) { \
|
|
/* (X + C1) + C2 --> X + C3 */ \
|
|
if (src2->isConst()) { \
|
|
int64_t right = inst1->src(1)->getValInt(); \
|
|
right OP##= src2->getValInt(); \
|
|
return gen(Op##NAME, inst1->src(0), cns(right)); \
|
|
} \
|
|
/* (X + C1) + (Y + C2) --> X + Y + C3 */ \
|
|
if (inst2->op() == Op##NAME && inst2->src(1)->isConst()) { \
|
|
int64_t right = inst1->src(1)->getValInt(); \
|
|
right OP##= inst2->src(1)->getValInt(); \
|
|
SSATmp* left = gen(Op##NAME, inst1->src(0), inst2->src(0)); \
|
|
return gen(Op##NAME, left, cns(right)); \
|
|
} \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
#define SIMPLIFY_DISTRIBUTIVE(OUTOP, INOP, OUTNAME, INNAME) do { \
|
|
/* assumes that OUTOP is commutative, don't use with subtract! */ \
|
|
SIMPLIFY_COMMUTATIVE(OUTOP, OUTNAME); \
|
|
IRInstruction* inst1 = src1->inst(); \
|
|
IRInstruction* inst2 = src2->inst(); \
|
|
Opcode op1 = inst1->op(); \
|
|
Opcode op2 = inst2->op(); \
|
|
/* all combinations of X * Y + X * Z --> X * (Y + Z) */ \
|
|
if (op1 == Op##INNAME && op2 == Op##INNAME) { \
|
|
if (inst1->src(0) == inst2->src(0)) { \
|
|
SSATmp* fold = gen(Op##OUTNAME, inst1->src(1), inst2->src(1)); \
|
|
return gen(Op##INNAME, inst1->src(0), fold); \
|
|
} \
|
|
if (inst1->src(0) == inst2->src(1)) { \
|
|
SSATmp* fold = gen(Op##OUTNAME, inst1->src(1), inst2->src(0)); \
|
|
return gen(Op##INNAME, inst1->src(0), fold); \
|
|
} \
|
|
if (inst1->src(1) == inst2->src(0)) { \
|
|
SSATmp* fold = gen(Op##OUTNAME, inst1->src(0), inst2->src(1)); \
|
|
return gen(Op##INNAME, inst1->src(1), fold); \
|
|
} \
|
|
if (inst1->src(1) == inst2->src(1)) { \
|
|
SSATmp* fold = gen(Op##OUTNAME, inst1->src(0), inst2->src(0)); \
|
|
return gen(Op##INNAME, inst1->src(1), fold); \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
SSATmp* Simplifier::simplifyAdd(SSATmp* src1, SSATmp* src2) {
|
|
SIMPLIFY_DISTRIBUTIVE(+, *, Add, Mul);
|
|
if (src2->isConst() && src2->type() == Type::Int) {
|
|
int64_t src2Val = src2->getValInt();
|
|
// X + 0 --> X
|
|
if (src2Val == 0) {
|
|
if (src1->type() == Type::Bool) {
|
|
return gen(ConvBoolToInt, src1);
|
|
}
|
|
return src1;
|
|
}
|
|
// X + -C --> X - C
|
|
if (src2Val < 0) {
|
|
return gen(OpSub, src1, cns(-src2Val));
|
|
}
|
|
}
|
|
// X + (0 - Y) --> X - Y
|
|
IRInstruction* inst2 = src2->inst();
|
|
Opcode op2 = inst2->op();
|
|
if (op2 == OpSub) {
|
|
SSATmp* src = inst2->src(0);
|
|
if (src->isConst() && src->type() == Type::Int) {
|
|
if (src->getValInt() == 0) {
|
|
return gen(OpSub, src1, inst2->src(1));
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifySub(SSATmp* src1, SSATmp* src2) {
|
|
SIMPLIFY_CONST(-);
|
|
// X - X --> 0
|
|
if (src1 == src2) {
|
|
return cns(0);
|
|
}
|
|
if (src2->isConst() && src2->type() == Type::Int) {
|
|
int64_t src2Val = src2->getValInt();
|
|
// X - 0 --> X
|
|
if (src2Val == 0) {
|
|
if (src1->type() == Type::Bool) {
|
|
return gen(ConvBoolToInt, src1);
|
|
}
|
|
return src1;
|
|
}
|
|
// X - -C --> X + C
|
|
if (src2Val < 0 && src2Val > std::numeric_limits<int64_t>::min()) {
|
|
return gen(OpAdd, src1, cns(-src2Val));
|
|
}
|
|
}
|
|
// X - (0 - Y) --> X + Y
|
|
IRInstruction* inst2 = src2->inst();
|
|
Opcode op2 = inst2->op();
|
|
if (op2 == OpSub) {
|
|
SSATmp* src = inst2->src(0);
|
|
if (src->isConst() && src->type() == Type::Int) {
|
|
if (src->getValInt() == 0) {
|
|
return gen(OpAdd, src1, inst2->src(1));
|
|
}
|
|
}
|
|
}
|
|
// TODO patterns in the form of:
|
|
// (X - C1) + (X - C2)
|
|
// (X - C1) + C2
|
|
// (X - C1) + (X + C2)
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyMul(SSATmp* src1, SSATmp* src2) {
|
|
SIMPLIFY_COMMUTATIVE(*, Mul);
|
|
if (src2->isConst() && src2->type() == Type::Int) {
|
|
// X * (-1) --> -X
|
|
if (src2->getValInt() == -1) {
|
|
return gen(OpSub, cns(0), src1);
|
|
}
|
|
// X * 0 --> 0
|
|
if (src2->getValInt() == 0) {
|
|
return cns(0);
|
|
}
|
|
// X * 1 --> X
|
|
if (src2->getValInt() == 1) {
|
|
if (src1->type() == Type::Bool) {
|
|
return gen(ConvBoolToInt, src1);
|
|
}
|
|
return src1;
|
|
}
|
|
// X * 2 --> X + X
|
|
if (src2->getValInt() == 2) {
|
|
return gen(OpAdd, src1, src1);
|
|
}
|
|
// TODO once IR has shifts
|
|
// X * 2^C --> X << C
|
|
// X * (2^C + 1) --> ((X << C) + X)
|
|
// X * (2^C - 1) --> ((X << C) - X)
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyMod(SSATmp* src1, SSATmp* src2) {
|
|
if (src2->isConst()) {
|
|
int64_t src2Val = src2->getValInt();
|
|
// refrain from generating undefined IR
|
|
assert(src2Val != 0);
|
|
// simplify const
|
|
if (src1->isConst()) {
|
|
// still don't want undefined IR
|
|
assert(src1->getValInt() != std::numeric_limits<int64_t>::min() ||
|
|
src2Val != -1);
|
|
return cns(src1->getValInt() % src2Val);
|
|
}
|
|
// X % 1, X % -1 --> 0
|
|
if (src2Val == 1 || src2Val == -1LL) {
|
|
return cns(0);
|
|
}
|
|
// X % LONG_MIN = X (largest magnitude possible as rhs)
|
|
if (src2Val == std::numeric_limits<int64_t>::min()) {
|
|
return src1;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyBitAnd(SSATmp* src1, SSATmp* src2) {
|
|
SIMPLIFY_DISTRIBUTIVE(&, |, BitAnd, BitOr);
|
|
// X & X --> X
|
|
if (src1 == src2) {
|
|
return src1;
|
|
}
|
|
if (src2->isConst()) {
|
|
// X & 0 --> 0
|
|
if (src2->getValInt() == 0) {
|
|
return cns(0);
|
|
}
|
|
// X & (~0) --> X
|
|
if (src2->getValInt() == ~0L) {
|
|
return src1;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyBitOr(SSATmp* src1, SSATmp* src2) {
|
|
SIMPLIFY_DISTRIBUTIVE(|, &, BitOr, BitAnd);
|
|
// X | X --> X
|
|
if (src1 == src2) {
|
|
return src1;
|
|
}
|
|
if (src2->isConst()) {
|
|
// X | 0 --> X
|
|
if (src2->getValInt() == 0) {
|
|
return src1;
|
|
}
|
|
// X | (~0) --> ~0
|
|
if (src2->getValInt() == ~uint64_t(0)) {
|
|
return cns(~uint64_t(0));
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyBitXor(SSATmp* src1, SSATmp* src2) {
|
|
SIMPLIFY_COMMUTATIVE(^, BitXor);
|
|
// X ^ X --> 0
|
|
if (src1 == src2)
|
|
return cns(0);
|
|
// X ^ 0 --> X; X ^ -1 --> ~X
|
|
if (src2->isConst()) {
|
|
if (src2->getValInt() == 0) {
|
|
return src1;
|
|
}
|
|
if (src2->getValInt() == -1) {
|
|
return gen(OpBitNot, src1);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyLogicXor(SSATmp* src1, SSATmp* src2) {
|
|
SIMPLIFY_COMMUTATIVE(^, LogicXor);
|
|
if (src1 == src2) {
|
|
return cns(false);
|
|
}
|
|
|
|
// SIMPLIFY_COMMUTATIVE takes care of the both-sides-const case, and
|
|
// canonicalizes a single const to the right
|
|
if (src2->isConst()) {
|
|
if (src2->getValBool()) {
|
|
return gen(OpNot, src1);
|
|
} else {
|
|
return src1;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static SSATmp* chaseIncRefs(SSATmp* tmp) {
|
|
while (tmp->inst()->op() == IncRef) {
|
|
tmp = tmp->inst()->src(0);
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
template<class T, class U>
|
|
static typename std::common_type<T,U>::type cmpOp(Opcode opName, T a, U b) {
|
|
switch (opName) {
|
|
case OpGt: return a > b;
|
|
case OpGte: return a >= b;
|
|
case OpLt: return a < b;
|
|
case OpLte: return a <= b;
|
|
case OpSame:
|
|
case OpEq: return a == b;
|
|
case OpNSame:
|
|
case OpNeq: return a != b;
|
|
default:
|
|
not_reached();
|
|
}
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyCmp(Opcode opName, IRInstruction* inst,
|
|
SSATmp* src1, SSATmp* src2) {
|
|
auto newInst = [inst, this](Opcode op, SSATmp* src1, SSATmp* src2) {
|
|
return gen(op, inst ? inst->taken() : (Block*)nullptr, src1, src2);
|
|
};
|
|
// ---------------------------------------------------------------------
|
|
// Perform some execution optimizations immediately
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Identity optimization
|
|
if ((src1 == src2 || chaseIncRefs(src1) == chaseIncRefs(src2)) &&
|
|
src1->type() != Type::Dbl) {
|
|
// (val1 == val1) does not simplify to true when val1 is a NaN
|
|
return cns(bool(cmpOp(opName, 0, 0)));
|
|
}
|
|
|
|
// need both types to be unboxed and known to simplify
|
|
if (!src1->type().notBoxed() || src1->type() == Type::Cell ||
|
|
!src2->type().notBoxed() || src2->type() == Type::Cell) {
|
|
return nullptr;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// OpSame and OpNSame have some special rules
|
|
// ---------------------------------------------------------------------
|
|
|
|
if (opName == OpSame || opName == OpNSame) {
|
|
// OpSame and OpNSame do not perform type juggling
|
|
if (src1->type() != src2->type()) {
|
|
if (!(src1->type().isString() && src2->type().isString())) {
|
|
return cns(opName == OpNSame);
|
|
}
|
|
}
|
|
|
|
// src1 and src2 are same type, treating Str and StaticStr as the same
|
|
|
|
// OpSame and OpNSame have special rules for string and object
|
|
// Other types may simplify to OpEq and OpNeq, respectively
|
|
if (src1->type().isString() && src2->type().isString()) {
|
|
if (src1->isConst() && src2->isConst()) {
|
|
auto str1 = src1->getValStr();
|
|
auto str2 = src2->getValStr();
|
|
bool same = str1->same(str2);
|
|
return cns(bool(cmpOp(opName, same, 1)));
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
if (src1->type() == Type::Obj && src2->type() == Type::Obj) {
|
|
return nullptr;
|
|
}
|
|
// for arrays, don't simplify Same to Eq
|
|
if (src1->type() == Type::Arr && src2->type() == Type::Arr) {
|
|
return nullptr;
|
|
}
|
|
// Type is neither a string nor an object - simplify to OpEq/OpNeq
|
|
if (opName == OpSame) {
|
|
return newInst(OpEq, src1, src2);
|
|
}
|
|
return newInst(OpNeq, src1, src2);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// We may now perform constant-constant optimizations
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Null cmp Null
|
|
if (src1->type().isNull() && src2->type().isNull()) {
|
|
return cns(bool(cmpOp(opName, 0, 0)));
|
|
}
|
|
// const cmp const
|
|
// TODO this list is incomplete - feel free to add more
|
|
// TODO: can simplify const arrays when sizes are different or both 0
|
|
if (src1->isConst() && src2->isConst()) {
|
|
// StaticStr cmp StaticStr
|
|
if (src1->type() == Type::StaticStr &&
|
|
src2->type() == Type::StaticStr) {
|
|
int cmp = src1->getValStr()->compare(src2->getValStr());
|
|
return cns(bool(cmpOp(opName, cmp, 0)));
|
|
}
|
|
// ConstInt cmp ConstInt
|
|
if (src1->type() == Type::Int && src2->type() == Type::Int) {
|
|
return cns(bool(
|
|
cmpOp(opName, src1->getValInt(), src2->getValInt())));
|
|
}
|
|
// ConstBool cmp ConstBool
|
|
if (src1->type() == Type::Bool && src2->type() == Type::Bool) {
|
|
return cns(bool(
|
|
cmpOp(opName, src1->getValBool(), src2->getValBool())));
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Constant bool comparisons can be strength-reduced
|
|
// NOTE: Comparisons with bools get juggled to bool.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Perform constant-bool optimizations
|
|
if (src2->type() == Type::Bool && src2->isConst()) {
|
|
bool b = src2->getValBool();
|
|
|
|
// The result of the comparison might be independent of the truth
|
|
// value of the LHS. If so, then simplify.
|
|
// E.g. `some-int > true`. some-int may juggle to false or true
|
|
// (0 or 1), but `0 > true` and `1 > true` are both false, so we can
|
|
// simplify to false immediately.
|
|
if (cmpOp(opName, false, b) == cmpOp(opName, true, b)) {
|
|
return cns(bool(cmpOp(opName, 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 src1 that results in the
|
|
// overall expression being true (after type-juggling).
|
|
// Hence we may check for equality with that boolean.
|
|
// E.g. `some-int > false` is equivalent to `some-int == true`
|
|
if (opName != OpEq) {
|
|
if (cmpOp(opName, false, b)) {
|
|
return newInst(OpEq, src1, cns(false));
|
|
} else {
|
|
return newInst(OpEq, src1, cns(true));
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// For same-type cmps, canonicalize any constants to the right
|
|
// Then stop - there are no more simplifications left
|
|
// ---------------------------------------------------------------------
|
|
|
|
if (src1->type() == src2->type() ||
|
|
(src1->type().isString() && src2->type().isString())) {
|
|
if (src1->isConst() && !src2->isConst()) {
|
|
return newInst(commuteQueryOp(opName), src2, src1);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Perform type juggling and type canonicalization for different types
|
|
// see http://www.php.net/manual/en/language.operators.comparison.php
|
|
// ---------------------------------------------------------------------
|
|
|
|
// nulls get canonicalized to the right
|
|
if (src1->type().isNull()) {
|
|
return newInst(commuteQueryOp(opName), src2, src1);
|
|
}
|
|
|
|
// case 1: null cmp string. Convert null to ""
|
|
if (src1->type().isString() && src2->type().isNull()) {
|
|
return newInst(opName, src1, cns(StringData::GetStaticString("")));
|
|
}
|
|
|
|
// case 2a: null cmp anything. Convert null to false
|
|
if (src2->type().isNull()) {
|
|
return newInst(opName, src1, cns(false));
|
|
}
|
|
|
|
// bools get canonicalized to the right
|
|
if (src1->type() == Type::Bool) {
|
|
return newInst(commuteQueryOp(opName), src2, src1);
|
|
}
|
|
|
|
// case 2b: bool cmp anything. Convert anything to bool
|
|
if (src2->type() == Type::Bool) {
|
|
if (src1->isConst()) {
|
|
if (src1->type() == Type::Int) {
|
|
return newInst(opName, cns(bool(src1->getValInt())), src2);
|
|
} else if (src1->type().isString()) {
|
|
auto str = src1->getValStr();
|
|
return newInst(opName, cns(str->toBoolean()), src2);
|
|
}
|
|
}
|
|
|
|
// Optimize comparison between int and const bool
|
|
if (src1->type() == Type::Int && src2->isConst()) {
|
|
// Based on the const bool optimization (above) opName should be OpEq
|
|
always_assert(opName == OpEq);
|
|
|
|
if (src2->getValBool()) {
|
|
return newInst(OpNeq, src1, cns(0));
|
|
} else {
|
|
return newInst(OpEq, src1, cns(0));
|
|
}
|
|
}
|
|
|
|
// Nothing fancy to do - perform juggling as normal.
|
|
return newInst(opName, gen(ConvCellToBool, src1), src2);
|
|
}
|
|
|
|
// From here on, we must be careful of how Type::Obj gets dealt with,
|
|
// since Type::Obj can refer to an object or to a resource.
|
|
|
|
// case 3: object cmp object. No juggling to do
|
|
// same-type simplification is performed above
|
|
|
|
// strings get canonicalized to the left
|
|
if (src2->type().isString()) {
|
|
return newInst(commuteQueryOp(opName), src2, src1);
|
|
}
|
|
|
|
// ints get canonicalized to the right
|
|
if (src1->type() == Type::Int) {
|
|
return newInst(commuteQueryOp(opName), src2, src1);
|
|
}
|
|
|
|
// case 4: number/string/resource cmp. Convert to number (int OR double)
|
|
// NOTE: The following if-test only checks for some of the SRON-SRON
|
|
// cases (specifically, string-int). Other cases (like string-string)
|
|
// are dealt with earlier, while other cases (like number-resource)
|
|
// are not caught at all (and end up exiting this macro at the bottom).
|
|
if (src1->type().isString() && src1->isConst() &&
|
|
src2->type() == Type::Int) {
|
|
auto str = src1->getValStr();
|
|
int64_t si; double sd;
|
|
auto st = str->isNumericWithVal(si, sd, true /* allow errors */);
|
|
if (st == KindOfDouble) {
|
|
return newInst(opName, cns(sd), src2);
|
|
}
|
|
if (st == KindOfNull) {
|
|
si = 0;
|
|
}
|
|
return newInst(opName, cns(si), src2);
|
|
}
|
|
|
|
// case 5: array cmp array. No juggling to do
|
|
// same-type simplification is performed above
|
|
|
|
// case 6: array cmp anything. Array is greater
|
|
if (src1->isArray()) {
|
|
return cns(bool(cmpOp(opName, 1, 0)));
|
|
}
|
|
if (src2->isArray()) {
|
|
return cns(bool(cmpOp(opName, 0, 1)));
|
|
}
|
|
|
|
// case 7: object cmp anything. Object is greater
|
|
// ---------------------------------------------------------------------
|
|
// Unfortunately, we are unsure of whether Type::Obj is an object or a
|
|
// resource, so this code cannot be applied.
|
|
// ---------------------------------------------------------------------
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyJmpIsType(IRInstruction* inst) {
|
|
SSATmp* res = simplifyIsType(inst);
|
|
if (res == nullptr) return nullptr;
|
|
assert(res->isConst());
|
|
if (res->getValBool()) {
|
|
// Taken jump
|
|
return gen(Jmp_, inst->taken());
|
|
} else {
|
|
// Not taken jump; turn jump into a nop
|
|
inst->convertToNop();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyIsType(IRInstruction* inst) {
|
|
bool trueSense =
|
|
inst->op() == IsType || inst->op() == JmpIsType;
|
|
auto type = inst->typeParam();
|
|
auto src = inst->src(0);
|
|
auto srcType = src->type();
|
|
|
|
// The comparisons below won't work for these cases covered by this
|
|
// assert, and we currently don't generate these types.
|
|
assert(type.isKnownUnboxedDataType() && type != Type::StaticStr);
|
|
|
|
// CountedStr and StaticStr are disjoint, but compatible for this purpose.
|
|
if (type.isString() && srcType.isString()) {
|
|
return cns(trueSense);
|
|
}
|
|
|
|
// The types are disjoint; the result must be false.
|
|
if ((srcType & type).equals(Type::Bottom)) {
|
|
return cns(!trueSense);
|
|
}
|
|
|
|
// The src type is a subtype of the tested type. You'd think the result would
|
|
// always be true, but it's not for is_object.
|
|
if (!type.subtypeOf(Type::Obj) && srcType.subtypeOf(type)) {
|
|
return cns(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. (Or it's the weird
|
|
// Obj case.) We can't simplify this away.
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConcat(SSATmp* src1, SSATmp* src2) {
|
|
if (src1->isConst() && src1->type() == Type::StaticStr &&
|
|
src2->isConst() && src2->type() == Type::StaticStr) {
|
|
StringData* str1 = const_cast<StringData *>(src1->getValStr());
|
|
StringData* str2 = const_cast<StringData *>(src2->getValStr());
|
|
StringData* merge = StringData::GetStaticString(concat_ss(str1, str2));
|
|
return cns(merge);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvToArr(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
Array arr = Array::Create(src->getValVariant());
|
|
return cns(ArrayData::GetScalarArray(arr.get()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvArrToBool(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
if (src->getValArr()->empty()) {
|
|
return cns(false);
|
|
}
|
|
return cns(true);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvDblToBool(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
return cns(bool(src->getValDbl()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvIntToBool(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
return cns(bool(src->getValInt()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvStrToBool(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
// only the strings "", and "0" convert to false, all other strings
|
|
// are converted to true
|
|
const StringData* str = src->getValStr();
|
|
return cns(!str->empty() && !str->isZero());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvArrToDbl(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
if (src->getValArr()->empty()) {
|
|
return cns(0.0);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvBoolToDbl(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
return cns(double(src->getValBool()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvIntToDbl(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
return cns(double(src->getValInt()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvStrToDbl(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
const StringData *str = src->getValStr();
|
|
int64_t lval;
|
|
double dval;
|
|
DataType ret = str->isNumericWithVal(lval, dval, 1);
|
|
if (ret == KindOfInt64) {
|
|
dval = (double)lval;
|
|
} else if (ret != KindOfDouble) {
|
|
dval = 0.0;
|
|
}
|
|
return cns(dval);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvArrToInt(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
if (src->getValArr()->empty()) {
|
|
return cns(0);
|
|
}
|
|
return cns(1);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvBoolToInt(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
return cns(int(src->getValBool()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvDblToInt(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
return cns(toInt64(src->getValDbl()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvStrToInt(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
const StringData *str = src->getValStr();
|
|
int64_t lval;
|
|
double dval;
|
|
DataType ret = str->isNumericWithVal(lval, dval, 1);
|
|
if (ret == KindOfDouble) {
|
|
lval = (int64_t)dval;
|
|
} else if (ret != KindOfInt64) {
|
|
lval = 0;
|
|
}
|
|
return cns(lval);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvBoolToStr(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
if (src->getValBool()) {
|
|
return cns(StringData::GetStaticString("1"));
|
|
}
|
|
return cns(StringData::GetStaticString(""));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvDblToStr(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
return cns(
|
|
StringData::convert_double_helper(src->getValDbl()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvIntToStr(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (src->isConst()) {
|
|
return cns(
|
|
StringData::convert_integer_helper(src->getValInt()));
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvCellToBool(IRInstruction* inst) {
|
|
auto const src = inst->src(0);
|
|
auto const srcType = src->type();
|
|
|
|
if (srcType.isBool()) return src;
|
|
if (srcType.isNull()) return cns(false);
|
|
if (srcType.isArray()) return gen(ConvArrToBool, src);
|
|
if (srcType.isDbl()) return gen(ConvDblToBool, src);
|
|
if (srcType.isInt()) return gen(ConvIntToBool, src);
|
|
if (srcType.isString()) return gen(ConvStrToBool, src);
|
|
if (srcType.isObj()) return cns(true);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvCellToInt(IRInstruction* inst) {
|
|
auto const src = inst->src(0);
|
|
auto const srcType = src->type();
|
|
|
|
if (srcType.isInt()) return src;
|
|
if (srcType.isNull()) return cns(0);
|
|
if (srcType.isArray()) return gen(ConvArrToInt, src);
|
|
if (srcType.isBool()) return gen(ConvBoolToInt, src);
|
|
if (srcType.isDbl()) return gen(ConvDblToInt, src);
|
|
if (srcType.isString()) return gen(ConvStrToInt, src);
|
|
if (srcType.isObj()) return gen(ConvObjToInt, inst->taken(), src);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyConvCellToDbl(IRInstruction* inst) {
|
|
auto const src = inst->src(0);
|
|
auto const srcType = src->type();
|
|
|
|
if (srcType.isDbl()) return src;
|
|
if (srcType.isNull()) return cns(0.0);
|
|
if (srcType.isArray()) return gen(ConvArrToDbl, src);
|
|
if (srcType.isBool()) return gen(ConvBoolToDbl, src);
|
|
if (srcType.isInt()) return gen(ConvIntToDbl, src);
|
|
if (srcType.isString()) return gen(ConvStrToDbl, src);
|
|
if (srcType.isObj()) return gen(ConvObjToDbl, inst->taken(), src);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyLdClsPropAddr(IRInstruction* inst) {
|
|
SSATmp* propName = inst->src(1);
|
|
if (!propName->isConst()) return nullptr;
|
|
|
|
SSATmp* cls = inst->src(0);
|
|
auto ctxCls = inst->src(2)->getValClass();
|
|
|
|
if (canUseSPropCache(cls, propName->getValStr(), ctxCls)) {
|
|
|
|
const StringData* clsNameStr = findClassName(cls);
|
|
|
|
return gen(LdClsPropAddrCached,
|
|
inst->taken(),
|
|
cls,
|
|
propName,
|
|
cns(clsNameStr),
|
|
inst->src(2));
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/*
|
|
* If we're in an inlined frame, use the this that we put in the
|
|
* inlined ActRec. (This could chase more intervening SpillStack
|
|
* instructions to find the SpillFrame, but for now we don't inline
|
|
* calls that will have that.)
|
|
*/
|
|
SSATmp* Simplifier::simplifyLdThis(IRInstruction* inst) {
|
|
auto fpInst = inst->src(0)->inst();
|
|
if (fpInst->op() == DefInlineFP) {
|
|
auto spInst = fpInst->src(0)->inst();
|
|
if (spInst->op() == SpillFrame &&
|
|
spInst->src(3)->isA(Type::Obj)) {
|
|
return spInst->src(3);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyUnbox(IRInstruction* inst) {
|
|
auto* src = inst->src(0);
|
|
auto type = outputType(inst);
|
|
|
|
Type srcType = src->type();
|
|
if (srcType.notBoxed()) {
|
|
assert(type.equals(srcType));
|
|
return src;
|
|
}
|
|
if (srcType.isBoxed()) {
|
|
srcType = srcType.innerType();
|
|
assert(type.equals(srcType));
|
|
return gen(LdRef, type, inst->taken()->trace(), src);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyUnboxPtr(IRInstruction* inst) {
|
|
if (inst->src(0)->isA(Type::PtrToCell)) {
|
|
// Nothing to unbox
|
|
return inst->src(0);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyCheckInit(IRInstruction* inst) {
|
|
Type srcType = inst->src(0)->type();
|
|
srcType = inst->op() == CheckInitMem ? srcType.deref() : srcType;
|
|
assert(srcType.notPtr());
|
|
assert(inst->taken());
|
|
if (srcType.isInit()) {
|
|
inst->convertToNop();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyPrint(IRInstruction* inst) {
|
|
if (inst->src(0)->type().isNull()) {
|
|
inst->convertToNop();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyDecRef(IRInstruction* inst) {
|
|
if (!isRefCounted(inst->src(0))) {
|
|
inst->convertToNop();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyIncRef(IRInstruction* inst) {
|
|
SSATmp* src = inst->src(0);
|
|
if (!isRefCounted(src)) {
|
|
return src;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyCondJmp(IRInstruction* inst) {
|
|
SSATmp* const src = inst->src(0);
|
|
IRInstruction* const srcInst = src->inst();
|
|
const Opcode srcOpcode = srcInst->op();
|
|
|
|
// After other simplifications below (isConvIntOrPtrToBool), we can
|
|
// end up with a non-Bool input. Nothing more to do in this case.
|
|
if (src->type() != Type::Bool) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Constant propagate.
|
|
if (src->isConst()) {
|
|
bool val = src->getValBool();
|
|
if (inst->op() == JmpZero) {
|
|
val = !val;
|
|
}
|
|
if (val) {
|
|
return gen(Jmp_, inst->taken());
|
|
}
|
|
inst->convertToNop();
|
|
return nullptr;
|
|
}
|
|
|
|
// Pull negations into the jump.
|
|
if (src->inst()->op() == OpNot) {
|
|
return gen(inst->op() == JmpZero ? JmpNZero : JmpZero,
|
|
inst->taken(),
|
|
srcInst->src(0));
|
|
}
|
|
|
|
/*
|
|
* Try to combine the src inst with the Jmp. We can't do any
|
|
* combinations of the src instruction with the jump if the src's
|
|
* are refcounted, since we may have dec refs between the src
|
|
* instruction and the jump.
|
|
*/
|
|
for (auto& src : srcInst->srcs()) {
|
|
if (isRefCounted(src)) return nullptr;
|
|
}
|
|
|
|
// If the source is conversion of an int or pointer to boolean, we
|
|
// can test the int/ptr value directly.
|
|
if (isConvIntOrPtrToBool(srcInst)) {
|
|
return gen(inst->op(), inst->taken(), srcInst->src(0));
|
|
}
|
|
|
|
// Fuse jumps with query operators.
|
|
if (isQueryOp(srcOpcode)) {
|
|
SrcRange ssas = srcInst->srcs();
|
|
return gen(
|
|
queryToJmpOp(
|
|
inst->op() == JmpZero
|
|
? negateQueryOp(srcOpcode)
|
|
: srcOpcode),
|
|
srcInst->marker(),
|
|
srcInst->typeParam(), // if it had a type param
|
|
inst->taken(),
|
|
std::make_pair(ssas.size(), ssas.begin())
|
|
);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyCastStk(IRInstruction* inst) {
|
|
auto const info = getStackValue(inst->src(0),
|
|
inst->extra<CastStk>()->offset);
|
|
if (info.knownType.subtypeOf(inst->typeParam())) {
|
|
// No need to cast---the type was as good or better.
|
|
inst->convertToNop();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyCoerceStk(IRInstruction* inst) {
|
|
auto const info = getStackValue(inst->src(0),
|
|
inst->extra<CoerceStk>()->offset);
|
|
if (info.knownType.subtypeOf(inst->typeParam())) {
|
|
// No need to cast---the type was as good or better.
|
|
inst->convertToNop();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyAssertStk(IRInstruction* inst) {
|
|
auto const info = getStackValue(inst->src(0),
|
|
inst->extra<AssertStk>()->offset);
|
|
|
|
// AssertStk indicated that we knew the type from static analysis,
|
|
// so this assert just double checks.
|
|
if (info.value) assert(info.value->isA(inst->typeParam()));
|
|
|
|
if (info.knownType.subtypeOf(inst->typeParam())) {
|
|
inst->convertToNop();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyLdStack(IRInstruction* inst) {
|
|
auto const info = getStackValue(inst->src(0),
|
|
inst->extra<LdStack>()->offset);
|
|
|
|
// We don't want to extend live ranges of tmps across calls, so we
|
|
// don't get the value if spansCall is true; however, we can use
|
|
// any type information known.
|
|
if (info.value && (!info.spansCall ||
|
|
info.value->inst()->op() == DefConst)) {
|
|
return info.value;
|
|
}
|
|
if (!info.knownType.equals(Type::None)) {
|
|
inst->setTypeParam(
|
|
Type::mostRefined(inst->typeParam(), info.knownType)
|
|
);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyDecRefLoc(IRInstruction* inst) {
|
|
if (inst->typeParam().notCounted()) {
|
|
inst->convertToNop();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyLdLoc(IRInstruction* inst) {
|
|
if (inst->typeParam().isNull()) {
|
|
return cns(inst->typeParam());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Replace StRef with StRefNT when we know we aren't going to change
|
|
// its m_type field.
|
|
SSATmp* Simplifier::simplifyStRef(IRInstruction* inst) {
|
|
auto const oldUnbox = inst->src(0)->type().unbox();
|
|
auto const newType = inst->src(1)->type();
|
|
if (oldUnbox.isKnownDataType() &&
|
|
oldUnbox.equals(newType) && !oldUnbox.isString()) {
|
|
inst->setOpcode(StRefNT);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyLdStackAddr(IRInstruction* inst) {
|
|
auto const info = getStackValue(inst->src(0),
|
|
inst->extra<StackOffset>()->offset);
|
|
if (!info.knownType.equals(Type::None)) {
|
|
inst->setTypeParam(
|
|
Type::mostRefined(inst->typeParam(), info.knownType.ptr())
|
|
);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyDecRefStack(IRInstruction* inst) {
|
|
auto const info = getStackValue(inst->src(0),
|
|
inst->extra<StackOffset>()->offset);
|
|
if (info.value && !info.spansCall) {
|
|
inst->convertToNop();
|
|
return gen(DecRef, info.value);
|
|
}
|
|
if (!info.knownType.equals(Type::None)) {
|
|
inst->setTypeParam(
|
|
Type::mostRefined(inst->typeParam(), info.knownType)
|
|
);
|
|
}
|
|
if (inst->typeParam().notCounted()) {
|
|
inst->convertToNop();
|
|
return nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SSATmp* Simplifier::simplifyAssertNonNull(IRInstruction* inst) {
|
|
auto t = inst->typeParam();
|
|
assert(t.maybe(Type::Nullptr));
|
|
if (t.subtypeOf(Type::Nullptr)) {
|
|
return inst->src(0);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
}}
|