hphp/runtime/vm/jit/dce.cpp (1,181 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/dce.h"
#include <array>
#include <folly/MapUtil.h>
#include "hphp/util/low-ptr.h"
#include "hphp/util/match.h"
#include "hphp/util/trace.h"
#include "hphp/runtime/vm/runtime.h"
#include "hphp/runtime/vm/jit/analysis.h"
#include "hphp/runtime/vm/jit/cfg.h"
#include "hphp/runtime/vm/jit/check.h"
#include "hphp/runtime/vm/jit/id-set.h"
#include "hphp/runtime/vm/jit/ir-opcode.h"
#include "hphp/runtime/vm/jit/ir-unit.h"
#include "hphp/runtime/vm/jit/mutation.h"
#include "hphp/runtime/vm/jit/memory-effects.h"
#include "hphp/runtime/vm/jit/opt.h"
#include "hphp/runtime/vm/jit/print.h"
#include "hphp/runtime/vm/jit/simple-propagation.h"
#include "hphp/runtime/vm/jit/state-vector.h"
#include "hphp/runtime/vm/jit/timer.h"
#include "hphp/runtime/vm/jit/translator-inline.h"
namespace HPHP::jit {
TRACE_SET_MOD(hhir_dce);
bool canDCE(const IRInstruction& inst) {
switch (inst.op()) {
case AssertNonNull:
case AssertType:
case AbsDbl:
case AddInt:
case SubInt:
case MulInt:
case AndInt:
case AddDbl:
case SubDbl:
case MulDbl:
case Sqrt:
case OrInt:
case XorInt:
case Shl:
case Shr:
case Lshr:
case Floor:
case Ceil:
case XorBool:
case Mod:
case ConvDblToBool:
case ConvIntToBool:
case ConvStrToBool:
case ConvBoolToDbl:
case ConvIntToDbl:
case ConvStrToDbl:
case ConvResToDbl:
case ConvBoolToInt:
case ConvDblToInt:
case ConvStrToInt:
case ConvResToInt:
case ConvDblToStr:
case ConvIntToStr:
case DblAsBits:
case ConvPtrToLval:
case NewColFromArray:
case GtInt:
case GteInt:
case LtInt:
case LteInt:
case EqInt:
case NeqInt:
case CmpInt:
case GtDbl:
case GteDbl:
case LtDbl:
case LteDbl:
case EqDbl:
case NeqDbl:
case CmpDbl:
case GtStr:
case GteStr:
case LtStr:
case LteStr:
case SameStr:
case NSameStr:
case CmpStr:
case GtBool:
case GteBool:
case LtBool:
case LteBool:
case EqBool:
case NeqBool:
case CmpBool:
case SameObj:
case NSameObj:
case EqRes:
case NeqRes:
case EqCls:
case EqLazyCls:
case EqFunc:
case EqStrPtr:
case EqArrayDataPtr:
case HasReifiedGenerics:
case InstanceOf:
case InstanceOfIface:
case InstanceOfIfaceVtable:
case ExtendsClass:
case InstanceOfBitmask:
case NInstanceOfBitmask:
case InterfaceSupportsArrLike:
case InterfaceSupportsStr:
case InterfaceSupportsInt:
case InterfaceSupportsDbl:
case HasToString:
case IsType:
case IsNType:
case IsTypeMem:
case IsNTypeMem:
case IsLegacyArrLike:
case IsWaitHandle:
case IsCol:
case LdStk:
case LdLoc:
case LdLocForeign:
case LdStkAddr:
case LdLocAddr:
case LdRDSAddr:
case LdMem:
case LdContField:
case LdClsInitElem:
case LdIterBase:
case LdIterPos:
case LdIterEnd:
case LdFrameThis:
case LdFrameCls:
case LdSmashable:
case LdSmashableFunc:
case LdClsFromClsMeth:
case LdFuncFromClsMeth:
case LdClsFromRClsMeth:
case LdFuncFromRClsMeth:
case LdGenericsFromRClsMeth:
case LdFuncFromRFunc:
case LdGenericsFromRFunc:
case LdTVFromRDS:
case ConvFuncPrologueFlagsToARFlags:
case DefConst:
case DefFuncPrologueCallee:
case DefFuncPrologueCtx:
case DefFuncPrologueFlags:
case DefFuncPrologueNumArgs:
case Conjure:
case LdClsInitData:
case LookupClsRDS:
case LdClsMethodCacheCls:
case LdFuncVecLen:
case LdClsMethod:
case LdSubClsCns:
case LdIfaceMethod:
case LdPropAddr:
case LdObjClass:
case LdClsName:
case LdLazyClsName:
case LdLazyCls:
case LdFuncCls:
case LdFuncInOutBits:
case LdFuncNumParams:
case LdFuncName:
case LdMethCallerName:
case LdStrLen:
case LdMonotypeDictTombstones:
case LdMonotypeDictKey:
case LdMonotypeDictVal:
case LdMonotypeVecElem:
case LdVecElem:
case LdVecElemAddr:
case NewInstanceRaw:
case NewLoggingArray:
case NewDictArray:
case NewCol:
case NewPair:
case NewRFunc:
case NewRClsMeth:
case LdRetVal:
case Mov:
case CountVec:
case CountDict:
case CountKeyset:
case CountCollection:
case Nop:
case AKExistsDict:
case AKExistsKeyset:
case LdBindAddr:
case LdSSwitchDest:
case LdClosureCls:
case LdClosureThis:
case CreateSSWH:
case LdContActRec:
case LdContArValue:
case LdContArKey:
case LdWHState:
case LdWHResult:
case LdWHNotDone:
case LdAFWHActRec:
case LdMIStateTempBaseAddr:
case StringIsset:
case ColIsEmpty:
case ColIsNEmpty:
case LdUnwinderValue:
case LdColVec:
case LdColDict:
case OrdStr:
case ChrInt:
case CheckRange:
case LdMBase:
case MethodExists:
case LdTVAux:
case DictGetQuiet:
case DictGetK:
case DictIsset:
case DictIdx:
case KeysetGetQuiet:
case KeysetGetK:
case KeysetIsset:
case KeysetIdx:
case VecFirst:
case VecLast:
case DictFirst:
case DictFirstKey:
case DictLast:
case DictLastKey:
case KeysetFirst:
case KeysetLast:
case GetTime:
case GetTimeNs:
case Select:
case LdARFlags:
case FuncHasAttr:
case ClassHasAttr:
case IsFunReifiedGenericsMatched:
case LdFuncRequiredCoeffects:
case LdARFunc:
case StrictlyIntegerConv:
case GetMemoKeyScalar:
case LookupSPropSlot:
case ConstructClosure:
case AllocBespokeStructDict:
case AllocStructDict:
case AllocVec:
case GetDictPtrIter:
case GetVecPtrIter:
case AdvanceDictPtrIter:
case AdvanceVecPtrIter:
case LdPtrIterKey:
case LdPtrIterVal:
case EqPtrIter:
case LdUnitPerRequestFilepath:
case DirFromFilepath:
case BespokeGet:
case BespokeIterFirstPos:
case BespokeIterLastPos:
case BespokeIterEnd:
case BespokeIterGetKey:
case BespokeIterGetVal:
case LoadBCSP:
case LdResolvedTypeCnsNoCheck:
case LdResolvedTypeCnsClsName:
case AllocInitROM:
case VoidPtrAsDataType:
case CopyArray:
case StructDictElemAddr:
case LdImplicitContext:
assertx(!inst.isControlFlow());
return true;
// These may raise oom, but its still ok to delete them if the
// result is unused
case ConcatIntStr:
case ConcatStrInt:
case ConcatStrStr:
case ConcatStr3:
case ConcatStr4:
case AddNewElemKeyset:
case AddNewElemVec:
return true;
// Some of these conversion functions can run arbitrary PHP code.
case ConvObjToDbl:
case ConvTVToDbl:
case ConvObjToInt:
case ConvTVToInt:
case ConvTVToBool:
case ConvObjToBool:
case ConvObjToStr:
case ConvTVToStr:
case ConvArrLikeToVec:
case ConvObjToVec:
case ConvArrLikeToDict:
case ConvObjToDict:
case ConvArrLikeToKeyset:
case ConvObjToKeyset:
case LdOutAddr:
return !opcodeMayRaise(inst.op()) &&
(!inst.consumesReferences() || inst.producesReference());
case DbgTraceCall:
case AKExistsObj:
case StStk:
case StStkMeta:
case StStkRange:
case StOutValue:
case CheckIter:
case CheckType:
case CheckNullptr:
case CheckTypeMem:
case CheckDictKeys:
case CheckSmashableClass:
case CheckLoc:
case CheckStk:
case CheckMBase:
case AssertLoc:
case AssertStk:
case AssertMBase:
case CheckInit:
case CheckInitMem:
case CheckCold:
case EndGuards:
case CheckNonNull:
case DivDbl:
case DivInt:
case AddIntO:
case AddOffset:
case SubIntO:
case MulIntO:
case GtObj:
case GteObj:
case LtObj:
case LteObj:
case EqObj:
case NeqObj:
case CmpObj:
case CmpRes:
case GtRes:
case GteRes:
case LtRes:
case LteRes:
case GtArrLike:
case GteArrLike:
case LtArrLike:
case LteArrLike:
case CmpArrLike:
case JmpZero:
case JmpNZero:
case JmpSSwitchDest:
case JmpSwitchDest:
case ProfileSwitchDest:
case CheckSurpriseFlags:
case CheckSurpriseAndStack:
case HandleRequestSurprise:
case ReturnHook:
case SuspendHookAwaitEF:
case SuspendHookAwaitEG:
case SuspendHookAwaitR:
case SuspendHookCreateCont:
case SuspendHookYield:
case EndBlock:
case Unreachable:
case Jmp:
case DefLabel:
case LdPairElem:
case LdClsCtor:
case LdCls:
case LdClsCached:
case LdClsCachedSafe:
case LdCns:
case LdTypeCns:
case LdTypeCnsNoThrow:
case LdTypeCnsClsName:
case IsTypeStructCached:
case LookupCnsE:
case LdClsCns:
case InitClsCns:
case InitSubClsCns:
case LdResolvedTypeCns:
case CheckSubClsCns:
case LdClsCnsVecLen:
case LookupClsMethodFCache:
case LookupClsMethodCache:
case LookupClsMethod:
case LdGblAddr:
case LdGblAddrDef:
case LdClsPropAddrOrNull:
case LdClsPropAddrOrRaise:
case LdInitRDSAddr:
case LdInitPropAddr:
case LdObjMethodD:
case LdObjMethodS:
case LdObjInvoke:
case LdFunc:
case LdFuncCached:
case LookupFuncCached:
case AllocObj:
case AllocObjReified:
case NewClsMeth:
case FuncCred:
case InitProps:
case PropTypeRedefineCheck:
case InitSProps:
case InitObjProps:
case InitObjMemoSlots:
case LockObj:
case DebugBacktrace:
case InitThrowableFileAndLine:
case ConstructInstance:
case InitDictElem:
case InitStructElem:
case InitStructPositions:
case InitVecElem:
case InitVecElemLoop:
case NewKeysetArray:
case NewStructDict:
case NewBespokeStructDict:
case Clone:
case InlineCall:
case Call:
case NativeImpl:
case CallBuiltin:
case RetCtrl:
case AsyncFuncRet:
case AsyncFuncRetPrefetch:
case AsyncFuncRetSlow:
case AsyncGenRetR:
case AsyncGenYieldR:
case AsyncSwitchFast:
case GenericRetDecRefs:
case StClsInitElem:
case StImplicitContext:
case StImplicitContextWH:
case StMem:
case StMemMeta:
case StIterBase:
case StIterType:
case StIterEnd:
case StIterPos:
case StLoc:
case StLocMeta:
case StLocRange:
case StPtrAt:
case StVMFP:
case StVMSP:
case StVMPC:
case StVMReturnAddr:
case StVMRegState:
case StTVInRDS:
case StTypeAt:
case ReqBindJmp:
case ReqInterpBBNoTranslate:
case ReqRetranslate:
case ReqRetranslateOpt:
case IncRef:
case DecRef:
case DecRefNZ:
case ProfileDecRef:
case ReleaseShallow:
case DecReleaseCheck:
case DefFP:
case DefFuncEntryFP:
case DefFrameRelSP:
case DefRegSP:
case InitFrame:
case Count:
case VerifyParamCls:
case VerifyParamCallable:
case VerifyParamFail:
case VerifyParamFailHard:
case VerifyReifiedLocalType:
case VerifyReifiedReturnType:
case VerifyRetCallable:
case VerifyRetCls:
case VerifyRetFail:
case VerifyRetFailHard:
case VerifyProp:
case VerifyPropAll:
case VerifyPropCls:
case VerifyPropCoerce:
case VerifyPropCoerceAll:
case VerifyPropFail:
case VerifyPropFailHard:
case ThrowUninitLoc:
case ThrowUndefPropException:
case RaiseTooManyArg:
case RaiseError:
case RaiseErrorOnInvalidIsAsExpressionType:
case RaiseWarning:
case RaiseNotice:
case ThrowArrayIndexException:
case ThrowArrayKeyException:
case RaiseForbiddenDynCall:
case RaiseForbiddenDynConstruct:
case RaiseCoeffectsCallViolation:
case RaiseCoeffectsFunParamTypeViolation:
case RaiseCoeffectsFunParamCoeffectRulesViolation:
case RaiseStrToClassNotice:
case CheckClsMethFunc:
case CheckClsReifiedGenericMismatch:
case CheckFunReifiedGenericMismatch:
case CheckInOutMismatch:
case CheckReadonlyMismatch:
case PrintStr:
case PrintInt:
case PrintBool:
case GetMemoKey:
case InterpOne:
case InterpOneCF:
case OODeclExists:
case StClosureArg:
case CreateGen:
case CreateAGen:
case CreateAAWH:
case CreateAFWH:
case CreateAGWH:
case AFWHPrepareChild:
case StArResumeAddr:
case ContEnter:
case ContCheckNext:
case ContValid:
case ContArIncKey:
case ContArIncIdx:
case ContArUpdateIdx:
case LdContResumeAddr:
case StContArState:
case StContArValue:
case StContArKey:
case AFWHBlockOn:
case AFWHPushTailFrame:
case CountWHNotDone:
case IncStat:
case IncProfCounter:
case IncCallCounter:
case DbgAssertRefCount:
case DbgAssertFunc:
case DbgCheckLocalsDecRefd:
case RBTraceEntry:
case RBTraceMsg:
case ZeroErrorLevel:
case RestoreErrorLevel:
case IterInit:
case IterInitK:
case LIterInit:
case LIterInitK:
case IterNext:
case IterNextK:
case LIterNext:
case LIterNextK:
case IterFree:
case KillActRec:
case KillIter:
case KillLoc:
case BaseG:
case PropX:
case PropQ:
case PropDX:
case CGetProp:
case CGetPropQ:
case SetProp:
case UnsetProp:
case SetOpProp:
case IncDecProp:
case IssetProp:
case ElemX:
case CheckMissingKeyInArrLike:
case CheckArrayCOW:
case ProfileDictAccess:
case CheckDictOffset:
case ProfileKeysetAccess:
case CheckKeysetOffset:
case ProfileArrayCOW:
case ElemDictD:
case ElemDictU:
case ElemDictK:
case ElemDX:
case ElemUX:
case DictGet:
case KeysetGet:
case StringGet:
case OrdStrIdx:
case MapGet:
case CGetElem:
case DictSet:
case MapSet:
case VectorSet:
case BespokeSet:
case BespokeUnset:
case StructDictUnset:
case BespokeAppend:
case SetElem:
case SetRange:
case SetRangeRev:
case UnsetElem:
case SetOpElem:
case IncDecElem:
case SetNewElem:
case SetNewElemDict:
case SetNewElemVec:
case SetNewElemKeyset:
case ReserveVecNewElem:
case VectorIsset:
case PairIsset:
case MapIsset:
case IssetElem:
case ProfileType:
case ProfileCall:
case ProfileMethod:
case ProfileSubClsCns:
case ProfileGlobal:
case CheckVecBounds:
case BespokeElem:
case BespokeEscalateToVanilla:
case BespokeGetThrow:
case LdVectorSize:
case BeginCatch:
case EndCatch:
case EnterTCUnwind:
case UnwindCheckSideExit:
case DbgTrashStk:
case DbgTrashFrame:
case DbgTrashMem:
case EnterPrologue:
case ExitPrologue:
case EnterTranslation:
case CheckStackOverflow:
case CheckSurpriseFlagsEnter:
case JmpPlaceholder:
case ThrowOutOfBounds:
case ThrowInvalidArrayKey:
case ThrowInvalidOperation:
case ThrowCallReifiedFunctionWithoutGenerics:
case ThrowDivisionByZeroException:
case ThrowHasThisNeedStatic:
case ThrowLateInitPropError:
case ThrowMissingArg:
case ThrowMissingThis:
case ThrowParameterWrongType:
case ThrowInOutMismatch:
case ThrowReadonlyMismatch:
case ThrowCannotModifyReadonlyCollection:
case ThrowLocalMustBeValueTypeException:
case ThrowMustBeEnclosedInReadonly:
case ThrowMustBeMutableException:
case ThrowMustBeReadonlyException:
case ThrowMustBeValueTypeException:
case StMBase:
case StMROProp:
case CheckMROProp:
case FinishMemberOp:
case BeginInlining:
case EndInlining:
case SetOpTV:
case OutlineSetOp:
case ConjureUse:
case LdClsMethodFCacheFunc:
case LdClsMethodCacheFunc:
case LogArrayReach:
case LogGuardFailure:
case ProfileInstanceCheck:
case MemoGetStaticValue:
case MemoGetStaticCache:
case MemoGetLSBValue:
case MemoGetLSBCache:
case MemoGetInstanceValue:
case MemoGetInstanceCache:
case MemoSetStaticValue:
case MemoSetStaticCache:
case MemoSetLSBValue:
case MemoSetLSBCache:
case MemoSetInstanceValue:
case MemoSetInstanceCache:
case ThrowAsTypeStructException:
case RecordReifiedGenericsAndGetTSList:
case ResolveTypeStruct:
case CheckRDSInitialized:
case MarkRDSInitialized:
case MarkRDSAccess:
case ProfileProp:
case ProfileIsTypeStruct:
case StFrameCtx:
case StFrameFunc:
case StFrameMeta:
case LookupClsCns:
case LookupClsCtxCns:
case ArrayMarkLegacyShallow:
case ArrayMarkLegacyRecursive:
case ArrayUnmarkLegacyShallow:
case ArrayUnmarkLegacyRecursive:
case ProfileArrLikeProps:
case CheckFuncNeedsCoverage:
case RecordFuncCall:
case StructDictSlot:
case StructDictAddNextSlot:
case StructDictTypeBoundCheck:
return false;
case IsTypeStruct:
case EqStr:
case NeqStr:
return !opcodeMayRaise(inst.op());
case EqArrLike:
case NeqArrLike:
case SameArrLike:
case NSameArrLike:
return !inst.mayRaiseErrorWithSources();
}
not_reached();
}
namespace {
/* DceFlags tracks the state of one instruction during dead code analysis. */
struct DceFlags {
DceFlags()
: m_state(DEAD)
{}
bool isDead() const { return m_state == DEAD; }
void setDead() { m_state = DEAD; }
void setLive() { m_state = LIVE; }
std::string toString() const {
std::array<const char*,2> const names = {{
"DEAD",
"LIVE",
}};
return folly::format(
"{}",
m_state < names.size() ? names[m_state] : "<invalid>"
).str();
}
private:
enum {
DEAD = 0,
LIVE,
};
uint8_t m_state:1;
};
static_assert(sizeof(DceFlags) == 1, "sizeof(DceFlags) should be 1 byte");
// DCE state indexed by instr->id().
typedef StateVector<IRInstruction, DceFlags> DceState;
typedef StateVector<SSATmp, uint32_t> UseCounts;
// Set of live DefLabel operands (keyed by DefLabel and index)
typedef jit::fast_set<std::pair<IRInstruction*, size_t>> DefLabelLiveness;
// Worklist is instruction to process. If the instruction is a
// DefLabel, the second item is the index of the operand (DefLabel is
// treated separately for each operand).
typedef jit::vector<std::pair<IRInstruction*, size_t>> WorkList;
void removeDeadInstructions(IRUnit& unit,
const DceState& state,
const DefLabelLiveness& defLabelLive) {
postorderWalk(
unit,
[&](Block* block) {
auto const next = block->next();
auto const bcctx = block->back().bcctx();
block->remove_if(
[&] (const IRInstruction& inst) {
// Don't attempt to remove a Jmp feeding a DefLabel. It will
// be dealt with below.
if (inst.is(Jmp) && inst.numSrcs() > 0) return false;
ONTRACE(
4,
if (state[inst].isDead()) {
FTRACE(1, "Removing dead instruction {}\n", inst.toString());
}
);
auto const dead = state[inst].isDead();
assertx(!dead || !inst.taken() || inst.taken()->isCatch());
return dead;
}
);
if (!block->empty()) {
auto& front = block->front();
if (front.is(DefLabel)) {
// If the DefLabel was completely dead, it would have been
// removed above.
assertx(!state[&front].isDead());
// Remove any dead individual operands
auto dstIdx = 0;
auto const numDsts = front.numDsts();
for (auto i = 0; i < numDsts; ++i) {
if (!defLabelLive.count(std::make_pair(&front, i))) {
FTRACE(1, "Removing dead DefLabel dst {}: {}\n",
i, front.toString());
front.deleteDst(dstIdx);
} else {
++dstIdx;
}
}
assertx(front.numDsts() > 0);
}
auto& back = block->back();
if (back.is(Jmp) && back.numSrcs() > 0) {
if (state[&back].isDead()) {
// If the Jmp's operands are completely dead, we can't
// remove it (because it's control flow), but we can turn
// it into a normal Jmp.
FTRACE(1, "Removing dead instruction {}\n", back.toString());
back.setSrcs(0, nullptr);
} else {
// Otherwise, similarily to the DefLabel, remove
// individual dead operands.
auto srcIdx = 0;
auto const numSrcs = back.numSrcs();
for (auto i = 0; i < numSrcs; ++i) {
auto& defLabel = block->taken()->front();
assertx(defLabel.is(DefLabel));
if (!defLabelLive.count(std::make_pair(&defLabel, i))) {
FTRACE(1, "Removing dead Jmp src {}: {}\n",
i, back.toString());
back.deleteSrc(srcIdx);
} else {
++srcIdx;
}
}
}
}
}
if (block->empty() || !block->back().isBlockEnd()) {
assertx(next);
block->push_back(unit.gen(Jmp, bcctx, next));
}
}
);
}
// removeUnreachable erases unreachable blocks from unit, and returns
// a sorted list of the remaining blocks.
BlockList prepareBlocks(IRUnit& unit) {
FTRACE(1, "RemoveUnreachable:vvvvvvvvvvvvvvvvvvvv\n");
SCOPE_EXIT { FTRACE(1, "RemoveUnreachable:^^^^^^^^^^^^^^^^^^^^\n"); };
auto const blocks = rpoSortCfg(unit);
// 1. perform copy propagation on every instruction
for (auto block : blocks) {
for (auto& inst : *block) {
copyProp(&inst);
}
}
// 2. erase unreachable blocks and get an rpo sorted list of what remains.
bool needsReflow = removeUnreachable(unit);
// 3. if we removed any whole blocks that ended in Jmp instructions, reflow
// all types in case they change the incoming types of DefLabel
// instructions.
if (needsReflow) reflowTypes(unit);
return blocks;
}
WorkList initInstructions(const IRUnit& unit, const BlockList& blocks,
DceState& state) {
TRACE(1, "DCE(initInstructions):vvvvvvvvvvvvvvvvvvvv\n");
// Mark reachable, essential, instructions live and enqueue them.
WorkList wl;
wl.reserve(unit.numInsts());
forEachInst(blocks, [&] (IRInstruction* inst) {
// Instructions that cannot be removed are automatically live. The
// exception is DefLabels and Jmps that feed a DefLabel. There
// aren't normally DCEable, but will be dealt with specially.
if (!canDCE(*inst) &&
!inst->is(DefLabel) &&
!(inst->is(Jmp) && inst->numSrcs() > 0)) {
state[inst].setLive();
wl.push_back(std::make_pair(inst, 0));
}
});
TRACE(1, "DCE:^^^^^^^^^^^^^^^^^^^^\n");
return wl;
}
//////////////////////////////////////////////////////////////////////
struct TrackedInstr {
// DecRefs which refer to the tracked instruction.
jit::vector<IRInstruction*> decs;
// Auxiliary instructions which must be killed if the tracked instruction is
// killed.
jit::vector<IRInstruction*> aux;
// Stores to the stack in catch traces that can be killed to kill the
// tracked instruction.
jit::vector<IRInstruction*> stores;
};
void processCatchBlock(IRUnit& unit, DceState& state, Block* block,
const UseCounts& uses,
jit::fast_map<IRInstruction*, TrackedInstr>& rcInsts) {
assertx(block->front().is(BeginCatch));
assertx(block->back().is(EndCatch));
auto constexpr numTrackedSlots = 64;
auto constexpr wholeRange = std::make_pair(0, numTrackedSlots);
auto const stackTop = block->back().extra<EndCatch>()->offset;
auto const stackRange = [&] {
// If the catch block occurs within an inlined frame the outer stack
// locations (those above the inlined frame) are not dead and cannot be
// elided as we may not throw through the outer callers.
if (block->back().src(0)->inst()->is(BeginInlining)) {
auto const extra = block->back().src(0)->inst()->extra<BeginInlining>();
auto const spOff = extra->spOffset;
assertx(stackTop <= spOff);
if (spOff < stackTop + numTrackedSlots) {
return AStack::range(stackTop, spOff);
}
}
return AStack::range(stackTop, stackTop + numTrackedSlots);
}();
// Nothing to optimize if the stack is empty
if (stackRange.size() == 0) return;
std::bitset<numTrackedSlots> usedLocations = {};
// stores that are only read by the EndCatch
jit::fast_set<IRInstruction*> candidateStores;
// Any IncRefs we see; if they correspond to stores above, we can
// replace the store with a store of Null, and kill the IncRef.
jit::fast_map<SSATmp*, std::vector<Block::iterator>> candidateIncRefs;
auto const range =
[&] (const AliasClass& cls) -> std::pair<int, int> {
if (!cls.maybe(stackRange)) return {};
auto const stk = cls.stack();
if (!stk) return wholeRange;
auto const lowest_upper = std::min(stackRange.high, stk->high);
auto const highest_lower = std::max(stackRange.low, stk->low);
if (lowest_upper <= highest_lower) return {};
return {
highest_lower.offset - stackRange.low.offset,
lowest_upper.offset - stackRange.low.offset
};
};
auto const process_stack =
[&] (const AliasClass& cls) {
auto r = range(cls);
if (r == wholeRange) {
usedLocations.set();
} else {
while (r.first < r.second) {
usedLocations.set(r.first++);
}
}
return false;
};
auto const do_store =
[&] (const AliasClass& cls, IRInstruction* store) {
if (!store->is(StStk, StStkMeta)) return false;
auto const stk = cls.is_stack();
if (!stk) return process_stack(cls);
auto const r = range(cls);
if (r.first != r.second) {
assertx(r.second == r.first + 1);
if (!usedLocations.test(r.first)) {
usedLocations.set(r.first);
candidateStores.insert(store);
}
}
return false;
};
auto done = false;
for (auto inst = block->end(); inst != block->begin(); ) {
--inst;
if (inst->is(EndCatch)) {
continue;
}
if (inst->is(IncRef)) {
candidateIncRefs[inst->src(0)].push_back(inst);
continue;
}
if (done) continue;
auto const effects = canonicalize(memory_effects(*inst));
done = match<bool>(
effects,
[&] (IrrelevantEffects) { return false; },
[&] (UnknownEffects) { return true; },
[&] (ReturnEffects x) { return true; },
[&] (CallEffects x) { return true; },
[&] (GeneralEffects x) {
return
process_stack(x.loads) ||
process_stack(x.inout) ||
process_stack(x.stores) ||
process_stack(x.backtrace) ||
process_stack(x.kills);
},
[&] (PureLoad x) { return process_stack(x.src); },
[&] (PureStore x) { return do_store(x.dst, &*inst); },
[&] (ExitEffects x) { return process_stack(x.live); },
[&] (PureInlineCall x) {
return
process_stack(x.base) ||
process_stack(x.actrec);
}
);
}
for (auto store : candidateStores) {
auto const src = store->src(1);
auto const it = candidateIncRefs.find(src);
if (it != candidateIncRefs.end()) {
FTRACE(3, "Erasing {} for {}\n",
it->second.back()->toString(), store->toString());
block->erase(it->second.back());
if (it->second.size() > 1) {
it->second.pop_back();
} else {
candidateIncRefs.erase(it);
}
} else if (src->type().maybe(TCounted)) {
auto const srcInst = src->inst();
if (!srcInst->producesReference() ||
!canDCE(*srcInst) ||
uses[src] != 1) {
if (srcInst->producesReference() && canDCE(*srcInst)) {
rcInsts[srcInst].stores.emplace_back(store);
}
continue;
} else {
FTRACE(3, "Erasing {} for {}\n",
srcInst->toString(), store->toString());
state[srcInst].setDead();
}
}
store->setSrc(1, unit.cns(TInitNull));
}
}
/*
* A store to the stack which is post-dominated by the EndCatch and
* not otherwise read is only there to ensure the unwinder DecRefs the
* value it contains. If there's also an IncRef of the value in the
* catch trace we can just store InitNull to the stack location and
* drop the IncRef (and later, maybe adjust the sp of the
* catch-trace's owner so we don't even have to do the store).
*/
void optimizeCatchBlocks(const BlockList& blocks,
DceState& state,
IRUnit& unit,
const UseCounts& uses,
jit::fast_map<IRInstruction*, TrackedInstr>& rcInsts) {
FTRACE(1, "OptimizeCatchBlocks:vvvvvvvvvvvvvvvvvvvv\n");
SCOPE_EXIT { FTRACE(1, "OptimizeCatchBlocks:^^^^^^^^^^^^^^^^^^^^\n"); };
for (auto block : blocks) {
if (block->back().is(EndCatch) &&
block->back().extra<EndCatch>()->mode !=
EndCatchData::CatchMode::SideExit &&
block->front().is(BeginCatch)) {
processCatchBlock(unit, state, block, uses, rcInsts);
}
}
}
void optimizeConcats(jit::vector<IRInstruction*>& concats,
DceState& state,
IRUnit& unit,
UseCounts& uses,
jit::fast_map<IRInstruction*, TrackedInstr>& rcInsts) {
FTRACE(1, "OptimizeConcats:vvvvvvvvvvvvvvvvvvvv\n");
SCOPE_EXIT { FTRACE(1, "OptimizeConcats:^^^^^^^^^^^^^^^^^^^^\n"); };
auto const incref = [&] (auto inst, auto src) {
auto const blk = inst->block();
auto const ins = unit.gen(IncRef, inst->bcctx(), src);
blk->insert(blk->iteratorTo(inst), ins);
state[ins].setLive();
++uses[src];
FTRACE(3, "Adding {}\n", ins->toString());
};
auto const decref = [&] (auto inst, auto src) {
auto const blk = inst->block();
auto const ins = unit.gen(DecRef, inst->bcctx(), DecRefData{}, src);
blk->insert(blk->iteratorTo(inst), ins);
state[ins].setLive();
++uses[src];
FTRACE(3, "Adding {}\n", ins->toString());
};
auto const combine = [&] (auto inst, auto inst_prev,
auto src1, auto src2, auto src3) {
/*
* ~~ Converting ~~
* t1 = ConcatStrStr a b (implicit Decref a)
* DecRef b
* t2 = ConcatStrStr t1 c (implicit Decref t1)
* DecRef c
*
* to
*
* IncRef a
* IncRef b
* t1 = ConcatStrStr a b (implicit Decref a)
* DecRef b
* t2 = ConcatStr3 a b c (implicit Decref a)
* DecRef b
* DecRef c
* ~~ Converting ~~
* t1 = ConcatStrStr a b (implicit Decref a)
* DecRef b
* t2 = ConcatStrStr c t1 (implicit Decref c)
* DecRef t1
*
* to
*
* IncRef a
* IncRef b
* t1 = ConcatStrStr a b (implicit Decref a)
* DecRef b
* t2 = ConcatStr3 c a b (implicit Decref c)
* DecRef a
* DecRef b
* DecRef t1
*
* We need to make sure refcounts are correct. We do this by increffing
* the sources of the first ConcatStrStr and then decreffing them after
* ConcatStr3 unless ConcatStr3 already consumes the reference
*
* Note that later stages of DCE will kill the extra ConcatStrStr and the
* refcounting.
*/
assertx(inst_prev->is(ConcatStrStr));
assertx(inst->is(ConcatStrStr));
if (uses[inst_prev->dst()] == 1 + rcInsts[inst_prev].decs.size() +
rcInsts[inst_prev].stores.size()) {
FTRACE(3, "Combining {} into {}",
inst_prev->toString(), inst->toString());
auto next = inst->next();
unit.replace(inst, ConcatStr3, inst->taken(), src1, src2, src3);
inst->setNext(next);
FTRACE(3, " and got {}\n", inst->toString());
state[inst].setLive();
--uses[inst_prev->dst()];
++uses[inst_prev->src(0)];
++uses[inst_prev->src(1)];
// Incref the first source since the first ConcatStrStr controls
// its refcount
incref(inst_prev, inst_prev->src(0));
incref(inst_prev, inst_prev->src(1));
// ConcatStr3 ends the blocks, so insert the decrefs to the next block
assertx(inst->next() && !inst->next()->empty());
decref(&inst->next()->front(), src2);
if (src3 == inst_prev->src(1)) decref(&inst->next()->front(), src3);
}
};
for (auto& inst : concats) {
if (!inst->is(ConcatStrStr)) continue;
auto const src1 = inst->src(0);
auto const src2 = inst->src(1);
if (src1->inst()->is(ConcatStrStr)) {
combine(inst, src1->inst(),
src1->inst()->src(0), src1->inst()->src(1), src2);
} else if (src2->inst()->is(ConcatStrStr)) {
combine(inst, src2->inst(),
src1, src2->inst()->src(0), src2->inst()->src(1));
}
}
}
////////////////////////////////////////////////////////////////////////////////
void killInstrAdjustRC(
DceState& state,
IRUnit& unit,
IRInstruction* inst,
jit::vector<IRInstruction*>& decs
) {
auto anyRemaining = false;
if (inst->consumesReferences()) {
// ConsumesReference inputs that are definitely not moved can
// simply be decreffed as a replacement for the dead consumesref
// instruction
auto srcIx = 0;
for (auto src : inst->srcs()) {
auto const ix = srcIx++;
if (inst->consumesReference(ix) && src->type().maybe(TCounted)) {
if (inst->mayMoveReference(ix)) {
anyRemaining = true;
continue;
}
auto const blk = inst->block();
auto const ins = unit.gen(DecRef, inst->bcctx(), DecRefData{}, src);
blk->insert(blk->iteratorTo(inst), ins);
FTRACE(3, "Inserting {} to replace {}\n",
ins->toString(), inst->toString());
state[ins].setLive();
}
}
}
for (auto dec : decs) {
auto replaced = dec->src(0) != inst->dst();
auto srcIx = 0;
if (anyRemaining) {
// The remaining inputs might be moved, so may need to survive
// until this instruction is decreffed
for (auto src : inst->srcs()) {
if (inst->mayMoveReference(srcIx++) && src->type().maybe(TCounted)) {
if (!replaced) {
FTRACE(3, "Converting {} to ", dec->toString());
dec->setSrc(0, src);
FTRACE(3, "{} for {}\n", dec->toString(), inst->toString());
replaced = true;
state[dec].setLive();
} else {
auto const blk = dec->block();
auto const ins = unit.gen(DecRef, dec->bcctx(), DecRefData{}, src);
blk->insert(blk->iteratorTo(dec), ins);
FTRACE(3, "Inserting {} before {} for {}\n",
ins->toString(), dec->toString(), inst->toString());
state[ins].setLive();
}
}
}
}
if (!replaced) {
FTRACE(3, "Killing {} for {}\n", dec->toString(), inst->toString());
state[dec].setDead();
}
}
state[inst].setDead();
}
//////////////////////////////////////////////////////////////////////
} // anonymous namespace
void mandatoryDCE(IRUnit& unit) {
if (removeUnreachable(unit)) {
// Removing unreachable incoming edges can change types, so if we changed
// anything we have to reflow to maintain that IR invariant.
reflowTypes(unit);
}
assertx(checkEverything(unit));
}
void fullDCE(IRUnit& unit) {
if (!RuntimeOption::EvalHHIRDeadCodeElim) {
// This portion of DCE cannot be turned off, because it restores IR
// invariants, and callers of fullDCE are allowed to rely on it for that.
return mandatoryDCE(unit);
}
Timer dceTimer(Timer::optimize_dce);
// kill unreachable code and remove any traces that are now empty
auto const blocks = prepareBlocks(unit);
// At this point, all IR invariants must hold, because we've restored the
// only one allowed to be violated before fullDCE in prepareBlocks.
assertx(checkEverything(unit));
// mark the essential instructions and add them to the initial
// work list; this will also mark reachable exit traces. All
// other instructions marked dead.
DceState state(unit, DceFlags());
WorkList wl = initInstructions(unit, blocks, state);
UseCounts uses(unit, 0);
jit::fast_map<IRInstruction*, TrackedInstr> rcInsts;
DefLabelLiveness defLabelLive;
jit::vector<IRInstruction*> concats;
// process the worklist
while (!wl.empty()) {
auto const [inst, defLabelIdx] = wl.back();
wl.pop_back();
assertx(!state[inst].isDead());
// Jmps which feed a DefLabel are dealt with as part of processing
// the DefLabel. They should never appear on the worklist.
assertx(IMPLIES(inst->is(Jmp), inst->numSrcs() == 0));
assertx(IMPLIES(!inst->is(DefLabel), defLabelIdx == 0));
auto const process = [&] (IRInstruction* inst, uint32_t ix) {
if (inst->is(ConcatStrStr)) concats.emplace_back(inst);
auto const src = inst->src(ix);
IRInstruction* srcInst = src->inst();
if (srcInst->op() == DefConst) return;
if (srcInst->producesReference() && canDCE(*srcInst)) {
++uses[src];
if (inst->is(DecRef)) {
rcInsts[srcInst].decs.emplace_back(inst);
}
if (inst->is(InitVecElem, InitStructElem, InitStructPositions,
StClosureArg)) {
if (ix == 0) rcInsts[srcInst].aux.emplace_back(inst);
}
}
if (srcInst->is(DefLabel)) {
// If the source instruction is a DefLabel, figure out which
// operand this SSATmp corresponds to, and schedule that
// particular operand.
auto dstIdx = 0;
for (; dstIdx < srcInst->numDsts(); dstIdx++) {
if (src == srcInst->dst(dstIdx)) break;
}
assertx(dstIdx != srcInst->numDsts());
// If the operand isn't already live, schedule it.
if (defLabelLive.emplace(std::make_pair(srcInst, dstIdx)).second) {
state[srcInst].setLive();
wl.push_back(std::make_pair(srcInst, dstIdx));
}
} else if (state[srcInst].isDead()) {
state[srcInst].setLive();
wl.push_back(std::make_pair(srcInst, 0));
}
};
if (inst->is(DefLabel)) {
// For a DefLabel operand, look "through" each corresponding Jmp
// and process the source as if we were processing that Jmp.
assertx(defLabelIdx < inst->numDsts());
assertx(defLabelLive.count(std::make_pair(inst, defLabelIdx)));
inst->block()->forEachPred(
[&, inst = inst, defLabelIdx = defLabelIdx] (Block* pred) {
auto& jmp = pred->back();
assertx(jmp.is(Jmp));
assertx(jmp.numSrcs() == inst->numDsts());
state[&jmp].setLive();
process(&jmp, defLabelIdx);
}
);
continue;
}
// For a normal instruction, just process each source
for (uint32_t ix = 0; ix < inst->numSrcs(); ++ix) process(inst, ix);
}
optimizeCatchBlocks(blocks, state, unit, uses, rcInsts);
optimizeConcats(concats, state, unit, uses, rcInsts);
// If every use of a dce-able PRc instruction is a DecRef or PureStore based
// on its dst, then we can kill it, and DecRef any of its consumesReference
// inputs.
for (auto& pair : rcInsts) {
auto& info = pair.second;
auto const trackedUses =
info.decs.size() + info.aux.size() + info.stores.size();
if (uses[pair.first->dst()] != trackedUses) continue;
killInstrAdjustRC(state, unit, pair.first, info.decs);
for (auto inst : info.aux) killInstrAdjustRC(state, unit, inst, info.decs);
for (auto store : info.stores) store->setSrc(1, unit.cns(TInitNull));
}
// Now remove instructions whose state is DEAD.
removeDeadInstructions(unit, state, defLabelLive);
// Kill unreachable catch blocks.
mandatoryDCE(unit);
}
}