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"
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;
132 circt::hw::InnerRefNamespace *innerRefNamespace;
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);
387 circt::hw::InnerRefNamespace theInnerRefNamespace{*symbolTable, istc};
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());
466 for (
auto port : module.getBodyBlock()->getArguments())
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(
507 circuit.getBodyBlock()->getOps<FModuleOp>())) {
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(
526 circuit.getBodyBlock()->getOps<hw::HierPathOp>()))
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()));
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.
#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.
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.
connect(destination, source)
bool hasDontTouch(Value value)
Check whether a block argument ("port") or the operation defining a value has a DontTouch annotation,...
igraph::InstanceRecord InstanceRecord
MatchingConnectOp getSingleConnectUserOf(Value value)
Scan all the uses of the specified value, checking to see if there is exactly one connect that has th...
igraph::InstanceGraphNode InstanceGraphNode
std::map< std::string, std::set< std::string > > InstanceGraph
Iterates over the handshake::FuncOp's in the program to build an instance graph.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Operation * lookupOp(hw::InnerRefAttr inner) const
Resolve the InnerRef to its target within this namespace, returning empty target if no such name exis...