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