CIRCT 24.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.
#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.
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)
Definition support.py:39
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
bool type_isa(Type type)
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...