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