243 lines
7.5 KiB
C++
243 lines
7.5 KiB
C++
//===--- RDFDeadCode.cpp --------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// RDF-based generic dead code elimination.
|
|
|
|
#include "RDFDeadCode.h"
|
|
|
|
#include "llvm/ADT/SetVector.h"
|
|
#include "llvm/CodeGen/MachineBasicBlock.h"
|
|
#include "llvm/CodeGen/MachineFunction.h"
|
|
#include "llvm/CodeGen/MachineRegisterInfo.h"
|
|
#include "llvm/CodeGen/RDFGraph.h"
|
|
#include "llvm/CodeGen/RDFLiveness.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
#include <queue>
|
|
|
|
using namespace llvm;
|
|
using namespace rdf;
|
|
|
|
// This drastically improves execution time in "collect" over using
|
|
// SetVector as a work queue, and popping the first element from it.
|
|
template<typename T> struct DeadCodeElimination::SetQueue {
|
|
SetQueue() : Set(), Queue() {}
|
|
|
|
bool empty() const {
|
|
return Queue.empty();
|
|
}
|
|
T pop_front() {
|
|
T V = Queue.front();
|
|
Queue.pop();
|
|
Set.erase(V);
|
|
return V;
|
|
}
|
|
void push_back(T V) {
|
|
if (Set.count(V))
|
|
return;
|
|
Queue.push(V);
|
|
Set.insert(V);
|
|
}
|
|
|
|
private:
|
|
DenseSet<T> Set;
|
|
std::queue<T> Queue;
|
|
};
|
|
|
|
|
|
// Check if the given instruction has observable side-effects, i.e. if
|
|
// it should be considered "live". It is safe for this function to be
|
|
// overly conservative (i.e. return "true" for all instructions), but it
|
|
// is not safe to return "false" for an instruction that should not be
|
|
// considered removable.
|
|
bool DeadCodeElimination::isLiveInstr(const MachineInstr *MI) const {
|
|
if (MI->mayStore() || MI->isBranch() || MI->isCall() || MI->isReturn())
|
|
return true;
|
|
if (MI->hasOrderedMemoryRef() || MI->hasUnmodeledSideEffects() ||
|
|
MI->isPosition())
|
|
return true;
|
|
if (MI->isPHI())
|
|
return false;
|
|
for (auto &Op : MI->operands()) {
|
|
if (Op.isReg() && MRI.isReserved(Op.getReg()))
|
|
return true;
|
|
if (Op.isRegMask()) {
|
|
const uint32_t *BM = Op.getRegMask();
|
|
for (unsigned R = 0, RN = DFG.getTRI().getNumRegs(); R != RN; ++R) {
|
|
if (BM[R/32] & (1u << (R%32)))
|
|
continue;
|
|
if (MRI.isReserved(R))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DeadCodeElimination::scanInstr(NodeAddr<InstrNode*> IA,
|
|
SetQueue<NodeId> &WorkQ) {
|
|
if (!DFG.IsCode<NodeAttrs::Stmt>(IA))
|
|
return;
|
|
if (!isLiveInstr(NodeAddr<StmtNode*>(IA).Addr->getCode()))
|
|
return;
|
|
for (NodeAddr<RefNode*> RA : IA.Addr->members(DFG)) {
|
|
if (!LiveNodes.count(RA.Id))
|
|
WorkQ.push_back(RA.Id);
|
|
}
|
|
}
|
|
|
|
void DeadCodeElimination::processDef(NodeAddr<DefNode*> DA,
|
|
SetQueue<NodeId> &WorkQ) {
|
|
NodeAddr<InstrNode*> IA = DA.Addr->getOwner(DFG);
|
|
for (NodeAddr<UseNode*> UA : IA.Addr->members_if(DFG.IsUse, DFG)) {
|
|
if (!LiveNodes.count(UA.Id))
|
|
WorkQ.push_back(UA.Id);
|
|
}
|
|
for (NodeAddr<DefNode*> TA : DFG.getRelatedRefs(IA, DA))
|
|
LiveNodes.insert(TA.Id);
|
|
}
|
|
|
|
void DeadCodeElimination::processUse(NodeAddr<UseNode*> UA,
|
|
SetQueue<NodeId> &WorkQ) {
|
|
for (NodeAddr<DefNode*> DA : LV.getAllReachingDefs(UA)) {
|
|
if (!LiveNodes.count(DA.Id))
|
|
WorkQ.push_back(DA.Id);
|
|
}
|
|
}
|
|
|
|
// Traverse the DFG and collect the set dead RefNodes and the set of
|
|
// dead instructions. Return "true" if any of these sets is non-empty,
|
|
// "false" otherwise.
|
|
bool DeadCodeElimination::collect() {
|
|
// This function works by first finding all live nodes. The dead nodes
|
|
// are then the complement of the set of live nodes.
|
|
//
|
|
// Assume that all nodes are dead. Identify instructions which must be
|
|
// considered live, i.e. instructions with observable side-effects, such
|
|
// as calls and stores. All arguments of such instructions are considered
|
|
// live. For each live def, all operands used in the corresponding
|
|
// instruction are considered live. For each live use, all its reaching
|
|
// defs are considered live.
|
|
LiveNodes.clear();
|
|
SetQueue<NodeId> WorkQ;
|
|
for (NodeAddr<BlockNode*> BA : DFG.getFunc().Addr->members(DFG))
|
|
for (NodeAddr<InstrNode*> IA : BA.Addr->members(DFG))
|
|
scanInstr(IA, WorkQ);
|
|
|
|
while (!WorkQ.empty()) {
|
|
NodeId N = WorkQ.pop_front();
|
|
LiveNodes.insert(N);
|
|
auto RA = DFG.addr<RefNode*>(N);
|
|
if (DFG.IsDef(RA))
|
|
processDef(RA, WorkQ);
|
|
else
|
|
processUse(RA, WorkQ);
|
|
}
|
|
|
|
if (trace()) {
|
|
dbgs() << "Live nodes:\n";
|
|
for (NodeId N : LiveNodes) {
|
|
auto RA = DFG.addr<RefNode*>(N);
|
|
dbgs() << PrintNode<RefNode*>(RA, DFG) << "\n";
|
|
}
|
|
}
|
|
|
|
auto IsDead = [this] (NodeAddr<InstrNode*> IA) -> bool {
|
|
for (NodeAddr<DefNode*> DA : IA.Addr->members_if(DFG.IsDef, DFG))
|
|
if (LiveNodes.count(DA.Id))
|
|
return false;
|
|
return true;
|
|
};
|
|
|
|
for (NodeAddr<BlockNode*> BA : DFG.getFunc().Addr->members(DFG)) {
|
|
for (NodeAddr<InstrNode*> IA : BA.Addr->members(DFG)) {
|
|
for (NodeAddr<RefNode*> RA : IA.Addr->members(DFG))
|
|
if (!LiveNodes.count(RA.Id))
|
|
DeadNodes.insert(RA.Id);
|
|
if (DFG.IsCode<NodeAttrs::Stmt>(IA))
|
|
if (isLiveInstr(NodeAddr<StmtNode*>(IA).Addr->getCode()))
|
|
continue;
|
|
if (IsDead(IA)) {
|
|
DeadInstrs.insert(IA.Id);
|
|
if (trace())
|
|
dbgs() << "Dead instr: " << PrintNode<InstrNode*>(IA, DFG) << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
return !DeadNodes.empty();
|
|
}
|
|
|
|
// Erase the nodes given in the Nodes set from DFG. In addition to removing
|
|
// them from the DFG, if a node corresponds to a statement, the corresponding
|
|
// machine instruction is erased from the function.
|
|
bool DeadCodeElimination::erase(const SetVector<NodeId> &Nodes) {
|
|
if (Nodes.empty())
|
|
return false;
|
|
|
|
// Prepare the actual set of ref nodes to remove: ref nodes from Nodes
|
|
// are included directly, for each InstrNode in Nodes, include the set
|
|
// of all RefNodes from it.
|
|
NodeList DRNs, DINs;
|
|
for (auto I : Nodes) {
|
|
auto BA = DFG.addr<NodeBase*>(I);
|
|
uint16_t Type = BA.Addr->getType();
|
|
if (Type == NodeAttrs::Ref) {
|
|
DRNs.push_back(DFG.addr<RefNode*>(I));
|
|
continue;
|
|
}
|
|
|
|
// If it's a code node, add all ref nodes from it.
|
|
uint16_t Kind = BA.Addr->getKind();
|
|
if (Kind == NodeAttrs::Stmt || Kind == NodeAttrs::Phi) {
|
|
append_range(DRNs, NodeAddr<CodeNode*>(BA).Addr->members(DFG));
|
|
DINs.push_back(DFG.addr<InstrNode*>(I));
|
|
} else {
|
|
llvm_unreachable("Unexpected code node");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Sort the list so that use nodes are removed first. This makes the
|
|
// "unlink" functions a bit faster.
|
|
auto UsesFirst = [] (NodeAddr<RefNode*> A, NodeAddr<RefNode*> B) -> bool {
|
|
uint16_t KindA = A.Addr->getKind(), KindB = B.Addr->getKind();
|
|
if (KindA == NodeAttrs::Use && KindB == NodeAttrs::Def)
|
|
return true;
|
|
if (KindA == NodeAttrs::Def && KindB == NodeAttrs::Use)
|
|
return false;
|
|
return A.Id < B.Id;
|
|
};
|
|
llvm::sort(DRNs, UsesFirst);
|
|
|
|
if (trace())
|
|
dbgs() << "Removing dead ref nodes:\n";
|
|
for (NodeAddr<RefNode*> RA : DRNs) {
|
|
if (trace())
|
|
dbgs() << " " << PrintNode<RefNode*>(RA, DFG) << '\n';
|
|
if (DFG.IsUse(RA))
|
|
DFG.unlinkUse(RA, true);
|
|
else if (DFG.IsDef(RA))
|
|
DFG.unlinkDef(RA, true);
|
|
}
|
|
|
|
// Now, remove all dead instruction nodes.
|
|
for (NodeAddr<InstrNode*> IA : DINs) {
|
|
NodeAddr<BlockNode*> BA = IA.Addr->getOwner(DFG);
|
|
BA.Addr->removeMember(IA, DFG);
|
|
if (!DFG.IsCode<NodeAttrs::Stmt>(IA))
|
|
continue;
|
|
|
|
MachineInstr *MI = NodeAddr<StmtNode*>(IA).Addr->getCode();
|
|
if (trace())
|
|
dbgs() << "erasing: " << *MI;
|
|
MI->eraseFromParent();
|
|
}
|
|
return true;
|
|
}
|