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