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> {
61 void runOnOperation()
override;
63 void rewriteModuleSignature(FModuleOp module);
64 void rewriteModuleBody(FModuleOp module);
65 void eraseEmptyModule(FModuleOp module);
66 void forwardConstantOutputPort(FModuleOp module);
69 bool isKnownAlive(Value value)
const {
70 assert(value &&
"null should not be used");
71 return liveElements.count(value);
75 bool isAssumedDead(Value value)
const {
return !isKnownAlive(value); }
76 bool isAssumedDead(Operation *op)
const {
77 return llvm::none_of(op->getResults(),
78 [&](Value value) { return isKnownAlive(value); });
82 bool isBlockExecutable(Block *block)
const {
83 return executableBlocks.count(block);
86 void visitUser(Operation *op);
87 void visitValue(Value value);
89 void visitConnect(FConnectLike connect);
90 void visitSubelement(Operation *op);
91 void markBlockExecutable(Block *block);
92 void markBlockUndeletable(Operation *op) {
93 markAlive(op->getParentOfType<FModuleOp>());
96 void markDeclaration(Operation *op);
97 void markFInstanceLikeOp(FInstanceLike instanceLike);
98 void markUnknownSideEffectOp(Operation *op);
99 void visitFInstanceLikeOp(FInstanceLike instanceLike);
100 void visitHierPathOp(hw::HierPathOp hierpath);
101 void visitModuleOp(FModuleOp module);
105 DenseSet<Block *> executableBlocks;
111 std::variant<Value, FModuleOp, FInstanceLike, hw::HierPathOp>;
113 void markAlive(ElementType element) {
114 if (!liveElements.insert(element).second)
116 worklist.push_back(element);
121 SmallVector<ElementType, 64> worklist;
122 llvm::DenseSet<ElementType> liveElements;
126 DenseMap<InstanceOp, SmallVector<hw::HierPathOp>> instanceToHierPaths;
129 DenseMap<hw::HierPathOp, SetVector<ElementType>> hierPathToElements;
133 mlir::SymbolTable *symbolTable;
137void IMDeadCodeElimPass::visitFInstanceLikeOp(FInstanceLike instanceLike) {
138 auto instance = dyn_cast<InstanceOp>(*instanceLike);
144 markBlockUndeletable(instance);
146 auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
155 for (
auto hierPath : instanceToHierPaths[instance])
159 for (
auto &blockArg : module.getBody().getArguments()) {
160 auto portNo = blockArg.getArgNumber();
161 if (module.getPortDirection(portNo) == Direction::In &&
162 isKnownAlive(module.getArgument(portNo)))
163 markAlive(instance.getResult(portNo));
167void IMDeadCodeElimPass::visitModuleOp(FModuleOp module) {
169 for (
auto *use : instanceGraph->lookup(module)->uses()) {
170 if (
auto instance = use->getInstance<FInstanceLike>())
175void IMDeadCodeElimPass::visitHierPathOp(hw::HierPathOp hierPathOp) {
177 for (
auto path : hierPathOp.getNamepathAttr())
178 if (auto innerRef = dyn_cast<
hw::InnerRefAttr>(path)) {
179 auto *op = innerRefNamespace->lookupOp(innerRef);
180 if (
auto instance = dyn_cast_or_null<InstanceOp>(op))
184 for (
auto elem : hierPathToElements[hierPathOp])
188void IMDeadCodeElimPass::markDeclaration(Operation *op) {
191 for (
auto result : op->getResults())
193 markBlockUndeletable(op);
197void IMDeadCodeElimPass::markUnknownSideEffectOp(Operation *op) {
200 for (
auto result : op->getResults())
202 for (
auto operand : op->getOperands())
204 markBlockUndeletable(op);
208 for (
auto ®ion : op->getRegions())
209 for (auto &block : region.getBlocks())
210 markBlockExecutable(&block);
213void IMDeadCodeElimPass::visitUser(Operation *op) {
214 LLVM_DEBUG(llvm::dbgs() <<
"Visit: " << *op <<
"\n");
215 if (
auto connectOp = dyn_cast<FConnectLike>(op))
216 return visitConnect(connectOp);
217 if (isa<SubfieldOp, SubindexOp, SubaccessOp, ObjectSubfieldOp>(op))
218 return visitSubelement(op);
221void IMDeadCodeElimPass::markFInstanceLikeOp(FInstanceLike instanceLike) {
222 if (
auto instance = dyn_cast<InstanceOp>(*instanceLike)) {
224 Operation *op = instance.getReferencedModule(*instanceGraph);
228 if (!isa<FModuleOp>(op)) {
229 auto module = dyn_cast<FModuleLike>(op);
230 for (
auto resultNo :
llvm::
seq(0u, instance.getNumResults())) {
232 if (module.getPortDirection(resultNo) == Direction::Out)
236 markAlive(instance.getResult(resultNo));
244 auto fModule = cast<FModuleOp>(op);
245 markBlockExecutable(fModule.getBodyBlock());
251 markAlive(instanceLike);
252 markBlockUndeletable(instanceLike);
255 for (
auto result : instanceLike->getResults())
259 for (
auto moduleName : instanceLike.getReferencedModuleNamesAttr()) {
260 auto *node = instanceGraph->
lookup(cast<StringAttr>(moduleName));
264 if (
auto fModule = dyn_cast<FModuleOp>(*node->getModule())) {
265 markBlockExecutable(fModule.getBodyBlock());
266 for (
auto result : fModule.
getBodyBlock()->getArguments())
272void IMDeadCodeElimPass::markBlockExecutable(Block *block) {
273 if (!executableBlocks.insert(block).second)
276 auto fmodule = dyn_cast<FModuleOp>(block->getParentOp());
277 if (fmodule && (fmodule.isPublic() || removePortsOnly))
281 for (
auto blockArg : block->getArguments())
288 for (
auto &op : *block) {
289 if (
auto instance = dyn_cast<FInstanceLike>(op)) {
290 markFInstanceLikeOp(instance);
295 if (isa<FConnectLike>(op))
298 if (removePortsOnly) {
300 markUnknownSideEffectOp(&op);
306 markDeclaration(&op);
308 markUnknownSideEffectOp(&op);
314void IMDeadCodeElimPass::forwardConstantOutputPort(FModuleOp module) {
317 SmallVector<std::pair<unsigned, std::optional<APSInt>>>
318 constantPortIndicesAndValues;
319 auto ports =
module.getPorts();
320 auto *instanceGraphNode = instanceGraph->
lookup(module);
322 for (
const auto &e :
llvm::enumerate(ports)) {
323 unsigned index = e.index();
324 auto port = e.value();
325 auto arg =
module.getArgument(index);
333 if (
auto constant =
connect.getSrc().getDefiningOp<ConstantOp>())
334 constantPortIndicesAndValues.push_back({index, constant.getValue()});
335 else if (
connect.getSrc().getDefiningOp<InvalidValueOp>())
336 constantPortIndicesAndValues.push_back({index, std::nullopt});
341 if (constantPortIndicesAndValues.empty())
345 for (
auto *use : instanceGraphNode->uses()) {
346 auto instance = use->getInstance<InstanceOp>();
350 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
351 for (
auto [index, constant] : constantPortIndicesAndValues) {
352 auto result = instance.getResult(index);
353 assert(ports[index].isOutput() &&
"must be an output port");
358 replacement = ConstantOp::create(builder, *constant);
360 replacement = InvalidValueOp::create(builder, result.getType());
361 result.replaceAllUsesWith(replacement);
366void IMDeadCodeElimPass::runOnOperation() {
369 auto circuits = getOperation().getOps<CircuitOp>();
370 if (circuits.empty())
373 auto circuit = *circuits.begin();
375 if (!llvm::hasSingleElement(circuits)) {
376 mlir::emitError(circuit.getLoc(),
377 "cannot process multiple circuit operations")
378 .attachNote((*std::next(circuits.begin())).getLoc())
379 <<
"second circuit here";
380 return signalPassFailure();
383 instanceGraph = &getChildAnalysis<InstanceGraph>(circuit);
384 symbolTable = &getChildAnalysis<SymbolTable>(circuit);
385 auto &istc = getChildAnalysis<hw::InnerSymbolTableCollection>(circuit);
388 innerRefNamespace = &theInnerRefNamespace;
391 getOperation().walk([&](Operation *op) {
392 if (isa<FModuleOp>(op))
395 if (
auto hierPath = dyn_cast<hw::HierPathOp>(op)) {
396 auto namePath = hierPath.getNamepath().getValue();
399 if (hierPath.isPublic() || namePath.size() <= 1 ||
400 isa<hw::InnerRefAttr>(namePath.back()))
401 return markAlive(hierPath);
404 dyn_cast_or_null<firrtl::InstanceOp>(innerRefNamespace->lookupOp(
405 cast<hw::InnerRefAttr>(namePath.drop_back().back()))))
406 instanceToHierPaths[instance].push_back(hierPath);
412 op->getAttrDictionary().walk([&](Attribute attr) {
413 if (
auto innerRef = dyn_cast<hw::InnerRefAttr>(attr)) {
415 if (
auto instance = dyn_cast_or_null<firrtl::InstanceOp>(
416 innerRefNamespace->lookupOp(innerRef)))
421 if (
auto symbolRef = dyn_cast<FlatSymbolRefAttr>(attr)) {
422 auto *symbol = symbolTable->lookup(symbolRef.getAttr());
427 if (
auto hierPath = dyn_cast<hw::HierPathOp>(symbol))
431 if (
auto module = dyn_cast<FModuleOp>(symbol)) {
432 if (!isa<firrtl::InstanceOp>(op)) {
433 LLVM_DEBUG(llvm::dbgs()
434 <<
"Unknown use of " << module.getModuleNameAttr()
435 <<
" in " << op->getName() <<
"\n");
437 markBlockExecutable(module.getBodyBlock());
449 SmallVector<FModuleOp, 0> modules(llvm::make_filter_range(
451 llvm::post_order(instanceGraph),
452 [](
auto *node) {
return dyn_cast<FModuleOp>(*node->getModule()); }),
453 [](
auto module) {
return module; }));
457 for (
auto module : modules)
458 forwardConstantOutputPort(module);
460 for (
auto module : circuit.
getBodyBlock()->getOps<FModuleOp>()) {
461 bool isPublic =
module.isPublic();
462 if (isPublic || removePortsOnly) {
463 markBlockExecutable(module.getBodyBlock());
472 auto visitAnnotation = [&](
int portId,
Annotation anno) ->
bool {
473 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>(
"circt.nonlocal");
474 hw::HierPathOp hierPathOp;
477 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
480 markAlive(hierPathOp);
482 markAlive(module.getArgument(portId));
489 module, std::bind(visitAnnotation, -1, std::placeholders::_1));
493 while (!worklist.empty()) {
494 auto v = worklist.pop_back_val();
495 if (
auto *value = std::get_if<Value>(&v))
497 else if (
auto *instance = std::get_if<FInstanceLike>(&v))
498 visitFInstanceLikeOp(*instance);
499 else if (
auto *hierpath = std::get_if<hw::HierPathOp>(&v))
500 visitHierPathOp(*hierpath);
501 else if (
auto *module = std::get_if<FModuleOp>(&v))
502 visitModuleOp(*module);
506 for (
auto module :
llvm::make_early_inc_range(
508 if (isBlockExecutable(module.getBodyBlock()))
509 rewriteModuleSignature(module);
520 mlir::parallelForEach(circuit.getContext(),
521 circuit.getBodyBlock()->getOps<FModuleOp>(),
522 [&](
auto op) { rewriteModuleBody(op); });
525 for (
auto op :
llvm::make_early_inc_range(
527 if (!liveElements.count(op))
530 if (!removePortsOnly)
531 for (
auto module : modules)
532 eraseEmptyModule(module);
535 executableBlocks.clear();
536 liveElements.clear();
537 instanceToHierPaths.clear();
538 hierPathToElements.clear();
541void IMDeadCodeElimPass::visitValue(Value value) {
542 assert(isKnownAlive(value) &&
"only alive values reach here");
545 for (Operation *user : value.getUsers())
548 if (
auto blockArg = dyn_cast<BlockArgument>(value)) {
550 dyn_cast<FModuleOp>(blockArg.getParentBlock()->getParentOp())) {
551 auto portDirection =
module.getPortDirection(blockArg.getArgNumber());
555 if (portDirection == Direction::In) {
556 for (
auto *instRec : instanceGraph->lookup(module)->uses()) {
558 if (
auto instance = instRec->getInstance<InstanceOp>()) {
559 if (liveElements.contains(instance))
560 markAlive(instance.getResult(blockArg.getArgNumber()));
566 if (!type_isa<DomainType>(blockArg.getType()))
567 for (
auto domain : cast<ArrayAttr>(
568 module.getDomainInfoAttrForPort(blockArg.getArgNumber())))
569 markAlive(module.getArgument(
570 cast<IntegerAttr>(domain).getValue().getZExtValue()));
577 if (
auto instance = value.getDefiningOp<InstanceOp>()) {
578 auto instanceResult = cast<mlir::OpResult>(value);
580 auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
583 if (!module || module.getPortDirection(instanceResult.getResultNumber()) ==
589 BlockArgument modulePortVal =
590 module.getArgument(instanceResult.getResultNumber());
591 return markAlive(modulePortVal);
595 if (
auto mem = value.getDefiningOp<MemOp>()) {
596 for (
auto port : mem->getResults())
603 if (
auto op = value.getDefiningOp()) {
604 for (
auto operand : op->getOperands())
606 for (
auto ®ion : op->getRegions())
607 for (auto &block : region)
608 markBlockExecutable(&block);
612 if (
auto fop = value.getDefiningOp<Forceable>();
613 fop && fop.isForceable() &&
614 (fop.getData() == value || fop.getDataRef() == value)) {
615 markAlive(fop.getData());
616 markAlive(fop.getDataRef());
620void IMDeadCodeElimPass::visitConnect(FConnectLike connect) {
622 if (isKnownAlive(
connect.getDest()))
626void IMDeadCodeElimPass::visitSubelement(Operation *op) {
627 if (isKnownAlive(op->getOperand(0)))
628 markAlive(op->getResult(0));
631void IMDeadCodeElimPass::rewriteModuleBody(FModuleOp module) {
632 assert(isBlockExecutable(module.getBodyBlock()) &&
633 "unreachable modules must be already deleted");
635 auto removeDeadNonLocalAnnotations = [&](
int _,
Annotation anno) ->
bool {
636 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>(
"circt.nonlocal");
640 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
641 return !liveElements.count(hierPathOp);
647 std::bind(removeDeadNonLocalAnnotations, -1, std::placeholders::_1));
650 module.walk<mlir::WalkOrder::PostOrder, mlir::ReverseIterator>(
653 LLVM_DEBUG(llvm::dbgs() << "Visit: " << *op << "\n");
654 if (
auto connect = dyn_cast<FConnectLike>(op)) {
655 if (isAssumedDead(
connect.getDest())) {
656 LLVM_DEBUG(llvm::dbgs() <<
"DEAD: " << connect <<
"\n";);
666 LLVM_DEBUG(llvm::dbgs() <<
"DEAD: " << *op <<
"\n";);
667 assert(op->use_empty() &&
"users should be already removed");
674 if (mlir::isOpTriviallyDead(op)) {
681void IMDeadCodeElimPass::rewriteModuleSignature(FModuleOp module) {
682 assert(isBlockExecutable(module.getBodyBlock()) &&
683 "unreachable modules must be already deleted");
685 LLVM_DEBUG(llvm::dbgs() <<
"Prune ports of module: " << module.getName()
688 auto replaceInstanceResultWithWire =
689 [&](ImplicitLocOpBuilder &builder,
unsigned index, InstanceOp instance) {
690 auto result = instance.getResult(index);
691 if (isAssumedDead(result)) {
695 mlir::UnrealizedConversionCastOp::create(
696 builder, ArrayRef<Type>{result.getType()}, ArrayRef<Value>{})
698 result.replaceAllUsesWith(wire);
702 Value wire = WireOp::create(builder, result.getType()).getResult();
703 result.replaceAllUsesWith(wire);
707 liveElements.erase(result);
708 liveElements.insert(wire);
712 for (
auto *use :
llvm::make_early_inc_range(instanceGraphNode->uses())) {
713 auto instanceLike = use->getInstance<FInstanceLike>();
714 if (instanceLike && !liveElements.count(instanceLike)) {
715 auto instance = cast<InstanceOp>(instanceLike);
717 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
718 for (
auto index :
llvm::
seq(0u, instance.getNumResults()))
719 replaceInstanceResultWithWire(builder, index, instance);
727 if (module.isPublic())
730 unsigned numOldPorts =
module.getNumPorts();
731 llvm::BitVector deadPortIndexes(numOldPorts);
733 ImplicitLocOpBuilder builder(module.getLoc(), module.getContext());
734 builder.setInsertionPointToStart(module.getBodyBlock());
735 auto oldPorts =
module.getPorts();
737 for (
auto index :
llvm::
seq(0u, numOldPorts)) {
738 auto argument =
module.getArgument(index);
740 "If the port has don't touch, it should be known alive");
746 if (isKnownAlive(argument)) {
750 if (module.getPortDirection(index) == Direction::In)
755 if (llvm::any_of(instanceGraph->
lookup(module)->
uses(),
758 record->getInstance().getOperation()->getResult(
765 auto wire = WireOp::create(builder, argument.getType()).getResult();
768 liveElements.erase(argument);
769 liveElements.insert(wire);
770 argument.replaceAllUsesWith(wire);
771 deadPortIndexes.set(index);
778 mlir::UnrealizedConversionCastOp::create(
779 builder, ArrayRef<Type>{argument.getType()}, ArrayRef<Value>{})
782 argument.replaceAllUsesWith(wire);
783 assert(isAssumedDead(wire) &&
"dummy wire must be dead");
784 deadPortIndexes.set(index);
788 if (deadPortIndexes.none())
793 for (
auto arg : module.getArguments())
794 liveElements.erase(arg);
797 module.erasePorts(deadPortIndexes);
800 for (
auto arg : module.getArguments())
801 liveElements.insert(arg);
804 for (
auto *use :
llvm::make_early_inc_range(instanceGraphNode->uses())) {
805 auto instance = use->getInstance<InstanceOp>();
807 assert(!use->getInstance<FInstanceLike>() &&
808 "if this is not an instance, it must be marked overdefined");
812 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
814 for (
auto index : deadPortIndexes.set_bits())
815 replaceInstanceResultWithWire(builder, index, instance);
819 for (
auto oldResult : instance.getResults())
820 liveElements.erase(oldResult);
824 instance.cloneWithErasedPortsAndReplaceUses(deadPortIndexes);
827 for (
auto newResult : newInstance->getResults())
828 liveElements.insert(newResult);
831 if (liveElements.contains(instance)) {
832 liveElements.erase(instance);
833 liveElements.insert(newInstance);
839 numRemovedPorts += deadPortIndexes.count();
842void IMDeadCodeElimPass::eraseEmptyModule(FModuleOp module) {
844 if (!module.getBodyBlock()->empty())
848 if (module.isPublic()) {
849 mlir::emitWarning(module.getLoc())
850 <<
"module `" <<
module.getName()
851 << "` is empty but cannot be removed because the module is public";
855 if (!module.getAnnotations().empty()) {
856 module.emitWarning() << "module `" << module.getName()
857 << "` is empty but cannot be removed "
858 "because the module has annotations "
859 << module.getAnnotations();
863 if (!module.getBodyBlock()->args_empty()) {
864 auto diag =
module.emitWarning()
865 << "module `" << module.getName()
866 << "` is empty but cannot be removed because the "
868 llvm::interleaveComma(module.getPortNames(), diag);
869 diag <<
" are referenced by name or dontTouched";
874 LLVM_DEBUG(llvm::dbgs() <<
"Erase " << module.getName() <<
"\n");
877 instanceGraph->
lookup(module.getModuleNameAttr());
879 SmallVector<Location> instancesWithSymbols;
880 for (
auto *use :
llvm::make_early_inc_range(instanceGraphNode->uses())) {
881 auto instance = use->getInstance<InstanceOp>();
884 if (instance.getInnerSym()) {
885 instancesWithSymbols.push_back(instance.getLoc());
893 if (!instancesWithSymbols.empty()) {
894 auto diag =
module.emitWarning()
895 << "module `" << module.getName()
896 << "` is empty but cannot be removed because an instance is "
897 "referenced by name";
898 diag.attachNote(FusedLoc::get(&getContext(), instancesWithSymbols))
899 <<
"these are instances with symbols";
904 if (liveElements.contains(module))
907 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.