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