CIRCT 20.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
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
22namespace circt {
23namespace firrtl {
24#define GEN_PASS_DEF_REMOVEUNUSEDPORTS
25#include "circt/Dialect/FIRRTL/Passes.h.inc"
26} // namespace firrtl
27} // namespace circt
28
29using namespace circt;
30using namespace firrtl;
31
32namespace {
33struct 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
47void 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
59void 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
208std::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.
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,...
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
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