CIRCT 23.0.0git
Loading...
Searching...
No Matches
ModuleInliner.cpp
Go to the documentation of this file.
1//===- ModuleInliner.cpp - FIRRTL module inlining ---------------*- 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//
9// This file implements FIRRTL module instance inlining.
10//
11//===----------------------------------------------------------------------===//
12
25#include "circt/Support/Debug.h"
26#include "circt/Support/LLVM.h"
27#include "circt/Support/Utils.h"
28#include "mlir/IR/IRMapping.h"
29#include "mlir/Pass/Pass.h"
30#include "llvm/ADT/BitVector.h"
31#include "llvm/ADT/SetOperations.h"
32#include "llvm/Support/Debug.h"
33#include "llvm/Support/FormatVariadic.h"
34
35#define DEBUG_TYPE "firrtl-inliner"
36
37namespace circt {
38namespace firrtl {
39#define GEN_PASS_DEF_INLINER
40#include "circt/Dialect/FIRRTL/Passes.h.inc"
41} // namespace firrtl
42} // namespace circt
43
44using namespace circt;
45using namespace firrtl;
46using namespace chirrtl;
47
48using hw::InnerRefAttr;
49using llvm::BitVector;
50
51using InnerRefToNewNameMap = DenseMap<hw::InnerRefAttr, StringAttr>;
52
53//===----------------------------------------------------------------------===//
54// Module Inlining Support
55//===----------------------------------------------------------------------===//
56
57namespace {
58/// A representation of an NLA that can be mutated. This is intended to be used
59/// in situations where you want to make a series of modifications to an NLA
60/// while also being able to query information about it. Finally, the NLA is
61/// written back to the IR to replace the original NLA.
62class MutableNLA {
63 // Storage of the NLA this represents.
64 hw::HierPathOp nla;
65
66 // A namespace that can be used to generate new symbol names if needed.
67 CircuitNamespace *circuitNamespace;
68
69 /// A mapping of symbol to index in the NLA.
70 DenseMap<Attribute, unsigned> symIdx;
71
72 /// Records which elements of the path are inlined. A bit set to true
73 /// indicates the module is still in the path. A bit set to false indicates
74 /// the module has been inlined/flattened and removed from the path.
75 BitVector inlinedSymbols;
76
77 /// Indicates if the _original_ NLA is dead and should be deleted. Updates
78 /// may still need to be written if the newTops vector below is non-empty.
79 bool dead = false;
80
81 /// Indicates if the NLA is only used to target a module
82 /// (i.e., no ports or operations use this HierPathOp).
83 /// This is needed to help determine when the HierPathOp is dead:
84 /// if we inline/flatten a module, NLA's targeting (only) that module
85 /// are now dead.
86 bool moduleOnly = false;
87
88 /// Stores new roots for the NLA. If this is non-empty, then it indicates
89 /// that the NLA should be copied and re-topped using the roots stored here.
90 /// This is non-empty when the NLA's root is inlined and the original NLA
91 /// migrates to each instantiator of the original NLA.
92 SmallVector<InnerRefAttr> newTops;
93
94 /// Stores the size of the NLA path.
95 unsigned int size;
96
97 /// A mapping of module name to _new_ inner symbol name. For convenience of
98 /// how this pass works (operations are inlined *into* a new module), the key
99 /// is the NEW module, after inlining/flattening as opposed to on the old
100 /// module.
101 DenseMap<Attribute, StringAttr> renames;
102
103 /// Lookup a reference and apply any renames to it. This requires both the
104 /// module where the NEW reference lives (to lookup the rename) and the
105 /// original ID of the reference (to fallback to if the reference was not
106 /// renamed).
107 StringAttr lookupRename(Attribute lastMod, unsigned idx = 0) {
108 if (renames.count(lastMod))
109 return renames[lastMod];
110 return nla.refPart(idx);
111 }
112
113public:
114 MutableNLA(hw::HierPathOp nla, CircuitNamespace *circuitNamespace)
115 : nla(nla), circuitNamespace(circuitNamespace),
116 inlinedSymbols(BitVector(nla.getNamepath().size(), true)),
117 size(nla.getNamepath().size()) {
118 for (size_t i = 0, e = size; i != e; ++i)
119 symIdx.insert({nla.modPart(i), i});
120 }
121
122 /// This default, erroring constructor exists because the pass uses
123 /// `DenseMap<Attribute, MutableNLA>`. `DenseMap` requires a default
124 /// constructor for the value type because its `[]` operator (which returns a
125 /// reference) must default construct the value type for a non-existent key.
126 /// This default constructor is never supposed to be used because the pass
127 /// prepopulates a `DenseMap<Attribute, MutableNLA>` before it runs and
128 /// thereby guarantees that `[]` will always hit and never need to use the
129 /// default constructor.
130 MutableNLA() {
131 llvm_unreachable(
132 "the default constructor for MutableNLA should never be used");
133 }
134
135 /// Set the state of the mutable NLA to indicate that the _original_ NLA
136 /// should be removed when updates are applied.
137 void markDead() { dead = true; }
138
139 /// Set the state of the mutable NLA to indicate the only target is a module.
140 void markModuleOnly() { moduleOnly = true; }
141
142 /// Return the original NLA that this was pointing at.
143 hw::HierPathOp getNLA() { return nla; }
144
145 /// Writeback updates accumulated in this MutableNLA to the IR. This method
146 /// should only ever be called once and, if a writeback occurrs, the
147 /// MutableNLA is NOT updated for further use. Interacting with the
148 /// MutableNLA in any way after calling this method may result in crashes.
149 /// (This is done to save unnecessary state cleanup of a pass-private
150 /// utility.)
151 hw::HierPathOp applyUpdates() {
152 // Delete an NLA which is dead.
153 if (isDead()) {
154 nla.erase();
155 return nullptr;
156 }
157
158 // The NLA was never updated, just return the NLA and do not writeback
159 // anything.
160 if (inlinedSymbols.all() && newTops.empty() && renames.empty())
161 return nla;
162
163 // The NLA has updates. Generate a new NLA with the same symbol and delete
164 // the original NLA.
165 OpBuilder b(nla);
166 auto writeBack = [&](StringAttr root, StringAttr sym) -> hw::HierPathOp {
167 SmallVector<Attribute> namepath;
168 StringAttr lastMod;
169
170 // Root of the namepath. If the next module has been inlined, set lastMod
171 // to root and skip adding to the namepath. Otherwise, add the root with
172 // its inner ref.
173 if (inlinedSymbols.size() == 1 || !inlinedSymbols.test(1)) {
174 lastMod = root;
175 } else {
176 namepath.push_back(InnerRefAttr::get(root, lookupRename(root)));
177 }
178
179 // Everything in the middle of the namepath (excluding the root and leaf).
180 for (signed i = 1, e = inlinedSymbols.size() - 1; i < e; ++i) {
181 if (!inlinedSymbols.test(i + 1)) {
182 if (!lastMod)
183 lastMod = nla.modPart(i);
184 continue;
185 }
186
187 // Update the inner symbol if it has been renamed.
188 auto modPart = lastMod ? lastMod : nla.modPart(i);
189 auto refPart = lookupRename(modPart, i);
190 namepath.push_back(InnerRefAttr::get(modPart, refPart));
191 lastMod = {};
192 }
193
194 // Leaf of the namepath.
195 auto modPart = lastMod ? lastMod : nla.modPart(size - 1);
196 auto refPart = lookupRename(modPart, size - 1);
197
198 if (refPart)
199 namepath.push_back(InnerRefAttr::get(modPart, refPart));
200 else
201 namepath.push_back(FlatSymbolRefAttr::get(modPart));
202
203 auto hp = hw::HierPathOp::create(b, b.getUnknownLoc(), sym,
204 b.getArrayAttr(namepath));
205 hp.setVisibility(nla.getVisibility());
206 return hp;
207 };
208
209 hw::HierPathOp last;
210 assert(!dead || !newTops.empty());
211 if (!dead)
212 last = writeBack(nla.root(), nla.getNameAttr());
213 for (auto root : newTops)
214 last = writeBack(root.getModule(), root.getName());
215
216 nla.erase();
217 return last;
218 }
219
220 void dump() {
221 llvm::errs() << " - orig: " << nla << "\n"
222 << " new: " << *this << "\n"
223 << " dead: " << dead << "\n"
224 << " isDead: " << isDead() << "\n"
225 << " isModuleOnly: " << isModuleOnly() << "\n"
226 << " isLocal: " << isLocal() << "\n"
227 << " inlinedSymbols: [";
228 llvm::interleaveComma(inlinedSymbols.getData(), llvm::errs(), [](auto a) {
229 llvm::errs() << llvm::formatv("{0:x-}", a);
230 });
231 llvm::errs() << "]\n"
232 << " renames:\n";
233 for (auto rename : renames)
234 llvm::errs() << " - " << rename.first << " -> " << rename.second
235 << "\n";
236 }
237
238 /// Write the current state of this MutableNLA to a string using a format that
239 /// looks like the NLA serialization. This is intended to be used for
240 /// debugging purposes.
241 friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, MutableNLA &x) {
242 auto writePathSegment = [&](StringAttr mod, StringAttr sym = {}) {
243 if (sym)
244 os << "#hw.innerNameRef<";
245 os << "@" << mod.getValue();
246 if (sym)
247 os << "::@" << sym.getValue() << ">";
248 };
249
250 auto writeOne = [&](StringAttr root, StringAttr sym) {
251 os << "firrtl.nla @" << sym.getValue() << " [";
252
253 StringAttr lastMod;
254 bool needsComma = false;
255
256 // Root of the namepath. If the next module has been inlined, set lastMod
257 // to root and skip adding to the output. Otherwise, write the root with
258 // its inner ref.
259 if (x.inlinedSymbols.size() == 1 || !x.inlinedSymbols.test(1)) {
260 lastMod = root;
261 } else {
262 writePathSegment(root, x.lookupRename(root));
263 needsComma = true;
264 }
265
266 // Everything in the middle of the namepath (excluding the root and leaf).
267 for (signed i = 1, e = x.inlinedSymbols.size() - 1; i < e; ++i) {
268 if (!x.inlinedSymbols.test(i + 1)) {
269 if (!lastMod)
270 lastMod = x.nla.modPart(i);
271 continue;
272 }
273
274 if (needsComma)
275 os << ", ";
276 auto modPart = lastMod ? lastMod : x.nla.modPart(i);
277 auto refPart = x.nla.refPart(i);
278 if (x.renames.count(modPart))
279 refPart = x.renames[modPart];
280 writePathSegment(modPart, refPart);
281 needsComma = true;
282 lastMod = {};
283 }
284
285 // Leaf of the namepath.
286 if (needsComma)
287 os << ", ";
288 auto modPart = lastMod ? lastMod : x.nla.modPart(x.size - 1);
289 auto refPart = x.nla.refPart(x.size - 1);
290 if (x.renames.count(modPart))
291 refPart = x.renames[modPart];
292 writePathSegment(modPart, refPart);
293 os << "]";
294 };
295
296 SmallVector<InnerRefAttr> tops;
297 if (!x.dead)
298 tops.push_back(InnerRefAttr::get(x.nla.root(), x.nla.getNameAttr()));
299 tops.append(x.newTops.begin(), x.newTops.end());
300
301 bool multiary = !x.newTops.empty();
302 if (multiary)
303 os << "[";
304 llvm::interleaveComma(tops, os, [&](InnerRefAttr a) {
305 writeOne(a.getModule(), a.getName());
306 });
307 if (multiary)
308 os << "]";
309
310 return os;
311 }
312
313 /// Returns true if this NLA is dead. There are several reasons why this
314 /// could be dead:
315 /// 1. This NLA has no uses and was not re-topped.
316 /// 2. This NLA was flattened and its leaf reference is a Module.
317 bool isDead() { return dead && newTops.empty(); }
318
319 /// Returns true if this NLA targets only a module.
320 bool isModuleOnly() { return moduleOnly; }
321
322 /// Returns true if this NLA is local. For this to be local, every module
323 /// after the root must be inlined. The root is never truly inlined as
324 /// inlining the root just sets a new root.
325 bool isLocal() {
326 return inlinedSymbols.find_first_in(1, inlinedSymbols.size()) == -1;
327 }
328
329 /// Return true if either this NLA is rooted at modName, or is retoped to it.
330 bool hasRoot(FModuleLike mod) { return hasRoot(mod.getModuleNameAttr()); }
331
332 /// Return true if either this NLA is rooted at modName, or is retoped to it.
333 bool hasRoot(StringAttr modName) {
334 return symIdx.lookup_or(modName, -1) == 0;
335 }
336
337 /// Mark a module as inlined. This will remove it from the NLA.
338 void inlineModule(FModuleOp module) {
339 auto sym = module.getNameAttr();
340 assert(sym != nla.root() && "unable to inline the root module");
341 assert(symIdx.count(sym) && "module is not in the symIdx map");
342 auto idx = symIdx[sym];
343 inlinedSymbols.reset(idx);
344 // If we inlined the last module in the path and the NLA targets only that
345 // module, then this NLA is dead.
346 if (idx == size - 1 && moduleOnly)
347 markDead();
348 }
349
350 /// Mark a module as flattened. This has the effect of inlining all of its
351 /// children. Also mark the NLA as dead if the leaf reference of this NLA is
352 /// a module and the only target is a module.
353 void flattenModule(FModuleOp module) {
354 auto sym = module.getNameAttr();
355 assert(symIdx.count(sym) && "module is not in the symIdx map");
356 // When flattening a module, all modules from this point onwards in the path
357 // are effectively inlined. Reset all bits from the module's index onwards.
358 auto moduleIdx = symIdx[sym];
359 inlinedSymbols.reset(moduleIdx, size);
360 // If the NLA only targets a module and we're flattening the NLA,
361 // then the NLA must be dead. Mark it as such.
362 if (moduleOnly)
363 markDead();
364 }
365
366 StringAttr reTop(FModuleOp module) {
367 StringAttr sym = nla.getSymNameAttr();
368 if (!newTops.empty())
369 sym = StringAttr::get(nla.getContext(),
370 circuitNamespace->newName(sym.getValue()));
371 newTops.push_back(InnerRefAttr::get(module.getNameAttr(), sym));
372 symIdx.insert({module.getNameAttr(), 0});
373 markDead();
374 return sym;
375 }
376
377 ArrayRef<InnerRefAttr> getAdditionalSymbols() { return ArrayRef(newTops); }
378
379 void setInnerSym(Attribute module, StringAttr innerSym) {
380 assert(symIdx.count(module) && "Mutable NLA did not contain symbol");
381 // Idempotent: a module may be renamed more than once in the same context
382 // (e.g., one wire carrying two annotations that reference the same NLA).
383 // Allow this as long as its the /same/ symbol.
384 [[maybe_unused]] auto [it, inserted] = renames.insert({module, innerSym});
385 assert((inserted || it->second == innerSym) && "Conflicting rename");
386 }
387};
388} // namespace
389
390/// This function is used after inlining a module, to handle the conversion
391/// between module ports and instance results. This maps each wire to the
392/// result of the instance operation. When future operations are cloned from
393/// the current block, they will use the value of the wire instead of the
394/// instance results.
395static void mapResultsToWires(IRMapping &mapper, SmallVectorImpl<Value> &wires,
396 InstanceOp instance) {
397 for (unsigned i = 0, e = instance.getNumResults(); i < e; ++i) {
398 auto result = instance.getResult(i);
399 auto wire = wires[i];
400 mapper.map(result, wire);
401 }
402}
403
404/// Process each operation, updating InnerRefAttr's using the specified map
405/// and the given name as the containing IST of the mapped-to sym names.
406static void replaceInnerRefUsers(ArrayRef<Operation *> newOps,
407 const InnerRefToNewNameMap &map,
408 StringAttr istName) {
409 mlir::AttrTypeReplacer replacer;
410 replacer.addReplacement([&](hw::InnerRefAttr innerRef) {
411 auto it = map.find(innerRef);
412 // TODO: what to do with users that aren't local (or not mapped?).
413 assert(it != map.end());
414
415 return std::pair{hw::InnerRefAttr::get(istName, it->second),
416 WalkResult::skip()};
417 });
418 llvm::for_each(newOps,
419 [&](auto *op) { replacer.recursivelyReplaceElementsIn(op); });
420}
421
422/// Generate and creating map entries for new inner symbol based on old one
423/// and an appropriate namespace for creating unique names for each.
424static hw::InnerSymAttr uniqueInNamespace(hw::InnerSymAttr old,
427 StringAttr istName) {
428 if (!old || old.empty())
429 return old;
430
431 bool anyChanged = false;
432
433 SmallVector<hw::InnerSymPropertiesAttr> newProps;
434 auto *context = old.getContext();
435 for (auto &prop : old) {
436 auto newSym = ns.newName(prop.getName().strref());
437 if (newSym == prop.getName()) {
438 newProps.push_back(prop);
439 continue;
440 }
441 auto newSymStrAttr = StringAttr::get(context, newSym);
442 auto newProp = hw::InnerSymPropertiesAttr::get(
443 context, newSymStrAttr, prop.getFieldID(), prop.getSymVisibility());
444 anyChanged = true;
445 newProps.push_back(newProp);
446 }
447
448 auto newSymAttr = anyChanged ? hw::InnerSymAttr::get(context, newProps) : old;
449
450 for (auto [oldProp, newProp] : llvm::zip(old, newSymAttr)) {
451 assert(oldProp.getFieldID() == newProp.getFieldID());
452 // Map InnerRef to this inner sym -> new inner sym.
453 map[hw::InnerRefAttr::get(istName, oldProp.getName())] = newProp.getName();
454 }
455
456 return newSymAttr;
457}
458
459//===----------------------------------------------------------------------===//
460// Inliner
461//===----------------------------------------------------------------------===//
462
463/// Inlines, flattens, and removes dead modules in a circuit.
464///
465/// The inliner works in a top down fashion, in parents-before-children order,
466/// visiting only live modules and inlines every possible instance.
467/// With this method of recursive top-down inlining, each operation will be
468/// cloned directly to its final location.
469///
470/// Modules are initially marked live if they are public or referenced by
471/// non-module operations. Additional modules become live as they are discovered
472/// during inlining (when a live module instantiates them after
473/// inlining/flattening). Dead modules are removed at the end.
474///
475/// During the inlining process, every cloned operation with a name must be
476/// prefixed with the instance's name. The top-down process means that we know
477/// the entire desired prefix when we clone an operation, and can set the name
478/// attribute once. This means that we will not create any intermediate name
479/// attributes (which will be interned by the compiler), and helps keep down the
480/// total memory usage.
481namespace {
482class Inliner {
483public:
484 /// Initialize the inliner to run on this circuit.
485 Inliner(CircuitOp circuit, SymbolTable &symbolTable,
486 InstanceGraph &instanceGraph);
487
488 /// Run the inliner.
489 LogicalResult run();
490
491private:
492 /// Inlining context, one per module being inlined into.
493 /// Cleans up backedges on destruction.
494 struct ModuleInliningContext {
495 ModuleInliningContext(FModuleOp module)
496 : module(module), modNamespace(module), b(module.getContext()) {}
497 /// Top-level module for current inlining task.
498 FModuleOp module;
499 /// Namespace for generating new names in `module`.
500 hw::InnerSymbolNamespace modNamespace;
501 /// Builder, insertion point into module.
502 OpBuilder b;
503 };
504
505 /// One inlining level, created for each instance inlined or flattened.
506 /// All inner symbols renamed are recorded in relocatedInnerSyms,
507 /// and new operations in newOps. On destruction newOps are fixed up.
508 struct InliningLevel {
509 InliningLevel(ModuleInliningContext &mic, FModuleOp childModule)
510 : mic(mic), childModule(childModule) {}
511
512 /// Top-level inlining context.
513 ModuleInliningContext &mic;
514 /// Map of inner-refs to the new inner sym.
515 InnerRefToNewNameMap relocatedInnerSyms;
516 /// All operations cloned are tracked here.
517 SmallVector<Operation *> newOps;
518 /// Wires and other values introduced for ports.
519 SmallVector<Value> wires;
520 /// The module being inlined (this "level").
521 FModuleOp childModule;
522 /// The explicit debug scope of the inlined instance.
523 Value debugScope;
524
525 ~InliningLevel() {
526 replaceInnerRefUsers(newOps, relocatedInnerSyms,
527 mic.module.getNameAttr());
528 }
529 };
530
531 /// Returns true if the NLA matches the current path. This will only return
532 /// false if there is a mismatch indicating that the NLA definitely is
533 /// referring to some other path.
534 bool doesNLAMatchCurrentPath(hw::HierPathOp nla);
535
536 /// Rename an operation and unique any symbols it has.
537 /// Returns true iff symbol was changed.
538 bool rename(StringRef prefix, Operation *op, InliningLevel &il);
539
540 /// Rename an InstanceOp and unique any symbols it has.
541 /// Requires old and new operations to appropriately update the `HierPathOp`'s
542 /// that it participates in.
543 bool renameInstance(StringRef prefix, InliningLevel &il, InstanceOp oldInst,
544 InstanceOp newInst,
545 const DenseMap<Attribute, Attribute> &symbolRenames);
546
547 /// Clone and rename an operation. Insert the operation into the inlining
548 /// level.
549 void cloneAndRename(StringRef prefix, InliningLevel &il, IRMapping &mapper,
550 Operation &op,
551 const DenseMap<Attribute, Attribute> &symbolRenames,
552 const DenseSet<Attribute> &localSymbols);
553
554 /// Rewrite the ports of a module as wires. This is similar to
555 /// cloneAndRename, but operating on ports.
556 /// Wires are added to il.wires.
557 void mapPortsToWires(StringRef prefix, InliningLevel &il, IRMapping &mapper,
558 const DenseSet<Attribute> &localSymbols);
559
560 /// Returns true if the operation is annotated to be flattened.
561 bool shouldFlatten(Operation *op);
562
563 /// Returns true if the operation is annotated to be inlined.
564 bool shouldInline(Operation *op);
565
566 /// Check not inlining into anything other than layerblock or module.
567 /// In the future, could check this per-inlined-operation.
568 LogicalResult checkInstanceParents(InstanceOp instance);
569
570 /// Walk the specified block, invoking `process` for operations visited
571 /// forward+pre-order. Handles cloning supported operations with regions,
572 /// so that `process` is only invoked on regionless operations.
573 LogicalResult
574 inliningWalk(OpBuilder &builder, Block *block, IRMapping &mapper,
575 llvm::function_ref<LogicalResult(Operation *op)> process);
576
577 /// Flattens a target module into the insertion point of the builder,
578 /// renaming all operations using the prefix. This clones all operations from
579 /// the target, and does not trigger inlining on the target itself.
580 LogicalResult flattenInto(StringRef prefix, InliningLevel &il,
581 IRMapping &mapper,
582 DenseSet<Attribute> localSymbols);
583
584 /// Inlines a target module into the insertion point of the builder,
585 /// prefixing all operations with prefix. This clones all operations from
586 /// the target, and does not trigger inlining on the target itself.
587 LogicalResult inlineInto(StringRef prefix, InliningLevel &il,
588 IRMapping &mapper,
589 DenseMap<Attribute, Attribute> &symbolRenames);
590
591 /// Recursively flatten all instances in a module.
592 LogicalResult flattenInstances(FModuleOp module);
593
594 /// Inline any instances in the module which were marked for inlining.
595 LogicalResult inlineInstances(FModuleOp module);
596
597 /// Create a debug scope for an inlined instance at the current insertion
598 /// point of the `il.mic` builder.
599 void createDebugScope(InliningLevel &il, InstanceOp instance,
600 Value parentScope = {});
601
602 /// Identify all module-only NLA's, marking their MutableNLA's accordingly.
603 void identifyNLAsTargetingOnlyModules();
604
605 /// Mark referenced modules of an unknown FInstanceLike operation as live.
606 /// For non-InstanceOp FInstanceLike operations (e.g., InstanceChoiceOp,
607 /// ObjectOp), we cannot inline them, so we need to mark their referenced
608 /// modules as live to prevent them from being deleted.
609 void markUnknownFInstanceLikeModulesLive(FInstanceLike instanceLike) {
610 for (auto module : instanceLike.getReferencedModuleNamesAttr()
611 .getAsValueRange<StringAttr>()) {
612 auto *moduleOp = symbolTable.lookup(module);
613 liveModules.insert(moduleOp);
614 }
615 }
616
617 /// Populate the activeHierpaths with the HierPaths that are active given the
618 /// current hierarchy. This is the set of HierPaths that were active in the
619 /// parent, and on the current instance. Also HierPaths that are rooted at
620 /// this module are also added to the active set.
621 void setActiveHierPaths(StringAttr moduleName, StringAttr instInnerSym) {
622 auto &instPaths =
623 instOpHierPaths[InnerRefAttr::get(moduleName, instInnerSym)];
624 if (currentPath.empty()) {
625 activeHierpaths.insert(instPaths.begin(), instPaths.end());
626 return;
627 }
628 DenseSet<StringAttr> hPaths(instPaths.begin(), instPaths.end());
629 // Only the hierPaths that this instance participates in, and is active in
630 // the current path must be kept active for the child modules.
631 llvm::set_intersect(activeHierpaths, hPaths);
632 // Also, the nlas, that have current instance as the top must be added to
633 // the active set.
634 for (auto hPath : instPaths)
635 if (nlaMap[hPath].hasRoot(moduleName))
636 activeHierpaths.insert(hPath);
637 }
638
639 CircuitOp circuit;
640 MLIRContext *context;
641
642 // A symbol table with references to each module in a circuit.
643 SymbolTable &symbolTable;
644
645 // The original instance graph. This is NOT maintained if mutations are made.
646 InstanceGraph &instanceGraph;
647
648 /// The set of live modules. Anything not recorded in this set will be
649 /// removed by dead code elimination.
650 DenseSet<Operation *> liveModules;
651
652 /// A mapping of NLA symbol name to mutable NLA.
653 DenseMap<Attribute, MutableNLA> nlaMap;
654
655 /// A mapping of module names to NLA symbols that originate from that module.
656 DenseMap<Attribute, SmallVector<Attribute>> rootMap;
657
658 /// The current instance path. This is a pair<ModuleName, InstanceName>.
659 /// This is used to distinguish if a non-local annotation applies to the
660 /// current instance or not.
661 SmallVector<std::pair<Attribute, Attribute>> currentPath;
662
663 DenseSet<StringAttr> activeHierpaths;
664
665 /// Record the HierPathOps that each InstanceOp participates in. This is a map
666 /// from the InnerRefAttr to the list of HierPathOp names. The InnerRefAttr
667 /// corresponds to the InstanceOp.
668 DenseMap<InnerRefAttr, SmallVector<StringAttr>> instOpHierPaths;
669
670 /// The debug scopes created for inlined instances. Scopes that are unused
671 /// after inlining will be deleted again.
672 SmallVector<debug::ScopeOp> debugScopes;
673};
674} // namespace
675
676/// Check if the NLA applies to our instance path. This works by verifying the
677/// instance paths backwards starting from the current module. We drop the back
678/// element from the NLA because it obviously matches the current operation.
679bool Inliner::doesNLAMatchCurrentPath(hw::HierPathOp nla) {
680 return (activeHierpaths.find(nla.getSymNameAttr()) != activeHierpaths.end());
681}
682
683/// If this operation or any child operation has a name, add the prefix to that
684/// operation's name. If the operation has any inner symbols, make sure that
685/// these are unique in the namespace. Record renamed inner symbols
686/// in relocatedInnerSyms map for renaming local users.
687bool Inliner::rename(StringRef prefix, Operation *op, InliningLevel &il) {
688 // Debug operations with implicit module scope now need an explicit scope,
689 // since inlining has destroyed the module whose scope they implicitly used.
690 auto updateDebugScope = [&](auto op) {
691 if (!op.getScope())
692 op.getScopeMutable().assign(il.debugScope);
693 };
694 if (auto varOp = dyn_cast<debug::VariableOp>(op))
695 return updateDebugScope(varOp), false;
696 if (auto scopeOp = dyn_cast<debug::ScopeOp>(op))
697 return updateDebugScope(scopeOp), false;
698
699 // Add a prefix to things that has a "name" attribute.
700 if (auto nameAttr = op->getAttrOfType<StringAttr>("name"))
701 op->setAttr("name", StringAttr::get(op->getContext(),
702 (prefix + nameAttr.getValue())));
703
704 // If the operation has an inner symbol, ensure that it is unique. Record
705 // renames for any NLAs that this participates in if the symbol was renamed.
706 auto symOp = dyn_cast<hw::InnerSymbolOpInterface>(op);
707 if (!symOp)
708 return false;
709 auto oldSymAttr = symOp.getInnerSymAttr();
710 auto newSymAttr =
711 uniqueInNamespace(oldSymAttr, il.relocatedInnerSyms, il.mic.modNamespace,
712 il.childModule.getNameAttr());
713
714 if (!newSymAttr)
715 return false;
716
717 // If there's a symbol on the root and it changed, do NLA work.
718 if (auto newSymStrAttr = newSymAttr.getSymName();
719 newSymStrAttr && newSymStrAttr != oldSymAttr.getSymName()) {
720 for (Annotation anno : AnnotationSet(op)) {
721 auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
722 if (!sym)
723 continue;
724 // If this is a breadcrumb, we update the annotation path
725 // unconditionally. If this is the leaf of the NLA, we need to make
726 // sure we only update the annotation if the current path matches the
727 // NLA. This matters when the same module is inlined twice and the NLA
728 // only applies to one of them.
729 auto &mnla = nlaMap[sym.getAttr()];
730 if (!doesNLAMatchCurrentPath(mnla.getNLA()))
731 continue;
732 mnla.setInnerSym(il.mic.module.getModuleNameAttr(), newSymStrAttr);
733 }
734 }
735
736 symOp.setInnerSymbolAttr(newSymAttr);
737
738 return newSymAttr != oldSymAttr;
739}
740
741bool Inliner::renameInstance(
742 StringRef prefix, InliningLevel &il, InstanceOp oldInst, InstanceOp newInst,
743 const DenseMap<Attribute, Attribute> &symbolRenames) {
744 // TODO: There is currently no good way to annotate an explicit parent scope
745 // on instances. Just emit a note in debug runs until this is resolved.
746 LLVM_DEBUG({
747 if (il.debugScope)
748 llvm::dbgs() << "Discarding parent debug scope for " << oldInst << "\n";
749 });
750
751 // Add this instance to the activeHierpaths. This ensures that NLAs that this
752 // instance participates in will be updated correctly.
753 auto parentActivePaths = activeHierpaths;
754 assert(oldInst->getParentOfType<FModuleOp>() == il.childModule);
755 if (auto instSym = getInnerSymName(oldInst))
756 setActiveHierPaths(oldInst->getParentOfType<FModuleOp>().getNameAttr(),
757 instSym);
758 // List of HierPathOps that are valid based on the InstanceOp being inlined
759 // and the InstanceOp which is being replaced after inlining. That is the set
760 // of HierPathOps that is common between these two.
761 SmallVector<StringAttr> validHierPaths;
762 auto oldParent = oldInst->getParentOfType<FModuleOp>().getNameAttr();
763 auto oldInstSym = getInnerSymName(oldInst);
764
765 if (oldInstSym) {
766 // Get the innerRef to the original InstanceOp that is being inlined here.
767 // For all the HierPathOps that the instance being inlined participates
768 // in.
769 auto oldInnerRef = InnerRefAttr::get(oldParent, oldInstSym);
770 for (auto old : instOpHierPaths[oldInnerRef]) {
771 // If this HierPathOp is valid at the inlining context, where the
772 // instance is being inlined at. That is, if it exists in the
773 // activeHierpaths.
774 if (activeHierpaths.find(old) != activeHierpaths.end())
775 validHierPaths.push_back(old);
776 else
777 // The HierPathOp could have been renamed, check for the other retoped
778 // names, if they are active at the inlining context.
779 for (auto additionalSym : nlaMap[old].getAdditionalSymbols())
780 if (activeHierpaths.find(additionalSym.getName()) !=
781 activeHierpaths.end()) {
782 validHierPaths.push_back(old);
783 break;
784 }
785 }
786 }
787
788 assert(getInnerSymName(newInst) == oldInstSym);
789
790 // Do the renaming, creating new symbol as needed.
791 auto symbolChanged = rename(prefix, newInst, il);
792
793 // If the symbol changed, update instOpHierPaths accordingly.
794 auto newSymAttr = getInnerSymName(newInst);
795 if (symbolChanged) {
796 assert(newSymAttr);
797 // The InstanceOp is renamed, so move the HierPathOps to the new
798 // InnerRefAttr.
799 auto newInnerRef = InnerRefAttr::get(
800 newInst->getParentOfType<FModuleOp>().getNameAttr(), newSymAttr);
801 instOpHierPaths[newInnerRef] = validHierPaths;
802 // Update the innerSym for all the affected HierPathOps.
803 for (auto nla : instOpHierPaths[newInnerRef]) {
804 if (!nlaMap.count(nla))
805 continue;
806 auto &mnla = nlaMap[nla];
807 mnla.setInnerSym(newInnerRef.getModule(), newSymAttr);
808 }
809 }
810
811 if (newSymAttr) {
812 auto innerRef = InnerRefAttr::get(
813 newInst->getParentOfType<FModuleOp>().getNameAttr(), newSymAttr);
814 SmallVector<StringAttr> &nlaList = instOpHierPaths[innerRef];
815 // Now rename the Updated HierPathOps that this InstanceOp participates in.
816 for (const auto &en : llvm::enumerate(nlaList)) {
817 auto oldNLA = en.value();
818 if (auto newSym = symbolRenames.lookup(oldNLA))
819 nlaList[en.index()] = cast<StringAttr>(newSym);
820 }
821 }
822 activeHierpaths = std::move(parentActivePaths);
823 return symbolChanged;
824}
825
826/// This function is used before inlining a module, to handle the conversion
827/// between module ports and instance results. For every port in the target
828/// module, create a wire, and assign a mapping from each module port to the
829/// wire. When the body of the module is cloned, the value of the wire will be
830/// used instead of the module's ports.
831void Inliner::mapPortsToWires(StringRef prefix, InliningLevel &il,
832 IRMapping &mapper,
833 const DenseSet<Attribute> &localSymbols) {
834 auto target = il.childModule;
835 auto portInfo = target.getPorts();
836 for (unsigned i = 0, e = target.getNumPorts(); i < e; ++i) {
837 auto arg = target.getArgument(i);
838 // Get the type of the wire.
839 auto type = type_cast<FIRRTLType>(arg.getType());
840
841 // Compute new symbols if needed.
842 auto oldSymAttr = portInfo[i].sym;
843 auto newSymAttr =
844 uniqueInNamespace(oldSymAttr, il.relocatedInnerSyms,
845 il.mic.modNamespace, target.getNameAttr());
846
847 StringAttr newRootSymName, oldRootSymName;
848 if (oldSymAttr)
849 oldRootSymName = oldSymAttr.getSymName();
850 if (newSymAttr)
851 newRootSymName = newSymAttr.getSymName();
852
853 SmallVector<Attribute> newAnnotations;
854 for (auto anno : AnnotationSet::forPort(target, i)) {
855 // If the annotation is not non-local, copy it to the clone.
856 if (auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
857 auto &mnla = nlaMap[sym.getAttr()];
858 // If the NLA does not match the path, we don't want to copy it over.
859 if (!doesNLAMatchCurrentPath(mnla.getNLA()))
860 continue;
861 // Update any NLAs with the new symbol name.
862 // This does not handle per-field symbols used in NLA's.
863 if (oldRootSymName != newRootSymName)
864 mnla.setInnerSym(il.mic.module.getModuleNameAttr(), newRootSymName);
865 // If all paths of the NLA have been inlined, make it local.
866 if (mnla.isLocal() || localSymbols.count(sym.getAttr()))
867 anno.removeMember("circt.nonlocal");
868 }
869 newAnnotations.push_back(anno.getAttr());
870 }
871
872 Value wire =
873 WireOp::create(
874 il.mic.b, target.getLoc(), type,
875 StringAttr::get(context, (prefix + portInfo[i].getName())),
876 NameKindEnumAttr::get(context, NameKindEnum::DroppableName),
877 ArrayAttr::get(context, newAnnotations), newSymAttr,
878 /*forceable=*/UnitAttr{})
879 .getResult();
880 il.wires.push_back(wire);
881 mapper.map(arg, wire);
882 }
883}
884
885/// Clone an operation, mapping used values and results with the mapper, and
886/// apply the prefix to the name of the operation. This will clone to the
887/// insert point of the builder. Insert the operation into the level.
888void Inliner::cloneAndRename(
889 StringRef prefix, InliningLevel &il, IRMapping &mapper, Operation &op,
890 const DenseMap<Attribute, Attribute> &symbolRenames,
891 const DenseSet<Attribute> &localSymbols) {
892 // Strip any non-local annotations which are local.
893 AnnotationSet oldAnnotations(&op);
894 SmallVector<Annotation> newAnnotations;
895 for (auto anno : oldAnnotations) {
896 // If the annotation is not non-local, it will apply to all inlined
897 // instances of this op. Add it to the cloned op.
898 if (auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
899 // Retrieve the corresponding NLA.
900 auto &mnla = nlaMap[sym.getAttr()];
901 // If the NLA does not match the path we don't want to copy it over.
902 if (!doesNLAMatchCurrentPath(mnla.getNLA()))
903 continue;
904 // The NLA has become local, rewrite the annotation to be local.
905 if (mnla.isLocal() || localSymbols.count(sym.getAttr()))
906 anno.removeMember("circt.nonlocal");
907 }
908 // Attach this annotation to the cloned operation.
909 newAnnotations.push_back(anno);
910 }
911
912 // Clone and rename.
913 assert(op.getNumRegions() == 0 &&
914 "operation with regions should not reach cloneAndRename");
915 auto *newOp = il.mic.b.cloneWithoutRegions(op, mapper);
916
917 // Rename the new operation.
918 // (add prefix to it, if named, and unique-ify symbol, updating NLA's).
919
920 // Instances require extra handling to update HierPathOp's if their symbols
921 // change.
922 if (auto oldInst = dyn_cast<InstanceOp>(op))
923 renameInstance(prefix, il, oldInst, cast<InstanceOp>(newOp), symbolRenames);
924 else
925 rename(prefix, newOp, il);
926
927 // We want to avoid attaching an empty annotation array on to an op that
928 // never had an annotation array in the first place.
929 if (!newAnnotations.empty() || !oldAnnotations.empty())
930 AnnotationSet(newAnnotations, context).applyToOperation(newOp);
931
932 il.newOps.push_back(newOp);
933}
934
935bool Inliner::shouldFlatten(Operation *op) {
936 return AnnotationSet::hasAnnotation(op, flattenAnnoClass);
937}
938
939bool Inliner::shouldInline(Operation *op) {
940 return AnnotationSet::hasAnnotation(op, inlineAnnoClass);
941}
942
943LogicalResult Inliner::inliningWalk(
944 OpBuilder &builder, Block *block, IRMapping &mapper,
945 llvm::function_ref<LogicalResult(Operation *op)> process) {
946 struct IPs {
947 OpBuilder::InsertPoint target;
948 Block::iterator source;
949 };
950 // Invariant: no Block::iterator == end(), can't getBlock().
951 SmallVector<IPs> inliningStack;
952 if (block->empty())
953 return success();
954
955 inliningStack.push_back(IPs{builder.saveInsertionPoint(), block->begin()});
956 OpBuilder::InsertionGuard guard(builder);
957
958 while (!inliningStack.empty()) {
959 auto target = inliningStack.back().target;
960 builder.restoreInsertionPoint(target);
961 Operation *source;
962 // Get next source operation.
963 {
964 auto &ips = inliningStack.back();
965 source = &*ips.source;
966 auto end = source->getBlock()->end();
967 if (++ips.source == end)
968 inliningStack.pop_back();
969 }
970
971 // Does the source have regions? If not, use callback to process.
972 if (source->getNumRegions() == 0) {
973 assert(builder.saveInsertionPoint().getPoint() == target.getPoint());
974 // Clone source into insertion point 'target'.
975 if (failed(process(source)))
976 return failure();
977 assert(builder.saveInsertionPoint().getPoint() == target.getPoint());
978
979 continue;
980 }
981
982 // Limited support for region-containing operations.
983 if (!isa<LayerBlockOp, WhenOp, MatchOp>(source))
984 return source->emitError("unsupported operation '")
985 << source->getName() << "' cannot be inlined";
986
987 // Note: This does not use cloneAndRename for simplicity,
988 // as there are no annotations, symbols to rename, or names
989 // to prefix. This does mean these operations do not appear
990 // in `il.newOps` for inner-ref renaming walk, FWIW.
991 auto *newOp = builder.cloneWithoutRegions(*source, mapper);
992 for (auto [newRegion, oldRegion] : llvm::reverse(
993 llvm::zip_equal(newOp->getRegions(), source->getRegions()))) {
994 // If region has no blocks, skip.
995 if (oldRegion.empty()) {
996 assert(newRegion.empty());
997 continue;
998 }
999 // Otherwise, assert single block. Multiple blocks is trickier.
1000 assert(oldRegion.hasOneBlock());
1001
1002 // Create new block and add to inlining stack for processing.
1003 auto &oldBlock = oldRegion.getBlocks().front();
1004 auto &newBlock = newRegion.emplaceBlock();
1005 mapper.map(&oldBlock, &newBlock);
1006
1007 // Copy block arguments, and add mapping for each.
1008 for (auto arg : oldBlock.getArguments())
1009 mapper.map(arg, newBlock.addArgument(arg.getType(), arg.getLoc()));
1010
1011 if (oldBlock.empty())
1012 continue;
1013
1014 inliningStack.push_back(
1015 IPs{OpBuilder::InsertPoint(&newBlock, newBlock.begin()),
1016 oldBlock.begin()});
1017 }
1018 }
1019 return success();
1020}
1021
1022LogicalResult Inliner::checkInstanceParents(InstanceOp instance) {
1023 auto *parent = instance->getParentOp();
1024 while (!isa<FModuleLike>(parent)) {
1025 if (!isa<LayerBlockOp>(parent))
1026 return instance->emitError("cannot inline instance")
1027 .attachNote(parent->getLoc())
1028 << "containing operation '" << parent->getName()
1029 << "' not safe to inline into";
1030 parent = parent->getParentOp();
1031 }
1032 return success();
1033}
1034
1035// NOLINTNEXTLINE(misc-no-recursion)
1036LogicalResult Inliner::flattenInto(StringRef prefix, InliningLevel &il,
1037 IRMapping &mapper,
1038 DenseSet<Attribute> localSymbols) {
1039 auto target = il.childModule;
1040 auto moduleName = target.getNameAttr();
1041 DenseMap<Attribute, Attribute> symbolRenames;
1042
1043 LLVM_DEBUG(llvm::dbgs() << "flattening " << target.getModuleName() << " into "
1044 << il.mic.module.getModuleName() << "\n");
1045 auto visit = [&](Operation *op) {
1046 // If it's not an instance op, clone it and continue.
1047 auto instance = dyn_cast<InstanceOp>(op);
1048 if (!instance) {
1049 if (auto instanceLike = dyn_cast<FInstanceLike>(op))
1050 markUnknownFInstanceLikeModulesLive(instanceLike);
1051 cloneAndRename(prefix, il, mapper, *op, symbolRenames, localSymbols);
1052 return success();
1053 }
1054
1055 // If it's not a regular module we can't inline it. Mark it as live.
1056 auto *moduleOp = symbolTable.lookup(instance.getModuleName());
1057 auto childModule = dyn_cast<FModuleOp>(moduleOp);
1058 if (!childModule) {
1059 liveModules.insert(moduleOp);
1060
1061 cloneAndRename(prefix, il, mapper, *op, symbolRenames, localSymbols);
1062 return success();
1063 }
1064
1065 if (failed(checkInstanceParents(instance)))
1066 return failure();
1067
1068 // Add any NLAs which start at this instance to the localSymbols set.
1069 // Anything in this set will be made local during the recursive flattenInto
1070 // walk.
1071 llvm::set_union(localSymbols, rootMap[childModule.getNameAttr()]);
1072 auto instInnerSym = getInnerSymName(instance);
1073 auto parentActivePaths = activeHierpaths;
1074 setActiveHierPaths(moduleName, instInnerSym);
1075 currentPath.emplace_back(moduleName, instInnerSym);
1076
1077 InliningLevel childIL(il.mic, childModule);
1078 createDebugScope(childIL, instance, il.debugScope);
1079
1080 // Create the wire mapping for results + ports.
1081 auto nestedPrefix = (prefix + instance.getName() + "_").str();
1082 mapPortsToWires(nestedPrefix, childIL, mapper, localSymbols);
1083 mapResultsToWires(mapper, childIL.wires, instance);
1084
1085 // Unconditionally flatten all instance operations.
1086 if (failed(flattenInto(nestedPrefix, childIL, mapper, localSymbols)))
1087 return failure();
1088 currentPath.pop_back();
1089 activeHierpaths = parentActivePaths;
1090 return success();
1091 };
1092 return inliningWalk(il.mic.b, target.getBodyBlock(), mapper, visit);
1093}
1094
1095LogicalResult Inliner::flattenInstances(FModuleOp module) {
1096 auto moduleName = module.getNameAttr();
1097 ModuleInliningContext mic(module);
1098
1099 auto visit = [&](FInstanceLike instanceLike) {
1100 auto instance = dyn_cast<InstanceOp>(*instanceLike);
1101 if (!instance) {
1102 markUnknownFInstanceLikeModulesLive(instanceLike);
1103 return WalkResult::advance();
1104 }
1105 // If it's not a regular module we can't inline it. Mark it as live.
1106 auto *targetModule = symbolTable.lookup(instance.getModuleName());
1107 auto target = dyn_cast<FModuleOp>(targetModule);
1108 if (!target) {
1109 liveModules.insert(targetModule);
1110 return WalkResult::advance();
1111 }
1112
1113 if (failed(checkInstanceParents(instance)))
1114 return WalkResult::interrupt();
1115
1116 if (auto instSym = getInnerSymName(instance)) {
1117 auto innerRef = InnerRefAttr::get(moduleName, instSym);
1118 // Preorder update of any non-local annotations this instance participates
1119 // in. This needs to happen _before_ visiting modules so that internal
1120 // non-local annotations can be deleted if they are now local.
1121 for (auto targetNLA : instOpHierPaths[innerRef])
1122 nlaMap[targetNLA].flattenModule(target);
1123 }
1124
1125 // Add any NLAs which start at this instance to the localSymbols set.
1126 // Anything in this set will be made local during the recursive flattenInto
1127 // walk.
1128 DenseSet<Attribute> localSymbols;
1129 llvm::set_union(localSymbols, rootMap[target.getNameAttr()]);
1130 auto instInnerSym = getInnerSymName(instance);
1131 auto parentActivePaths = activeHierpaths;
1132 setActiveHierPaths(moduleName, instInnerSym);
1133 currentPath.emplace_back(moduleName, instInnerSym);
1134
1135 // Create the wire mapping for results + ports. We RAUW the results instead
1136 // of mapping them.
1137 IRMapping mapper;
1138 mic.b.setInsertionPoint(instance);
1139
1140 InliningLevel il(mic, target);
1141 createDebugScope(il, instance);
1142
1143 auto nestedPrefix = (instance.getName() + "_").str();
1144 mapPortsToWires(nestedPrefix, il, mapper, localSymbols);
1145 for (unsigned i = 0, e = instance.getNumResults(); i < e; ++i)
1146 instance.getResult(i).replaceAllUsesWith(il.wires[i]);
1147
1148 // Recursively flatten the target module.
1149 if (failed(flattenInto(nestedPrefix, il, mapper, localSymbols)))
1150 return WalkResult::interrupt();
1151 currentPath.pop_back();
1152 activeHierpaths = parentActivePaths;
1153
1154 // Erase the replaced instance.
1155 instance.erase();
1156 return WalkResult::skip();
1157 };
1158 return failure(module.getBodyBlock()
1159 ->walk<mlir::WalkOrder::PreOrder>(visit)
1160 .wasInterrupted());
1161}
1162
1163// NOLINTNEXTLINE(misc-no-recursion)
1164LogicalResult
1165Inliner::inlineInto(StringRef prefix, InliningLevel &il, IRMapping &mapper,
1166 DenseMap<Attribute, Attribute> &symbolRenames) {
1167 auto target = il.childModule;
1168 auto inlineToParent = il.mic.module;
1169 auto moduleName = target.getNameAttr();
1170
1171 LLVM_DEBUG(llvm::dbgs() << "inlining " << target.getModuleName() << " into "
1172 << inlineToParent.getModuleName() << "\n");
1173
1174 auto visit = [&](Operation *op) {
1175 // If it's not an instance op, clone it and continue.
1176 auto instance = dyn_cast<InstanceOp>(op);
1177 if (!instance) {
1178 if (auto instanceLike = dyn_cast<FInstanceLike>(op))
1179 markUnknownFInstanceLikeModulesLive(instanceLike);
1180
1181 cloneAndRename(prefix, il, mapper, *op, symbolRenames, {});
1182 return success();
1183 }
1184
1185 // If it's not a regular module we can't inline it. Mark it as live.
1186 auto *moduleOp = symbolTable.lookup(instance.getModuleName());
1187 auto childModule = dyn_cast<FModuleOp>(moduleOp);
1188 if (!childModule) {
1189 liveModules.insert(moduleOp);
1190 cloneAndRename(prefix, il, mapper, *op, symbolRenames, {});
1191 return success();
1192 }
1193
1194 // If we aren't inlining the target, mark it live.
1195 if (!shouldInline(childModule)) {
1196 liveModules.insert(childModule);
1197 cloneAndRename(prefix, il, mapper, *op, symbolRenames, {});
1198 return success();
1199 }
1200
1201 if (failed(checkInstanceParents(instance)))
1202 return failure();
1203
1204 auto toBeFlattened = shouldFlatten(childModule);
1205 if (auto instSym = getInnerSymName(instance)) {
1206 auto innerRef = InnerRefAttr::get(moduleName, instSym);
1207 // Preorder update of any non-local annotations this instance participates
1208 // in. This needs to happen _before_ visiting modules so that internal
1209 // non-local annotations can be deleted if they are now local.
1210 for (auto sym : instOpHierPaths[innerRef]) {
1211 if (toBeFlattened)
1212 nlaMap[sym].flattenModule(childModule);
1213 else
1214 nlaMap[sym].inlineModule(childModule);
1215 }
1216 }
1217
1218 // The InstanceOp `instance` might not have a symbol, if it does not
1219 // participate in any HierPathOp. But the reTop might add a symbol to it, if
1220 // a HierPathOp is added to this Op. If we're about to inline a module that
1221 // contains a non-local annotation that starts at that module, then we need
1222 // to both update the mutable NLA to indicate that this has a new top and
1223 // add an annotation on the instance saying that this now participates in
1224 // this new NLA.
1225 DenseMap<Attribute, Attribute> symbolRenames;
1226 if (!rootMap[childModule.getNameAttr()].empty()) {
1227 for (auto sym : rootMap[childModule.getNameAttr()]) {
1228 auto &mnla = nlaMap[sym];
1229 // Retop to the new parent, which is the topmost module (and not
1230 // immediate parent) in case of recursive inlining.
1231 sym = mnla.reTop(inlineToParent);
1232 StringAttr instSym = getInnerSymName(instance);
1233 if (!instSym) {
1234 instSym = StringAttr::get(
1235 context, il.mic.modNamespace.newName(instance.getName()));
1236 instance.setInnerSymAttr(hw::InnerSymAttr::get(instSym));
1237 }
1238 instOpHierPaths[InnerRefAttr::get(moduleName, instSym)].push_back(
1239 cast<StringAttr>(sym));
1240 // TODO: Update any symbol renames which need to be used by the next
1241 // call of inlineInto. This will then check each instance and rename
1242 // any symbols appropriately for that instance.
1243 symbolRenames.insert({mnla.getNLA().getNameAttr(), sym});
1244 }
1245 }
1246 auto instInnerSym = getInnerSymName(instance);
1247 auto parentActivePaths = activeHierpaths;
1248 setActiveHierPaths(moduleName, instInnerSym);
1249 // This must be done after the reTop, since it might introduce an innerSym.
1250 currentPath.emplace_back(moduleName, instInnerSym);
1251
1252 InliningLevel childIL(il.mic, childModule);
1253 createDebugScope(childIL, instance, il.debugScope);
1254
1255 // Create the wire mapping for results + ports.
1256 auto nestedPrefix = (prefix + instance.getName() + "_").str();
1257 mapPortsToWires(nestedPrefix, childIL, mapper, {});
1258 mapResultsToWires(mapper, childIL.wires, instance);
1259
1260 // Inline the module, it can be marked as flatten and inline.
1261 if (toBeFlattened) {
1262 if (failed(flattenInto(nestedPrefix, childIL, mapper, {})))
1263 return failure();
1264 } else {
1265 if (failed(inlineInto(nestedPrefix, childIL, mapper, symbolRenames)))
1266 return failure();
1267 }
1268 currentPath.pop_back();
1269 activeHierpaths = parentActivePaths;
1270 return success();
1271 };
1272
1273 return inliningWalk(il.mic.b, target.getBodyBlock(), mapper, visit);
1274}
1275
1276LogicalResult Inliner::inlineInstances(FModuleOp module) {
1277 // Generate a namespace for this module so that we can safely inline
1278 // symbols.
1279 auto moduleName = module.getNameAttr();
1280 ModuleInliningContext mic(module);
1281
1282 auto visit = [&](FInstanceLike instanceLike) {
1283 auto instance = dyn_cast<InstanceOp>(*instanceLike);
1284 if (!instance) {
1285 markUnknownFInstanceLikeModulesLive(instanceLike);
1286 return WalkResult::advance();
1287 }
1288 // If it's not a regular module we can't inline it. Mark it as live.
1289 auto *childModule = symbolTable.lookup(instance.getModuleName());
1290 auto target = dyn_cast<FModuleOp>(childModule);
1291 if (!target) {
1292 liveModules.insert(childModule);
1293 return WalkResult::advance();
1294 }
1295
1296 // If we aren't inlining the target, mark it live.
1297 if (!shouldInline(target)) {
1298 liveModules.insert(target);
1299 return WalkResult::advance();
1300 }
1301
1302 if (failed(checkInstanceParents(instance)))
1303 return WalkResult::interrupt();
1304
1305 auto toBeFlattened = shouldFlatten(target);
1306 if (auto instSym = getInnerSymName(instance)) {
1307 auto innerRef = InnerRefAttr::get(moduleName, instSym);
1308 // Preorder update of any non-local annotations this instance participates
1309 // in. This needs to happen _before_ visiting modules so that internal
1310 // non-local annotations can be deleted if they are now local.
1311 for (auto sym : instOpHierPaths[innerRef]) {
1312 if (toBeFlattened)
1313 nlaMap[sym].flattenModule(target);
1314 else
1315 nlaMap[sym].inlineModule(target);
1316 }
1317 }
1318
1319 // The InstanceOp `instance` might not have a symbol, if it does not
1320 // participate in any HierPathOp. But the reTop might add a symbol to it, if
1321 // a HierPathOp is added to this Op.
1322 DenseMap<Attribute, Attribute> symbolRenames;
1323 if (!rootMap[target.getNameAttr()].empty() && !toBeFlattened) {
1324 for (auto sym : rootMap[target.getNameAttr()]) {
1325 auto &mnla = nlaMap[sym];
1326 sym = mnla.reTop(module);
1327 StringAttr instSym = getOrAddInnerSym(
1328 instance, [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
1329 return mic.modNamespace;
1330 });
1331 instOpHierPaths[InnerRefAttr::get(moduleName, instSym)].push_back(
1332 cast<StringAttr>(sym));
1333 // TODO: Update any symbol renames which need to be used by the next
1334 // call of inlineInto. This will then check each instance and rename
1335 // any symbols appropriately for that instance.
1336 symbolRenames.insert({mnla.getNLA().getNameAttr(), sym});
1337 }
1338 }
1339 auto instInnerSym = getInnerSymName(instance);
1340 auto parentActivePaths = activeHierpaths;
1341 setActiveHierPaths(moduleName, instInnerSym);
1342 // This must be done after the reTop, since it might introduce an innerSym.
1343 currentPath.emplace_back(moduleName, instInnerSym);
1344 // Create the wire mapping for results + ports. We RAUW the results instead
1345 // of mapping them.
1346 IRMapping mapper;
1347 mic.b.setInsertionPoint(instance);
1348 auto nestedPrefix = (instance.getName() + "_").str();
1349
1350 InliningLevel childIL(mic, target);
1351 createDebugScope(childIL, instance);
1352
1353 mapPortsToWires(nestedPrefix, childIL, mapper, {});
1354 for (unsigned i = 0, e = instance.getNumResults(); i < e; ++i)
1355 instance.getResult(i).replaceAllUsesWith(childIL.wires[i]);
1356
1357 // Inline the module, it can be marked as flatten and inline.
1358 if (toBeFlattened) {
1359 if (failed(flattenInto(nestedPrefix, childIL, mapper, {})))
1360 return WalkResult::interrupt();
1361 } else {
1362 // Recursively inline all the child modules under `parent`, that are
1363 // marked to be inlined.
1364 if (failed(inlineInto(nestedPrefix, childIL, mapper, symbolRenames)))
1365 return WalkResult::interrupt();
1366 }
1367 currentPath.pop_back();
1368 activeHierpaths = parentActivePaths;
1369
1370 // Erase the replaced instance.
1371 instance.erase();
1372 return WalkResult::skip();
1373 };
1374
1375 return failure(module.getBodyBlock()
1376 ->walk<mlir::WalkOrder::PreOrder>(visit)
1377 .wasInterrupted());
1378}
1379
1380void Inliner::createDebugScope(InliningLevel &il, InstanceOp instance,
1381 Value parentScope) {
1382 auto op = debug::ScopeOp::create(
1383 il.mic.b, instance.getLoc(), instance.getInstanceNameAttr(),
1384 instance.getModuleNameAttr().getAttr(), parentScope);
1385 debugScopes.push_back(op);
1386 il.debugScope = op;
1387}
1388
1389void Inliner::identifyNLAsTargetingOnlyModules() {
1390 DenseSet<Operation *> nlaTargetedModules;
1391
1392 // Identify candidate NLA's: those that end in a module
1393 for (auto &[sym, mnla] : nlaMap) {
1394 auto nla = mnla.getNLA();
1395 if (nla.isModule()) {
1396 auto mod = symbolTable.lookup<FModuleLike>(nla.leafMod());
1397 assert(mod &&
1398 "NLA ends in module reference but does not target FModuleLike?");
1399 nlaTargetedModules.insert(mod);
1400 }
1401 }
1402
1403 // Helper to scan leaf modules for users of NLAs, gathering by symbol names
1404 auto scanForNLARefs = [&](FModuleLike mod) {
1405 DenseSet<StringAttr> referencedNLASyms;
1406 auto scanAnnos = [&](const AnnotationSet &annos) {
1407 for (auto anno : annos)
1408 if (auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal"))
1409 referencedNLASyms.insert(sym.getAttr());
1410 };
1411 // Scan ports
1412 for (unsigned i = 0, e = mod.getNumPorts(); i != e; ++i)
1413 scanAnnos(AnnotationSet::forPort(mod, i));
1414
1415 // Scan operations (and not the module itself):
1416 // (Walk includes module for lack of simple/generic way to walk body only)
1417 mod.walk([&](Operation *op) {
1418 if (op == mod.getOperation())
1419 return;
1420 scanAnnos(AnnotationSet(op));
1421
1422 // Check MemOp and InstanceOp port annotations, special case
1423 TypeSwitch<Operation *>(op).Case<MemOp, InstanceOp>([&](auto op) {
1424 for (auto portAnnoAttr : op.getPortAnnotations())
1425 scanAnnos(AnnotationSet(cast<ArrayAttr>(portAnnoAttr)));
1426 });
1427 });
1428
1429 return referencedNLASyms;
1430 };
1431
1432 // Reduction operator
1433 auto mergeSets = [](auto &&a, auto &&b) {
1434 a.insert(b.begin(), b.end());
1435 return std::move(a);
1436 };
1437
1438 // Walk modules in parallel, scanning for references to NLA's
1439 // Gather set of NLA's referenced by each module's ports/operations.
1440 SmallVector<FModuleLike, 0> mods(nlaTargetedModules.begin(),
1441 nlaTargetedModules.end());
1442 auto nonModOnlyNLAs =
1443 transformReduce(circuit->getContext(), mods, DenseSet<StringAttr>{},
1444 mergeSets, scanForNLARefs);
1445
1446 // Mark NLA's that were not referenced as module-only
1447 for (auto &[_, mnla] : nlaMap) {
1448 auto nla = mnla.getNLA();
1449 if (nla.isModule() && !nonModOnlyNLAs.count(nla.getSymNameAttr()))
1450 mnla.markModuleOnly();
1451 }
1452}
1453
1454Inliner::Inliner(CircuitOp circuit, SymbolTable &symbolTable,
1455 InstanceGraph &instanceGraph)
1456 : circuit(circuit), context(circuit.getContext()), symbolTable(symbolTable),
1457 instanceGraph(instanceGraph) {}
1458
1459LogicalResult Inliner::run() {
1460 CircuitNamespace circuitNamespace(circuit);
1461
1462 // Gather all NLA's, build information about the instance ops used:
1463 for (auto nla : circuit.getBodyBlock()->getOps<hw::HierPathOp>()) {
1464 auto mnla = MutableNLA(nla, &circuitNamespace);
1465 nlaMap.insert({nla.getSymNameAttr(), mnla});
1466 rootMap[mnla.getNLA().root()].push_back(nla.getSymNameAttr());
1467 for (auto p : nla.getNamepath())
1468 if (auto ref = dyn_cast<InnerRefAttr>(p))
1469 instOpHierPaths[ref].push_back(nla.getSymNameAttr());
1470 }
1471 // Mark 'module-only' the NLA's that only target modules.
1472 // These may be deleted when their module is inlined/flattened.
1473 identifyNLAsTargetingOnlyModules();
1474
1475 for (auto &op : circuit.getOps()) {
1476 // Mark public/non-discardable modules as live.
1477 if (auto module = dyn_cast<FModuleLike>(op)) {
1478 if (module.canDiscardOnUseEmpty())
1479 continue;
1480 liveModules.insert(module);
1481 continue;
1482 }
1483
1484 // Ignore symbol uses in NLAs.
1485 if (isa<hw::HierPathOp>(op))
1486 continue;
1487
1488 // Mark modules live whose symbols are referenced in other ops.
1489 auto symbolUses = SymbolTable::getSymbolUses(&op);
1490 if (!symbolUses)
1491 continue;
1492 for (const auto &use : *symbolUses) {
1493 if (auto flat = dyn_cast<FlatSymbolRefAttr>(use.getSymbolRef()))
1494 if (auto moduleLike = symbolTable.lookup<FModuleLike>(flat.getAttr()))
1495 liveModules.insert(moduleLike);
1496 }
1497 }
1498
1499 // Populate module list with ALL modules in parents-before-children order.
1500 // We will only visit those known to be live, partially dynamically
1501 // discovered. This ensures overall we still process parents strictly before
1502 // children.
1503 SmallVector<FModuleOp, 16> modules;
1504 instanceGraph.walkInversePostOrder([&](igraph::InstanceGraphNode &node) {
1505 if (auto module = dyn_cast<FModuleOp>(*node.getModule()))
1506 modules.push_back(module);
1507 });
1508
1509 // If the module is marked for flattening, flatten it. Otherwise, inline
1510 // every instance marked to be inlined.
1511 for (auto moduleOp : modules) {
1512 // Skip modules we haven't determined to be 'live' so far.
1513 if (!liveModules.count(moduleOp))
1514 continue;
1515 if (shouldFlatten(moduleOp)) {
1516 if (failed(flattenInstances(moduleOp)))
1517 return failure();
1518 // Delete the flatten annotation, the transform was performed.
1519 // Even if visited again in our walk (for inlining),
1520 // we've just flattened it and so the annotation is no longer needed.
1521 AnnotationSet::removeAnnotations(moduleOp, flattenAnnoClass);
1522 } else {
1523 if (failed(inlineInstances(moduleOp)))
1524 return failure();
1525 }
1526 }
1527
1528 // Delete debug scopes that ended up being unused. Erase them in reverse order
1529 // since scopes at the back may have uses on scopes at the front.
1530 for (auto scopeOp : llvm::reverse(debugScopes))
1531 if (scopeOp.use_empty())
1532 scopeOp.erase();
1533 debugScopes.clear();
1534
1535 // Delete all unreferenced modules. Mark any NLAs that originate from dead
1536 // modules as also dead.
1537 for (auto mod : llvm::make_early_inc_range(
1538 circuit.getBodyBlock()->getOps<FModuleLike>())) {
1539 if (liveModules.count(mod))
1540 continue;
1541 for (auto nla : rootMap[mod.getModuleNameAttr()])
1542 nlaMap[nla].markDead();
1543 mod.erase();
1544 }
1545
1546 // Remove leftover inline annotations, and check no flatten annotations
1547 // remain as they should have been processed and removed.
1548 for (auto mod : circuit.getBodyBlock()->getOps<FModuleLike>()) {
1549 if (shouldInline(mod)) {
1550 assert(mod.isPublic() &&
1551 "non-public module with inline annotation still present");
1552 AnnotationSet::removeAnnotations(mod, inlineAnnoClass);
1553 }
1554 assert(!shouldFlatten(mod) && "flatten annotation found on live module");
1555 }
1556
1557 LLVM_DEBUG({
1558 llvm::dbgs() << "NLA modifications:\n";
1559 for (auto nla : circuit.getBodyBlock()->getOps<hw::HierPathOp>()) {
1560 auto &mnla = nlaMap[nla.getNameAttr()];
1561 mnla.dump();
1562 }
1563 });
1564
1565 // Writeback all NLAs to MLIR.
1566 for (auto &nla : nlaMap)
1567 nla.getSecond().applyUpdates();
1568
1569 // Garbage collect any annotations which are now dead. Duplicate annotations
1570 // which are now split.
1571 for (auto fmodule : circuit.getBodyBlock()->getOps<FModuleOp>()) {
1572 SmallVector<Attribute> newAnnotations;
1573 auto processNLAs = [&](Annotation anno) -> bool {
1574 if (auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
1575 // If the symbol isn't in the NLA map, just skip it. This avoids
1576 // problems where the nlaMap "[]" will try to construct a default
1577 // MutableNLA map (which it should never do).
1578 if (!nlaMap.count(sym.getAttr()))
1579 return false;
1580
1581 auto mnla = nlaMap[sym.getAttr()];
1582
1583 // Garbage collect dead NLA references. This cleans up NLAs that go
1584 // through modules which we never visited.
1585 if (mnla.isDead())
1586 return true;
1587
1588 // If the NLA has become local:
1589 // - if it is rooted at this module, replace it with a local version
1590 // of the annotation (drop the nonlocal field);
1591 // - otherwise it is local elsewhere but not here (this module needed
1592 // the NLA to selectively enable it), so just drop the annotation.
1593 if (mnla.isLocal()) {
1594 if (mnla.hasRoot(fmodule)) {
1595 anno.removeMember("circt.nonlocal");
1596 newAnnotations.push_back(anno.getAttr());
1597 }
1598 return true;
1599 }
1600
1601 // Do nothing if there are no additional NLAs to add or if we're
1602 // dealing with a root module. Root modules have already been updated
1603 // earlier in the pass. We only need to update NLA paths which are
1604 // not the root.
1605 auto newTops = mnla.getAdditionalSymbols();
1606 if (newTops.empty() || mnla.hasRoot(fmodule))
1607 return false;
1608
1609 // Add NLAs to the non-root portion of the NLA. This only needs to
1610 // add symbols for NLAs which are after the first one. We reused the
1611 // old symbol name for the first NLA.
1612 for (auto rootAndSym : newTops.drop_front()) {
1613 NamedAttrList newAnnotation;
1614 for (auto pair : anno.getDict()) {
1615 if (pair.getName().getValue() != "circt.nonlocal") {
1616 newAnnotation.push_back(pair);
1617 continue;
1618 }
1619 newAnnotation.push_back(
1620 {pair.getName(), FlatSymbolRefAttr::get(rootAndSym.getName())});
1621 }
1622 newAnnotations.push_back(DictionaryAttr::get(context, newAnnotation));
1623 }
1624 }
1625 return false;
1626 };
1627 fmodule.walk([&](Operation *op) {
1628 AnnotationSet annotations(op);
1629 // Early exit to avoid adding an empty annotations attribute to operations
1630 // which did not previously have annotations.
1631 if (annotations.empty())
1632 return;
1633
1634 // Update annotations on the op.
1635 newAnnotations.clear();
1636 annotations.removeAnnotations(processNLAs);
1637 annotations.addAnnotations(newAnnotations);
1638 annotations.applyToOperation(op);
1639 });
1640
1641 // Update annotations on the ports.
1642 SmallVector<Attribute> newPortAnnotations;
1643 for (auto port : fmodule.getPorts()) {
1644 newAnnotations.clear();
1645 port.annotations.removeAnnotations(processNLAs);
1646 port.annotations.addAnnotations(newAnnotations);
1647 newPortAnnotations.push_back(
1648 ArrayAttr::get(context, port.annotations.getArray()));
1649 }
1650 fmodule->setAttr("portAnnotations",
1651 ArrayAttr::get(context, newPortAnnotations));
1652 }
1653 return success();
1654}
1655
1656//===----------------------------------------------------------------------===//
1657// Pass Infrastructure
1658//===----------------------------------------------------------------------===//
1659
1660namespace {
1661class InlinerPass : public circt::firrtl::impl::InlinerBase<InlinerPass> {
1662 void runOnOperation() override {
1664 Inliner inliner(getOperation(), getAnalysis<SymbolTable>(),
1665 getAnalysis<InstanceGraph>());
1666 if (failed(inliner.run()))
1667 signalPassFailure();
1668 }
1669};
1670} // namespace
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static void dump(DIModule &module, raw_indented_ostream &os)
static AnnotationSet forPort(Operation *op, size_t portNo)
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:222
DenseMap< hw::InnerRefAttr, StringAttr > InnerRefToNewNameMap
static hw::InnerSymAttr uniqueInNamespace(hw::InnerSymAttr old, InnerRefToNewNameMap &map, hw::InnerSymbolNamespace &ns, StringAttr istName)
Generate and creating map entries for new inner symbol based on old one and an appropriate namespace ...
static void mapResultsToWires(IRMapping &mapper, SmallVectorImpl< Value > &wires, InstanceOp instance)
This function is used after inlining a module, to handle the conversion between module ports and inst...
static void replaceInnerRefUsers(ArrayRef< Operation * > newOps, const InnerRefToNewNameMap &map, StringAttr istName)
Process each operation, updating InnerRefAttr's using the specified map and the given name as the con...
static Block * getBodyBlock(FModuleLike mod)
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
Definition Debug.h:70
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:87
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
bool applyToOperation(Operation *op) const
Store the annotations in this set in an operation's annotations attribute, overwriting any existing a...
bool hasAnnotation(StringRef className) const
Return true if we have an annotation with the specified class name.
static AnnotationSet forPort(FModuleLike op, size_t portNo)
Get an annotation set for the specified port.
This class provides a read-only projection of an annotation.
This graph tracks modules and where they are instantiated.
This is a Node in the InstanceGraph.
auto getModule()
Get the module that this node is tracking.
std::pair< hw::InnerSymAttr, StringAttr > getOrAddInnerSym(MLIRContext *context, hw::InnerSymAttr attr, uint64_t fieldID, llvm::function_ref< hw::InnerSymbolNamespace &()> getNamespace)
Ensure that the the InnerSymAttr has a symbol on the field specified.
llvm::raw_ostream & operator<<(llvm::raw_ostream &os, const InstanceInfo::LatticeValue &value)
StringAttr getInnerSymName(Operation *op)
Return the StringAttr for the inner_sym name, if it exists.
Definition FIRRTLOps.h:108
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
static ResultTy transformReduce(MLIRContext *context, IterTy begin, IterTy end, ResultTy init, ReduceFuncTy reduce, TransformFuncTy transform)
Wrapper for llvm::parallelTransformReduce that performs the transform_reduce serially when MLIR multi...
Definition Utils.h:81
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
Definition hw.py:1
The namespace of a CircuitOp, generally inhabited by modules.
Definition Namespace.h:24