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