CIRCT  20.0.0git
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 
12 #include "circt/Dialect/HW/HWOps.h"
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 
26 namespace circt {
27 namespace firrtl {
28 #define GEN_PASS_DEF_IMDEADCODEELIM
29 #include "circt/Dialect/FIRRTL/Passes.h.inc"
30 } // namespace firrtl
31 } // namespace circt
32 
33 using namespace circt;
34 using namespace firrtl;
35 
36 // Return true if this op has side-effects except for alloc and read.
37 static 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.
44 static 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.
49 static 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 
56 namespace {
57 struct 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 
102 private:
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 
136 void 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 
160 void 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 
166 void 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 
179 void 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 
188 void 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 
198 void 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 
206 void 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 
232 void IMDeadCodeElimPass::markObjectOp(ObjectOp object) {
233  // unconditionally keep all objects alive.
234  markAlive(object);
235 }
236 
237 void 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 
276 void 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 
315 void 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 use of inner sym or hierpath, just mark all of
359  // them alive.
360  for (NamedAttribute namedAttr : op->getAttrs()) {
361  namedAttr.getValue().walk([&](Attribute subAttr) {
362  if (auto innerRef = dyn_cast<hw::InnerRefAttr>(subAttr))
363  if (auto instance = dyn_cast_or_null<firrtl::InstanceOp>(
364  innerRefNamespace->lookupOp(innerRef)))
365  markAlive(instance);
366 
367  if (auto flatSymbolRefAttr = dyn_cast<FlatSymbolRefAttr>(subAttr))
368  if (auto hierPath = symbolTable->template lookup<hw::HierPathOp>(
369  flatSymbolRefAttr.getAttr()))
370  markAlive(hierPath);
371  });
372  }
373  });
374 
375  // Create a vector of modules in the post order of instance graph.
376  // FIXME: We copy the list of modules into a vector first to avoid iterator
377  // invalidation while we mutate the instance graph. See issue 3387.
378  SmallVector<FModuleOp, 0> modules(llvm::make_filter_range(
379  llvm::map_range(
380  llvm::post_order(instanceGraph),
381  [](auto *node) { return dyn_cast<FModuleOp>(*node->getModule()); }),
382  [](auto module) { return module; }));
383 
384  // Forward constant output ports to caller sides so that we can eliminate
385  // constant outputs.
386  for (auto module : modules)
387  forwardConstantOutputPort(module);
388 
389  for (auto module : circuit.getBodyBlock()->getOps<FModuleOp>()) {
390  // Mark the ports of public modules as alive.
391  if (module.isPublic()) {
392  markBlockExecutable(module.getBodyBlock());
393  for (auto port : module.getBodyBlock()->getArguments())
394  markAlive(port);
395  }
396 
397  // Walk annotations and populate a map from hierpath to attached annotation
398  // targets. `portId` is `-1` for module annotations.
399  auto visitAnnotation = [&](int portId, Annotation anno) -> bool {
400  auto hierPathSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
401  hw::HierPathOp hierPathOp;
402  if (hierPathSym)
403  hierPathOp =
404  symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
405 
406  if (hierPathOp)
407  markAlive(hierPathOp);
408  if (portId >= 0)
409  markAlive(module.getArgument(portId));
410  markAlive(module);
411  return false;
412  };
413 
414  AnnotationSet::removePortAnnotations(module, visitAnnotation);
416  module, std::bind(visitAnnotation, -1, std::placeholders::_1));
417  }
418 
419  // If an element changed liveness then propagate liveness through it.
420  while (!worklist.empty()) {
421  auto v = worklist.pop_back_val();
422  if (auto *value = std::get_if<Value>(&v))
423  visitValue(*value);
424  else if (auto *instance = std::get_if<InstanceOp>(&v))
425  visitInstanceOp(*instance);
426  else if (auto *hierpath = std::get_if<hw::HierPathOp>(&v))
427  visitHierPathOp(*hierpath);
428  else if (auto *module = std::get_if<FModuleOp>(&v))
429  visitModuleOp(*module);
430  }
431 
432  // Rewrite module signatures or delete unreachable modules.
433  for (auto module : llvm::make_early_inc_range(
434  circuit.getBodyBlock()->getOps<FModuleOp>())) {
435  if (isBlockExecutable(module.getBodyBlock()))
436  rewriteModuleSignature(module);
437  else {
438  // If the module is unreachable from the toplevel, just delete it.
439  // Note that post-order traversal on the instance graph never visit
440  // unreachable modules so it's safe to erase the module even though
441  // `modules` seems to be capturing module pointers.
442  module.erase();
443  }
444  }
445 
446  // Rewrite module bodies parallelly.
447  mlir::parallelForEach(circuit.getContext(),
448  circuit.getBodyBlock()->getOps<FModuleOp>(),
449  [&](auto op) { rewriteModuleBody(op); });
450 
451  // Clean up hierpaths.
452  for (auto op : llvm::make_early_inc_range(
453  circuit.getBodyBlock()->getOps<hw::HierPathOp>()))
454  if (!liveElements.count(op))
455  op.erase();
456 
457  for (auto module : modules)
458  eraseEmptyModule(module);
459 
460  // Clean up data structures.
461  executableBlocks.clear();
462  liveElements.clear();
463  instanceToHierPaths.clear();
464  hierPathToElements.clear();
465 }
466 
467 void IMDeadCodeElimPass::visitValue(Value value) {
468  assert(isKnownAlive(value) && "only alive values reach here");
469 
470  // Propagate liveness through users.
471  for (Operation *user : value.getUsers())
472  visitUser(user);
473 
474  // Requiring an input port propagates the liveness to each instance.
475  if (auto blockArg = dyn_cast<BlockArgument>(value)) {
476  auto module = cast<FModuleOp>(blockArg.getParentBlock()->getParentOp());
477  auto portDirection = module.getPortDirection(blockArg.getArgNumber());
478  // If the port is input, it's necessary to mark corresponding input ports of
479  // instances as alive. We don't have to propagate the liveness of output
480  // ports.
481  if (portDirection == Direction::In) {
482  for (auto *instRec : instanceGraph->lookup(module)->uses()) {
483  auto instance = cast<InstanceOp>(instRec->getInstance());
484  if (liveElements.contains(instance))
485  markAlive(instance.getResult(blockArg.getArgNumber()));
486  }
487  }
488  return;
489  }
490 
491  // Marking an instance port as alive propagates to the corresponding port of
492  // the module.
493  if (auto instance = value.getDefiningOp<InstanceOp>()) {
494  auto instanceResult = cast<mlir::OpResult>(value);
495  // Update the src, when it's an instance op.
496  auto module = instance.getReferencedModule<FModuleOp>(*instanceGraph);
497 
498  // Propagate liveness only when a port is output.
499  if (!module || module.getPortDirection(instanceResult.getResultNumber()) ==
500  Direction::In)
501  return;
502 
503  markAlive(instance);
504 
505  BlockArgument modulePortVal =
506  module.getArgument(instanceResult.getResultNumber());
507  return markAlive(modulePortVal);
508  }
509 
510  // If a port of a memory is alive, all other ports are.
511  if (auto mem = value.getDefiningOp<MemOp>()) {
512  for (auto port : mem->getResults())
513  markAlive(port);
514  return;
515  }
516 
517  // If op is defined by an operation, mark its operands as alive.
518  if (auto op = value.getDefiningOp())
519  for (auto operand : op->getOperands())
520  markAlive(operand);
521 
522  // If either result of a forceable declaration is alive, they both are.
523  if (auto fop = value.getDefiningOp<Forceable>();
524  fop && fop.isForceable() &&
525  (fop.getData() == value || fop.getDataRef() == value)) {
526  markAlive(fop.getData());
527  markAlive(fop.getDataRef());
528  }
529 }
530 
531 void IMDeadCodeElimPass::visitConnect(FConnectLike connect) {
532  // If the dest is alive, mark the source value as alive.
533  if (isKnownAlive(connect.getDest()))
534  markAlive(connect.getSrc());
535 }
536 
537 void IMDeadCodeElimPass::visitSubelement(Operation *op) {
538  if (isKnownAlive(op->getOperand(0)))
539  markAlive(op->getResult(0));
540 }
541 
542 void IMDeadCodeElimPass::rewriteModuleBody(FModuleOp module) {
543  assert(isBlockExecutable(module.getBodyBlock()) &&
544  "unreachable modules must be already deleted");
545 
546  auto removeDeadNonLocalAnnotations = [&](int _, Annotation anno) -> bool {
547  auto hierPathSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
548  if (!hierPathSym)
549  return false;
550  auto hierPathOp =
551  symbolTable->template lookup<hw::HierPathOp>(hierPathSym.getAttr());
552  return !liveElements.count(hierPathOp);
553  };
554 
555  AnnotationSet::removePortAnnotations(module, removeDeadNonLocalAnnotations);
556  AnnotationSet::removeAnnotations(
557  module,
558  std::bind(removeDeadNonLocalAnnotations, -1, std::placeholders::_1));
559 
560  // Walk the IR bottom-up when deleting operations.
561  module.walk<mlir::WalkOrder::PostOrder, mlir::ReverseIterator>(
562  [&](Operation *op) {
563  // Connects to values that we found to be dead can be dropped.
564  LLVM_DEBUG(llvm::dbgs() << "Visit: " << *op << "\n");
565  if (auto connect = dyn_cast<FConnectLike>(op)) {
566  if (isAssumedDead(connect.getDest())) {
567  LLVM_DEBUG(llvm::dbgs() << "DEAD: " << connect << "\n";);
568  connect.erase();
569  ++numErasedOps;
570  }
571  return;
572  }
573 
574  // Delete dead wires, regs, nodes and alloc/read ops.
575  if ((isDeclaration(op) || !hasUnknownSideEffect(op)) &&
576  isAssumedDead(op)) {
577  LLVM_DEBUG(llvm::dbgs() << "DEAD: " << *op << "\n";);
578  assert(op->use_empty() && "users should be already removed");
579  op->erase();
580  ++numErasedOps;
581  return;
582  }
583 
584  // Remove non-sideeffect op using `isOpTriviallyDead`.
585  if (mlir::isOpTriviallyDead(op)) {
586  op->erase();
587  ++numErasedOps;
588  }
589  });
590 }
591 
592 void IMDeadCodeElimPass::rewriteModuleSignature(FModuleOp module) {
593  assert(isBlockExecutable(module.getBodyBlock()) &&
594  "unreachable modules must be already deleted");
595  InstanceGraphNode *instanceGraphNode = instanceGraph->lookup(module);
596  LLVM_DEBUG(llvm::dbgs() << "Prune ports of module: " << module.getName()
597  << "\n");
598 
599  auto replaceInstanceResultWithWire = [&](ImplicitLocOpBuilder &builder,
600  unsigned index,
601  InstanceOp instance) {
602  auto result = instance.getResult(index);
603  if (isAssumedDead(result)) {
604  // If the result is dead, replace the result with an unrealized conversion
605  // cast which works as a dummy placeholder.
606  auto wire = builder
607  .create<mlir::UnrealizedConversionCastOp>(
608  ArrayRef<Type>{result.getType()}, ArrayRef<Value>{})
609  ->getResult(0);
610  result.replaceAllUsesWith(wire);
611  return;
612  }
613 
614  Value wire = builder.create<WireOp>(result.getType()).getResult();
615  result.replaceAllUsesWith(wire);
616  // If a module port is dead but its instance result is alive, the port
617  // is used as a temporary wire so make sure that a replaced wire is
618  // putted into `liveSet`.
619  liveElements.erase(result);
620  liveElements.insert(wire);
621  };
622 
623  // First, delete dead instances.
624  for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
625  auto instance = cast<InstanceOp>(*use->getInstance());
626  if (!liveElements.count(instance)) {
627  // Replace old instance results with dummy wires.
628  ImplicitLocOpBuilder builder(instance.getLoc(), instance);
629  for (auto index : llvm::seq(0u, instance.getNumResults()))
630  replaceInstanceResultWithWire(builder, index, instance);
631  // Make sure that we update the instance graph.
632  use->erase();
633  instance.erase();
634  }
635  }
636 
637  // Ports of public modules cannot be modified.
638  if (module.isPublic())
639  return;
640 
641  unsigned numOldPorts = module.getNumPorts();
642  llvm::BitVector deadPortIndexes(numOldPorts);
643 
644  ImplicitLocOpBuilder builder(module.getLoc(), module.getContext());
645  builder.setInsertionPointToStart(module.getBodyBlock());
646  auto oldPorts = module.getPorts();
647 
648  for (auto index : llvm::seq(0u, numOldPorts)) {
649  auto argument = module.getArgument(index);
650  assert((!hasDontTouch(argument) || isKnownAlive(argument)) &&
651  "If the port has don't touch, it should be known alive");
652 
653  // If the port has dontTouch, skip.
654  if (hasDontTouch(argument))
655  continue;
656 
657  if (isKnownAlive(argument)) {
658 
659  // If an output port is only used internally in the module, then we can
660  // remove the port and replace it with a wire.
661  if (module.getPortDirection(index) == Direction::In)
662  continue;
663 
664  // Check if the output port is demanded by any instance. If not, then it
665  // is only demanded internally to the module.
666  if (llvm::any_of(instanceGraph->lookup(module)->uses(),
667  [&](InstanceRecord *record) {
668  return isKnownAlive(
669  record->getInstance()->getResult(index));
670  }))
671  continue;
672 
673  // Ok, this port is used only within its defined module. So we can replace
674  // the port with a wire.
675  auto wire = builder.create<WireOp>(argument.getType()).getResult();
676 
677  // Since `liveSet` contains the port, we have to erase it from the set.
678  liveElements.erase(argument);
679  liveElements.insert(wire);
680  argument.replaceAllUsesWith(wire);
681  deadPortIndexes.set(index);
682  continue;
683  }
684 
685  // Replace the port with a dummy wire. This wire should be erased within
686  // `rewriteModuleBody`.
687  Value wire = builder
688  .create<mlir::UnrealizedConversionCastOp>(
689  ArrayRef<Type>{argument.getType()}, ArrayRef<Value>{})
690  ->getResult(0);
691 
692  argument.replaceAllUsesWith(wire);
693  assert(isAssumedDead(wire) && "dummy wire must be dead");
694  deadPortIndexes.set(index);
695  }
696 
697  // If there is nothing to remove, abort.
698  if (deadPortIndexes.none())
699  return;
700 
701  // Erase arguments of the old module from liveSet to prevent from creating
702  // dangling pointers.
703  for (auto arg : module.getArguments())
704  liveElements.erase(arg);
705 
706  // Delete ports from the module.
707  module.erasePorts(deadPortIndexes);
708 
709  // Add arguments of the new module to liveSet.
710  for (auto arg : module.getArguments())
711  liveElements.insert(arg);
712 
713  // Rewrite all uses.
714  for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
715  auto instance = cast<InstanceOp>(*use->getInstance());
716  ImplicitLocOpBuilder builder(instance.getLoc(), instance);
717  // Replace old instance results with dummy wires.
718  for (auto index : deadPortIndexes.set_bits())
719  replaceInstanceResultWithWire(builder, index, instance);
720 
721  // Since we will rewrite instance op, it is necessary to remove old
722  // instance results from liveSet.
723  for (auto oldResult : instance.getResults())
724  liveElements.erase(oldResult);
725 
726  // Create a new instance op without dead ports.
727  auto newInstance = instance.erasePorts(builder, deadPortIndexes);
728 
729  // Mark new results as alive.
730  for (auto newResult : newInstance.getResults())
731  liveElements.insert(newResult);
732 
733  instanceGraph->replaceInstance(instance, newInstance);
734  if (liveElements.contains(instance)) {
735  liveElements.erase(instance);
736  liveElements.insert(newInstance);
737  }
738  // Remove old one.
739  instance.erase();
740  }
741 
742  numRemovedPorts += deadPortIndexes.count();
743 }
744 
745 void IMDeadCodeElimPass::eraseEmptyModule(FModuleOp module) {
746  // If the module is not empty, just skip.
747  if (!module.getBodyBlock()->empty())
748  return;
749 
750  // We cannot delete public modules so generate a warning.
751  if (module.isPublic()) {
752  mlir::emitWarning(module.getLoc())
753  << "module `" << module.getName()
754  << "` is empty but cannot be removed because the module is public";
755  return;
756  }
757 
758  if (!module.getAnnotations().empty()) {
759  module.emitWarning() << "module `" << module.getName()
760  << "` is empty but cannot be removed "
761  "because the module has annotations "
762  << module.getAnnotations();
763  return;
764  }
765 
766  if (!module.getBodyBlock()->args_empty()) {
767  auto diag = module.emitWarning()
768  << "module `" << module.getName()
769  << "` is empty but cannot be removed because the "
770  "module has ports ";
771  llvm::interleaveComma(module.getPortNames(), diag);
772  diag << " are referenced by name or dontTouched";
773  return;
774  }
775 
776  // Ok, the module is empty. Delete instances unless they have symbols.
777  LLVM_DEBUG(llvm::dbgs() << "Erase " << module.getName() << "\n");
778 
779  InstanceGraphNode *instanceGraphNode =
780  instanceGraph->lookup(module.getModuleNameAttr());
781 
782  SmallVector<Location> instancesWithSymbols;
783  for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) {
784  auto instance = cast<InstanceOp>(use->getInstance());
785  if (instance.getInnerSym()) {
786  instancesWithSymbols.push_back(instance.getLoc());
787  continue;
788  }
789  use->erase();
790  instance.erase();
791  }
792 
793  // If there is an instance with a symbol, we don't delete the module itself.
794  if (!instancesWithSymbols.empty()) {
795  auto diag = module.emitWarning()
796  << "module `" << module.getName()
797  << "` is empty but cannot be removed because an instance is "
798  "referenced by name";
799  diag.attachNote(FusedLoc::get(&getContext(), instancesWithSymbols))
800  << "these are instances with symbols";
801  return;
802  }
803 
804  instanceGraph->erase(instanceGraphNode);
805  module.erase();
806  ++numErasedModules;
807 }
808 
809 std::unique_ptr<mlir::Pass> circt::firrtl::createIMDeadCodeElimPass() {
810  return std::make_unique<IMDeadCodeElimPass>();
811 }
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.
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()
This is an edge in the InstanceGraph.
Definition: InstanceGraph.h:55
def connect(destination, source)
Definition: support.py:39
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
bool hasDontTouch(Value value)
Check whether a block argument ("port") or the operation defining a value has a DontTouch annotation,...
Definition: FIRRTLOps.cpp:317
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.
Definition: DebugAnalysis.h:21
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
This class represents the namespace in which InnerRef's can be resolved.