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