CIRCT 23.0.0git
Loading...
Searching...
No Matches
IMDeadCodeElim.cpp
Go to the documentation of this file.
1//===- IMDeadCodeElim.cpp - Intermodule Dead Code Elimination ---*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
14#include "circt/Support/Debug.h"
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"
23
24#define DEBUG_TYPE "firrtl-imdeadcodeelim"
25
26namespace circt {
27namespace firrtl {
28#define GEN_PASS_DEF_IMDEADCODEELIM
29#include "circt/Dialect/FIRRTL/Passes.h.inc"
30} // namespace firrtl
31} // namespace circt
32
33using namespace circt;
34using namespace firrtl;
35
36// Return true if this op has side-effects except for alloc and read.
37static bool hasUnknownSideEffect(Operation *op) {
38 return !(mlir::isMemoryEffectFree(op) ||
39 mlir::hasSingleEffect<mlir::MemoryEffects::Allocate>(op) ||
40 mlir::hasSingleEffect<mlir::MemoryEffects::Read>(op));
41}
42
43/// Return true if this is a wire or a register or a node.
44static bool isDeclaration(Operation *op) {
45 return isa<WireOp, RegResetOp, RegOp, NodeOp, MemOp>(op);
46}
47
48/// Return true if this is a wire or register we're allowed to delete.
49static bool isDeletableDeclaration(Operation *op) {
50 if (auto name = dyn_cast<FNamableOp>(op))
51 if (!name.hasDroppableName())
52 return false;
53 return !hasDontTouch(op) && AnnotationSet(op).empty();
54}
55
56namespace {
57struct IMDeadCodeElimPass
58 : public circt::firrtl::impl::IMDeadCodeElimBase<IMDeadCodeElimPass> {
59 using Base::Base;
60
61 void runOnOperation() override;
62
63 void rewriteModuleSignature(FModuleOp module);
64 void rewriteModuleBody(FModuleOp module);
65 void eraseEmptyModule(FModuleOp module);
66 void forwardConstantOutputPort(FModuleOp module);
67
68 /// Return true if the value is known alive.
69 bool isKnownAlive(Value value) const {
70 assert(value && "null should not be used");
71 return liveElements.count(value);
72 }
73
74 /// Return true if the value is assumed dead.
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); });
79 }
80
81 /// Return true if the block is alive.
82 bool isBlockExecutable(Block *block) const {
83 return executableBlocks.count(block);
84 }
85
86 void visitUser(Operation *op);
87 void visitValue(Value value);
88
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>());
94 }
95
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);
102
103private:
104 /// The set of blocks that are known to execute, or are intrinsically alive.
105 DenseSet<Block *> executableBlocks;
106
107 InstanceGraph *instanceGraph;
108
109 // The type with which we associate liveness.
110 using ElementType =
111 std::variant<Value, FModuleOp, FInstanceLike, hw::HierPathOp>;
112
113 void markAlive(ElementType element) {
114 if (!liveElements.insert(element).second)
115 return;
116 worklist.push_back(element);
117 }
118
119 /// A worklist of values whose liveness recently changed, indicating
120 /// the users need to be reprocessed.
121 SmallVector<ElementType, 64> worklist;
122 llvm::DenseSet<ElementType> liveElements;
123
124 /// A map from instances to hierpaths whose last path is the associated
125 /// instance.
126 DenseMap<InstanceOp, SmallVector<hw::HierPathOp>> instanceToHierPaths;
127
128 /// Hierpath to its users (=non-local annotation targets).
129 DenseMap<hw::HierPathOp, SetVector<ElementType>> hierPathToElements;
130
131 /// A cache for a (inner)symbol lookp.
132 circt::hw::InnerRefNamespace *innerRefNamespace;
133 mlir::SymbolTable *symbolTable;
134};
135} // namespace
136
137void IMDeadCodeElimPass::visitFInstanceLikeOp(FInstanceLike instanceLike) {
138 auto instance = dyn_cast<InstanceOp>(*instanceLike);
139
140 // This is already marked as alive in the initialization.
141 if (!instance)
142 return;
143
144 markBlockUndeletable(instance);
145
146 auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
147
148 if (!module)
149 return;
150
151 // NOTE: Don't call `markAlive(module)` here as liveness of instance doesn't
152 // imply the global liveness of the module.
153
154 // Propgate liveness through hierpath.
155 for (auto hierPath : instanceToHierPaths[instance])
156 markAlive(hierPath);
157
158 // Input ports get alive only when the instance is alive.
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));
164 }
165}
166
167void IMDeadCodeElimPass::visitModuleOp(FModuleOp module) {
168 // If the module needs to be alive, so are its instances and instance choices.
169 for (auto *use : instanceGraph->lookup(module)->uses()) {
170 if (auto instance = use->getInstance<FInstanceLike>())
171 markAlive(instance);
172 }
173}
174
175void IMDeadCodeElimPass::visitHierPathOp(hw::HierPathOp hierPathOp) {
176 // If the hierpath is alive, mark all instances on the path alive.
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))
181 markAlive(instance);
182 }
183
184 for (auto elem : hierPathToElements[hierPathOp])
185 markAlive(elem);
186}
187
188void IMDeadCodeElimPass::markDeclaration(Operation *op) {
189 assert(isDeclaration(op) && "only a declaration is expected");
190 if (!isDeletableDeclaration(op)) {
191 for (auto result : op->getResults())
192 markAlive(result);
193 markBlockUndeletable(op);
194 }
195}
196
197void IMDeadCodeElimPass::markUnknownSideEffectOp(Operation *op) {
198 // For operations with side effects, pessimistically mark results and
199 // operands as alive.
200 for (auto result : op->getResults())
201 markAlive(result);
202 for (auto operand : op->getOperands())
203 markAlive(operand);
204 markBlockUndeletable(op);
205
206 // Recursively mark any blocks contained within these operations as
207 // executable.
208 for (auto &region : op->getRegions())
209 for (auto &block : region.getBlocks())
210 markBlockExecutable(&block);
211}
212
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);
219}
220
221void IMDeadCodeElimPass::markFInstanceLikeOp(FInstanceLike instanceLike) {
222 if (auto instance = dyn_cast<InstanceOp>(*instanceLike)) {
223 // Get the module being referenced.
224 Operation *op = instance.getReferencedModule(*instanceGraph);
225
226 // If this is an extmodule, just remember that any inputs and inouts are
227 // alive.
228 if (!isa<FModuleOp>(op)) {
229 auto module = dyn_cast<FModuleLike>(op);
230 for (auto resultNo : llvm::seq(0u, instance.getNumResults())) {
231 // If this is an output to the extmodule, we can ignore it.
232 if (module.getPortDirection(resultNo) == Direction::Out)
233 continue;
234
235 // Otherwise this is an input from it or an inout, mark it as alive.
236 markAlive(instance.getResult(resultNo));
237 }
238 markAlive(instance);
239
240 return;
241 }
242
243 // Otherwise this is a defined module.
244 auto fModule = cast<FModuleOp>(op);
245 markBlockExecutable(fModule.getBodyBlock());
246 return;
247 }
248
249 // Conservatively mark everything.
250
251 markAlive(instanceLike);
252 markBlockUndeletable(instanceLike);
253
254 // Mark all results (ports) as alive.
255 for (auto result : instanceLike->getResults())
256 markAlive(result);
257
258 // Mark all possible target modules as executable.
259 for (auto moduleName : instanceLike.getReferencedModuleNamesAttr()) {
260 auto *node = instanceGraph->lookup(cast<StringAttr>(moduleName));
261 if (!node)
262 continue;
263
264 if (auto fModule = dyn_cast<FModuleOp>(*node->getModule())) {
265 markBlockExecutable(fModule.getBodyBlock());
266 for (auto result : fModule.getBodyBlock()->getArguments())
267 markAlive(result);
268 }
269 }
270}
271
272void IMDeadCodeElimPass::markBlockExecutable(Block *block) {
273 if (!executableBlocks.insert(block).second)
274 return; // Already executable.
275
276 auto fmodule = dyn_cast<FModuleOp>(block->getParentOp());
277 if (fmodule && (fmodule.isPublic() || removePortsOnly))
278 markAlive(fmodule);
279
280 // Mark ports with don't touch as alive.
281 for (auto blockArg : block->getArguments())
282 if (hasDontTouch(blockArg)) {
283 markAlive(blockArg);
284 if (fmodule)
285 markAlive(fmodule);
286 }
287
288 for (auto &op : *block) {
289 if (auto instance = dyn_cast<FInstanceLike>(op)) {
290 markFInstanceLikeOp(instance);
291 continue;
292 }
293
294 // Skip connects in both modes.
295 if (isa<FConnectLike>(op))
296 continue;
297
298 if (removePortsOnly) {
299 // In port-only mode, all non-instance, non-connect ops are alive.
300 markUnknownSideEffectOp(&op);
301 continue;
302 }
303
304 // Full IMDCE mode: handle declarations and side effects.
305 if (isDeclaration(&op))
306 markDeclaration(&op);
307 else if (hasUnknownSideEffect(&op))
308 markUnknownSideEffectOp(&op);
309
310 // TODO: Handle attach etc.
311 }
312}
313
314void IMDeadCodeElimPass::forwardConstantOutputPort(FModuleOp module) {
315 // This tracks constant values of output ports. std::nullopt represents an
316 // invalid value.
317 SmallVector<std::pair<unsigned, std::optional<APSInt>>>
318 constantPortIndicesAndValues;
319 auto ports = module.getPorts();
320 auto *instanceGraphNode = instanceGraph->lookup(module);
321
322 for (const auto &e : llvm::enumerate(ports)) {
323 unsigned index = e.index();
324 auto port = e.value();
325 auto arg = module.getArgument(index);
326
327 // If the port has don't touch, don't propagate the constant value.
328 if (!port.isOutput() || hasDontTouch(arg))
329 continue;
330
331 // Remember the index and constant value connected to an output port.
332 if (auto connect = getSingleConnectUserOf(arg)) {
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});
337 }
338 }
339
340 // If there is no constant or invalid port, abort.
341 if (constantPortIndicesAndValues.empty())
342 return;
343
344 // Rewrite all uses.
345 for (auto *use : instanceGraphNode->uses()) {
346 auto instance = use->getInstance<InstanceOp>();
347 // Only constprop for instance ops.
348 if (!instance)
349 continue;
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");
354
355 // Replace the port with the constant or invalid value.
356 Value replacement;
357 if (constant)
358 replacement = ConstantOp::create(builder, *constant);
359 else
360 replacement = InvalidValueOp::create(builder, result.getType());
361 result.replaceAllUsesWith(replacement);
362 }
363 }
364}
365
366void IMDeadCodeElimPass::runOnOperation() {
368
369 auto circuits = getOperation().getOps<CircuitOp>();
370 if (circuits.empty())
371 return;
372
373 auto circuit = *circuits.begin();
374
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();
381 }
382
383 instanceGraph = &getChildAnalysis<InstanceGraph>(circuit);
384 symbolTable = &getChildAnalysis<SymbolTable>(circuit);
385 auto &istc = getChildAnalysis<hw::InnerSymbolTableCollection>(circuit);
386
387 circt::hw::InnerRefNamespace theInnerRefNamespace{*symbolTable, istc};
388 innerRefNamespace = &theInnerRefNamespace;
389
390 // Walk attributes and find unknown uses of inner symbols or hierpaths.
391 getOperation().walk([&](Operation *op) {
392 if (isa<FModuleOp>(op)) // Port or module annotations are ok to ignore.
393 return;
394
395 if (auto hierPath = dyn_cast<hw::HierPathOp>(op)) {
396 auto namePath = hierPath.getNamepath().getValue();
397 // If the hierpath is public or ill-formed, the verifier should have
398 // caught the error. Conservatively mark the symbol as alive.
399 if (hierPath.isPublic() || namePath.size() <= 1 ||
400 isa<hw::InnerRefAttr>(namePath.back()))
401 return markAlive(hierPath);
402
403 if (auto instance =
404 dyn_cast_or_null<firrtl::InstanceOp>(innerRefNamespace->lookupOp(
405 cast<hw::InnerRefAttr>(namePath.drop_back().back()))))
406 instanceToHierPaths[instance].push_back(hierPath);
407 return;
408 }
409
410 // If there is an unknown symbol or inner symbol use, mark all of them
411 // alive.
412 op->getAttrDictionary().walk([&](Attribute attr) {
413 if (auto innerRef = dyn_cast<hw::InnerRefAttr>(attr)) {
414 // Mark instances alive that are targeted by an inner ref.
415 if (auto instance = dyn_cast_or_null<firrtl::InstanceOp>(
416 innerRefNamespace->lookupOp(innerRef)))
417 markAlive(instance);
418 return;
419 }
420
421 if (auto symbolRef = dyn_cast<FlatSymbolRefAttr>(attr)) {
422 auto *symbol = symbolTable->lookup(symbolRef.getAttr());
423 if (!symbol)
424 return;
425
426 // Mark referenced hierarchical paths alive.
427 if (auto hierPath = dyn_cast<hw::HierPathOp>(symbol))
428 markAlive(hierPath);
429
430 // Mark modules referenced by unknown ops alive.
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");
436 markAlive(module);
437 markBlockExecutable(module.getBodyBlock());
438 }
439 }
440
441 return;
442 }
443 });
444 });
445
446 // Create a vector of modules in the post order of instance graph.
447 // FIXME: We copy the list of modules into a vector first to avoid iterator
448 // invalidation while we mutate the instance graph. See issue 3387.
449 SmallVector<FModuleOp, 0> modules(llvm::make_filter_range(
450 llvm::map_range(
451 llvm::post_order(instanceGraph),
452 [](auto *node) { return dyn_cast<FModuleOp>(*node->getModule()); }),
453 [](auto module) { return module; }));
454
455 // Forward constant output ports to caller sides so that we can eliminate
456 // constant outputs.
457 for (auto module : modules)
458 forwardConstantOutputPort(module);
459
460 for (auto module : circuit.getBodyBlock()->getOps<FModuleOp>()) {
461 bool isPublic = module.isPublic();
462 if (isPublic || removePortsOnly) {
463 markBlockExecutable(module.getBodyBlock());
464 // Mark the ports of public modules as alive.
465 if (isPublic)
466 for (auto port : module.getBodyBlock()->getArguments())
467 markAlive(port);
468 }
469
470 // Walk annotations and populate a map from hierpath to attached annotation
471 // targets. `portId` is `-1` for module annotations.
472 auto visitAnnotation = [&](int portId, Annotation anno) -> bool {
473 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
474 hw::HierPathOp hierPathOp;
475 if (hierPathSym)
476 hierPathOp =
477 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
478
479 if (hierPathOp)
480 markAlive(hierPathOp);
481 if (portId >= 0)
482 markAlive(module.getArgument(portId));
483 markAlive(module);
484 return false;
485 };
486
487 AnnotationSet::removePortAnnotations(module, visitAnnotation);
489 module, std::bind(visitAnnotation, -1, std::placeholders::_1));
490 }
491
492 // If an element changed liveness then propagate liveness through it.
493 while (!worklist.empty()) {
494 auto v = worklist.pop_back_val();
495 if (auto *value = std::get_if<Value>(&v))
496 visitValue(*value);
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);
503 }
504
505 // Rewrite module signatures or delete unreachable modules.
506 for (auto module : llvm::make_early_inc_range(
507 circuit.getBodyBlock()->getOps<FModuleOp>())) {
508 if (isBlockExecutable(module.getBodyBlock()))
509 rewriteModuleSignature(module);
510 else {
511 // If the module is unreachable from the toplevel, just delete it.
512 // Note that post-order traversal on the instance graph never visit
513 // unreachable modules so it's safe to erase the module even though
514 // `modules` seems to be capturing module pointers.
515 module.erase();
516 }
517 }
518
519 // Rewrite module bodies parallelly.
520 mlir::parallelForEach(circuit.getContext(),
521 circuit.getBodyBlock()->getOps<FModuleOp>(),
522 [&](auto op) { rewriteModuleBody(op); });
523
524 // Clean up hierpaths.
525 for (auto op : llvm::make_early_inc_range(
526 circuit.getBodyBlock()->getOps<hw::HierPathOp>()))
527 if (!liveElements.count(op))
528 op.erase();
529
530 if (!removePortsOnly)
531 for (auto module : modules)
532 eraseEmptyModule(module);
533
534 // Clean up data structures.
535 executableBlocks.clear();
536 liveElements.clear();
537 instanceToHierPaths.clear();
538 hierPathToElements.clear();
539}
540
541void IMDeadCodeElimPass::visitValue(Value value) {
542 assert(isKnownAlive(value) && "only alive values reach here");
543
544 // Propagate liveness through users.
545 for (Operation *user : value.getUsers())
546 visitUser(user);
547
548 if (auto blockArg = dyn_cast<BlockArgument>(value)) {
549 if (auto module =
550 dyn_cast<FModuleOp>(blockArg.getParentBlock()->getParentOp())) {
551 auto portDirection = module.getPortDirection(blockArg.getArgNumber());
552 // If the port is input, it's necessary to mark corresponding input ports
553 // of instances as alive. We don't have to propagate the liveness of
554 // output ports.
555 if (portDirection == Direction::In) {
556 for (auto *instRec : instanceGraph->lookup(module)->uses()) {
557 // If this is not an instance, it must be marked overdefined already.
558 if (auto instance = instRec->getInstance<InstanceOp>()) {
559 if (liveElements.contains(instance))
560 markAlive(instance.getResult(blockArg.getArgNumber()));
561 }
562 }
563 }
564
565 // Any domain ports associated with non-domain ports are also alive.
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()));
571 return;
572 }
573 }
574
575 // Marking an instance port as alive propagates to the corresponding port of
576 // the module.
577 if (auto instance = value.getDefiningOp<InstanceOp>()) {
578 auto instanceResult = cast<mlir::OpResult>(value);
579 // Update the src, when it's an instance op.
580 auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
581
582 // Propagate liveness only when a port is output.
583 if (!module || module.getPortDirection(instanceResult.getResultNumber()) ==
584 Direction::In)
585 return;
586
587 markAlive(instance);
588
589 BlockArgument modulePortVal =
590 module.getArgument(instanceResult.getResultNumber());
591 return markAlive(modulePortVal);
592 }
593
594 // If a port of a memory is alive, all other ports are.
595 if (auto mem = value.getDefiningOp<MemOp>()) {
596 for (auto port : mem->getResults())
597 markAlive(port);
598 return;
599 }
600
601 // If the value is defined by an operation, mark its operands alive and any
602 // nested blocks executable.
603 if (auto op = value.getDefiningOp()) {
604 for (auto operand : op->getOperands())
605 markAlive(operand);
606 for (auto &region : op->getRegions())
607 for (auto &block : region)
608 markBlockExecutable(&block);
609 }
610
611 // If either result of a forceable declaration is alive, they both are.
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());
617 }
618}
619
620void IMDeadCodeElimPass::visitConnect(FConnectLike connect) {
621 // If the dest is alive, mark the source value as alive.
622 if (isKnownAlive(connect.getDest()))
623 markAlive(connect.getSrc());
624}
625
626void IMDeadCodeElimPass::visitSubelement(Operation *op) {
627 if (isKnownAlive(op->getOperand(0)))
628 markAlive(op->getResult(0));
629}
630
631void IMDeadCodeElimPass::rewriteModuleBody(FModuleOp module) {
632 assert(isBlockExecutable(module.getBodyBlock()) &&
633 "unreachable modules must be already deleted");
634
635 auto removeDeadNonLocalAnnotations = [&](int _, Annotation anno) -> bool {
636 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
637 if (!hierPathSym)
638 return false;
639 auto hierPathOp =
640 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
641 return !liveElements.count(hierPathOp);
642 };
643
644 AnnotationSet::removePortAnnotations(module, removeDeadNonLocalAnnotations);
646 module,
647 std::bind(removeDeadNonLocalAnnotations, -1, std::placeholders::_1));
648
649 // Walk the IR bottom-up when deleting operations.
650 module.walk<mlir::WalkOrder::PostOrder, mlir::ReverseIterator>(
651 [&](Operation *op) {
652 // Connects to values that we found to be dead can be dropped.
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";);
657 connect.erase();
658 ++numErasedOps;
659 }
660 return;
661 }
662
663 // Delete dead wires, regs, nodes and alloc/read ops.
664 if ((isDeclaration(op) || !hasUnknownSideEffect(op)) &&
665 isAssumedDead(op)) {
666 LLVM_DEBUG(llvm::dbgs() << "DEAD: " << *op << "\n";);
667 assert(op->use_empty() && "users should be already removed");
668 op->erase();
669 ++numErasedOps;
670 return;
671 }
672
673 // Remove non-sideeffect op using `isOpTriviallyDead`.
674 if (mlir::isOpTriviallyDead(op)) {
675 op->erase();
676 ++numErasedOps;
677 }
678 });
679}
680
681void IMDeadCodeElimPass::rewriteModuleSignature(FModuleOp module) {
682 assert(isBlockExecutable(module.getBodyBlock()) &&
683 "unreachable modules must be already deleted");
684 InstanceGraphNode *instanceGraphNode = instanceGraph->lookup(module);
685 LLVM_DEBUG(llvm::dbgs() << "Prune ports of module: " << module.getName()
686 << "\n");
687
688 auto replaceInstanceResultWithWire =
689 [&](ImplicitLocOpBuilder &builder, unsigned index, InstanceOp instance) {
690 auto result = instance.getResult(index);
691 if (isAssumedDead(result)) {
692 // If the result is dead, replace the result with an unrealized
693 // conversion cast which works as a dummy placeholder.
694 auto wire =
695 mlir::UnrealizedConversionCastOp::create(
696 builder, ArrayRef<Type>{result.getType()}, ArrayRef<Value>{})
697 ->getResult(0);
698 result.replaceAllUsesWith(wire);
699 return;
700 }
701
702 Value wire = WireOp::create(builder, result.getType()).getResult();
703 result.replaceAllUsesWith(wire);
704 // If a module port is dead but its instance result is alive, the port
705 // is used as a temporary wire so make sure that a replaced wire is
706 // putted into `liveSet`.
707 liveElements.erase(result);
708 liveElements.insert(wire);
709 };
710
711 // First, delete dead instances.
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);
716 // Replace old instance results with dummy wires.
717 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
718 for (auto index : llvm::seq(0u, instance.getNumResults()))
719 replaceInstanceResultWithWire(builder, index, instance);
720 // Make sure that we update the instance graph.
721 use->erase();
722 instance.erase();
723 }
724 }
725
726 // Ports of public modules cannot be modified.
727 if (module.isPublic())
728 return;
729
730 unsigned numOldPorts = module.getNumPorts();
731 llvm::BitVector deadPortIndexes(numOldPorts);
732
733 ImplicitLocOpBuilder builder(module.getLoc(), module.getContext());
734 builder.setInsertionPointToStart(module.getBodyBlock());
735 auto oldPorts = module.getPorts();
736
737 for (auto index : llvm::seq(0u, numOldPorts)) {
738 auto argument = module.getArgument(index);
739 assert((!hasDontTouch(argument) || isKnownAlive(argument)) &&
740 "If the port has don't touch, it should be known alive");
741
742 // If the port has dontTouch, skip.
743 if (hasDontTouch(argument))
744 continue;
745
746 if (isKnownAlive(argument)) {
747
748 // If an output port is only used internally in the module, then we can
749 // remove the port and replace it with a wire.
750 if (module.getPortDirection(index) == Direction::In)
751 continue;
752
753 // Check if the output port is demanded by any instance. If not, then it
754 // is only demanded internally to the module.
755 if (llvm::any_of(instanceGraph->lookup(module)->uses(),
756 [&](InstanceRecord *record) {
757 return isKnownAlive(
758 record->getInstance().getOperation()->getResult(
759 index));
760 }))
761 continue;
762
763 // Ok, this port is used only within its defined module. So we can replace
764 // the port with a wire.
765 auto wire = WireOp::create(builder, argument.getType()).getResult();
766
767 // Since `liveSet` contains the port, we have to erase it from the set.
768 liveElements.erase(argument);
769 liveElements.insert(wire);
770 argument.replaceAllUsesWith(wire);
771 deadPortIndexes.set(index);
772 continue;
773 }
774
775 // Replace the port with a dummy wire. This wire should be erased within
776 // `rewriteModuleBody`.
777 Value wire =
778 mlir::UnrealizedConversionCastOp::create(
779 builder, ArrayRef<Type>{argument.getType()}, ArrayRef<Value>{})
780 ->getResult(0);
781
782 argument.replaceAllUsesWith(wire);
783 assert(isAssumedDead(wire) && "dummy wire must be dead");
784 deadPortIndexes.set(index);
785 }
786
787 // If there is nothing to remove, abort.
788 if (deadPortIndexes.none())
789 return;
790
791 // Erase arguments of the old module from liveSet to prevent from creating
792 // dangling pointers.
793 for (auto arg : module.getArguments())
794 liveElements.erase(arg);
795
796 // Delete ports from the module.
797 module.erasePorts(deadPortIndexes);
798
799 // Add arguments of the new module to liveSet.
800 for (auto arg : module.getArguments())
801 liveElements.insert(arg);
802
803 // Rewrite all uses.
804 for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
805 auto instance = use->getInstance<InstanceOp>();
806 if (!instance) {
807 assert(!use->getInstance<FInstanceLike>() &&
808 "if this is not an instance, it must be marked overdefined");
809 continue;
810 }
811
812 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
813 // Replace old instance results with dummy wires.
814 for (auto index : deadPortIndexes.set_bits())
815 replaceInstanceResultWithWire(builder, index, instance);
816
817 // Since we will rewrite instance op, it is necessary to remove old
818 // instance results from liveSet.
819 for (auto oldResult : instance.getResults())
820 liveElements.erase(oldResult);
821
822 // Create a new instance op without dead ports.
823 auto newInstance =
824 instance.cloneWithErasedPortsAndReplaceUses(deadPortIndexes);
825
826 // Mark new results as alive.
827 for (auto newResult : newInstance->getResults())
828 liveElements.insert(newResult);
829
830 instanceGraph->replaceInstance(instance, newInstance);
831 if (liveElements.contains(instance)) {
832 liveElements.erase(instance);
833 liveElements.insert(newInstance);
834 }
835 // Remove old one.
836 instance.erase();
837 }
838
839 numRemovedPorts += deadPortIndexes.count();
840}
841
842void IMDeadCodeElimPass::eraseEmptyModule(FModuleOp module) {
843 // If the module is not empty, just skip.
844 if (!module.getBodyBlock()->empty())
845 return;
846
847 // We cannot delete public modules so generate a warning.
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";
852 return;
853 }
854
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();
860 return;
861 }
862
863 if (!module.getBodyBlock()->args_empty()) {
864 auto diag = module.emitWarning()
865 << "module `" << module.getName()
866 << "` is empty but cannot be removed because the "
867 "module has ports ";
868 llvm::interleaveComma(module.getPortNames(), diag);
869 diag << " are referenced by name or dontTouched";
870 return;
871 }
872
873 // Ok, the module is empty. Delete instances unless they have symbols.
874 LLVM_DEBUG(llvm::dbgs() << "Erase " << module.getName() << "\n");
875
876 InstanceGraphNode *instanceGraphNode =
877 instanceGraph->lookup(module.getModuleNameAttr());
878
879 SmallVector<Location> instancesWithSymbols;
880 for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
881 auto instance = use->getInstance<InstanceOp>();
882 if (!instance)
883 continue;
884 if (instance.getInnerSym()) {
885 instancesWithSymbols.push_back(instance.getLoc());
886 continue;
887 }
888 use->erase();
889 instance.erase();
890 }
891
892 // If there is an instance with a symbol, we don't delete the module itself.
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";
900 return;
901 }
902
903 // We cannot delete alive modules.
904 if (liveElements.contains(module))
905 return;
906
907 instanceGraph->erase(instanceGraphNode);
908 module.erase();
909 ++numErasedModules;
910}
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)
Definition Debug.h:70
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)
Definition support.py:39
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.
Definition hw.py:1
Definition seq.py:1
This class represents the namespace in which InnerRef's can be resolved.