mirror of https://github.com/llvm/circt.git
804 lines
28 KiB
C++
804 lines
28 KiB
C++
//===- IMDeadCodeElim.cpp - Intermodule Dead Code Elimination ---*- C++ -*-===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "PassDetails.h"
|
|
#include "circt/Dialect/FIRRTL/AnnotationDetails.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h"
|
|
#include "circt/Dialect/FIRRTL/Passes.h"
|
|
#include "circt/Dialect/HW/HWOps.h"
|
|
#include "circt/Dialect/HW/InnerSymbolTable.h"
|
|
#include "circt/Support/Debug.h"
|
|
#include "mlir/IR/ImplicitLocOpBuilder.h"
|
|
#include "mlir/IR/Threading.h"
|
|
#include "mlir/Interfaces/SideEffectInterfaces.h"
|
|
#include "llvm/ADT/BitVector.h"
|
|
#include "llvm/ADT/DenseMapInfoVariant.h"
|
|
#include "llvm/ADT/PostOrderIterator.h"
|
|
#include "llvm/ADT/TinyPtrVector.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
#define DEBUG_TYPE "firrtl-imdeadcodeelim"
|
|
|
|
using namespace circt;
|
|
using namespace firrtl;
|
|
|
|
// Return true if this op has side-effects except for alloc and read.
|
|
static bool hasUnknownSideEffect(Operation *op) {
|
|
return !(mlir::isMemoryEffectFree(op) ||
|
|
mlir::hasSingleEffect<mlir::MemoryEffects::Allocate>(op) ||
|
|
mlir::hasSingleEffect<mlir::MemoryEffects::Read>(op));
|
|
}
|
|
|
|
/// Return true if this is a wire or a register or a node.
|
|
static bool isDeclaration(Operation *op) {
|
|
return isa<WireOp, RegResetOp, RegOp, NodeOp, MemOp>(op);
|
|
}
|
|
|
|
/// Return true if this is a wire or register we're allowed to delete.
|
|
static bool isDeletableDeclaration(Operation *op) {
|
|
if (auto name = dyn_cast<FNamableOp>(op))
|
|
if (!name.hasDroppableName())
|
|
return false;
|
|
return !hasDontTouch(op) && AnnotationSet(op).canBeDeleted();
|
|
}
|
|
|
|
namespace {
|
|
struct IMDeadCodeElimPass : public IMDeadCodeElimBase<IMDeadCodeElimPass> {
|
|
void runOnOperation() override;
|
|
|
|
void rewriteModuleSignature(FModuleOp module);
|
|
void rewriteModuleBody(FModuleOp module);
|
|
void eraseEmptyModule(FModuleOp module);
|
|
void forwardConstantOutputPort(FModuleOp module);
|
|
|
|
/// Return true if the value is known alive.
|
|
bool isKnownAlive(Value value) const {
|
|
assert(value && "null should not be used");
|
|
return liveElements.count(value);
|
|
}
|
|
|
|
/// Return true if the value is assumed dead.
|
|
bool isAssumedDead(Value value) const { return !isKnownAlive(value); }
|
|
bool isAssumedDead(Operation *op) const {
|
|
return llvm::none_of(op->getResults(),
|
|
[&](Value value) { return isKnownAlive(value); });
|
|
}
|
|
|
|
/// Return true if the block is alive.
|
|
bool isBlockExecutable(Block *block) const {
|
|
return executableBlocks.count(block);
|
|
}
|
|
|
|
void visitUser(Operation *op);
|
|
void visitValue(Value value);
|
|
|
|
void visitConnect(FConnectLike connect);
|
|
void visitSubelement(Operation *op);
|
|
void markBlockExecutable(Block *block);
|
|
void markBlockUndeletable(Operation *op) {
|
|
markAlive(op->getParentOfType<FModuleOp>());
|
|
}
|
|
|
|
void markDeclaration(Operation *op);
|
|
void markInstanceOp(InstanceOp instanceOp);
|
|
void markObjectOp(ObjectOp objectOp);
|
|
void markUnknownSideEffectOp(Operation *op);
|
|
void visitInstanceOp(InstanceOp instance);
|
|
void visitHierPathOp(hw::HierPathOp hierpath);
|
|
void visitModuleOp(FModuleOp module);
|
|
|
|
private:
|
|
/// The set of blocks that are known to execute, or are intrinsically alive.
|
|
DenseSet<Block *> executableBlocks;
|
|
|
|
InstanceGraph *instanceGraph;
|
|
|
|
// The type with which we associate liveness.
|
|
using ElementType =
|
|
std::variant<Value, FModuleOp, InstanceOp, hw::HierPathOp>;
|
|
|
|
void markAlive(ElementType element) {
|
|
if (!liveElements.insert(element).second)
|
|
return;
|
|
worklist.push_back(element);
|
|
}
|
|
|
|
/// A worklist of values whose liveness recently changed, indicating
|
|
/// the users need to be reprocessed.
|
|
SmallVector<ElementType, 64> worklist;
|
|
llvm::DenseSet<ElementType> liveElements;
|
|
|
|
/// A map from instances to hierpaths whose last path is the associated
|
|
/// instance.
|
|
DenseMap<InstanceOp, SmallVector<hw::HierPathOp>> instanceToHierPaths;
|
|
|
|
/// Hierpath to its users (=non-local annotation targets).
|
|
DenseMap<hw::HierPathOp, SetVector<ElementType>> hierPathToElements;
|
|
|
|
/// A cache for a (inner)symbol lookp.
|
|
circt::hw::InnerRefNamespace *innerRefNamespace;
|
|
mlir::SymbolTable *symbolTable;
|
|
};
|
|
} // namespace
|
|
|
|
void IMDeadCodeElimPass::visitInstanceOp(InstanceOp instance) {
|
|
markBlockUndeletable(instance);
|
|
|
|
auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
|
|
|
|
if (!module)
|
|
return;
|
|
|
|
// NOTE: Don't call `markAlive(module)` here as liveness of instance doesn't
|
|
// imply the global liveness of the module.
|
|
|
|
// Propgate liveness through hierpath.
|
|
for (auto hierPath : instanceToHierPaths[instance])
|
|
markAlive(hierPath);
|
|
|
|
// Input ports get alive only when the instance is alive.
|
|
for (auto &blockArg : module.getBody().getArguments()) {
|
|
auto portNo = blockArg.getArgNumber();
|
|
if (module.getPortDirection(portNo) == Direction::In &&
|
|
isKnownAlive(module.getArgument(portNo)))
|
|
markAlive(instance.getResult(portNo));
|
|
}
|
|
}
|
|
|
|
void IMDeadCodeElimPass::visitModuleOp(FModuleOp module) {
|
|
// If the module needs to be alive, so are its instances.
|
|
for (auto *use : instanceGraph->lookup(module)->uses())
|
|
markAlive(cast<InstanceOp>(*use->getInstance()));
|
|
}
|
|
|
|
void IMDeadCodeElimPass::visitHierPathOp(hw::HierPathOp hierPathOp) {
|
|
// If the hierpath is alive, mark all instances on the path alive.
|
|
for (auto path : hierPathOp.getNamepathAttr())
|
|
if (auto innerRef = path.dyn_cast<hw::InnerRefAttr>()) {
|
|
auto *op = innerRefNamespace->lookupOp(innerRef);
|
|
if (auto instance = dyn_cast_or_null<InstanceOp>(op))
|
|
markAlive(instance);
|
|
}
|
|
|
|
for (auto elem : hierPathToElements[hierPathOp])
|
|
markAlive(elem);
|
|
}
|
|
|
|
void IMDeadCodeElimPass::markDeclaration(Operation *op) {
|
|
assert(isDeclaration(op) && "only a declaration is expected");
|
|
if (!isDeletableDeclaration(op)) {
|
|
for (auto result : op->getResults())
|
|
markAlive(result);
|
|
markBlockUndeletable(op);
|
|
}
|
|
}
|
|
|
|
void IMDeadCodeElimPass::markUnknownSideEffectOp(Operation *op) {
|
|
// For operations with side effects, pessimistically mark results and
|
|
// operands as alive.
|
|
for (auto result : op->getResults())
|
|
markAlive(result);
|
|
for (auto operand : op->getOperands())
|
|
markAlive(operand);
|
|
markBlockUndeletable(op);
|
|
}
|
|
|
|
void IMDeadCodeElimPass::visitUser(Operation *op) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Visit: " << *op << "\n");
|
|
if (auto connectOp = dyn_cast<FConnectLike>(op))
|
|
return visitConnect(connectOp);
|
|
if (isa<SubfieldOp, SubindexOp, SubaccessOp, ObjectSubfieldOp>(op))
|
|
return visitSubelement(op);
|
|
}
|
|
|
|
void IMDeadCodeElimPass::markInstanceOp(InstanceOp instance) {
|
|
// Get the module being referenced.
|
|
Operation *op = instance.getReferencedModule(*instanceGraph);
|
|
|
|
// If this is an extmodule, just remember that any inputs and inouts are
|
|
// alive.
|
|
if (!isa<FModuleOp>(op)) {
|
|
auto module = dyn_cast<FModuleLike>(op);
|
|
for (auto resultNo : llvm::seq(0u, instance.getNumResults())) {
|
|
// If this is an output to the extmodule, we can ignore it.
|
|
if (module.getPortDirection(resultNo) == Direction::Out)
|
|
continue;
|
|
|
|
// Otherwise this is an input from it or an inout, mark it as alive.
|
|
markAlive(instance.getResult(resultNo));
|
|
}
|
|
markAlive(instance);
|
|
|
|
return;
|
|
}
|
|
|
|
// Otherwise this is a defined module.
|
|
auto fModule = cast<FModuleOp>(op);
|
|
markBlockExecutable(fModule.getBodyBlock());
|
|
}
|
|
|
|
void IMDeadCodeElimPass::markObjectOp(ObjectOp object) {
|
|
// unconditionally keep all objects alive.
|
|
markAlive(object);
|
|
}
|
|
|
|
void IMDeadCodeElimPass::markBlockExecutable(Block *block) {
|
|
if (!executableBlocks.insert(block).second)
|
|
return; // Already executable.
|
|
|
|
auto fmodule = cast<FModuleOp>(block->getParentOp());
|
|
if (fmodule.isPublic())
|
|
markAlive(fmodule);
|
|
|
|
// Mark ports with don't touch as alive.
|
|
for (auto blockArg : block->getArguments())
|
|
if (hasDontTouch(blockArg)) {
|
|
markAlive(blockArg);
|
|
markAlive(fmodule);
|
|
}
|
|
|
|
for (auto &op : *block) {
|
|
if (isDeclaration(&op))
|
|
markDeclaration(&op);
|
|
else if (auto instance = dyn_cast<InstanceOp>(op))
|
|
markInstanceOp(instance);
|
|
else if (auto object = dyn_cast<ObjectOp>(op))
|
|
markObjectOp(object);
|
|
else if (isa<FConnectLike>(op))
|
|
// Skip connect op.
|
|
continue;
|
|
else if (hasUnknownSideEffect(&op))
|
|
markUnknownSideEffectOp(&op);
|
|
|
|
// TODO: Handle attach etc.
|
|
}
|
|
}
|
|
|
|
void IMDeadCodeElimPass::forwardConstantOutputPort(FModuleOp module) {
|
|
// This tracks constant values of output ports.
|
|
SmallVector<std::pair<unsigned, APSInt>> constantPortIndicesAndValues;
|
|
auto ports = module.getPorts();
|
|
auto *instanceGraphNode = instanceGraph->lookup(module);
|
|
|
|
for (const auto &e : llvm::enumerate(ports)) {
|
|
unsigned index = e.index();
|
|
auto port = e.value();
|
|
auto arg = module.getArgument(index);
|
|
|
|
// If the port has don't touch, don't propagate the constant value.
|
|
if (!port.isOutput() || hasDontTouch(arg))
|
|
continue;
|
|
|
|
// Remember the index and constant value connected to an output port.
|
|
if (auto connect = getSingleConnectUserOf(arg))
|
|
if (auto constant = connect.getSrc().getDefiningOp<ConstantOp>())
|
|
constantPortIndicesAndValues.push_back({index, constant.getValue()});
|
|
}
|
|
|
|
// If there is no constant port, abort.
|
|
if (constantPortIndicesAndValues.empty())
|
|
return;
|
|
|
|
// Rewrite all uses.
|
|
for (auto *use : instanceGraphNode->uses()) {
|
|
auto instance = cast<InstanceOp>(*use->getInstance());
|
|
ImplicitLocOpBuilder builder(instance.getLoc(), instance);
|
|
for (auto [index, constant] : constantPortIndicesAndValues) {
|
|
auto result = instance.getResult(index);
|
|
assert(ports[index].isOutput() && "must be an output port");
|
|
|
|
// Replace the port with the constant.
|
|
result.replaceAllUsesWith(builder.create<ConstantOp>(constant));
|
|
}
|
|
}
|
|
}
|
|
|
|
void IMDeadCodeElimPass::runOnOperation() {
|
|
LLVM_DEBUG(debugPassHeader(this) << "\n";);
|
|
auto circuits = getOperation().getOps<CircuitOp>();
|
|
if (circuits.empty())
|
|
return;
|
|
|
|
auto circuit = *circuits.begin();
|
|
|
|
if (!llvm::hasSingleElement(circuits)) {
|
|
mlir::emitError(circuit.getLoc(),
|
|
"cannot process multiple circuit operations")
|
|
.attachNote((*std::next(circuits.begin())).getLoc())
|
|
<< "second circuit here";
|
|
return signalPassFailure();
|
|
}
|
|
|
|
instanceGraph = &getChildAnalysis<InstanceGraph>(circuit);
|
|
symbolTable = &getChildAnalysis<SymbolTable>(circuit);
|
|
auto &istc = getChildAnalysis<hw::InnerSymbolTableCollection>(circuit);
|
|
|
|
circt::hw::InnerRefNamespace theInnerRefNamespace{*symbolTable, istc};
|
|
innerRefNamespace = &theInnerRefNamespace;
|
|
|
|
// Walk attributes and find unknown uses of inner symbols or hierpaths.
|
|
getOperation().walk([&](Operation *op) {
|
|
if (isa<FModuleOp>(op)) // Port or module annotations are ok to ignore.
|
|
return;
|
|
|
|
if (auto hierPath = dyn_cast<hw::HierPathOp>(op)) {
|
|
auto namePath = hierPath.getNamepath().getValue();
|
|
// If the hierpath is public or ill-formed, the verifier should have
|
|
// caught the error. Conservatively mark the symbol as alive.
|
|
if (hierPath.isPublic() || namePath.size() <= 1 ||
|
|
namePath.back().isa<hw::InnerRefAttr>())
|
|
return markAlive(hierPath);
|
|
|
|
if (auto instance =
|
|
dyn_cast_or_null<firrtl::InstanceOp>(innerRefNamespace->lookupOp(
|
|
namePath.drop_back().back().cast<hw::InnerRefAttr>())))
|
|
instanceToHierPaths[instance].push_back(hierPath);
|
|
return;
|
|
}
|
|
|
|
// If there is an unknown use of inner sym or hierpath, just mark all of
|
|
// them alive.
|
|
for (NamedAttribute namedAttr : op->getAttrs()) {
|
|
namedAttr.getValue().walk([&](Attribute subAttr) {
|
|
if (auto innerRef = dyn_cast<hw::InnerRefAttr>(subAttr))
|
|
if (auto instance = dyn_cast_or_null<firrtl::InstanceOp>(
|
|
innerRefNamespace->lookupOp(innerRef)))
|
|
markAlive(instance);
|
|
|
|
if (auto flatSymbolRefAttr = dyn_cast<FlatSymbolRefAttr>(subAttr))
|
|
if (auto hierPath = symbolTable->template lookup<hw::HierPathOp>(
|
|
flatSymbolRefAttr.getAttr()))
|
|
markAlive(hierPath);
|
|
});
|
|
}
|
|
});
|
|
|
|
// Create a vector of modules in the post order of instance graph.
|
|
// FIXME: We copy the list of modules into a vector first to avoid iterator
|
|
// invalidation while we mutate the instance graph. See issue 3387.
|
|
SmallVector<FModuleOp, 0> modules(llvm::make_filter_range(
|
|
llvm::map_range(
|
|
llvm::post_order(instanceGraph),
|
|
[](auto *node) { return dyn_cast<FModuleOp>(*node->getModule()); }),
|
|
[](auto module) { return module; }));
|
|
|
|
// Forward constant output ports to caller sides so that we can eliminate
|
|
// constant outputs.
|
|
for (auto module : modules)
|
|
forwardConstantOutputPort(module);
|
|
|
|
for (auto module : circuit.getBodyBlock()->getOps<FModuleOp>()) {
|
|
// Mark the ports of public modules as alive.
|
|
if (module.isPublic()) {
|
|
markBlockExecutable(module.getBodyBlock());
|
|
for (auto port : module.getBodyBlock()->getArguments())
|
|
markAlive(port);
|
|
}
|
|
|
|
// Walk annotations and populate a map from hierpath to attached annotation
|
|
// targets. `portId` is `-1` for module annotations.
|
|
auto visitAnnotation = [&](int portId, Annotation anno) -> bool {
|
|
auto hierPathSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
|
|
hw::HierPathOp hierPathOp;
|
|
if (hierPathSym)
|
|
hierPathOp =
|
|
symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
|
|
|
|
if (anno.canBeDeleted()) {
|
|
if (hierPathOp && portId >= 0)
|
|
hierPathToElements[hierPathOp].insert(module.getArgument(portId));
|
|
return false;
|
|
}
|
|
if (hierPathOp)
|
|
markAlive(hierPathOp);
|
|
if (portId >= 0)
|
|
markAlive(module.getArgument(portId));
|
|
markAlive(module);
|
|
return false;
|
|
};
|
|
|
|
AnnotationSet::removePortAnnotations(module, visitAnnotation);
|
|
AnnotationSet::removeAnnotations(
|
|
module, std::bind(visitAnnotation, -1, std::placeholders::_1));
|
|
}
|
|
|
|
// If an element changed liveness then propagate liveness through it.
|
|
while (!worklist.empty()) {
|
|
auto v = worklist.pop_back_val();
|
|
if (auto *value = std::get_if<Value>(&v))
|
|
visitValue(*value);
|
|
else if (auto *instance = std::get_if<InstanceOp>(&v))
|
|
visitInstanceOp(*instance);
|
|
else if (auto *hierpath = std::get_if<hw::HierPathOp>(&v))
|
|
visitHierPathOp(*hierpath);
|
|
else if (auto *module = std::get_if<FModuleOp>(&v))
|
|
visitModuleOp(*module);
|
|
}
|
|
|
|
// Rewrite module signatures or delete unreachable modules.
|
|
for (auto module : llvm::make_early_inc_range(
|
|
circuit.getBodyBlock()->getOps<FModuleOp>())) {
|
|
if (isBlockExecutable(module.getBodyBlock()))
|
|
rewriteModuleSignature(module);
|
|
else {
|
|
// If the module is unreachable from the toplevel, just delete it.
|
|
// Note that post-order traversal on the instance graph never visit
|
|
// unreachable modules so it's safe to erase the module even though
|
|
// `modules` seems to be capturing module pointers.
|
|
module.erase();
|
|
}
|
|
}
|
|
|
|
// Rewrite module bodies parallelly.
|
|
mlir::parallelForEach(circuit.getContext(),
|
|
circuit.getBodyBlock()->getOps<FModuleOp>(),
|
|
[&](auto op) { rewriteModuleBody(op); });
|
|
|
|
// Clean up hierpaths.
|
|
for (auto op : llvm::make_early_inc_range(
|
|
circuit.getBodyBlock()->getOps<hw::HierPathOp>()))
|
|
if (!liveElements.count(op))
|
|
op.erase();
|
|
|
|
for (auto module : modules)
|
|
eraseEmptyModule(module);
|
|
|
|
// Clean up data structures.
|
|
executableBlocks.clear();
|
|
liveElements.clear();
|
|
instanceToHierPaths.clear();
|
|
hierPathToElements.clear();
|
|
}
|
|
|
|
void IMDeadCodeElimPass::visitValue(Value value) {
|
|
assert(isKnownAlive(value) && "only alive values reach here");
|
|
|
|
// Propagate liveness through users.
|
|
for (Operation *user : value.getUsers())
|
|
visitUser(user);
|
|
|
|
// Requiring an input port propagates the liveness to each instance.
|
|
if (auto blockArg = dyn_cast<BlockArgument>(value)) {
|
|
auto module = cast<FModuleOp>(blockArg.getParentBlock()->getParentOp());
|
|
auto portDirection = module.getPortDirection(blockArg.getArgNumber());
|
|
// If the port is input, it's necessary to mark corresponding input ports of
|
|
// instances as alive. We don't have to propagate the liveness of output
|
|
// ports.
|
|
if (portDirection == Direction::In) {
|
|
for (auto *instRec : instanceGraph->lookup(module)->uses()) {
|
|
auto instance = cast<InstanceOp>(instRec->getInstance());
|
|
if (liveElements.contains(instance))
|
|
markAlive(instance.getResult(blockArg.getArgNumber()));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Marking an instance port as alive propagates to the corresponding port of
|
|
// the module.
|
|
if (auto instance = value.getDefiningOp<InstanceOp>()) {
|
|
auto instanceResult = value.cast<mlir::OpResult>();
|
|
// Update the src, when it's an instance op.
|
|
auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
|
|
|
|
// Propagate liveness only when a port is output.
|
|
if (!module || module.getPortDirection(instanceResult.getResultNumber()) ==
|
|
Direction::In)
|
|
return;
|
|
|
|
markAlive(instance);
|
|
|
|
BlockArgument modulePortVal =
|
|
module.getArgument(instanceResult.getResultNumber());
|
|
return markAlive(modulePortVal);
|
|
}
|
|
|
|
// If a port of a memory is alive, all other ports are.
|
|
if (auto mem = value.getDefiningOp<MemOp>()) {
|
|
for (auto port : mem->getResults())
|
|
markAlive(port);
|
|
return;
|
|
}
|
|
|
|
// If op is defined by an operation, mark its operands as alive.
|
|
if (auto op = value.getDefiningOp())
|
|
for (auto operand : op->getOperands())
|
|
markAlive(operand);
|
|
|
|
// If either result of a forceable declaration is alive, they both are.
|
|
if (auto fop = value.getDefiningOp<Forceable>();
|
|
fop && fop.isForceable() &&
|
|
(fop.getData() == value || fop.getDataRef() == value)) {
|
|
markAlive(fop.getData());
|
|
markAlive(fop.getDataRef());
|
|
}
|
|
}
|
|
|
|
void IMDeadCodeElimPass::visitConnect(FConnectLike connect) {
|
|
// If the dest is alive, mark the source value as alive.
|
|
if (isKnownAlive(connect.getDest()))
|
|
markAlive(connect.getSrc());
|
|
}
|
|
|
|
void IMDeadCodeElimPass::visitSubelement(Operation *op) {
|
|
if (isKnownAlive(op->getOperand(0)))
|
|
markAlive(op->getResult(0));
|
|
}
|
|
|
|
void IMDeadCodeElimPass::rewriteModuleBody(FModuleOp module) {
|
|
auto *body = module.getBodyBlock();
|
|
assert(isBlockExecutable(body) &&
|
|
"unreachable modules must be already deleted");
|
|
|
|
auto removeDeadNonLocalAnnotations = [&](int _, Annotation anno) -> bool {
|
|
auto hierPathSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
|
|
// We only clean up non-local annotations here as local annotations will
|
|
// be deleted afterwards.
|
|
if (!anno.canBeDeleted() || !hierPathSym)
|
|
return false;
|
|
auto hierPathOp =
|
|
symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
|
|
return !liveElements.count(hierPathOp);
|
|
};
|
|
|
|
AnnotationSet::removePortAnnotations(module, removeDeadNonLocalAnnotations);
|
|
AnnotationSet::removeAnnotations(
|
|
module,
|
|
std::bind(removeDeadNonLocalAnnotations, -1, std::placeholders::_1));
|
|
|
|
// Walk the IR bottom-up when deleting operations.
|
|
for (auto &op : llvm::make_early_inc_range(llvm::reverse(*body))) {
|
|
// Connects to values that we found to be dead can be dropped.
|
|
if (auto connect = dyn_cast<FConnectLike>(op)) {
|
|
if (isAssumedDead(connect.getDest())) {
|
|
LLVM_DEBUG(llvm::dbgs() << "DEAD: " << connect << "\n";);
|
|
connect.erase();
|
|
++numErasedOps;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Delete dead wires, regs, nodes and alloc/read ops.
|
|
if ((isDeclaration(&op) || !hasUnknownSideEffect(&op)) &&
|
|
isAssumedDead(&op)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "DEAD: " << op << "\n";);
|
|
assert(op.use_empty() && "users should be already removed");
|
|
op.erase();
|
|
++numErasedOps;
|
|
continue;
|
|
}
|
|
|
|
// Remove non-sideeffect op using `isOpTriviallyDead`.
|
|
if (mlir::isOpTriviallyDead(&op)) {
|
|
op.erase();
|
|
++numErasedOps;
|
|
}
|
|
}
|
|
}
|
|
|
|
void IMDeadCodeElimPass::rewriteModuleSignature(FModuleOp module) {
|
|
assert(isBlockExecutable(module.getBodyBlock()) &&
|
|
"unreachable modules must be already deleted");
|
|
InstanceGraphNode *instanceGraphNode = instanceGraph->lookup(module);
|
|
LLVM_DEBUG(llvm::dbgs() << "Prune ports of module: " << module.getName()
|
|
<< "\n");
|
|
|
|
auto replaceInstanceResultWithWire = [&](ImplicitLocOpBuilder &builder,
|
|
unsigned index,
|
|
InstanceOp instance) {
|
|
auto result = instance.getResult(index);
|
|
if (isAssumedDead(result)) {
|
|
// If the result is dead, replace the result with an unrealized conversion
|
|
// cast which works as a dummy placeholder.
|
|
auto wire = builder
|
|
.create<mlir::UnrealizedConversionCastOp>(
|
|
ArrayRef<Type>{result.getType()}, ArrayRef<Value>{})
|
|
->getResult(0);
|
|
result.replaceAllUsesWith(wire);
|
|
return;
|
|
}
|
|
|
|
Value wire = builder.create<WireOp>(result.getType()).getResult();
|
|
result.replaceAllUsesWith(wire);
|
|
// If a module port is dead but its instance result is alive, the port
|
|
// is used as a temporary wire so make sure that a replaced wire is
|
|
// putted into `liveSet`.
|
|
liveElements.erase(result);
|
|
liveElements.insert(wire);
|
|
};
|
|
|
|
// First, delete dead instances.
|
|
for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
|
|
auto instance = cast<InstanceOp>(*use->getInstance());
|
|
if (!liveElements.count(instance)) {
|
|
// Replace old instance results with dummy wires.
|
|
ImplicitLocOpBuilder builder(instance.getLoc(), instance);
|
|
for (auto index : llvm::seq(0u, instance.getNumResults()))
|
|
replaceInstanceResultWithWire(builder, index, instance);
|
|
// Make sure that we update the instance graph.
|
|
use->erase();
|
|
instance.erase();
|
|
}
|
|
}
|
|
|
|
// Ports of public modules cannot be modified.
|
|
if (module.isPublic())
|
|
return;
|
|
|
|
unsigned numOldPorts = module.getNumPorts();
|
|
llvm::BitVector deadPortIndexes(numOldPorts);
|
|
|
|
ImplicitLocOpBuilder builder(module.getLoc(), module.getContext());
|
|
builder.setInsertionPointToStart(module.getBodyBlock());
|
|
auto oldPorts = module.getPorts();
|
|
|
|
for (auto index : llvm::seq(0u, numOldPorts)) {
|
|
auto argument = module.getArgument(index);
|
|
assert((!hasDontTouch(argument) || isKnownAlive(argument)) &&
|
|
"If the port has don't touch, it should be known alive");
|
|
|
|
// If the port has dontTouch, skip.
|
|
if (hasDontTouch(argument))
|
|
continue;
|
|
|
|
if (isKnownAlive(argument)) {
|
|
|
|
// If an output port is only used internally in the module, then we can
|
|
// remove the port and replace it with a wire.
|
|
if (module.getPortDirection(index) == Direction::In)
|
|
continue;
|
|
|
|
// Check if the output port is demanded by any instance. If not, then it
|
|
// is only demanded internally to the module.
|
|
if (llvm::any_of(instanceGraph->lookup(module)->uses(),
|
|
[&](InstanceRecord *record) {
|
|
return isKnownAlive(
|
|
record->getInstance()->getResult(index));
|
|
}))
|
|
continue;
|
|
|
|
// Ok, this port is used only within its defined module. So we can replace
|
|
// the port with a wire.
|
|
auto wire = builder.create<WireOp>(argument.getType()).getResult();
|
|
|
|
// Since `liveSet` contains the port, we have to erase it from the set.
|
|
liveElements.erase(argument);
|
|
liveElements.insert(wire);
|
|
argument.replaceAllUsesWith(wire);
|
|
deadPortIndexes.set(index);
|
|
continue;
|
|
}
|
|
|
|
// Replace the port with a dummy wire. This wire should be erased within
|
|
// `rewriteModuleBody`.
|
|
Value wire = builder
|
|
.create<mlir::UnrealizedConversionCastOp>(
|
|
ArrayRef<Type>{argument.getType()}, ArrayRef<Value>{})
|
|
->getResult(0);
|
|
|
|
argument.replaceAllUsesWith(wire);
|
|
assert(isAssumedDead(wire) && "dummy wire must be dead");
|
|
deadPortIndexes.set(index);
|
|
}
|
|
|
|
// If there is nothing to remove, abort.
|
|
if (deadPortIndexes.none())
|
|
return;
|
|
|
|
// Erase arguments of the old module from liveSet to prevent from creating
|
|
// dangling pointers.
|
|
for (auto arg : module.getArguments())
|
|
liveElements.erase(arg);
|
|
|
|
// Delete ports from the module.
|
|
module.erasePorts(deadPortIndexes);
|
|
|
|
// Add arguments of the new module to liveSet.
|
|
for (auto arg : module.getArguments())
|
|
liveElements.insert(arg);
|
|
|
|
// Rewrite all uses.
|
|
for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
|
|
auto instance = cast<InstanceOp>(*use->getInstance());
|
|
ImplicitLocOpBuilder builder(instance.getLoc(), instance);
|
|
// Replace old instance results with dummy wires.
|
|
for (auto index : deadPortIndexes.set_bits())
|
|
replaceInstanceResultWithWire(builder, index, instance);
|
|
|
|
// Since we will rewrite instance op, it is necessary to remove old
|
|
// instance results from liveSet.
|
|
for (auto oldResult : instance.getResults())
|
|
liveElements.erase(oldResult);
|
|
|
|
// Create a new instance op without dead ports.
|
|
auto newInstance = instance.erasePorts(builder, deadPortIndexes);
|
|
|
|
// Mark new results as alive.
|
|
for (auto newResult : newInstance.getResults())
|
|
liveElements.insert(newResult);
|
|
|
|
instanceGraph->replaceInstance(instance, newInstance);
|
|
if (liveElements.contains(instance)) {
|
|
liveElements.erase(instance);
|
|
liveElements.insert(newInstance);
|
|
}
|
|
// Remove old one.
|
|
instance.erase();
|
|
}
|
|
|
|
numRemovedPorts += deadPortIndexes.count();
|
|
}
|
|
|
|
void IMDeadCodeElimPass::eraseEmptyModule(FModuleOp module) {
|
|
// If the module is not empty, just skip.
|
|
if (!module.getBodyBlock()->empty())
|
|
return;
|
|
|
|
// We cannot delete public modules so generate a warning.
|
|
if (module.isPublic()) {
|
|
mlir::emitWarning(module.getLoc())
|
|
<< "module `" << module.getName()
|
|
<< "` is empty but cannot be removed because the module is public";
|
|
return;
|
|
}
|
|
|
|
if (!module.getAnnotations().empty()) {
|
|
module.emitWarning() << "module `" << module.getName()
|
|
<< "` is empty but cannot be removed "
|
|
"because the module has annotations "
|
|
<< module.getAnnotations();
|
|
return;
|
|
}
|
|
|
|
if (!module.getBodyBlock()->args_empty()) {
|
|
auto diag = module.emitWarning()
|
|
<< "module `" << module.getName()
|
|
<< "` is empty but cannot be removed because the "
|
|
"module has ports ";
|
|
llvm::interleaveComma(module.getPortNames(), diag);
|
|
diag << " are referenced by name or dontTouched";
|
|
return;
|
|
}
|
|
|
|
// Ok, the module is empty. Delete instances unless they have symbols.
|
|
LLVM_DEBUG(llvm::dbgs() << "Erase " << module.getName() << "\n");
|
|
|
|
InstanceGraphNode *instanceGraphNode =
|
|
instanceGraph->lookup(module.getModuleNameAttr());
|
|
|
|
SmallVector<Location> instancesWithSymbols;
|
|
for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
|
|
auto instance = cast<InstanceOp>(use->getInstance());
|
|
if (instance.getInnerSym()) {
|
|
instancesWithSymbols.push_back(instance.getLoc());
|
|
continue;
|
|
}
|
|
use->erase();
|
|
instance.erase();
|
|
}
|
|
|
|
// If there is an instance with a symbol, we don't delete the module itself.
|
|
if (!instancesWithSymbols.empty()) {
|
|
auto diag = module.emitWarning()
|
|
<< "module `" << module.getName()
|
|
<< "` is empty but cannot be removed because an instance is "
|
|
"referenced by name";
|
|
diag.attachNote(FusedLoc::get(&getContext(), instancesWithSymbols))
|
|
<< "these are instances with symbols";
|
|
return;
|
|
}
|
|
|
|
instanceGraph->erase(instanceGraphNode);
|
|
module.erase();
|
|
++numErasedModules;
|
|
}
|
|
|
|
std::unique_ptr<mlir::Pass> circt::firrtl::createIMDeadCodeElimPass() {
|
|
return std::make_unique<IMDeadCodeElimPass>();
|
|
}
|