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