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