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