x86CodeOptimizer_DeadCodeCleaner(BEx86FunctionBuilder *builder): m_builder(builder) { for (int pass = 0; pass < 2; ++pass) { redirectJmpTarget(); removeDeadInstructions(); removeTailJmp(); } }
void eliminateDeadCode(Trace* trace, IRFactory* irFactory) { IRInstruction::List wl; // worklist of live instructions Trace::List& exitTraces = trace->getExitTraces(); // first mark all exit traces as unreachable by setting the id on // their labels to 0 for (Trace::Iterator it = exitTraces.begin(); it != exitTraces.end(); it++) { Trace* trace = *it; trace->getLabel()->setId(DEAD); } // mark the essential instructions and add them to the initial // work list; also mark the exit traces that are reachable by // any control flow instruction in the main trace. initInstructions(trace, wl); for (Trace::Iterator it = exitTraces.begin(); it != exitTraces.end(); it++) { // only process those exit traces that are reachable from // the main trace Trace* trace = *it; if (trace->getLabel()->getId() != DEAD) { initInstructions(trace, wl); } } // process the worklist while (!wl.empty()) { IRInstruction* inst = wl.front(); wl.pop_front(); for (uint32 i = 0; i < inst->getNumSrcs(); i++) { SSATmp* src = inst->getSrc(i); if (src->getInstruction()->isDefConst()) { continue; } IRInstruction* srcInst = src->getInstruction(); if (srcInst->getId() == DEAD) { srcInst->setId(LIVE); wl.push_back(srcInst); } // <inst> consumes <srcInst> which is an IncRef, // so we mark <srcInst> as REFCOUNT_CONSUMED. if (inst->consumesReference(i) && srcInst->getOpcode() == IncRef) { if (inst->getParent()->isMain() || !srcInst->getParent()->isMain()) { // <srcInst> is consumed from its own trace. srcInst->setId(REFCOUNT_CONSUMED); } else { // <srcInst> is consumed off trace. if (srcInst->getId() != REFCOUNT_CONSUMED) { // mark <srcInst> as REFCOUNT_CONSUMED_OFF_TRACE unless it is // also consumed from its own trace. srcInst->setId(REFCOUNT_CONSUMED_OFF_TRACE); } } } } } // Optimize IncRefs and DecRefs. optimizeRefCount(trace); for (Trace::Iterator it = exitTraces.begin(); it != exitTraces.end(); ++it) { optimizeRefCount(*it); } if (RuntimeOption::EvalHHIREnableSinking) { // Sink IncRefs consumed off trace. IRInstruction::List toSink; sinkIncRefs(trace, irFactory, toSink); } // now remove instructions whose id == DEAD removeDeadInstructions(trace); for (Trace::Iterator it = exitTraces.begin(); it != exitTraces.end(); it++) { removeDeadInstructions(*it); } // If main trace ends with an unconditional jump, copy the target of // the jump to the end of the trace IRInstruction::List& instList = trace->getInstructionList(); IRInstruction::Iterator lastInst = instList.end(); lastInst--; // go back to the last instruction IRInstruction* jmpInst = *lastInst; if (jmpInst->getOpcode() == Jmp_) { Trace* targetTrace = jmpInst->getLabel()->getTrace(); IRInstruction::List& targetInstList = targetTrace->getInstructionList(); IRInstruction::Iterator instIter = targetInstList.begin(); instIter++; // skip over label // update the parent trace of the moved instructions for (IRInstruction::Iterator it = instIter; it != targetInstList.end(); ++it) { (*it)->setParent(trace); } instList.splice(lastInst, targetInstList, instIter, targetInstList.end()); // delete the jump instruction instList.erase(lastInst); } // If main trace ends with a conditional jump with no side-effects on exit, // hook it to the exitTrace and make it a TraceExitType::NormalCc if (RuntimeOption::EvalHHIRDirectExit) { IRInstruction::List& instList = trace->getInstructionList(); IRInstruction::Iterator tail = instList.end(); IRInstruction* jccInst = NULL; IRInstruction* exitInst = NULL; IRInstruction* exitCcInst = NULL; Opcode opc = OpAdd; // Normally Jcc comes before a Marker for (int idx = 3; idx >= 0; idx--) { tail--; // go back to the previous instruction IRInstruction* inst = *tail; opc = inst->getOpcode(); if (opc == ExitTrace) { exitInst = *tail; continue; } if (opc == Marker) { continue; } if (jccCanBeDirectExit(opc)) { jccInst = inst; break; } break; } if (jccCanBeDirectExit(opc)) { SSATmp* dst = jccInst->getDst(); Trace* targetTrace = jccInst->getLabel()->getTrace(); IRInstruction::List& targetInstList = targetTrace->getInstructionList(); IRInstruction::Iterator targetInstIter = targetInstList.begin(); targetInstIter++; // skip over label // Check for a NormalCc exit with no side effects for (IRInstruction::Iterator it = targetInstIter; it != targetInstList.end(); ++it) { IRInstruction* instr = (*it); // Extend to support ExitSlow, ExitSlowNoProgress, ... Opcode opc = instr->getOpcode(); if (opc == ExitTraceCc) { exitCcInst = instr; break; } else if (opc == Marker) { continue; } else { // Do not optimize if there are other instructions break; } } if (exitInst && exitCcInst && exitCcInst->getNumSrcs() > NUM_FIXED_SRCS && exitInst->getNumSrcs() > NUM_FIXED_SRCS) { // Found both exits, link them to Jcc for codegen ASSERT(dst); ExtendedInstruction* exCcInst = (ExtendedInstruction*)exitCcInst; exCcInst->appendExtendedSrc(*irFactory, dst); ExtendedInstruction* exInst = (ExtendedInstruction*)exitInst; exInst->appendExtendedSrc(*irFactory, dst); // Set flag so Jcc and exits know this is active dst->setTCA(kIRDirectJccJmpActive); } } } // If main trace starts with guards, have them generate a patchable jump // to the anchor trace if (RuntimeOption::EvalHHIRDirectExit) { LabelInstruction* guardLabel = NULL; IRInstruction::List& instList = trace->getInstructionList(); // Check the beginning of the trace for guards for (IRInstruction::Iterator it = instList.begin(); it != instList.end(); ++it) { IRInstruction* inst = *it; Opcode opc = inst->getOpcode(); if (inst->getLabel() && (opc == LdLoc || opc == LdStack || opc == GuardLoc || opc == GuardStk)) { LabelInstruction* exitLabel = inst->getLabel(); // Find the GuardFailure's label and confirm this branches there if (guardLabel == NULL) { Trace* exitTrace = exitLabel->getTrace(); IRInstruction::List& xList = exitTrace->getInstructionList(); IRInstruction::Iterator instIter = xList.begin(); instIter++; // skip over label // Confirm this is a GuardExit for (IRInstruction::Iterator it = instIter; it != xList.end(); ++it) { IRInstruction* i = *it; Opcode op = i->getOpcode(); if (op == Marker) { continue; } if (op == ExitGuardFailure) { guardLabel = exitLabel; } // Do not optimize if other instructions are on exit trace break; } } if (exitLabel == guardLabel) { inst->setTCA(kIRDirectGuardActive); continue; } break; } if (opc == Marker || opc == DefLabel || opc == DefSP || opc == DefFP || opc == LdStack) { continue; } break; } } }
void MemMap::optimizeMemoryAccesses(Trace* trace) { StoreList tracking; for (IRInstruction* inst : trace->getInstructionList()) { // initialize each instruction as live inst->setId(LIVE); int offset = -1; Opcode op = inst->getOpcode(); if (isLoad(op)) { if (op == LdProp) { offset = inst->getSrc(1)->getConstValAsInt(); } optimizeLoad(inst, offset); } else if (isStore(op)) { if (op == StProp || op == StPropNT) { offset = inst->getSrc(1)->getConstValAsInt(); } // if we see a store, first check if its last available access is a store // if it is, then the last access is a dead store IRInstruction* access = getLastAccess(inst->getSrc(0), offset); if (access != NULL && isStore(access->getOpcode())) { // if a dead St* is followed by a St*NT, then the second store needs to // now write in the type because the first store will be removed if (access->getOpcode() == StProp && op == StPropNT) { inst->setOpcode(StProp); } else if (access->getOpcode() == StLoc && op == StLocNT) { inst->setOpcode(StLoc); } else if (access->getOpcode() == StRef && op == StRefNT) { inst->setOpcode(StRef); } access->setId(DEAD); } // start tracking the current store tracking.push_back(std::make_pair(inst, std::vector<IRInstruction*>())); } else if (inst->mayRaiseError()) { // if the function has an exit edge that we don't know anything about // (raising an error), then all stores we're currently tracking need to // be erased. all stores already declared dead are untouched StoreList::iterator it, end; for (it = tracking.begin(), end = tracking.end(); it != end; ) { StoreList::iterator copy = it; ++it; if (copy->first->getId() != DEAD) { // XXX: t1779667 tracking.erase(copy); } } } // if the current instruction is guarded, make sure all of our stores that // are not yet dead know about it if (inst->getLabel() != NULL) { for (auto& entry : tracking) { if (entry.first->getId() != DEAD) { entry.second.push_back(inst); } } } Simplifier::copyProp(inst); processInstruction(inst); } sinkStores(tracking); // kill the dead stores removeDeadInstructions(trace); }
void MemMap::optimizeMemoryAccesses(Trace* trace) { if (hasInternalFlow(trace)) { // This algorithm only works with linear traces. // TODO t2066994: reset state after each block, at least. return; } StoreList tracking; const Func* curFunc = nullptr; for (Block* block : trace->getBlocks()) { for (IRInstruction& inst : *block) { if (inst.getOpcode() == Marker) { curFunc = inst.getExtra<Marker>()->func; } // initialize each instruction as live setLive(inst, true); int offset = -1; Opcode op = inst.getOpcode(); if (isLoad(op)) { if (op == LdProp) { offset = inst.getSrc(1)->getValInt(); } // initialize each instruction as live setLive(inst, true); optimizeLoad(&inst, offset); } else if (isStore(op)) { if (op == StProp || op == StPropNT) { offset = inst.getSrc(1)->getValInt(); } // if we see a store, first check if its last available access is a store // if it is, then the last access is a dead store auto access = inst.getOpcode() == StLoc || inst.getOpcode() == StLocNT ? lastLocalAccess(inst.getExtra<LocalId>()->locId) : getLastAccess(inst.getSrc(0), offset); if (access && isStore(access->getOpcode())) { // if a dead St* is followed by a St*NT, then the second store needs to // now write in the type because the first store will be removed if (access->getOpcode() == StProp && op == StPropNT) { inst.setOpcode(StProp); } else if (access->getOpcode() == StLoc && op == StLocNT) { inst.setOpcode(StLoc); } else if (access->getOpcode() == StRef && op == StRefNT) { inst.setOpcode(StRef); } setLive(*access, false); } // start tracking the current store tracking.push_back(std::make_pair(&inst, std::vector<IRInstruction*>())); } else if (inst.mayRaiseError()) { // if the function has an exit edge that we don't know anything about // (raising an error), then all stores we're currently tracking need to // be erased. all stores already declared dead are untouched StoreList::iterator it, end; for (it = tracking.begin(), end = tracking.end(); it != end; ) { StoreList::iterator copy = it; ++it; if (isLive(copy->first)) { // XXX: t1779667 tracking.erase(copy); } } } // if the current instruction is guarded, make sure all of our stores that // are not yet dead know about it if (inst.getTaken()) { for (auto& entry : tracking) { if (isLive(entry.first)) { entry.second.push_back(&inst); } } } Simplifier::copyProp(&inst); processInstruction(&inst, curFunc && curFunc->isPseudoMain()); } } sinkStores(tracking); // kill the dead stores removeDeadInstructions(trace, m_liveInsts); }
void eliminateDeadCode(Trace* trace, IRFactory* irFactory) { IRInstruction::List wl; // worklist of live instructions Trace::List& exitTraces = trace->getExitTraces(); // first mark all exit traces as unreachable by setting the id on // their labels to 0 for (Trace::Iterator it = exitTraces.begin(); it != exitTraces.end(); it++) { Trace* trace = *it; trace->getLabel()->setId(DEAD); } // mark the essential instructions and add them to the initial // work list; also mark the exit traces that are reachable by // any control flow instruction in the main trace. initInstructions(trace, wl); for (Trace::Iterator it = exitTraces.begin(); it != exitTraces.end(); it++) { // only process those exit traces that are reachable from // the main trace Trace* trace = *it; if (trace->getLabel()->getId() != DEAD) { initInstructions(trace, wl); } } // process the worklist while (!wl.empty()) { IRInstruction* inst = wl.front(); wl.pop_front(); for (uint32 i = 0; i < inst->getNumSrcs(); i++) { SSATmp* src = inst->getSrc(i); if (src->getInstruction()->isDefConst()) { continue; } IRInstruction* srcInst = src->getInstruction(); if (srcInst->getId() == DEAD) { srcInst->setId(LIVE); wl.push_back(srcInst); } // <inst> consumes <srcInst> which is an IncRef, // so we mark <srcInst> as REFCOUNT_CONSUMED. if (inst->consumesReference(i) && srcInst->getOpcode() == IncRef) { if (inst->getParent()->isMain() || !srcInst->getParent()->isMain()) { // <srcInst> is consumed from its own trace. srcInst->setId(REFCOUNT_CONSUMED); } else { // <srcInst> is consumed off trace. if (srcInst->getId() != REFCOUNT_CONSUMED) { // mark <srcInst> as REFCOUNT_CONSUMED_OFF_TRACE unless it is // also consumed from its own trace. srcInst->setId(REFCOUNT_CONSUMED_OFF_TRACE); } } } } } // Optimize IncRefs and DecRefs. optimizeRefCount(trace); for (Trace::Iterator it = exitTraces.begin(); it != exitTraces.end(); ++it) { optimizeRefCount(*it); } if (RuntimeOption::EvalHHIREnableSinking) { // Sink IncRefs consumed off trace. IRInstruction::List toSink; sinkIncRefs(trace, irFactory, toSink); } // now remove instructions whose id == DEAD removeDeadInstructions(trace); for (Trace::Iterator it = exitTraces.begin(); it != exitTraces.end(); it++) { removeDeadInstructions(*it); } }
void eliminateDeadCode(Trace* trace, IRFactory* irFactory) { auto removeEmptyExitTraces = [&] { trace->getExitTraces().remove_if([](Trace* exit) { return exit->getBlocks().empty(); }); }; // kill unreachable code and remove any traces that are now empty BlockList blocks = removeUnreachable(trace, irFactory); removeEmptyExitTraces(); // 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(irFactory, DceFlags()); WorkList wl = initInstructions(trace, blocks, state, irFactory); // process the worklist while (!wl.empty()) { auto* inst = wl.front(); wl.pop_front(); for (uint32_t i = 0; i < inst->getNumSrcs(); i++) { SSATmp* src = inst->getSrc(i); if (src->getInstruction()->getOpcode() == DefConst) { continue; } IRInstruction* srcInst = src->getInstruction(); if (state[srcInst].isDead()) { state[srcInst].setLive(); wl.push_back(srcInst); } // <inst> consumes <srcInst> which is an IncRef, so we mark <srcInst> as // REFCOUNT_CONSUMED. If the source instruction is a GuardType and guards // to a maybeCounted type, we need to trace through to the source for // refcounting purposes. while (srcInst->getOpcode() == GuardType && srcInst->getTypeParam().maybeCounted()) { srcInst = srcInst->getSrc(0)->getInstruction(); } if (inst->consumesReference(i) && srcInst->getOpcode() == IncRef) { if (inst->getTrace()->isMain() || !srcInst->getTrace()->isMain()) { // <srcInst> is consumed from its own trace. state[srcInst].setCountConsumed(); } else { // <srcInst> is consumed off trace. if (!state[srcInst].countConsumed()) { // mark <srcInst> as REFCOUNT_CONSUMED_OFF_TRACE unless it is // also consumed from its own trace. state[srcInst].setCountConsumedOffTrace(); } } } } } // Optimize IncRefs and DecRefs. forEachTrace(trace, [&](Trace* t) { optimizeRefCount(t, state); }); if (RuntimeOption::EvalHHIREnableSinking) { // Sink IncRefs consumed off trace. sinkIncRefs(trace, irFactory, state); } // now remove instructions whose id == DEAD removeDeadInstructions(trace, state); for (Trace* exit : trace->getExitTraces()) { removeDeadInstructions(exit, state); } // and remove empty exit traces removeEmptyExitTraces(); }