15#include "mlir/IR/Iterators.h"
16#include "mlir/IR/Threading.h"
17#include "mlir/Interfaces/SideEffectInterfaces.h"
18#include "mlir/Pass/Pass.h"
19#include "llvm/ADT/BitVector.h"
20#include "llvm/ADT/DenseMapInfoVariant.h"
21#include "llvm/ADT/PostOrderIterator.h"
22#include "llvm/Support/Debug.h"
24#define DEBUG_TYPE "firrtl-imdeadcodeelim"
28#define GEN_PASS_DEF_IMDEADCODEELIM
29#include "circt/Dialect/FIRRTL/Passes.h.inc"
34using namespace firrtl;
38 return !(mlir::isMemoryEffectFree(op) ||
39 mlir::hasSingleEffect<mlir::MemoryEffects::Allocate>(op) ||
40 mlir::hasSingleEffect<mlir::MemoryEffects::Read>(op));
45 return isa<WireOp, RegResetOp, RegOp, NodeOp, MemOp>(op);
50 if (
auto name = dyn_cast<FNamableOp>(op))
51 if (!name.hasDroppableName())
57struct IMDeadCodeElimPass
58 :
public circt::firrtl::impl::IMDeadCodeElimBase<IMDeadCodeElimPass> {
59 void runOnOperation()
override;
61 void rewriteModuleSignature(FModuleOp module);
62 void rewriteModuleBody(FModuleOp module);
63 void eraseEmptyModule(FModuleOp module);
64 void forwardConstantOutputPort(FModuleOp module);
67 bool isKnownAlive(Value value)
const {
68 assert(value &&
"null should not be used");
69 return liveElements.count(value);
73 bool isAssumedDead(Value value)
const {
return !isKnownAlive(value); }
74 bool isAssumedDead(Operation *op)
const {
75 return llvm::none_of(op->getResults(),
76 [&](Value value) { return isKnownAlive(value); });
80 bool isBlockExecutable(Block *block)
const {
81 return executableBlocks.count(block);
84 void visitUser(Operation *op);
85 void visitValue(Value value);
87 void visitConnect(FConnectLike connect);
88 void visitSubelement(Operation *op);
89 void markBlockExecutable(Block *block);
90 void markBlockUndeletable(Operation *op) {
91 markAlive(op->getParentOfType<FModuleOp>());
94 void markDeclaration(Operation *op);
95 void markFInstanceLikeOp(FInstanceLike instanceLike);
96 void markUnknownSideEffectOp(Operation *op);
97 void visitFInstanceLikeOp(FInstanceLike instanceLike);
98 void visitHierPathOp(hw::HierPathOp hierpath);
99 void visitModuleOp(FModuleOp module);
103 DenseSet<Block *> executableBlocks;
109 std::variant<Value, FModuleOp, FInstanceLike, hw::HierPathOp>;
111 void markAlive(ElementType element) {
112 if (!liveElements.insert(element).second)
114 worklist.push_back(element);
119 SmallVector<ElementType, 64> worklist;
120 llvm::DenseSet<ElementType> liveElements;
124 DenseMap<InstanceOp, SmallVector<hw::HierPathOp>> instanceToHierPaths;
127 DenseMap<hw::HierPathOp, SetVector<ElementType>> hierPathToElements;
131 mlir::SymbolTable *symbolTable;
135void IMDeadCodeElimPass::visitFInstanceLikeOp(FInstanceLike instanceLike) {
136 auto instance = dyn_cast<InstanceOp>(*instanceLike);
142 markBlockUndeletable(instance);
144 auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
153 for (
auto hierPath : instanceToHierPaths[instance])
157 for (
auto &blockArg : module.getBody().getArguments()) {
158 auto portNo = blockArg.getArgNumber();
159 if (module.getPortDirection(portNo) == Direction::In &&
160 isKnownAlive(module.getArgument(portNo)))
161 markAlive(instance.getResult(portNo));
165void IMDeadCodeElimPass::visitModuleOp(FModuleOp module) {
167 for (
auto *use : instanceGraph->lookup(module)->uses()) {
168 if (
auto instance = use->getInstance<FInstanceLike>())
173void IMDeadCodeElimPass::visitHierPathOp(hw::HierPathOp hierPathOp) {
175 for (
auto path : hierPathOp.getNamepathAttr())
176 if (auto innerRef = dyn_cast<
hw::InnerRefAttr>(path)) {
177 auto *op = innerRefNamespace->lookupOp(innerRef);
178 if (
auto instance = dyn_cast_or_null<InstanceOp>(op))
182 for (
auto elem : hierPathToElements[hierPathOp])
186void IMDeadCodeElimPass::markDeclaration(Operation *op) {
189 for (
auto result : op->getResults())
191 markBlockUndeletable(op);
195void IMDeadCodeElimPass::markUnknownSideEffectOp(Operation *op) {
198 for (
auto result : op->getResults())
200 for (
auto operand : op->getOperands())
202 markBlockUndeletable(op);
205void IMDeadCodeElimPass::visitUser(Operation *op) {
206 LLVM_DEBUG(llvm::dbgs() <<
"Visit: " << *op <<
"\n");
207 if (
auto connectOp = dyn_cast<FConnectLike>(op))
208 return visitConnect(connectOp);
209 if (isa<SubfieldOp, SubindexOp, SubaccessOp, ObjectSubfieldOp>(op))
210 return visitSubelement(op);
213void IMDeadCodeElimPass::markFInstanceLikeOp(FInstanceLike instanceLike) {
214 if (
auto instance = dyn_cast<InstanceOp>(*instanceLike)) {
216 Operation *op = instance.getReferencedModule(*instanceGraph);
220 if (!isa<FModuleOp>(op)) {
221 auto module = dyn_cast<FModuleLike>(op);
222 for (
auto resultNo :
llvm::
seq(0u, instance.getNumResults())) {
224 if (module.getPortDirection(resultNo) == Direction::Out)
228 markAlive(instance.getResult(resultNo));
236 auto fModule = cast<FModuleOp>(op);
237 markBlockExecutable(fModule.getBodyBlock());
243 markAlive(instanceLike);
244 markBlockUndeletable(instanceLike);
247 for (
auto result : instanceLike->getResults())
251 for (
auto moduleName : instanceLike.getReferencedModuleNamesAttr()) {
252 auto *node = instanceGraph->
lookup(cast<StringAttr>(moduleName));
256 if (
auto fModule = dyn_cast<FModuleOp>(*node->getModule())) {
257 markBlockExecutable(fModule.getBodyBlock());
258 for (
auto result : fModule.
getBodyBlock()->getArguments())
264void IMDeadCodeElimPass::markBlockExecutable(Block *block) {
265 if (!executableBlocks.insert(block).second)
268 auto fmodule = dyn_cast<FModuleOp>(block->getParentOp());
269 if (fmodule && fmodule.isPublic())
273 for (
auto blockArg : block->getArguments())
280 for (
auto &op : *block) {
282 markDeclaration(&op);
283 else if (
auto instance = dyn_cast<FInstanceLike>(op))
284 markFInstanceLikeOp(instance);
285 else if (isa<FConnectLike>(op))
289 markUnknownSideEffectOp(&op);
292 for (
auto ®ion : op.getRegions())
293 for (auto &block : region.getBlocks())
294 markBlockExecutable(&block);
301void IMDeadCodeElimPass::forwardConstantOutputPort(FModuleOp module) {
303 SmallVector<std::pair<unsigned, APSInt>> constantPortIndicesAndValues;
304 auto ports =
module.getPorts();
305 auto *instanceGraphNode = instanceGraph->
lookup(module);
307 for (
const auto &e :
llvm::enumerate(ports)) {
308 unsigned index = e.index();
309 auto port = e.value();
310 auto arg =
module.getArgument(index);
318 if (
auto constant =
connect.getSrc().getDefiningOp<ConstantOp>())
319 constantPortIndicesAndValues.push_back({index, constant.getValue()});
323 if (constantPortIndicesAndValues.empty())
327 for (
auto *use : instanceGraphNode->uses()) {
328 auto instance = use->getInstance<InstanceOp>();
332 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
333 for (
auto [index, constant] : constantPortIndicesAndValues) {
334 auto result = instance.getResult(index);
335 assert(ports[index].isOutput() &&
"must be an output port");
338 result.replaceAllUsesWith(ConstantOp::create(builder, constant));
343void IMDeadCodeElimPass::runOnOperation() {
346 auto circuits = getOperation().getOps<CircuitOp>();
347 if (circuits.empty())
350 auto circuit = *circuits.begin();
352 if (!llvm::hasSingleElement(circuits)) {
353 mlir::emitError(circuit.getLoc(),
354 "cannot process multiple circuit operations")
355 .attachNote((*std::next(circuits.begin())).getLoc())
356 <<
"second circuit here";
357 return signalPassFailure();
360 instanceGraph = &getChildAnalysis<InstanceGraph>(circuit);
361 symbolTable = &getChildAnalysis<SymbolTable>(circuit);
362 auto &istc = getChildAnalysis<hw::InnerSymbolTableCollection>(circuit);
365 innerRefNamespace = &theInnerRefNamespace;
368 getOperation().walk([&](Operation *op) {
369 if (isa<FModuleOp>(op))
372 if (
auto hierPath = dyn_cast<hw::HierPathOp>(op)) {
373 auto namePath = hierPath.getNamepath().getValue();
376 if (hierPath.isPublic() || namePath.size() <= 1 ||
377 isa<hw::InnerRefAttr>(namePath.back()))
378 return markAlive(hierPath);
381 dyn_cast_or_null<firrtl::InstanceOp>(innerRefNamespace->lookupOp(
382 cast<hw::InnerRefAttr>(namePath.drop_back().back()))))
383 instanceToHierPaths[instance].push_back(hierPath);
389 op->getAttrDictionary().walk([&](Attribute attr) {
390 if (
auto innerRef = dyn_cast<hw::InnerRefAttr>(attr)) {
392 if (
auto instance = dyn_cast_or_null<firrtl::InstanceOp>(
393 innerRefNamespace->lookupOp(innerRef)))
398 if (
auto symbolRef = dyn_cast<FlatSymbolRefAttr>(attr)) {
399 auto *symbol = symbolTable->lookup(symbolRef.getAttr());
404 if (
auto hierPath = dyn_cast<hw::HierPathOp>(symbol))
408 if (
auto module = dyn_cast<FModuleOp>(symbol)) {
409 if (!isa<firrtl::InstanceOp>(op)) {
410 LLVM_DEBUG(llvm::dbgs()
411 <<
"Unknown use of " << module.getModuleNameAttr()
412 <<
" in " << op->getName() <<
"\n");
414 markBlockExecutable(module.getBodyBlock());
426 SmallVector<FModuleOp, 0> modules(llvm::make_filter_range(
428 llvm::post_order(instanceGraph),
429 [](
auto *node) {
return dyn_cast<FModuleOp>(*node->getModule()); }),
430 [](
auto module) {
return module; }));
434 for (
auto module : modules)
435 forwardConstantOutputPort(module);
437 for (
auto module : circuit.
getBodyBlock()->getOps<FModuleOp>()) {
439 if (module.isPublic()) {
440 markBlockExecutable(module.getBodyBlock());
447 auto visitAnnotation = [&](
int portId,
Annotation anno) ->
bool {
448 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>(
"circt.nonlocal");
449 hw::HierPathOp hierPathOp;
452 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
455 markAlive(hierPathOp);
457 markAlive(module.getArgument(portId));
464 module, std::bind(visitAnnotation, -1, std::placeholders::_1));
468 while (!worklist.empty()) {
469 auto v = worklist.pop_back_val();
470 if (
auto *value = std::get_if<Value>(&v))
472 else if (
auto *instance = std::get_if<FInstanceLike>(&v))
473 visitFInstanceLikeOp(*instance);
474 else if (
auto *hierpath = std::get_if<hw::HierPathOp>(&v))
475 visitHierPathOp(*hierpath);
476 else if (
auto *module = std::get_if<FModuleOp>(&v))
477 visitModuleOp(*module);
481 for (
auto module :
llvm::make_early_inc_range(
483 if (isBlockExecutable(module.getBodyBlock()))
484 rewriteModuleSignature(module);
495 mlir::parallelForEach(circuit.getContext(),
496 circuit.getBodyBlock()->getOps<FModuleOp>(),
497 [&](
auto op) { rewriteModuleBody(op); });
500 for (
auto op :
llvm::make_early_inc_range(
502 if (!liveElements.count(op))
505 for (
auto module : modules)
506 eraseEmptyModule(module);
509 executableBlocks.clear();
510 liveElements.clear();
511 instanceToHierPaths.clear();
512 hierPathToElements.clear();
515void IMDeadCodeElimPass::visitValue(Value value) {
516 assert(isKnownAlive(value) &&
"only alive values reach here");
519 for (Operation *user : value.getUsers())
522 if (
auto blockArg = dyn_cast<BlockArgument>(value)) {
524 dyn_cast<FModuleOp>(blockArg.getParentBlock()->getParentOp())) {
525 auto portDirection =
module.getPortDirection(blockArg.getArgNumber());
529 if (portDirection == Direction::In) {
530 for (
auto *instRec : instanceGraph->lookup(module)->uses()) {
532 if (
auto instance = instRec->getInstance<InstanceOp>()) {
533 if (liveElements.contains(instance))
534 markAlive(instance.getResult(blockArg.getArgNumber()));
540 if (!type_isa<DomainType>(blockArg.getType()))
541 for (
auto domain : cast<ArrayAttr>(
542 module.getDomainInfoAttrForPort(blockArg.getArgNumber())))
543 markAlive(module.getArgument(
544 cast<IntegerAttr>(domain).getValue().getZExtValue()));
551 if (
auto instance = value.getDefiningOp<InstanceOp>()) {
552 auto instanceResult = cast<mlir::OpResult>(value);
554 auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
557 if (!module || module.getPortDirection(instanceResult.getResultNumber()) ==
563 BlockArgument modulePortVal =
564 module.getArgument(instanceResult.getResultNumber());
565 return markAlive(modulePortVal);
569 if (
auto mem = value.getDefiningOp<MemOp>()) {
570 for (
auto port : mem->getResults())
577 if (
auto op = value.getDefiningOp()) {
578 for (
auto operand : op->getOperands())
580 for (
auto ®ion : op->getRegions())
581 for (auto &block : region)
582 markBlockExecutable(&block);
586 if (
auto fop = value.getDefiningOp<Forceable>();
587 fop && fop.isForceable() &&
588 (fop.getData() == value || fop.getDataRef() == value)) {
589 markAlive(fop.getData());
590 markAlive(fop.getDataRef());
594void IMDeadCodeElimPass::visitConnect(FConnectLike connect) {
596 if (isKnownAlive(
connect.getDest()))
600void IMDeadCodeElimPass::visitSubelement(Operation *op) {
601 if (isKnownAlive(op->getOperand(0)))
602 markAlive(op->getResult(0));
605void IMDeadCodeElimPass::rewriteModuleBody(FModuleOp module) {
606 assert(isBlockExecutable(module.getBodyBlock()) &&
607 "unreachable modules must be already deleted");
609 auto removeDeadNonLocalAnnotations = [&](
int _,
Annotation anno) ->
bool {
610 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>(
"circt.nonlocal");
614 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
615 return !liveElements.count(hierPathOp);
621 std::bind(removeDeadNonLocalAnnotations, -1, std::placeholders::_1));
624 module.walk<mlir::WalkOrder::PostOrder, mlir::ReverseIterator>(
627 LLVM_DEBUG(llvm::dbgs() << "Visit: " << *op << "\n");
628 if (
auto connect = dyn_cast<FConnectLike>(op)) {
629 if (isAssumedDead(
connect.getDest())) {
630 LLVM_DEBUG(llvm::dbgs() <<
"DEAD: " << connect <<
"\n";);
640 LLVM_DEBUG(llvm::dbgs() <<
"DEAD: " << *op <<
"\n";);
641 assert(op->use_empty() &&
"users should be already removed");
648 if (mlir::isOpTriviallyDead(op)) {
655void IMDeadCodeElimPass::rewriteModuleSignature(FModuleOp module) {
656 assert(isBlockExecutable(module.getBodyBlock()) &&
657 "unreachable modules must be already deleted");
659 LLVM_DEBUG(llvm::dbgs() <<
"Prune ports of module: " << module.getName()
662 auto replaceInstanceResultWithWire =
663 [&](ImplicitLocOpBuilder &builder,
unsigned index, InstanceOp instance) {
664 auto result = instance.getResult(index);
665 if (isAssumedDead(result)) {
669 mlir::UnrealizedConversionCastOp::create(
670 builder, ArrayRef<Type>{result.getType()}, ArrayRef<Value>{})
672 result.replaceAllUsesWith(wire);
676 Value wire = WireOp::create(builder, result.getType()).getResult();
677 result.replaceAllUsesWith(wire);
681 liveElements.erase(result);
682 liveElements.insert(wire);
686 for (
auto *use :
llvm::make_early_inc_range(instanceGraphNode->uses())) {
687 auto instanceLike = use->getInstance<FInstanceLike>();
688 if (instanceLike && !liveElements.count(instanceLike)) {
689 auto instance = cast<InstanceOp>(instanceLike);
691 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
692 for (
auto index :
llvm::
seq(0u, instance.getNumResults()))
693 replaceInstanceResultWithWire(builder, index, instance);
701 if (module.isPublic())
704 unsigned numOldPorts =
module.getNumPorts();
705 llvm::BitVector deadPortIndexes(numOldPorts);
707 ImplicitLocOpBuilder builder(module.getLoc(), module.getContext());
708 builder.setInsertionPointToStart(module.getBodyBlock());
709 auto oldPorts =
module.getPorts();
711 for (
auto index :
llvm::
seq(0u, numOldPorts)) {
712 auto argument =
module.getArgument(index);
714 "If the port has don't touch, it should be known alive");
720 if (isKnownAlive(argument)) {
724 if (module.getPortDirection(index) == Direction::In)
729 if (llvm::any_of(instanceGraph->
lookup(module)->
uses(),
732 record->getInstance().getOperation()->getResult(
739 auto wire = WireOp::create(builder, argument.getType()).getResult();
742 liveElements.erase(argument);
743 liveElements.insert(wire);
744 argument.replaceAllUsesWith(wire);
745 deadPortIndexes.set(index);
752 mlir::UnrealizedConversionCastOp::create(
753 builder, ArrayRef<Type>{argument.getType()}, ArrayRef<Value>{})
756 argument.replaceAllUsesWith(wire);
757 assert(isAssumedDead(wire) &&
"dummy wire must be dead");
758 deadPortIndexes.set(index);
762 if (deadPortIndexes.none())
767 for (
auto arg : module.getArguments())
768 liveElements.erase(arg);
771 module.erasePorts(deadPortIndexes);
774 for (
auto arg : module.getArguments())
775 liveElements.insert(arg);
778 for (
auto *use :
llvm::make_early_inc_range(instanceGraphNode->uses())) {
779 auto instance = use->getInstance<InstanceOp>();
781 assert(!use->getInstance<FInstanceLike>() &&
782 "if this is not an instance, it must be marked overdefined");
786 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
788 for (
auto index : deadPortIndexes.set_bits())
789 replaceInstanceResultWithWire(builder, index, instance);
793 for (
auto oldResult : instance.getResults())
794 liveElements.erase(oldResult);
798 instance.cloneWithErasedPortsAndReplaceUses(deadPortIndexes);
801 for (
auto newResult : newInstance.getResults())
802 liveElements.insert(newResult);
805 if (liveElements.contains(instance)) {
806 liveElements.erase(instance);
807 liveElements.insert(newInstance);
813 numRemovedPorts += deadPortIndexes.count();
816void IMDeadCodeElimPass::eraseEmptyModule(FModuleOp module) {
818 if (!module.getBodyBlock()->empty())
822 if (module.isPublic()) {
823 mlir::emitWarning(module.getLoc())
824 <<
"module `" <<
module.getName()
825 << "` is empty but cannot be removed because the module is public";
829 if (!module.getAnnotations().empty()) {
830 module.emitWarning() << "module `" << module.getName()
831 << "` is empty but cannot be removed "
832 "because the module has annotations "
833 << module.getAnnotations();
837 if (!module.getBodyBlock()->args_empty()) {
838 auto diag =
module.emitWarning()
839 << "module `" << module.getName()
840 << "` is empty but cannot be removed because the "
842 llvm::interleaveComma(module.getPortNames(), diag);
843 diag <<
" are referenced by name or dontTouched";
848 LLVM_DEBUG(llvm::dbgs() <<
"Erase " << module.getName() <<
"\n");
851 instanceGraph->
lookup(module.getModuleNameAttr());
853 SmallVector<Location> instancesWithSymbols;
854 for (
auto *use :
llvm::make_early_inc_range(instanceGraphNode->uses())) {
855 auto instance = use->getInstance<InstanceOp>();
858 if (instance.getInnerSym()) {
859 instancesWithSymbols.push_back(instance.getLoc());
867 if (!instancesWithSymbols.empty()) {
868 auto diag =
module.emitWarning()
869 << "module `" << module.getName()
870 << "` is empty but cannot be removed because an instance is "
871 "referenced by name";
872 diag.attachNote(FusedLoc::get(&getContext(), instancesWithSymbols))
873 <<
"these are instances with symbols";
878 if (liveElements.contains(module))
881 instanceGraph->
erase(instanceGraphNode);
assert(baseType &&"element must be base type")
static bool isDeletableDeclaration(Operation *op)
Return true if this is a wire or register we're allowed to delete.
static bool hasUnknownSideEffect(Operation *op)
static bool isDeclaration(Operation *op)
Return true if this is a wire or a register or a node.
static Block * getBodyBlock(FModuleLike mod)
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
static bool removePortAnnotations(Operation *module, llvm::function_ref< bool(unsigned, Annotation)> predicate)
Remove all port annotations from a module or extmodule for which predicate returns true.
This class provides a read-only projection of an annotation.
This graph tracks modules and where they are instantiated.
This is a Node in the InstanceGraph.
llvm::iterator_range< UseIterator > uses()
virtual void replaceInstance(InstanceOpInterface inst, InstanceOpInterface newInst)
Replaces an instance of a module with another instance.
virtual void erase(InstanceGraphNode *node)
Remove this module from the instance graph.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
This is an edge in the InstanceGraph.
connect(destination, source)
bool hasDontTouch(Value value)
Check whether a block argument ("port") or the operation defining a value has a DontTouch annotation,...
MatchingConnectOp getSingleConnectUserOf(Value value)
Scan all the uses of the specified value, checking to see if there is exactly one connect that has th...
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
This class represents the namespace in which InnerRef's can be resolved.