CIRCT 21.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 void runOnOperation() override;
60
61 void rewriteModuleSignature(FModuleOp module);
62 void rewriteModuleBody(FModuleOp module);
63 void eraseEmptyModule(FModuleOp module);
64 void forwardConstantOutputPort(FModuleOp module);
65
66 /// Return true if the value is known alive.
67 bool isKnownAlive(Value value) const {
68 assert(value && "null should not be used");
69 return liveElements.count(value);
70 }
71
72 /// Return true if the value is assumed dead.
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); });
77 }
78
79 /// Return true if the block is alive.
80 bool isBlockExecutable(Block *block) const {
81 return executableBlocks.count(block);
82 }
83
84 void visitUser(Operation *op);
85 void visitValue(Value value);
86
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>());
92 }
93
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);
101
102private:
103 /// The set of blocks that are known to execute, or are intrinsically alive.
104 DenseSet<Block *> executableBlocks;
105
106 InstanceGraph *instanceGraph;
107
108 // The type with which we associate liveness.
109 using ElementType =
110 std::variant<Value, FModuleOp, InstanceOp, hw::HierPathOp>;
111
112 void markAlive(ElementType element) {
113 if (!liveElements.insert(element).second)
114 return;
115 worklist.push_back(element);
116 }
117
118 /// A worklist of values whose liveness recently changed, indicating
119 /// the users need to be reprocessed.
120 SmallVector<ElementType, 64> worklist;
121 llvm::DenseSet<ElementType> liveElements;
122
123 /// A map from instances to hierpaths whose last path is the associated
124 /// instance.
125 DenseMap<InstanceOp, SmallVector<hw::HierPathOp>> instanceToHierPaths;
126
127 /// Hierpath to its users (=non-local annotation targets).
128 DenseMap<hw::HierPathOp, SetVector<ElementType>> hierPathToElements;
129
130 /// A cache for a (inner)symbol lookp.
131 circt::hw::InnerRefNamespace *innerRefNamespace;
132 mlir::SymbolTable *symbolTable;
133};
134} // namespace
135
136void IMDeadCodeElimPass::visitInstanceOp(InstanceOp instance) {
137 markBlockUndeletable(instance);
138
139 auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
140
141 if (!module)
142 return;
143
144 // NOTE: Don't call `markAlive(module)` here as liveness of instance doesn't
145 // imply the global liveness of the module.
146
147 // Propgate liveness through hierpath.
148 for (auto hierPath : instanceToHierPaths[instance])
149 markAlive(hierPath);
150
151 // Input ports get alive only when the instance is alive.
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));
157 }
158}
159
160void IMDeadCodeElimPass::visitModuleOp(FModuleOp module) {
161 // If the module needs to be alive, so are its instances.
162 for (auto *use : instanceGraph->lookup(module)->uses())
163 markAlive(cast<InstanceOp>(*use->getInstance()));
164}
165
166void IMDeadCodeElimPass::visitHierPathOp(hw::HierPathOp hierPathOp) {
167 // If the hierpath is alive, mark all instances on the path alive.
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))
172 markAlive(instance);
173 }
174
175 for (auto elem : hierPathToElements[hierPathOp])
176 markAlive(elem);
177}
178
179void IMDeadCodeElimPass::markDeclaration(Operation *op) {
180 assert(isDeclaration(op) && "only a declaration is expected");
181 if (!isDeletableDeclaration(op)) {
182 for (auto result : op->getResults())
183 markAlive(result);
184 markBlockUndeletable(op);
185 }
186}
187
188void IMDeadCodeElimPass::markUnknownSideEffectOp(Operation *op) {
189 // For operations with side effects, pessimistically mark results and
190 // operands as alive.
191 for (auto result : op->getResults())
192 markAlive(result);
193 for (auto operand : op->getOperands())
194 markAlive(operand);
195 markBlockUndeletable(op);
196}
197
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);
204}
205
206void IMDeadCodeElimPass::markInstanceOp(InstanceOp instance) {
207 // Get the module being referenced.
208 Operation *op = instance.getReferencedModule(*instanceGraph);
209
210 // If this is an extmodule, just remember that any inputs and inouts are
211 // alive.
212 if (!isa<FModuleOp>(op)) {
213 auto module = dyn_cast<FModuleLike>(op);
214 for (auto resultNo : llvm::seq(0u, instance.getNumResults())) {
215 // If this is an output to the extmodule, we can ignore it.
216 if (module.getPortDirection(resultNo) == Direction::Out)
217 continue;
218
219 // Otherwise this is an input from it or an inout, mark it as alive.
220 markAlive(instance.getResult(resultNo));
221 }
222 markAlive(instance);
223
224 return;
225 }
226
227 // Otherwise this is a defined module.
228 auto fModule = cast<FModuleOp>(op);
229 markBlockExecutable(fModule.getBodyBlock());
230}
231
232void IMDeadCodeElimPass::markObjectOp(ObjectOp object) {
233 // unconditionally keep all objects alive.
234 markAlive(object);
235}
236
237void IMDeadCodeElimPass::markBlockExecutable(Block *block) {
238 if (!executableBlocks.insert(block).second)
239 return; // Already executable.
240
241 auto fmodule = dyn_cast<FModuleOp>(block->getParentOp());
242 if (fmodule && fmodule.isPublic())
243 markAlive(fmodule);
244
245 // Mark ports with don't touch as alive.
246 for (auto blockArg : block->getArguments())
247 if (hasDontTouch(blockArg)) {
248 markAlive(blockArg);
249 if (fmodule)
250 markAlive(fmodule);
251 }
252
253 for (auto &op : *block) {
254 if (isDeclaration(&op))
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))
261 // Skip connect op.
262 continue;
263 else if (hasUnknownSideEffect(&op)) {
264 markUnknownSideEffectOp(&op);
265 // Recursively mark any blocks contained within these operations as
266 // executable.
267 for (auto &region : op.getRegions())
268 for (auto &block : region.getBlocks())
269 markBlockExecutable(&block);
270 }
271
272 // TODO: Handle attach etc.
273 }
274}
275
276void IMDeadCodeElimPass::forwardConstantOutputPort(FModuleOp module) {
277 // This tracks constant values of output ports.
278 SmallVector<std::pair<unsigned, APSInt>> constantPortIndicesAndValues;
279 auto ports = module.getPorts();
280 auto *instanceGraphNode = instanceGraph->lookup(module);
281
282 for (const auto &e : llvm::enumerate(ports)) {
283 unsigned index = e.index();
284 auto port = e.value();
285 auto arg = module.getArgument(index);
286
287 // If the port has don't touch, don't propagate the constant value.
288 if (!port.isOutput() || hasDontTouch(arg))
289 continue;
290
291 // Remember the index and constant value connected to an output port.
292 if (auto connect = getSingleConnectUserOf(arg))
293 if (auto constant = connect.getSrc().getDefiningOp<ConstantOp>())
294 constantPortIndicesAndValues.push_back({index, constant.getValue()});
295 }
296
297 // If there is no constant port, abort.
298 if (constantPortIndicesAndValues.empty())
299 return;
300
301 // Rewrite all uses.
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");
308
309 // Replace the port with the constant.
310 result.replaceAllUsesWith(builder.create<ConstantOp>(constant));
311 }
312 }
313}
314
315void IMDeadCodeElimPass::runOnOperation() {
316 LLVM_DEBUG(debugPassHeader(this) << "\n";);
317 auto circuits = getOperation().getOps<CircuitOp>();
318 if (circuits.empty())
319 return;
320
321 auto circuit = *circuits.begin();
322
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();
329 }
330
331 instanceGraph = &getChildAnalysis<InstanceGraph>(circuit);
332 symbolTable = &getChildAnalysis<SymbolTable>(circuit);
333 auto &istc = getChildAnalysis<hw::InnerSymbolTableCollection>(circuit);
334
335 circt::hw::InnerRefNamespace theInnerRefNamespace{*symbolTable, istc};
336 innerRefNamespace = &theInnerRefNamespace;
337
338 // Walk attributes and find unknown uses of inner symbols or hierpaths.
339 getOperation().walk([&](Operation *op) {
340 if (isa<FModuleOp>(op)) // Port or module annotations are ok to ignore.
341 return;
342
343 if (auto hierPath = dyn_cast<hw::HierPathOp>(op)) {
344 auto namePath = hierPath.getNamepath().getValue();
345 // If the hierpath is public or ill-formed, the verifier should have
346 // caught the error. Conservatively mark the symbol as alive.
347 if (hierPath.isPublic() || namePath.size() <= 1 ||
348 isa<hw::InnerRefAttr>(namePath.back()))
349 return markAlive(hierPath);
350
351 if (auto instance =
352 dyn_cast_or_null<firrtl::InstanceOp>(innerRefNamespace->lookupOp(
353 cast<hw::InnerRefAttr>(namePath.drop_back().back()))))
354 instanceToHierPaths[instance].push_back(hierPath);
355 return;
356 }
357
358 // If there is an unknown symbol or inner symbol use, mark all of them
359 // alive.
360 op->getAttrDictionary().walk([&](Attribute attr) {
361 if (auto innerRef = dyn_cast<hw::InnerRefAttr>(attr)) {
362 // Mark instances alive that are targeted by an inner ref.
363 if (auto instance = dyn_cast_or_null<firrtl::InstanceOp>(
364 innerRefNamespace->lookupOp(innerRef)))
365 markAlive(instance);
366 return;
367 }
368
369 if (auto symbolRef = dyn_cast<FlatSymbolRefAttr>(attr)) {
370 auto *symbol = symbolTable->lookup(symbolRef.getAttr());
371 if (!symbol)
372 return;
373
374 // Mark referenced hierarchical paths alive.
375 if (auto hierPath = dyn_cast<hw::HierPathOp>(symbol))
376 markAlive(hierPath);
377
378 // Mark modules referenced by unknown ops alive.
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");
384 markAlive(module);
385 markBlockExecutable(module.getBodyBlock());
386 }
387 }
388
389 return;
390 }
391 });
392 });
393
394 // Create a vector of modules in the post order of instance graph.
395 // FIXME: We copy the list of modules into a vector first to avoid iterator
396 // invalidation while we mutate the instance graph. See issue 3387.
397 SmallVector<FModuleOp, 0> modules(llvm::make_filter_range(
398 llvm::map_range(
399 llvm::post_order(instanceGraph),
400 [](auto *node) { return dyn_cast<FModuleOp>(*node->getModule()); }),
401 [](auto module) { return module; }));
402
403 // Forward constant output ports to caller sides so that we can eliminate
404 // constant outputs.
405 for (auto module : modules)
406 forwardConstantOutputPort(module);
407
408 for (auto module : circuit.getBodyBlock()->getOps<FModuleOp>()) {
409 // Mark the ports of public modules as alive.
410 if (module.isPublic()) {
411 markBlockExecutable(module.getBodyBlock());
412 for (auto port : module.getBodyBlock()->getArguments())
413 markAlive(port);
414 }
415
416 // Walk annotations and populate a map from hierpath to attached annotation
417 // targets. `portId` is `-1` for module annotations.
418 auto visitAnnotation = [&](int portId, Annotation anno) -> bool {
419 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
420 hw::HierPathOp hierPathOp;
421 if (hierPathSym)
422 hierPathOp =
423 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
424
425 if (hierPathOp)
426 markAlive(hierPathOp);
427 if (portId >= 0)
428 markAlive(module.getArgument(portId));
429 markAlive(module);
430 return false;
431 };
432
433 AnnotationSet::removePortAnnotations(module, visitAnnotation);
435 module, std::bind(visitAnnotation, -1, std::placeholders::_1));
436 }
437
438 // If an element changed liveness then propagate liveness through it.
439 while (!worklist.empty()) {
440 auto v = worklist.pop_back_val();
441 if (auto *value = std::get_if<Value>(&v))
442 visitValue(*value);
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);
449 }
450
451 // Rewrite module signatures or delete unreachable modules.
452 for (auto module : llvm::make_early_inc_range(
453 circuit.getBodyBlock()->getOps<FModuleOp>())) {
454 if (isBlockExecutable(module.getBodyBlock()))
455 rewriteModuleSignature(module);
456 else {
457 // If the module is unreachable from the toplevel, just delete it.
458 // Note that post-order traversal on the instance graph never visit
459 // unreachable modules so it's safe to erase the module even though
460 // `modules` seems to be capturing module pointers.
461 module.erase();
462 }
463 }
464
465 // Rewrite module bodies parallelly.
466 mlir::parallelForEach(circuit.getContext(),
467 circuit.getBodyBlock()->getOps<FModuleOp>(),
468 [&](auto op) { rewriteModuleBody(op); });
469
470 // Clean up hierpaths.
471 for (auto op : llvm::make_early_inc_range(
472 circuit.getBodyBlock()->getOps<hw::HierPathOp>()))
473 if (!liveElements.count(op))
474 op.erase();
475
476 for (auto module : modules)
477 eraseEmptyModule(module);
478
479 // Clean up data structures.
480 executableBlocks.clear();
481 liveElements.clear();
482 instanceToHierPaths.clear();
483 hierPathToElements.clear();
484}
485
486void IMDeadCodeElimPass::visitValue(Value value) {
487 assert(isKnownAlive(value) && "only alive values reach here");
488
489 // Propagate liveness through users.
490 for (Operation *user : value.getUsers())
491 visitUser(user);
492
493 // Requiring an input port propagates the liveness to each instance.
494 if (auto blockArg = dyn_cast<BlockArgument>(value)) {
495 if (auto module =
496 dyn_cast<FModuleOp>(blockArg.getParentBlock()->getParentOp())) {
497 auto portDirection = module.getPortDirection(blockArg.getArgNumber());
498 // If the port is input, it's necessary to mark corresponding input ports
499 // of instances as alive. We don't have to propagate the liveness of
500 // output ports.
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()));
506 }
507 }
508 return;
509 }
510 }
511
512 // Marking an instance port as alive propagates to the corresponding port of
513 // the module.
514 if (auto instance = value.getDefiningOp<InstanceOp>()) {
515 auto instanceResult = cast<mlir::OpResult>(value);
516 // Update the src, when it's an instance op.
517 auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
518
519 // Propagate liveness only when a port is output.
520 if (!module || module.getPortDirection(instanceResult.getResultNumber()) ==
521 Direction::In)
522 return;
523
524 markAlive(instance);
525
526 BlockArgument modulePortVal =
527 module.getArgument(instanceResult.getResultNumber());
528 return markAlive(modulePortVal);
529 }
530
531 // If a port of a memory is alive, all other ports are.
532 if (auto mem = value.getDefiningOp<MemOp>()) {
533 for (auto port : mem->getResults())
534 markAlive(port);
535 return;
536 }
537
538 // If the value is defined by an operation, mark its operands alive and any
539 // nested blocks executable.
540 if (auto op = value.getDefiningOp()) {
541 for (auto operand : op->getOperands())
542 markAlive(operand);
543 for (auto &region : op->getRegions())
544 for (auto &block : region)
545 markBlockExecutable(&block);
546 }
547
548 // If either result of a forceable declaration is alive, they both are.
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());
554 }
555}
556
557void IMDeadCodeElimPass::visitConnect(FConnectLike connect) {
558 // If the dest is alive, mark the source value as alive.
559 if (isKnownAlive(connect.getDest()))
560 markAlive(connect.getSrc());
561}
562
563void IMDeadCodeElimPass::visitSubelement(Operation *op) {
564 if (isKnownAlive(op->getOperand(0)))
565 markAlive(op->getResult(0));
566}
567
568void IMDeadCodeElimPass::rewriteModuleBody(FModuleOp module) {
569 assert(isBlockExecutable(module.getBodyBlock()) &&
570 "unreachable modules must be already deleted");
571
572 auto removeDeadNonLocalAnnotations = [&](int _, Annotation anno) -> bool {
573 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
574 if (!hierPathSym)
575 return false;
576 auto hierPathOp =
577 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
578 return !liveElements.count(hierPathOp);
579 };
580
581 AnnotationSet::removePortAnnotations(module, removeDeadNonLocalAnnotations);
583 module,
584 std::bind(removeDeadNonLocalAnnotations, -1, std::placeholders::_1));
585
586 // Walk the IR bottom-up when deleting operations.
587 module.walk<mlir::WalkOrder::PostOrder, mlir::ReverseIterator>(
588 [&](Operation *op) {
589 // Connects to values that we found to be dead can be dropped.
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";);
594 connect.erase();
595 ++numErasedOps;
596 }
597 return;
598 }
599
600 // Delete dead wires, regs, nodes and alloc/read ops.
601 if ((isDeclaration(op) || !hasUnknownSideEffect(op)) &&
602 isAssumedDead(op)) {
603 LLVM_DEBUG(llvm::dbgs() << "DEAD: " << *op << "\n";);
604 assert(op->use_empty() && "users should be already removed");
605 op->erase();
606 ++numErasedOps;
607 return;
608 }
609
610 // Remove non-sideeffect op using `isOpTriviallyDead`.
611 if (mlir::isOpTriviallyDead(op)) {
612 op->erase();
613 ++numErasedOps;
614 }
615 });
616}
617
618void IMDeadCodeElimPass::rewriteModuleSignature(FModuleOp module) {
619 assert(isBlockExecutable(module.getBodyBlock()) &&
620 "unreachable modules must be already deleted");
621 InstanceGraphNode *instanceGraphNode = instanceGraph->lookup(module);
622 LLVM_DEBUG(llvm::dbgs() << "Prune ports of module: " << module.getName()
623 << "\n");
624
625 auto replaceInstanceResultWithWire = [&](ImplicitLocOpBuilder &builder,
626 unsigned index,
627 InstanceOp instance) {
628 auto result = instance.getResult(index);
629 if (isAssumedDead(result)) {
630 // If the result is dead, replace the result with an unrealized conversion
631 // cast which works as a dummy placeholder.
632 auto wire = builder
633 .create<mlir::UnrealizedConversionCastOp>(
634 ArrayRef<Type>{result.getType()}, ArrayRef<Value>{})
635 ->getResult(0);
636 result.replaceAllUsesWith(wire);
637 return;
638 }
639
640 Value wire = builder.create<WireOp>(result.getType()).getResult();
641 result.replaceAllUsesWith(wire);
642 // If a module port is dead but its instance result is alive, the port
643 // is used as a temporary wire so make sure that a replaced wire is
644 // putted into `liveSet`.
645 liveElements.erase(result);
646 liveElements.insert(wire);
647 };
648
649 // First, delete dead instances.
650 for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
651 auto instance = cast<InstanceOp>(*use->getInstance());
652 if (!liveElements.count(instance)) {
653 // Replace old instance results with dummy wires.
654 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
655 for (auto index : llvm::seq(0u, instance.getNumResults()))
656 replaceInstanceResultWithWire(builder, index, instance);
657 // Make sure that we update the instance graph.
658 use->erase();
659 instance.erase();
660 }
661 }
662
663 // Ports of public modules cannot be modified.
664 if (module.isPublic())
665 return;
666
667 unsigned numOldPorts = module.getNumPorts();
668 llvm::BitVector deadPortIndexes(numOldPorts);
669
670 ImplicitLocOpBuilder builder(module.getLoc(), module.getContext());
671 builder.setInsertionPointToStart(module.getBodyBlock());
672 auto oldPorts = module.getPorts();
673
674 for (auto index : llvm::seq(0u, numOldPorts)) {
675 auto argument = module.getArgument(index);
676 assert((!hasDontTouch(argument) || isKnownAlive(argument)) &&
677 "If the port has don't touch, it should be known alive");
678
679 // If the port has dontTouch, skip.
680 if (hasDontTouch(argument))
681 continue;
682
683 if (isKnownAlive(argument)) {
684
685 // If an output port is only used internally in the module, then we can
686 // remove the port and replace it with a wire.
687 if (module.getPortDirection(index) == Direction::In)
688 continue;
689
690 // Check if the output port is demanded by any instance. If not, then it
691 // is only demanded internally to the module.
692 if (llvm::any_of(instanceGraph->lookup(module)->uses(),
693 [&](InstanceRecord *record) {
694 return isKnownAlive(
695 record->getInstance()->getResult(index));
696 }))
697 continue;
698
699 // Ok, this port is used only within its defined module. So we can replace
700 // the port with a wire.
701 auto wire = builder.create<WireOp>(argument.getType()).getResult();
702
703 // Since `liveSet` contains the port, we have to erase it from the set.
704 liveElements.erase(argument);
705 liveElements.insert(wire);
706 argument.replaceAllUsesWith(wire);
707 deadPortIndexes.set(index);
708 continue;
709 }
710
711 // Replace the port with a dummy wire. This wire should be erased within
712 // `rewriteModuleBody`.
713 Value wire = builder
714 .create<mlir::UnrealizedConversionCastOp>(
715 ArrayRef<Type>{argument.getType()}, ArrayRef<Value>{})
716 ->getResult(0);
717
718 argument.replaceAllUsesWith(wire);
719 assert(isAssumedDead(wire) && "dummy wire must be dead");
720 deadPortIndexes.set(index);
721 }
722
723 // If there is nothing to remove, abort.
724 if (deadPortIndexes.none())
725 return;
726
727 // Erase arguments of the old module from liveSet to prevent from creating
728 // dangling pointers.
729 for (auto arg : module.getArguments())
730 liveElements.erase(arg);
731
732 // Delete ports from the module.
733 module.erasePorts(deadPortIndexes);
734
735 // Add arguments of the new module to liveSet.
736 for (auto arg : module.getArguments())
737 liveElements.insert(arg);
738
739 // Rewrite all uses.
740 for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
741 auto instance = cast<InstanceOp>(*use->getInstance());
742 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
743 // Replace old instance results with dummy wires.
744 for (auto index : deadPortIndexes.set_bits())
745 replaceInstanceResultWithWire(builder, index, instance);
746
747 // Since we will rewrite instance op, it is necessary to remove old
748 // instance results from liveSet.
749 for (auto oldResult : instance.getResults())
750 liveElements.erase(oldResult);
751
752 // Create a new instance op without dead ports.
753 auto newInstance = instance.erasePorts(builder, deadPortIndexes);
754
755 // Mark new results as alive.
756 for (auto newResult : newInstance.getResults())
757 liveElements.insert(newResult);
758
759 instanceGraph->replaceInstance(instance, newInstance);
760 if (liveElements.contains(instance)) {
761 liveElements.erase(instance);
762 liveElements.insert(newInstance);
763 }
764 // Remove old one.
765 instance.erase();
766 }
767
768 numRemovedPorts += deadPortIndexes.count();
769}
770
771void IMDeadCodeElimPass::eraseEmptyModule(FModuleOp module) {
772 // If the module is not empty, just skip.
773 if (!module.getBodyBlock()->empty())
774 return;
775
776 // We cannot delete public modules so generate a warning.
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";
781 return;
782 }
783
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();
789 return;
790 }
791
792 if (!module.getBodyBlock()->args_empty()) {
793 auto diag = module.emitWarning()
794 << "module `" << module.getName()
795 << "` is empty but cannot be removed because the "
796 "module has ports ";
797 llvm::interleaveComma(module.getPortNames(), diag);
798 diag << " are referenced by name or dontTouched";
799 return;
800 }
801
802 // Ok, the module is empty. Delete instances unless they have symbols.
803 LLVM_DEBUG(llvm::dbgs() << "Erase " << module.getName() << "\n");
804
805 InstanceGraphNode *instanceGraphNode =
806 instanceGraph->lookup(module.getModuleNameAttr());
807
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());
813 continue;
814 }
815 use->erase();
816 instance.erase();
817 }
818
819 // If there is an instance with a symbol, we don't delete the module itself.
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";
827 return;
828 }
829
830 // We cannot delete alive modules.
831 if (liveElements.contains(module))
832 return;
833
834 instanceGraph->erase(instanceGraphNode);
835 module.erase();
836 ++numErasedModules;
837}
838
839std::unique_ptr<mlir::Pass> circt::firrtl::createIMDeadCodeElimPass() {
840 return std::make_unique<IMDeadCodeElimPass>();
841}
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)
Definition support.py:39
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.
Definition Debug.cpp:31
Definition hw.py:1
Definition seq.py:1
This class represents the namespace in which InnerRef's can be resolved.