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 markInstanceOp(InstanceOp instanceOp);
96 void markObjectOp(ObjectOp objectOp);
97 void markUnknownSideEffectOp(Operation *op);
98 void visitInstanceOp(InstanceOp instance);
99 void visitHierPathOp(hw::HierPathOp hierpath);
100 void visitModuleOp(FModuleOp module);
104 DenseSet<Block *> executableBlocks;
110 std::variant<Value, FModuleOp, InstanceOp, hw::HierPathOp>;
112 void markAlive(ElementType element) {
113 if (!liveElements.insert(element).second)
115 worklist.push_back(element);
120 SmallVector<ElementType, 64> worklist;
121 llvm::DenseSet<ElementType> liveElements;
125 DenseMap<InstanceOp, SmallVector<hw::HierPathOp>> instanceToHierPaths;
128 DenseMap<hw::HierPathOp, SetVector<ElementType>> hierPathToElements;
132 mlir::SymbolTable *symbolTable;
136void IMDeadCodeElimPass::visitInstanceOp(InstanceOp instance) {
137 markBlockUndeletable(instance);
139 auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
148 for (
auto hierPath : instanceToHierPaths[instance])
152 for (
auto &blockArg : module.getBody().getArguments()) {
153 auto portNo = blockArg.getArgNumber();
154 if (module.getPortDirection(portNo) == Direction::In &&
155 isKnownAlive(module.getArgument(portNo)))
156 markAlive(instance.getResult(portNo));
160void IMDeadCodeElimPass::visitModuleOp(FModuleOp module) {
162 for (
auto *use : instanceGraph->lookup(module)->uses())
163 markAlive(cast<InstanceOp>(*use->getInstance()));
166void IMDeadCodeElimPass::visitHierPathOp(hw::HierPathOp hierPathOp) {
168 for (
auto path : hierPathOp.getNamepathAttr())
169 if (auto innerRef = dyn_cast<
hw::InnerRefAttr>(path)) {
170 auto *op = innerRefNamespace->lookupOp(innerRef);
171 if (
auto instance = dyn_cast_or_null<InstanceOp>(op))
175 for (
auto elem : hierPathToElements[hierPathOp])
179void IMDeadCodeElimPass::markDeclaration(Operation *op) {
182 for (
auto result : op->getResults())
184 markBlockUndeletable(op);
188void IMDeadCodeElimPass::markUnknownSideEffectOp(Operation *op) {
191 for (
auto result : op->getResults())
193 for (
auto operand : op->getOperands())
195 markBlockUndeletable(op);
198void IMDeadCodeElimPass::visitUser(Operation *op) {
199 LLVM_DEBUG(llvm::dbgs() <<
"Visit: " << *op <<
"\n");
200 if (
auto connectOp = dyn_cast<FConnectLike>(op))
201 return visitConnect(connectOp);
202 if (isa<SubfieldOp, SubindexOp, SubaccessOp, ObjectSubfieldOp>(op))
203 return visitSubelement(op);
206void IMDeadCodeElimPass::markInstanceOp(InstanceOp instance) {
208 Operation *op = instance.getReferencedModule(*instanceGraph);
212 if (!isa<FModuleOp>(op)) {
213 auto module = dyn_cast<FModuleLike>(op);
214 for (
auto resultNo :
llvm::
seq(0u, instance.getNumResults())) {
216 if (module.getPortDirection(resultNo) == Direction::Out)
220 markAlive(instance.getResult(resultNo));
228 auto fModule = cast<FModuleOp>(op);
229 markBlockExecutable(fModule.getBodyBlock());
232void IMDeadCodeElimPass::markObjectOp(ObjectOp
object) {
237void IMDeadCodeElimPass::markBlockExecutable(Block *block) {
238 if (!executableBlocks.insert(block).second)
241 auto fmodule = dyn_cast<FModuleOp>(block->getParentOp());
242 if (fmodule && fmodule.isPublic())
246 for (
auto blockArg : block->getArguments())
253 for (
auto &op : *block) {
255 markDeclaration(&op);
256 else if (
auto instance = dyn_cast<InstanceOp>(op))
257 markInstanceOp(instance);
258 else if (
auto object = dyn_cast<ObjectOp>(op))
259 markObjectOp(
object);
260 else if (isa<FConnectLike>(op))
264 markUnknownSideEffectOp(&op);
267 for (
auto ®ion : op.getRegions())
268 for (auto &block : region.getBlocks())
269 markBlockExecutable(&block);
276void IMDeadCodeElimPass::forwardConstantOutputPort(FModuleOp module) {
278 SmallVector<std::pair<unsigned, APSInt>> constantPortIndicesAndValues;
279 auto ports =
module.getPorts();
280 auto *instanceGraphNode = instanceGraph->
lookup(module);
282 for (
const auto &e :
llvm::enumerate(ports)) {
283 unsigned index = e.index();
284 auto port = e.value();
285 auto arg =
module.getArgument(index);
293 if (
auto constant =
connect.getSrc().getDefiningOp<ConstantOp>())
294 constantPortIndicesAndValues.push_back({index, constant.getValue()});
298 if (constantPortIndicesAndValues.empty())
302 for (
auto *use : instanceGraphNode->uses()) {
303 auto instance = cast<InstanceOp>(*use->getInstance());
304 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
305 for (
auto [index, constant] : constantPortIndicesAndValues) {
306 auto result = instance.getResult(index);
307 assert(ports[index].isOutput() &&
"must be an output port");
310 result.replaceAllUsesWith(builder.create<ConstantOp>(constant));
315void IMDeadCodeElimPass::runOnOperation() {
317 auto circuits = getOperation().getOps<CircuitOp>();
318 if (circuits.empty())
321 auto circuit = *circuits.begin();
323 if (!llvm::hasSingleElement(circuits)) {
324 mlir::emitError(circuit.getLoc(),
325 "cannot process multiple circuit operations")
326 .attachNote((*std::next(circuits.begin())).getLoc())
327 <<
"second circuit here";
328 return signalPassFailure();
331 instanceGraph = &getChildAnalysis<InstanceGraph>(circuit);
332 symbolTable = &getChildAnalysis<SymbolTable>(circuit);
333 auto &istc = getChildAnalysis<hw::InnerSymbolTableCollection>(circuit);
336 innerRefNamespace = &theInnerRefNamespace;
339 getOperation().walk([&](Operation *op) {
340 if (isa<FModuleOp>(op))
343 if (
auto hierPath = dyn_cast<hw::HierPathOp>(op)) {
344 auto namePath = hierPath.getNamepath().getValue();
347 if (hierPath.isPublic() || namePath.size() <= 1 ||
348 isa<hw::InnerRefAttr>(namePath.back()))
349 return markAlive(hierPath);
352 dyn_cast_or_null<firrtl::InstanceOp>(innerRefNamespace->lookupOp(
353 cast<hw::InnerRefAttr>(namePath.drop_back().back()))))
354 instanceToHierPaths[instance].push_back(hierPath);
360 op->getAttrDictionary().walk([&](Attribute attr) {
361 if (
auto innerRef = dyn_cast<hw::InnerRefAttr>(attr)) {
363 if (
auto instance = dyn_cast_or_null<firrtl::InstanceOp>(
364 innerRefNamespace->lookupOp(innerRef)))
369 if (
auto symbolRef = dyn_cast<FlatSymbolRefAttr>(attr)) {
370 auto *symbol = symbolTable->lookup(symbolRef.getAttr());
375 if (
auto hierPath = dyn_cast<hw::HierPathOp>(symbol))
379 if (
auto module = dyn_cast<FModuleOp>(symbol)) {
380 if (!isa<firrtl::InstanceOp>(op)) {
381 LLVM_DEBUG(llvm::dbgs()
382 <<
"Unknown use of " << module.getModuleNameAttr()
383 <<
" in " << op->getName() <<
"\n");
385 markBlockExecutable(module.getBodyBlock());
397 SmallVector<FModuleOp, 0> modules(llvm::make_filter_range(
399 llvm::post_order(instanceGraph),
400 [](
auto *node) {
return dyn_cast<FModuleOp>(*node->getModule()); }),
401 [](
auto module) {
return module; }));
405 for (
auto module : modules)
406 forwardConstantOutputPort(module);
408 for (
auto module : circuit.
getBodyBlock()->getOps<FModuleOp>()) {
410 if (module.isPublic()) {
411 markBlockExecutable(module.getBodyBlock());
418 auto visitAnnotation = [&](
int portId,
Annotation anno) ->
bool {
419 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>(
"circt.nonlocal");
420 hw::HierPathOp hierPathOp;
423 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
426 markAlive(hierPathOp);
428 markAlive(module.getArgument(portId));
435 module, std::bind(visitAnnotation, -1, std::placeholders::_1));
439 while (!worklist.empty()) {
440 auto v = worklist.pop_back_val();
441 if (
auto *value = std::get_if<Value>(&v))
443 else if (
auto *instance = std::get_if<InstanceOp>(&v))
444 visitInstanceOp(*instance);
445 else if (
auto *hierpath = std::get_if<hw::HierPathOp>(&v))
446 visitHierPathOp(*hierpath);
447 else if (
auto *module = std::get_if<FModuleOp>(&v))
448 visitModuleOp(*module);
452 for (
auto module :
llvm::make_early_inc_range(
454 if (isBlockExecutable(module.getBodyBlock()))
455 rewriteModuleSignature(module);
466 mlir::parallelForEach(circuit.getContext(),
467 circuit.getBodyBlock()->getOps<FModuleOp>(),
468 [&](
auto op) { rewriteModuleBody(op); });
471 for (
auto op :
llvm::make_early_inc_range(
473 if (!liveElements.count(op))
476 for (
auto module : modules)
477 eraseEmptyModule(module);
480 executableBlocks.clear();
481 liveElements.clear();
482 instanceToHierPaths.clear();
483 hierPathToElements.clear();
486void IMDeadCodeElimPass::visitValue(Value value) {
487 assert(isKnownAlive(value) &&
"only alive values reach here");
490 for (Operation *user : value.getUsers())
494 if (
auto blockArg = dyn_cast<BlockArgument>(value)) {
496 dyn_cast<FModuleOp>(blockArg.getParentBlock()->getParentOp())) {
497 auto portDirection =
module.getPortDirection(blockArg.getArgNumber());
501 if (portDirection == Direction::In) {
502 for (
auto *instRec : instanceGraph->lookup(module)->uses()) {
503 auto instance = cast<InstanceOp>(instRec->getInstance());
504 if (liveElements.contains(instance))
505 markAlive(instance.getResult(blockArg.getArgNumber()));
514 if (
auto instance = value.getDefiningOp<InstanceOp>()) {
515 auto instanceResult = cast<mlir::OpResult>(value);
517 auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
520 if (!module || module.getPortDirection(instanceResult.getResultNumber()) ==
526 BlockArgument modulePortVal =
527 module.getArgument(instanceResult.getResultNumber());
528 return markAlive(modulePortVal);
532 if (
auto mem = value.getDefiningOp<MemOp>()) {
533 for (
auto port : mem->getResults())
540 if (
auto op = value.getDefiningOp()) {
541 for (
auto operand : op->getOperands())
543 for (
auto ®ion : op->getRegions())
544 for (auto &block : region)
545 markBlockExecutable(&block);
549 if (
auto fop = value.getDefiningOp<Forceable>();
550 fop && fop.isForceable() &&
551 (fop.getData() == value || fop.getDataRef() == value)) {
552 markAlive(fop.getData());
553 markAlive(fop.getDataRef());
557void IMDeadCodeElimPass::visitConnect(FConnectLike connect) {
559 if (isKnownAlive(
connect.getDest()))
563void IMDeadCodeElimPass::visitSubelement(Operation *op) {
564 if (isKnownAlive(op->getOperand(0)))
565 markAlive(op->getResult(0));
568void IMDeadCodeElimPass::rewriteModuleBody(FModuleOp module) {
569 assert(isBlockExecutable(module.getBodyBlock()) &&
570 "unreachable modules must be already deleted");
572 auto removeDeadNonLocalAnnotations = [&](
int _,
Annotation anno) ->
bool {
573 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>(
"circt.nonlocal");
577 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
578 return !liveElements.count(hierPathOp);
584 std::bind(removeDeadNonLocalAnnotations, -1, std::placeholders::_1));
587 module.walk<mlir::WalkOrder::PostOrder, mlir::ReverseIterator>(
590 LLVM_DEBUG(llvm::dbgs() << "Visit: " << *op << "\n");
591 if (
auto connect = dyn_cast<FConnectLike>(op)) {
592 if (isAssumedDead(
connect.getDest())) {
593 LLVM_DEBUG(llvm::dbgs() <<
"DEAD: " << connect <<
"\n";);
603 LLVM_DEBUG(llvm::dbgs() <<
"DEAD: " << *op <<
"\n";);
604 assert(op->use_empty() &&
"users should be already removed");
611 if (mlir::isOpTriviallyDead(op)) {
618void IMDeadCodeElimPass::rewriteModuleSignature(FModuleOp module) {
619 assert(isBlockExecutable(module.getBodyBlock()) &&
620 "unreachable modules must be already deleted");
622 LLVM_DEBUG(llvm::dbgs() <<
"Prune ports of module: " << module.getName()
625 auto replaceInstanceResultWithWire = [&](ImplicitLocOpBuilder &builder,
627 InstanceOp instance) {
628 auto result = instance.getResult(index);
629 if (isAssumedDead(result)) {
633 .create<mlir::UnrealizedConversionCastOp>(
634 ArrayRef<Type>{result.getType()}, ArrayRef<Value>{})
636 result.replaceAllUsesWith(wire);
640 Value wire = builder.create<WireOp>(result.getType()).getResult();
641 result.replaceAllUsesWith(wire);
645 liveElements.erase(result);
646 liveElements.insert(wire);
650 for (
auto *use :
llvm::make_early_inc_range(instanceGraphNode->uses())) {
651 auto instance = cast<InstanceOp>(*use->getInstance());
652 if (!liveElements.count(instance)) {
654 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
655 for (
auto index :
llvm::
seq(0u, instance.getNumResults()))
656 replaceInstanceResultWithWire(builder, index, instance);
664 if (module.isPublic())
667 unsigned numOldPorts =
module.getNumPorts();
668 llvm::BitVector deadPortIndexes(numOldPorts);
670 ImplicitLocOpBuilder builder(module.getLoc(), module.getContext());
671 builder.setInsertionPointToStart(module.getBodyBlock());
672 auto oldPorts =
module.getPorts();
674 for (
auto index :
llvm::
seq(0u, numOldPorts)) {
675 auto argument =
module.getArgument(index);
677 "If the port has don't touch, it should be known alive");
683 if (isKnownAlive(argument)) {
687 if (module.getPortDirection(index) == Direction::In)
692 if (llvm::any_of(instanceGraph->
lookup(module)->
uses(),
695 record->getInstance()->getResult(index));
701 auto wire = builder.create<WireOp>(argument.getType()).getResult();
704 liveElements.erase(argument);
705 liveElements.insert(wire);
706 argument.replaceAllUsesWith(wire);
707 deadPortIndexes.set(index);
714 .create<mlir::UnrealizedConversionCastOp>(
715 ArrayRef<Type>{argument.getType()}, ArrayRef<Value>{})
718 argument.replaceAllUsesWith(wire);
719 assert(isAssumedDead(wire) &&
"dummy wire must be dead");
720 deadPortIndexes.set(index);
724 if (deadPortIndexes.none())
729 for (
auto arg : module.getArguments())
730 liveElements.erase(arg);
733 module.erasePorts(deadPortIndexes);
736 for (
auto arg : module.getArguments())
737 liveElements.insert(arg);
740 for (
auto *use :
llvm::make_early_inc_range(instanceGraphNode->uses())) {
741 auto instance = cast<InstanceOp>(*use->getInstance());
742 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
744 for (
auto index : deadPortIndexes.set_bits())
745 replaceInstanceResultWithWire(builder, index, instance);
749 for (
auto oldResult : instance.getResults())
750 liveElements.erase(oldResult);
753 auto newInstance = instance.erasePorts(builder, deadPortIndexes);
756 for (
auto newResult : newInstance.getResults())
757 liveElements.insert(newResult);
760 if (liveElements.contains(instance)) {
761 liveElements.erase(instance);
762 liveElements.insert(newInstance);
768 numRemovedPorts += deadPortIndexes.count();
771void IMDeadCodeElimPass::eraseEmptyModule(FModuleOp module) {
773 if (!module.getBodyBlock()->empty())
777 if (module.isPublic()) {
778 mlir::emitWarning(module.getLoc())
779 <<
"module `" <<
module.getName()
780 << "` is empty but cannot be removed because the module is public";
784 if (!module.getAnnotations().empty()) {
785 module.emitWarning() << "module `" << module.getName()
786 << "` is empty but cannot be removed "
787 "because the module has annotations "
788 << module.getAnnotations();
792 if (!module.getBodyBlock()->args_empty()) {
793 auto diag =
module.emitWarning()
794 << "module `" << module.getName()
795 << "` is empty but cannot be removed because the "
797 llvm::interleaveComma(module.getPortNames(), diag);
798 diag <<
" are referenced by name or dontTouched";
803 LLVM_DEBUG(llvm::dbgs() <<
"Erase " << module.getName() <<
"\n");
806 instanceGraph->
lookup(module.getModuleNameAttr());
808 SmallVector<Location> instancesWithSymbols;
809 for (
auto *use :
llvm::make_early_inc_range(instanceGraphNode->uses())) {
810 auto instance = cast<InstanceOp>(use->getInstance());
811 if (instance.getInnerSym()) {
812 instancesWithSymbols.push_back(instance.getLoc());
820 if (!instancesWithSymbols.empty()) {
821 auto diag =
module.emitWarning()
822 << "module `" << module.getName()
823 << "` is empty but cannot be removed because an instance is "
824 "referenced by name";
825 diag.attachNote(FusedLoc::get(&getContext(), instancesWithSymbols))
826 <<
"these are instances with symbols";
831 if (liveElements.contains(module))
834 instanceGraph->
erase(instanceGraphNode);
840 return std::make_unique<IMDeadCodeElimPass>();
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)
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,...
std::unique_ptr< mlir::Pass > createIMDeadCodeElimPass()
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.
llvm::raw_ostream & debugPassHeader(const mlir::Pass *pass, int width=80)
Write a boilerplate header for a pass to the debug stream.
This class represents the namespace in which InnerRef's can be resolved.