CIRCT 23.0.0git
Loading...
Searching...
No Matches
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
14#include "circt/Support/Debug.h"
15#include "mlir/IR/ImplicitLocOpBuilder.h"
16#include "mlir/Pass/Pass.h"
17#include "llvm/ADT/BitVector.h"
18#include "llvm/ADT/PostOrderIterator.h"
19#include "llvm/Support/Debug.h"
20
21#define DEBUG_TYPE "firrtl-remove-unused-ports"
22
23namespace circt {
24namespace firrtl {
25#define GEN_PASS_DEF_REMOVEUNUSEDPORTS
26#include "circt/Dialect/FIRRTL/Passes.h.inc"
27} // namespace firrtl
28} // namespace circt
29
30using namespace circt;
31using namespace firrtl;
32
33namespace {
34
35struct RemoveUnusedPortsPass
36 : public circt::firrtl::impl::RemoveUnusedPortsBase<RemoveUnusedPortsPass> {
37 using Base::Base;
38
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
51void RemoveUnusedPortsPass::runOnOperation() {
53
54 auto &instanceGraph = getAnalysis<InstanceGraph>();
55 // Iterate in the reverse order of instance graph iterator, i.e. from leaves
56 // to top.
57 for (auto *node : llvm::post_order(&instanceGraph))
58 if (auto module = dyn_cast<FModuleOp>(*node->getModule()))
59 // Don't prune the main module.
60 if (!module.isPublic())
61 removeUnusedModulePorts(module, node);
62}
63
64void RemoveUnusedPortsPass::removeUnusedModulePorts(
65 FModuleOp module, InstanceGraphNode *instanceGraphNode) {
66 LLVM_DEBUG(llvm::dbgs() << "Prune ports of module: " << module.getName()
67 << "\n");
68 // This tracks constant values of output ports. None indicates an invalid
69 // value.
70 SmallVector<std::optional<APSInt>> outputPortConstants;
71 auto ports = module.getPorts();
72 // This tracks port indexes that can be erased.
73 llvm::BitVector removalPortIndexes(ports.size());
74
75 for (const auto &e : llvm::enumerate(ports)) {
76 unsigned index = e.index();
77 auto port = e.value();
78 auto arg = module.getArgument(index);
79
80 // If the port is don't touch or has unprocessed annotations, we cannot
81 // remove the port.
82 if ((hasDontTouch(arg) || !port.annotations.empty()) && !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 = WireOp::create(builder, 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 TieOffCache tieOffCache(builder);
159 unsigned outputPortIndex = 0;
160 for (auto index : removalPortIndexes.set_bits()) {
161 auto result = instance.getResult(index);
162 assert(!ports[index].isInOut() && "don't expect inout ports");
163
164 // If the port is input, replace the port with an unwritten wire
165 // so that we can remove use-chains in SV dialect canonicalization.
166 if (ports[index].isInput()) {
167 WireOp wire = WireOp::create(builder, result.getType());
168
169 // Check that the input port is only written. Sometimes input ports are
170 // used as temporary wires. In that case, we cannot erase connections.
171 bool onlyWritten = llvm::all_of(result.getUsers(), [&](Operation *op) {
172 if (auto connect = dyn_cast<FConnectLike>(op))
173 return connect.getDest() == result;
174 return false;
175 });
176
177 result.replaceUsesWithIf(wire.getResult(), [&](OpOperand &op) -> bool {
178 // Connects can be deleted directly.
179 if (onlyWritten && isa<FConnectLike>(op.getOwner())) {
180 op.getOwner()->erase();
181 return false;
182 }
183 return true;
184 });
185
186 // If the wire doesn't have an user, just erase it.
187 if (wire.use_empty())
188 wire.erase();
189
190 continue;
191 }
192
193 // Output port. Replace with the output port with an invalid or constant
194 // value.
195 auto portConstant = outputPortConstants[outputPortIndex++];
196 Value value;
197 if (portConstant)
198 value = ConstantOp::create(builder, *portConstant);
199 else {
200 // Use TieOffCache to create tie-off values for output ports.
201 if (auto baseType =
202 dyn_cast<firrtl::FIRRTLBaseType>(result.getType())) {
203 value = tieOffCache.getInvalid(baseType);
204 } else {
205 // For non-base types like ref types, we cannot create an invalid
206 // value. Skip replacing uses for these types.
207 continue;
208 }
209 }
210
211 result.replaceAllUsesWith(value);
212 }
213
214 // Create a new instance op without unused ports.
215 instance.cloneWithErasedPortsAndReplaceUses(removalPortIndexes);
216 // Remove old one.
217 instance.erase();
218 }
219
220 numRemovedPorts += removalPortIndexes.count();
221}
assert(baseType &&"element must be base type")
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
Definition Debug.h:70
Helper class to cache tie-off values for different FIRRTL types.
Definition FIRRTLUtils.h:62
This is a Node in the InstanceGraph.
llvm::iterator_range< UseIterator > uses()
This is an edge in the InstanceGraph.
bool hasDontTouch(Value value)
Check whether a block argument ("port") or the operation defining a value has a DontTouch annotation,...
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.