CIRCT 22.0.0git
Loading...
Searching...
No Matches
FlattenModules.cpp
Go to the documentation of this file.
1//===- FlattenModules.cpp -------------------------------------------------===//
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
15#include "mlir/IR/AttrTypeSubElements.h"
16#include "mlir/IR/Builders.h"
17#include "mlir/IR/IRMapping.h"
18#include "mlir/Pass/Pass.h"
19#include "mlir/Transforms/Inliner.h"
20#include "mlir/Transforms/InliningUtils.h"
21#include "llvm/ADT/PostOrderIterator.h"
22
23#define DEBUG_TYPE "hw-flatten-modules"
24
25namespace circt {
26namespace hw {
27#define GEN_PASS_DEF_FLATTENMODULES
28#include "circt/Dialect/HW/Passes.h.inc"
29} // namespace hw
30} // namespace circt
31
32using namespace circt;
33using namespace hw;
34using namespace igraph;
35using mlir::InlinerConfig;
36using mlir::InlinerInterface;
37
38using HierPathTable = DenseMap<hw::InnerRefAttr, SmallVector<hw::HierPathOp>>;
39
40namespace {
41
42// Cache the inner symbol attribute name to avoid repeated lookups
43static const StringRef innerSymAttrName =
45struct FlattenModulesPass
46 : public circt::hw::impl::FlattenModulesBase<FlattenModulesPass> {
47 using Base::Base;
48
49 void runOnOperation() override;
50
51private:
52 /// Determine if a module should be inlined based on various heuristics.
53 bool shouldInline(HWModuleOp module, igraph::InstanceGraphNode *instanceNode,
54 size_t bodySize);
55};
56
57/// A simple implementation of the `InlinerInterface` that marks all inlining as
58/// legal since we know that we only ever attempt to inline `HWModuleOp` bodies
59/// at `InstanceOp` sites.
60struct PrefixingInliner : public InlinerInterface {
61 StringRef prefix;
63 HWModuleOp parentModule;
64 HWModuleOp sourceModule;
65 DenseMap<StringAttr, StringAttr> *symMapping;
66 mlir::AttrTypeReplacer *replacer;
67 HierPathTable *pathsTable;
68 hw::InnerRefAttr instanceRef;
69
70 PrefixingInliner(MLIRContext *context, StringRef prefix,
71 InnerSymbolNamespace *ns, HWModuleOp parentModule,
72 HWModuleOp sourceModule,
73 DenseMap<StringAttr, StringAttr> *symMapping,
74 mlir::AttrTypeReplacer *replacer, HierPathTable *pathsTable,
75 hw::InnerRefAttr instanceRef)
76 : InlinerInterface(context), prefix(prefix), ns(ns),
77 parentModule(parentModule), sourceModule(sourceModule),
78 symMapping(symMapping), replacer(replacer), pathsTable(pathsTable),
79 instanceRef(instanceRef) {}
80
81 bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
82 IRMapping &valueMapping) const override {
83 return true;
84 }
85 bool isLegalToInline(Operation *op, Region *dest, bool wouldBeCloned,
86 IRMapping &valueMapping) const override {
87 return true;
88 }
89 void handleTerminator(Operation *op,
90 mlir::ValueRange valuesToRepl) const override {
91 assert(isa<hw::OutputOp>(op));
92 for (auto [from, to] : llvm::zip(valuesToRepl, op->getOperands()))
93 from.replaceAllUsesWith(to);
94 }
95
96 void processInlinedBlocks(
97 iterator_range<Region::iterator> inlinedBlocks) override {
98 for (Block &block : inlinedBlocks)
99 block.walk([&](Operation *op) {
100 updateNames(op);
101 updateInnerSymbols(op);
102 });
103
104 // Update hierarchical paths that reference the inlined instance
105 updateHierPaths();
106 }
107
108 void updateHierPaths() const {
109 // If the instance has an inner symbol, update any hierarchical paths
110 // that reference it
111 if (!instanceRef)
112 return;
113
114 auto it = pathsTable->find(instanceRef);
115 if (it == pathsTable->end())
116 return;
117
118 // For each hierarchical path that references this instance
119 for (hw::HierPathOp path : it->second) {
120 SmallVector<Attribute, 4> newPath;
121 for (auto elem : path.getNamepath()) {
122 // Skip the instance reference being inlined
123 if (elem != instanceRef)
124 newPath.push_back(replacer->replace(elem));
125 }
126 path.setNamepathAttr(ArrayAttr::get(path.getContext(), newPath));
127 }
128 }
129
130 StringAttr updateName(StringAttr attr) const {
131 if (attr.getValue().empty())
132 return attr;
133 return StringAttr::get(attr.getContext(), prefix + "/" + attr.getValue());
134 }
135
136 void updateNames(Operation *op) const {
137 if (auto name = op->getAttrOfType<StringAttr>("name"))
138 op->setAttr("name", updateName(name));
139 if (auto name = op->getAttrOfType<StringAttr>("instanceName"))
140 op->setAttr("instanceName", updateName(name));
141 if (auto namesAttr = op->getAttrOfType<ArrayAttr>("names")) {
142 SmallVector<Attribute> names(namesAttr.getValue().begin(),
143 namesAttr.getValue().end());
144 for (auto &name : names)
145 if (auto nameStr = dyn_cast<StringAttr>(name))
146 name = updateName(nameStr);
147 op->setAttr("names", ArrayAttr::get(namesAttr.getContext(), names));
148 }
149 }
150
151 void updateInnerSymbols(Operation *op) const {
152 // Rename inner symbols to avoid conflicts
153 if (auto innerSymAttr =
154 op->getAttrOfType<hw::InnerSymAttr>(innerSymAttrName)) {
155 StringAttr symName = innerSymAttr.getSymName();
156 auto it = symMapping->find(symName);
157 if (it != symMapping->end())
158 op->setAttr(innerSymAttrName, hw::InnerSymAttr::get(it->second));
159 }
160
161 // Apply attribute replacements for InnerRefAttr
162 replacer->replaceElementsIn(op);
163 }
164
165 bool allowSingleBlockOptimization(
166 iterator_range<Region::iterator> inlinedBlocks) const final {
167 return true;
168 }
169};
170} // namespace
171
172bool FlattenModulesPass::shouldInline(HWModuleOp module,
173 igraph::InstanceGraphNode *instanceNode,
174 size_t bodySize) {
175 // If inlineAll is enabled, inline everything (default behavior)
176 if (this->inlineAll)
177 return true;
178
179 // Check whether the module should be inlined based on heuristics.
180 bool isEmpty = bodySize == 1;
181 bool hasNoOutputs = module.getNumOutputPorts() == 0;
182 bool hasOneUse = instanceNode->getNumUses() == 1;
183 bool hasState = false;
184 module.walk([&](Operation *op) {
185 // Check for stateful operations (registers and memories)
186 if (isa<seq::FirRegOp, seq::CompRegOp, seq::CompRegClockEnabledOp,
187 seq::ShiftRegOp, seq::FirMemOp, seq::HLMemOp>(op)) {
188 hasState = true;
189 return WalkResult::interrupt();
190 }
191 return WalkResult::advance();
192 });
193
194 // Don't inline modules with state unless explicitly allowed
195 if (hasState && !this->inlineWithState)
196 return false;
197
198 // Inline if any of the enabled conditions are met:
199 return (this->inlineEmpty && isEmpty) ||
200 (this->inlineNoOutputs && hasNoOutputs) ||
201 (this->inlineSingleUse && hasOneUse) ||
202 (this->inlineSmall && bodySize < this->smallThreshold);
203}
204
205void FlattenModulesPass::runOnOperation() {
206 auto &instanceGraph = getAnalysis<hw::InstanceGraph>();
207 DenseSet<Operation *> handled;
208
209 InlinerConfig config;
210
211 // Build a mapping of hierarchical path ops.
212 DenseSet<StringAttr> leafModules;
213 HierPathTable pathsTable;
214 for (auto path : getOperation().getOps<hw::HierPathOp>()) {
215 // Record leaf modules to be banned from inlining.
216 if (path.isModule())
217 leafModules.insert(path.leafMod());
218
219 // For each instance in the path, record the path
220 for (auto name : path.getNamepath()) {
221 if (auto ref = dyn_cast<hw::InnerRefAttr>(name))
222 pathsTable[ref].push_back(path);
223 }
224 }
225
226 // Cache InnerSymbolNamespace objects per parent module to avoid
227 // recreating them for each instance in the same parent.
228 DenseMap<HWModuleOp, std::unique_ptr<InnerSymbolNamespace>> nsCache;
229
230 // Iterate over all instances in the instance graph. This ensures we visit
231 // every module, even private top modules (private and never instantiated).
232 for (auto *startNode : instanceGraph) {
233 if (handled.count(startNode->getModule().getOperation()))
234 continue;
235
236 // Visit the instance subhierarchy starting at the current module, in a
237 // depth-first manner. This allows us to inline child modules into parents
238 // before we attempt to inline parents into their parents.
239 for (InstanceGraphNode *node : llvm::post_order(startNode)) {
240 if (!handled.insert(node->getModule().getOperation()).second)
241 continue;
242
243 unsigned numUsesLeft = node->getNumUses();
244 if (numUsesLeft == 0)
245 continue;
246
247 // Only inline private `HWModuleOp`s (no extern or generated modules).
248 auto module =
249 dyn_cast_or_null<HWModuleOp>(node->getModule().getOperation());
250 if (!module || !module.isPrivate())
251 continue;
252
253 // Do not inline a module if it is targeted by a module NLA.
254 if (leafModules.count(module.getNameAttr()))
255 continue;
256
257 // Check if module should be inlined based on heuristics
258 auto *body = module.getBodyBlock();
259 size_t bodySize = std::distance(body->begin(), body->end());
260 if (!shouldInline(module, node, bodySize))
261 continue;
262
263 // Build symbol mapping for the module before inlining any instances
264 DenseMap<StringAttr, StringAttr> inlineModuleInnerSyms;
265 mlir::AttrTypeReplacer innerRefReplacer;
266
267 // Scan the module body to collect all inner symbols that need renaming
268 module.walk([&](Operation *op) {
269 if (auto innerSymAttr =
270 op->getAttrOfType<hw::InnerSymAttr>(innerSymAttrName))
271 inlineModuleInnerSyms.insert(
272 {innerSymAttr.getSymName(), StringAttr()});
273 });
274
275 for (auto *instRecord : node->uses()) {
276 // Only inline at plain old HW `InstanceOp`s.
277 auto inst = dyn_cast_or_null<InstanceOp>(
278 instRecord->getInstance().getOperation());
279 if (!inst)
280 continue;
281
282 bool isLastModuleUse = --numUsesLeft == 0;
283
284 // Get the parent module
285 HWModuleOp parentModule = inst->getParentOfType<HWModuleOp>();
286
287 // Get or create the InnerSymbolNamespace for the parent module
288 auto &nsPtr = nsCache[parentModule];
289 if (!nsPtr)
290 nsPtr = std::make_unique<InnerSymbolNamespace>(parentModule);
291
292 // Create fresh symbol names for this instance
293 DenseMap<StringAttr, StringAttr> oldToNewInnerSyms;
294 for (auto [oldSym, _] : inlineModuleInnerSyms)
295 oldToNewInnerSyms.insert(
296 {oldSym, StringAttr::get(&getContext(),
297 nsPtr->newName(oldSym.getValue()))});
298
299 // Setup the replacer for InnerRefAttr
300 mlir::AttrTypeReplacer instanceReplacer;
301 instanceReplacer.addReplacement(
302 [&](InnerRefAttr attr) -> std::pair<Attribute, WalkResult> {
303 if (attr.getModule() != module.getModuleNameAttr())
304 return {attr, WalkResult::skip()};
305
306 auto it = oldToNewInnerSyms.find(attr.getName());
307 if (it == oldToNewInnerSyms.end())
308 return {attr, WalkResult::skip()};
309
310 auto newAttr = InnerRefAttr::get(parentModule.getModuleNameAttr(),
311 it->second);
312 return {newAttr, WalkResult::skip()};
313 });
314
315 // Get the instance's inner reference if it has one
316 hw::InnerRefAttr instanceRef;
317 if (auto sym = inst.getInnerSymAttr())
318 instanceRef = inst.getInnerRef();
319
320 PrefixingInliner inliner(&getContext(), inst.getInstanceName(),
321 nsPtr.get(), parentModule, module,
322 &oldToNewInnerSyms, &instanceReplacer,
323 &pathsTable, instanceRef);
324
325 if (failed(mlir::inlineRegion(inliner, config.getCloneCallback(),
326 &module.getBody(), inst,
327 inst.getOperands(), inst.getResults(),
328 std::nullopt, !isLastModuleUse))) {
329 inst.emitError("failed to inline '")
330 << module.getModuleName() << "' into instance '"
331 << inst.getInstanceName() << "'";
332 return signalPassFailure();
333 }
334
335 inst.erase();
336 if (isLastModuleUse)
337 module->erase();
338 }
339 }
340 }
341}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static void updateName(PatternRewriter &rewriter, Operation *op, StringAttr name)
Set the name of an op based on the best of two names: The current name, and the name passed in.
DenseMap< hw::InnerRefAttr, SmallVector< hw::HierPathOp > > HierPathTable
static StringRef getInnerSymbolAttrName()
Return the name of the attribute used for inner symbol names.
This is a Node in the InstanceGraph.
size_t getNumUses()
Get the number of direct instantiations of this module.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1