CIRCT  20.0.0git
RemoveUnusedPorts.cpp
Go to the documentation of this file.
1 //===- RemoveUnusedPorts.cpp - Remove Dead Ports ----------------*- 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 
13 #include "circt/Support/Debug.h"
14 #include "mlir/IR/ImplicitLocOpBuilder.h"
15 #include "mlir/Pass/Pass.h"
16 #include "llvm/ADT/BitVector.h"
17 #include "llvm/ADT/PostOrderIterator.h"
18 #include "llvm/Support/Debug.h"
19 
20 #define DEBUG_TYPE "firrtl-remove-unused-ports"
21 
22 namespace circt {
23 namespace firrtl {
24 #define GEN_PASS_DEF_REMOVEUNUSEDPORTS
25 #include "circt/Dialect/FIRRTL/Passes.h.inc"
26 } // namespace firrtl
27 } // namespace circt
28 
29 using namespace circt;
30 using namespace firrtl;
31 
32 namespace {
33 struct RemoveUnusedPortsPass
34  : public circt::firrtl::impl::RemoveUnusedPortsBase<RemoveUnusedPortsPass> {
35  void runOnOperation() override;
36  void removeUnusedModulePorts(FModuleOp module,
37  InstanceGraphNode *instanceGraphNode);
38 
39  /// If true, the pass will remove unused ports even if they have carry a
40  /// symbol or annotations. This is likely to break the IR, but may be useful
41  /// for `circt-reduce` where preserving functional correctness of the IR is
42  /// not important.
43  bool ignoreDontTouch = false;
44 };
45 } // namespace
46 
47 void RemoveUnusedPortsPass::runOnOperation() {
48  auto &instanceGraph = getAnalysis<InstanceGraph>();
49  LLVM_DEBUG(debugPassHeader(this) << "\n");
50  // Iterate in the reverse order of instance graph iterator, i.e. from leaves
51  // to top.
52  for (auto *node : llvm::post_order(&instanceGraph))
53  if (auto module = dyn_cast<FModuleOp>(*node->getModule()))
54  // Don't prune the main module.
55  if (!module.isPublic())
56  removeUnusedModulePorts(module, node);
57 }
58 
59 void RemoveUnusedPortsPass::removeUnusedModulePorts(
60  FModuleOp module, InstanceGraphNode *instanceGraphNode) {
61  LLVM_DEBUG(llvm::dbgs() << "Prune ports of module: " << module.getName()
62  << "\n");
63  // This tracks constant values of output ports. None indicates an invalid
64  // value.
65  SmallVector<std::optional<APSInt>> outputPortConstants;
66  auto ports = module.getPorts();
67  // This tracks port indexes that can be erased.
68  llvm::BitVector removalPortIndexes(ports.size());
69 
70  for (const auto &e : llvm::enumerate(ports)) {
71  unsigned index = e.index();
72  auto port = e.value();
73  auto arg = module.getArgument(index);
74 
75  // If the port is don't touch or has unprocessed annotations, we cannot
76  // remove the port.
77  if ((hasDontTouch(arg) || !port.annotations.empty()) && !ignoreDontTouch)
78  continue;
79 
80  // TODO: Handle inout ports.
81  if (port.isInOut())
82  continue;
83 
84  // If the port is input and has an user, we cannot remove the
85  // port.
86  if (port.isInput() && !arg.use_empty())
87  continue;
88 
89  auto portIsUnused = [&](InstanceRecord *a) -> bool {
90  auto port = a->getInstance()->getResult(arg.getArgNumber());
91  return port.getUses().empty();
92  };
93 
94  // Output port.
95  if (port.isOutput()) {
96  if (arg.use_empty()) {
97  // Sometimes the connection is already removed possibly by IMCP.
98  // In that case, regard the port value as an invalid value.
99  outputPortConstants.push_back(std::nullopt);
100  } else if (llvm::all_of(instanceGraphNode->uses(), portIsUnused)) {
101  // Replace the port with a wire if it is unused.
102  auto builder = ImplicitLocOpBuilder::atBlockBegin(
103  arg.getLoc(), module.getBodyBlock());
104  auto wire = builder.create<WireOp>(arg.getType());
105  arg.replaceAllUsesWith(wire.getResult());
106  outputPortConstants.push_back(std::nullopt);
107  } else if (arg.hasOneUse()) {
108  // If the port has a single use, check the port is only connected to
109  // invalid or constant
110  Operation *op = arg.use_begin().getUser();
111  auto connectLike = dyn_cast<FConnectLike>(op);
112  if (!connectLike)
113  continue;
114  auto *srcOp = connectLike.getSrc().getDefiningOp();
115  if (!isa_and_nonnull<InvalidValueOp, ConstantOp>(srcOp))
116  continue;
117 
118  if (auto constant = dyn_cast<ConstantOp>(srcOp))
119  outputPortConstants.push_back(constant.getValue());
120  else {
121  assert(isa<InvalidValueOp>(srcOp) && "only expect invalid");
122  outputPortConstants.push_back(std::nullopt);
123  }
124 
125  // Erase connect op because we are going to remove this output ports.
126  op->erase();
127 
128  if (srcOp->use_empty())
129  srcOp->erase();
130  } else {
131  // Otherwise, we cannot remove the port.
132  continue;
133  }
134  }
135 
136  removalPortIndexes.set(index);
137  }
138 
139  // If there is nothing to remove, abort.
140  if (removalPortIndexes.none())
141  return;
142 
143  // Delete ports from the module.
144  module.erasePorts(removalPortIndexes);
145  LLVM_DEBUG(llvm::for_each(removalPortIndexes.set_bits(), [&](unsigned index) {
146  llvm::dbgs() << "Delete port: " << ports[index].name << "\n";
147  }););
148 
149  // Rewrite all uses.
150  for (auto *use : instanceGraphNode->uses()) {
151  auto instance = ::cast<InstanceOp>(*use->getInstance());
152  ImplicitLocOpBuilder builder(instance.getLoc(), instance);
153  unsigned outputPortIndex = 0;
154  for (auto index : removalPortIndexes.set_bits()) {
155  auto result = instance.getResult(index);
156  assert(!ports[index].isInOut() && "don't expect inout ports");
157 
158  // If the port is input, replace the port with an unwritten wire
159  // so that we can remove use-chains in SV dialect canonicalization.
160  if (ports[index].isInput()) {
161  WireOp wire = builder.create<WireOp>(result.getType());
162 
163  // Check that the input port is only written. Sometimes input ports are
164  // used as temporary wires. In that case, we cannot erase connections.
165  bool onlyWritten = llvm::all_of(result.getUsers(), [&](Operation *op) {
166  if (auto connect = dyn_cast<FConnectLike>(op))
167  return connect.getDest() == result;
168  return false;
169  });
170 
171  result.replaceUsesWithIf(wire.getResult(), [&](OpOperand &op) -> bool {
172  // Connects can be deleted directly.
173  if (onlyWritten && isa<FConnectLike>(op.getOwner())) {
174  op.getOwner()->erase();
175  return false;
176  }
177  return true;
178  });
179 
180  // If the wire doesn't have an user, just erase it.
181  if (wire.use_empty())
182  wire.erase();
183 
184  continue;
185  }
186 
187  // Output port. Replace with the output port with an invalid or constant
188  // value.
189  auto portConstant = outputPortConstants[outputPortIndex++];
190  Value value;
191  if (portConstant)
192  value = builder.create<ConstantOp>(*portConstant);
193  else
194  value = builder.create<InvalidValueOp>(result.getType());
195 
196  result.replaceAllUsesWith(value);
197  }
198 
199  // Create a new instance op without unused ports.
200  instance.erasePorts(builder, removalPortIndexes);
201  // Remove old one.
202  instance.erase();
203  }
204 
205  numRemovedPorts += removalPortIndexes.count();
206 }
207 
208 std::unique_ptr<mlir::Pass>
210  auto pass = std::make_unique<RemoveUnusedPortsPass>();
211  pass->ignoreDontTouch = ignoreDontTouch;
212  return pass;
213 }
assert(baseType &&"element must be base type")
This is a Node in the InstanceGraph.
llvm::iterator_range< UseIterator > uses()
This is an edge in the InstanceGraph.
Definition: InstanceGraph.h:55
std::unique_ptr< mlir::Pass > createRemoveUnusedPortsPass(bool ignoreDontTouch=false)
bool hasDontTouch(Value value)
Check whether a block argument ("port") or the operation defining a value has a DontTouch annotation,...
Definition: FIRRTLOps.cpp:317
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