CIRCT 22.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(ConstantOp::create(builder, constant));
311 }
312 }
313}
314
315void IMDeadCodeElimPass::runOnOperation() {
317
318 auto circuits = getOperation().getOps<CircuitOp>();
319 if (circuits.empty())
320 return;
321
322 auto circuit = *circuits.begin();
323
324 if (!llvm::hasSingleElement(circuits)) {
325 mlir::emitError(circuit.getLoc(),
326 "cannot process multiple circuit operations")
327 .attachNote((*std::next(circuits.begin())).getLoc())
328 << "second circuit here";
329 return signalPassFailure();
330 }
331
332 instanceGraph = &getChildAnalysis<InstanceGraph>(circuit);
333 symbolTable = &getChildAnalysis<SymbolTable>(circuit);
334 auto &istc = getChildAnalysis<hw::InnerSymbolTableCollection>(circuit);
335
336 circt::hw::InnerRefNamespace theInnerRefNamespace{*symbolTable, istc};
337 innerRefNamespace = &theInnerRefNamespace;
338
339 // Walk attributes and find unknown uses of inner symbols or hierpaths.
340 getOperation().walk([&](Operation *op) {
341 if (isa<FModuleOp>(op)) // Port or module annotations are ok to ignore.
342 return;
343
344 if (auto hierPath = dyn_cast<hw::HierPathOp>(op)) {
345 auto namePath = hierPath.getNamepath().getValue();
346 // If the hierpath is public or ill-formed, the verifier should have
347 // caught the error. Conservatively mark the symbol as alive.
348 if (hierPath.isPublic() || namePath.size() <= 1 ||
349 isa<hw::InnerRefAttr>(namePath.back()))
350 return markAlive(hierPath);
351
352 if (auto instance =
353 dyn_cast_or_null<firrtl::InstanceOp>(innerRefNamespace->lookupOp(
354 cast<hw::InnerRefAttr>(namePath.drop_back().back()))))
355 instanceToHierPaths[instance].push_back(hierPath);
356 return;
357 }
358
359 // If there is an unknown symbol or inner symbol use, mark all of them
360 // alive.
361 op->getAttrDictionary().walk([&](Attribute attr) {
362 if (auto innerRef = dyn_cast<hw::InnerRefAttr>(attr)) {
363 // Mark instances alive that are targeted by an inner ref.
364 if (auto instance = dyn_cast_or_null<firrtl::InstanceOp>(
365 innerRefNamespace->lookupOp(innerRef)))
366 markAlive(instance);
367 return;
368 }
369
370 if (auto symbolRef = dyn_cast<FlatSymbolRefAttr>(attr)) {
371 auto *symbol = symbolTable->lookup(symbolRef.getAttr());
372 if (!symbol)
373 return;
374
375 // Mark referenced hierarchical paths alive.
376 if (auto hierPath = dyn_cast<hw::HierPathOp>(symbol))
377 markAlive(hierPath);
378
379 // Mark modules referenced by unknown ops alive.
380 if (auto module = dyn_cast<FModuleOp>(symbol)) {
381 if (!isa<firrtl::InstanceOp>(op)) {
382 LLVM_DEBUG(llvm::dbgs()
383 << "Unknown use of " << module.getModuleNameAttr()
384 << " in " << op->getName() << "\n");
385 markAlive(module);
386 markBlockExecutable(module.getBodyBlock());
387 }
388 }
389
390 return;
391 }
392 });
393 });
394
395 // Create a vector of modules in the post order of instance graph.
396 // FIXME: We copy the list of modules into a vector first to avoid iterator
397 // invalidation while we mutate the instance graph. See issue 3387.
398 SmallVector<FModuleOp, 0> modules(llvm::make_filter_range(
399 llvm::map_range(
400 llvm::post_order(instanceGraph),
401 [](auto *node) { return dyn_cast<FModuleOp>(*node->getModule()); }),
402 [](auto module) { return module; }));
403
404 // Forward constant output ports to caller sides so that we can eliminate
405 // constant outputs.
406 for (auto module : modules)
407 forwardConstantOutputPort(module);
408
409 for (auto module : circuit.getBodyBlock()->getOps<FModuleOp>()) {
410 // Mark the ports of public modules as alive.
411 if (module.isPublic()) {
412 markBlockExecutable(module.getBodyBlock());
413 for (auto port : module.getBodyBlock()->getArguments())
414 markAlive(port);
415 }
416
417 // Walk annotations and populate a map from hierpath to attached annotation
418 // targets. `portId` is `-1` for module annotations.
419 auto visitAnnotation = [&](int portId, Annotation anno) -> bool {
420 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
421 hw::HierPathOp hierPathOp;
422 if (hierPathSym)
423 hierPathOp =
424 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
425
426 if (hierPathOp)
427 markAlive(hierPathOp);
428 if (portId >= 0)
429 markAlive(module.getArgument(portId));
430 markAlive(module);
431 return false;
432 };
433
434 AnnotationSet::removePortAnnotations(module, visitAnnotation);
436 module, std::bind(visitAnnotation, -1, std::placeholders::_1));
437 }
438
439 // If an element changed liveness then propagate liveness through it.
440 while (!worklist.empty()) {
441 auto v = worklist.pop_back_val();
442 if (auto *value = std::get_if<Value>(&v))
443 visitValue(*value);
444 else if (auto *instance = std::get_if<InstanceOp>(&v))
445 visitInstanceOp(*instance);
446 else if (auto *hierpath = std::get_if<hw::HierPathOp>(&v))
447 visitHierPathOp(*hierpath);
448 else if (auto *module = std::get_if<FModuleOp>(&v))
449 visitModuleOp(*module);
450 }
451
452 // Rewrite module signatures or delete unreachable modules.
453 for (auto module : llvm::make_early_inc_range(
454 circuit.getBodyBlock()->getOps<FModuleOp>())) {
455 if (isBlockExecutable(module.getBodyBlock()))
456 rewriteModuleSignature(module);
457 else {
458 // If the module is unreachable from the toplevel, just delete it.
459 // Note that post-order traversal on the instance graph never visit
460 // unreachable modules so it's safe to erase the module even though
461 // `modules` seems to be capturing module pointers.
462 module.erase();
463 }
464 }
465
466 // Rewrite module bodies parallelly.
467 mlir::parallelForEach(circuit.getContext(),
468 circuit.getBodyBlock()->getOps<FModuleOp>(),
469 [&](auto op) { rewriteModuleBody(op); });
470
471 // Clean up hierpaths.
472 for (auto op : llvm::make_early_inc_range(
473 circuit.getBodyBlock()->getOps<hw::HierPathOp>()))
474 if (!liveElements.count(op))
475 op.erase();
476
477 for (auto module : modules)
478 eraseEmptyModule(module);
479
480 // Clean up data structures.
481 executableBlocks.clear();
482 liveElements.clear();
483 instanceToHierPaths.clear();
484 hierPathToElements.clear();
485}
486
487void IMDeadCodeElimPass::visitValue(Value value) {
488 assert(isKnownAlive(value) && "only alive values reach here");
489
490 // Propagate liveness through users.
491 for (Operation *user : value.getUsers())
492 visitUser(user);
493
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
509 // Any domain ports associated with non-domain ports are also alive.
510 if (!type_isa<DomainType>(blockArg.getType()))
511 for (auto domain : cast<ArrayAttr>(
512 module.getDomainInfoAttrForPort(blockArg.getArgNumber())))
513 markAlive(module.getArgument(
514 cast<IntegerAttr>(domain).getValue().getZExtValue()));
515 return;
516 }
517 }
518
519 // Marking an instance port as alive propagates to the corresponding port of
520 // the module.
521 if (auto instance = value.getDefiningOp<InstanceOp>()) {
522 auto instanceResult = cast<mlir::OpResult>(value);
523 // Update the src, when it's an instance op.
524 auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
525
526 // Propagate liveness only when a port is output.
527 if (!module || module.getPortDirection(instanceResult.getResultNumber()) ==
528 Direction::In)
529 return;
530
531 markAlive(instance);
532
533 BlockArgument modulePortVal =
534 module.getArgument(instanceResult.getResultNumber());
535 return markAlive(modulePortVal);
536 }
537
538 // If a port of a memory is alive, all other ports are.
539 if (auto mem = value.getDefiningOp<MemOp>()) {
540 for (auto port : mem->getResults())
541 markAlive(port);
542 return;
543 }
544
545 // If the value is defined by an operation, mark its operands alive and any
546 // nested blocks executable.
547 if (auto op = value.getDefiningOp()) {
548 for (auto operand : op->getOperands())
549 markAlive(operand);
550 for (auto &region : op->getRegions())
551 for (auto &block : region)
552 markBlockExecutable(&block);
553 }
554
555 // If either result of a forceable declaration is alive, they both are.
556 if (auto fop = value.getDefiningOp<Forceable>();
557 fop && fop.isForceable() &&
558 (fop.getData() == value || fop.getDataRef() == value)) {
559 markAlive(fop.getData());
560 markAlive(fop.getDataRef());
561 }
562}
563
564void IMDeadCodeElimPass::visitConnect(FConnectLike connect) {
565 // If the dest is alive, mark the source value as alive.
566 if (isKnownAlive(connect.getDest()))
567 markAlive(connect.getSrc());
568}
569
570void IMDeadCodeElimPass::visitSubelement(Operation *op) {
571 if (isKnownAlive(op->getOperand(0)))
572 markAlive(op->getResult(0));
573}
574
575void IMDeadCodeElimPass::rewriteModuleBody(FModuleOp module) {
576 assert(isBlockExecutable(module.getBodyBlock()) &&
577 "unreachable modules must be already deleted");
578
579 auto removeDeadNonLocalAnnotations = [&](int _, Annotation anno) -> bool {
580 auto hierPathSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
581 if (!hierPathSym)
582 return false;
583 auto hierPathOp =
584 symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
585 return !liveElements.count(hierPathOp);
586 };
587
588 AnnotationSet::removePortAnnotations(module, removeDeadNonLocalAnnotations);
590 module,
591 std::bind(removeDeadNonLocalAnnotations, -1, std::placeholders::_1));
592
593 // Walk the IR bottom-up when deleting operations.
594 module.walk<mlir::WalkOrder::PostOrder, mlir::ReverseIterator>(
595 [&](Operation *op) {
596 // Connects to values that we found to be dead can be dropped.
597 LLVM_DEBUG(llvm::dbgs() << "Visit: " << *op << "\n");
598 if (auto connect = dyn_cast<FConnectLike>(op)) {
599 if (isAssumedDead(connect.getDest())) {
600 LLVM_DEBUG(llvm::dbgs() << "DEAD: " << connect << "\n";);
601 connect.erase();
602 ++numErasedOps;
603 }
604 return;
605 }
606
607 // Delete dead wires, regs, nodes and alloc/read ops.
608 if ((isDeclaration(op) || !hasUnknownSideEffect(op)) &&
609 isAssumedDead(op)) {
610 LLVM_DEBUG(llvm::dbgs() << "DEAD: " << *op << "\n";);
611 assert(op->use_empty() && "users should be already removed");
612 op->erase();
613 ++numErasedOps;
614 return;
615 }
616
617 // Remove non-sideeffect op using `isOpTriviallyDead`.
618 if (mlir::isOpTriviallyDead(op)) {
619 op->erase();
620 ++numErasedOps;
621 }
622 });
623}
624
625void IMDeadCodeElimPass::rewriteModuleSignature(FModuleOp module) {
626 assert(isBlockExecutable(module.getBodyBlock()) &&
627 "unreachable modules must be already deleted");
628 InstanceGraphNode *instanceGraphNode = instanceGraph->lookup(module);
629 LLVM_DEBUG(llvm::dbgs() << "Prune ports of module: " << module.getName()
630 << "\n");
631
632 auto replaceInstanceResultWithWire = [&](ImplicitLocOpBuilder &builder,
633 unsigned index,
634 InstanceOp instance) {
635 auto result = instance.getResult(index);
636 if (isAssumedDead(result)) {
637 // If the result is dead, replace the result with an unrealized conversion
638 // cast which works as a dummy placeholder.
639 auto wire =
640 mlir::UnrealizedConversionCastOp::create(
641 builder, ArrayRef<Type>{result.getType()}, ArrayRef<Value>{})
642 ->getResult(0);
643 result.replaceAllUsesWith(wire);
644 return;
645 }
646
647 Value wire = WireOp::create(builder, result.getType()).getResult();
648 result.replaceAllUsesWith(wire);
649 // If a module port is dead but its instance result is alive, the port
650 // is used as a temporary wire so make sure that a replaced wire is
651 // putted into `liveSet`.
652 liveElements.erase(result);
653 liveElements.insert(wire);
654 };
655
656 // First, delete dead instances.
657 for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
658 auto instance = cast<InstanceOp>(*use->getInstance());
659 if (!liveElements.count(instance)) {
660 // Replace old instance results with dummy wires.
661 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
662 for (auto index : llvm::seq(0u, instance.getNumResults()))
663 replaceInstanceResultWithWire(builder, index, instance);
664 // Make sure that we update the instance graph.
665 use->erase();
666 instance.erase();
667 }
668 }
669
670 // Ports of public modules cannot be modified.
671 if (module.isPublic())
672 return;
673
674 unsigned numOldPorts = module.getNumPorts();
675 llvm::BitVector deadPortIndexes(numOldPorts);
676
677 ImplicitLocOpBuilder builder(module.getLoc(), module.getContext());
678 builder.setInsertionPointToStart(module.getBodyBlock());
679 auto oldPorts = module.getPorts();
680
681 for (auto index : llvm::seq(0u, numOldPorts)) {
682 auto argument = module.getArgument(index);
683 assert((!hasDontTouch(argument) || isKnownAlive(argument)) &&
684 "If the port has don't touch, it should be known alive");
685
686 // If the port has dontTouch, skip.
687 if (hasDontTouch(argument))
688 continue;
689
690 if (isKnownAlive(argument)) {
691
692 // If an output port is only used internally in the module, then we can
693 // remove the port and replace it with a wire.
694 if (module.getPortDirection(index) == Direction::In)
695 continue;
696
697 // Check if the output port is demanded by any instance. If not, then it
698 // is only demanded internally to the module.
699 if (llvm::any_of(instanceGraph->lookup(module)->uses(),
700 [&](InstanceRecord *record) {
701 return isKnownAlive(
702 record->getInstance()->getResult(index));
703 }))
704 continue;
705
706 // Ok, this port is used only within its defined module. So we can replace
707 // the port with a wire.
708 auto wire = WireOp::create(builder, argument.getType()).getResult();
709
710 // Since `liveSet` contains the port, we have to erase it from the set.
711 liveElements.erase(argument);
712 liveElements.insert(wire);
713 argument.replaceAllUsesWith(wire);
714 deadPortIndexes.set(index);
715 continue;
716 }
717
718 // Replace the port with a dummy wire. This wire should be erased within
719 // `rewriteModuleBody`.
720 Value wire =
721 mlir::UnrealizedConversionCastOp::create(
722 builder, ArrayRef<Type>{argument.getType()}, ArrayRef<Value>{})
723 ->getResult(0);
724
725 argument.replaceAllUsesWith(wire);
726 assert(isAssumedDead(wire) && "dummy wire must be dead");
727 deadPortIndexes.set(index);
728 }
729
730 // If there is nothing to remove, abort.
731 if (deadPortIndexes.none())
732 return;
733
734 // Erase arguments of the old module from liveSet to prevent from creating
735 // dangling pointers.
736 for (auto arg : module.getArguments())
737 liveElements.erase(arg);
738
739 // Delete ports from the module.
740 module.erasePorts(deadPortIndexes);
741
742 // Add arguments of the new module to liveSet.
743 for (auto arg : module.getArguments())
744 liveElements.insert(arg);
745
746 // Rewrite all uses.
747 for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
748 auto instance = cast<InstanceOp>(*use->getInstance());
749 ImplicitLocOpBuilder builder(instance.getLoc(), instance);
750 // Replace old instance results with dummy wires.
751 for (auto index : deadPortIndexes.set_bits())
752 replaceInstanceResultWithWire(builder, index, instance);
753
754 // Since we will rewrite instance op, it is necessary to remove old
755 // instance results from liveSet.
756 for (auto oldResult : instance.getResults())
757 liveElements.erase(oldResult);
758
759 // Create a new instance op without dead ports.
760 auto newInstance =
761 instance.cloneWithErasedPortsAndReplaceUses(deadPortIndexes);
762
763 // Mark new results as alive.
764 for (auto newResult : newInstance.getResults())
765 liveElements.insert(newResult);
766
767 instanceGraph->replaceInstance(instance, newInstance);
768 if (liveElements.contains(instance)) {
769 liveElements.erase(instance);
770 liveElements.insert(newInstance);
771 }
772 // Remove old one.
773 instance.erase();
774 }
775
776 numRemovedPorts += deadPortIndexes.count();
777}
778
779void IMDeadCodeElimPass::eraseEmptyModule(FModuleOp module) {
780 // If the module is not empty, just skip.
781 if (!module.getBodyBlock()->empty())
782 return;
783
784 // We cannot delete public modules so generate a warning.
785 if (module.isPublic()) {
786 mlir::emitWarning(module.getLoc())
787 << "module `" << module.getName()
788 << "` is empty but cannot be removed because the module is public";
789 return;
790 }
791
792 if (!module.getAnnotations().empty()) {
793 module.emitWarning() << "module `" << module.getName()
794 << "` is empty but cannot be removed "
795 "because the module has annotations "
796 << module.getAnnotations();
797 return;
798 }
799
800 if (!module.getBodyBlock()->args_empty()) {
801 auto diag = module.emitWarning()
802 << "module `" << module.getName()
803 << "` is empty but cannot be removed because the "
804 "module has ports ";
805 llvm::interleaveComma(module.getPortNames(), diag);
806 diag << " are referenced by name or dontTouched";
807 return;
808 }
809
810 // Ok, the module is empty. Delete instances unless they have symbols.
811 LLVM_DEBUG(llvm::dbgs() << "Erase " << module.getName() << "\n");
812
813 InstanceGraphNode *instanceGraphNode =
814 instanceGraph->lookup(module.getModuleNameAttr());
815
816 SmallVector<Location> instancesWithSymbols;
817 for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
818 auto instance = cast<InstanceOp>(use->getInstance());
819 if (instance.getInnerSym()) {
820 instancesWithSymbols.push_back(instance.getLoc());
821 continue;
822 }
823 use->erase();
824 instance.erase();
825 }
826
827 // If there is an instance with a symbol, we don't delete the module itself.
828 if (!instancesWithSymbols.empty()) {
829 auto diag = module.emitWarning()
830 << "module `" << module.getName()
831 << "` is empty but cannot be removed because an instance is "
832 "referenced by name";
833 diag.attachNote(FusedLoc::get(&getContext(), instancesWithSymbols))
834 << "these are instances with symbols";
835 return;
836 }
837
838 // We cannot delete alive modules.
839 if (liveElements.contains(module))
840 return;
841
842 instanceGraph->erase(instanceGraphNode);
843 module.erase();
844 ++numErasedModules;
845}
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.