CIRCT  19.0.0git
LowerClasses.cpp
Go to the documentation of this file.
1 //===- LowerClasses.cpp - Lower to OM classes and objects -----------------===//
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 defines the LowerClasses pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
22 #include "circt/Dialect/OM/OMOps.h"
23 #include "mlir/IR/BuiltinOps.h"
24 #include "mlir/IR/IRMapping.h"
25 #include "mlir/IR/PatternMatch.h"
26 #include "mlir/IR/Threading.h"
27 #include "mlir/Pass/Pass.h"
28 #include "mlir/Support/LogicalResult.h"
29 #include "mlir/Transforms/DialectConversion.h"
30 #include "llvm/ADT/DepthFirstIterator.h"
31 #include "llvm/ADT/STLExtras.h"
32 #include "llvm/Support/raw_ostream.h"
33 
34 namespace circt {
35 namespace firrtl {
36 #define GEN_PASS_DEF_LOWERCLASSES
37 #include "circt/Dialect/FIRRTL/Passes.h.inc"
38 } // namespace firrtl
39 } // namespace circt
40 
41 using namespace mlir;
42 using namespace circt;
43 using namespace circt::firrtl;
44 using namespace circt::om;
45 
46 namespace {
47 
48 static bool shouldCreateClassImpl(igraph::InstanceGraphNode *node) {
49  auto moduleLike = node->getModule<FModuleLike>();
50  if (!moduleLike)
51  return false;
52 
53  if (isa<firrtl::ClassLike>(moduleLike.getOperation()))
54  return true;
55 
56  // Always create a class for public modules.
57  if (moduleLike.isPublic())
58  return true;
59 
60  // Create a class for modules with property ports.
61  bool hasClassPorts = llvm::any_of(moduleLike.getPorts(), [](PortInfo port) {
62  return isa<PropertyType>(port.type);
63  });
64 
65  if (hasClassPorts)
66  return true;
67 
68  // Create a class for modules that instantiate classes or modules with
69  // property ports.
70  for (auto *instance : *node) {
71  if (auto op = instance->getInstance<FInstanceLike>())
72  for (auto result : op->getResults())
73  if (type_isa<PropertyType>(result.getType()))
74  return true;
75  }
76 
77  return false;
78 }
79 
80 /// Helper class which holds a hierarchical path op reference and a pointer to
81 /// to the targeted operation.
82 struct PathInfo {
83  PathInfo() = default;
84  PathInfo(Operation *op, FlatSymbolRefAttr symRef,
85  StringAttr altBasePathModule)
86  : op(op), symRef(symRef), altBasePathModule(altBasePathModule) {
87  assert(op && "op must not be null");
88  assert(symRef && "symRef must not be null");
89  }
90 
91  operator bool() const { return op != nullptr; }
92 
93  /// The hardware component targeted by this path.
94  Operation *op = nullptr;
95 
96  /// A reference to the hierarchical path targeting the op.
97  FlatSymbolRefAttr symRef = nullptr;
98 
99  /// The module name of the root module from which we take an alternative base
100  /// path.
101  StringAttr altBasePathModule = nullptr;
102 };
103 
104 /// Maps a FIRRTL path id to the lowered PathInfo.
105 struct PathInfoTable {
106  // Add an alternative base path root module. The default base path from this
107  // module will be passed through to where it is needed.
108  void addAltBasePathRoot(StringAttr rootModuleName) {
109  altBasePathRoots.insert(rootModuleName);
110  }
111 
112  // Add a passthrough module for a given root module. The default base path
113  // from the root module will be passed through the passthrough module.
114  void addAltBasePathPassthrough(StringAttr passthroughModuleName,
115  StringAttr rootModuleName) {
116  auto &rootSequence = altBasePathsPassthroughs[passthroughModuleName];
117  rootSequence.push_back(rootModuleName);
118  }
119 
120  // Get an iterator range over the alternative base path root module names.
121  llvm::iterator_range<SmallPtrSetImpl<StringAttr>::iterator>
122  getAltBasePathRoots() const {
123  return llvm::make_range(altBasePathRoots.begin(), altBasePathRoots.end());
124  }
125 
126  // Get the number of alternative base paths passing through the given
127  // passthrough module.
128  size_t getNumAltBasePaths(StringAttr passthroughModuleName) const {
129  return altBasePathsPassthroughs.lookup(passthroughModuleName).size();
130  }
131 
132  // Get the root modules that are passing an alternative base path through the
133  // given passthrough module.
134  llvm::iterator_range<const StringAttr *>
135  getRootsForPassthrough(StringAttr passthroughModuleName) const {
136  auto it = altBasePathsPassthroughs.find(passthroughModuleName);
137  assert(it != altBasePathsPassthroughs.end() &&
138  "expected passthrough module to already exist");
139  return llvm::make_range(it->second.begin(), it->second.end());
140  }
141 
142  // Collect alternative base paths passing through `instance`, by looking up
143  // its associated `moduleNameAttr`. The results are collected in `result`.
144  void collectAltBasePaths(Operation *instance, StringAttr moduleNameAttr,
145  SmallVectorImpl<Value> &result) const {
146  auto altBasePaths = altBasePathsPassthroughs.lookup(moduleNameAttr);
147  auto parent = instance->getParentOfType<om::ClassOp>();
148 
149  // Handle each alternative base path for instances of this module-like.
150  for (auto [i, altBasePath] : llvm::enumerate(altBasePaths)) {
151  if (parent.getName().starts_with(altBasePath)) {
152  // If we are passing down from the root, take the root base path.
153  result.push_back(instance->getBlock()->getArgument(0));
154  } else {
155  // Otherwise, pass through the appropriate base path from above.
156  // + 1 to skip default base path
157  auto basePath = instance->getBlock()->getArgument(1 + i);
158  assert(isa<om::BasePathType>(basePath.getType()) &&
159  "expected a passthrough base path");
160  result.push_back(basePath);
161  }
162  }
163  }
164 
165  // The table mapping DistinctAttrs to PathInfo structs.
166  DenseMap<DistinctAttr, PathInfo> table;
167 
168 private:
169  // Module name attributes indicating modules whose base path input should
170  // be used as alternate base paths.
171  SmallPtrSet<StringAttr, 16> altBasePathRoots;
172 
173  // Module name attributes mapping from modules who pass through alternative
174  // base paths from their parents to a sequence of the parents' module names.
175  DenseMap<StringAttr, SmallVector<StringAttr>> altBasePathsPassthroughs;
176 };
177 
178 /// The suffix to append to lowered module names.
179 static constexpr StringRef kClassNameSuffix = "_Class";
180 
181 /// Helper class to capture details about a property.
182 struct Property {
183  size_t index;
184  StringRef name;
185  Type type;
186  Location loc;
187 };
188 
189 /// Helper class to capture state about a Class being lowered.
190 struct ClassLoweringState {
191  FModuleLike moduleLike;
192  std::vector<hw::HierPathOp> paths;
193 };
194 
195 struct LoweringState {
196  PathInfoTable pathInfoTable;
197  DenseMap<om::ClassLike, ClassLoweringState> classLoweringStateTable;
198 };
199 
200 struct LowerClassesPass
201  : public circt::firrtl::impl::LowerClassesBase<LowerClassesPass> {
202  void runOnOperation() override;
203 
204 private:
205  LogicalResult processPaths(InstanceGraph &instanceGraph,
206  hw::InnerSymbolNamespaceCollection &namespaces,
207  HierPathCache &cache, PathInfoTable &pathInfoTable,
208  SymbolTable &symbolTable);
209 
210  // Predicate to check if a module-like needs a Class to be created.
211  bool shouldCreateClass(StringAttr modName);
212 
213  // Create an OM Class op from a FIRRTL Class op.
214  om::ClassLike createClass(FModuleLike moduleLike,
215  const PathInfoTable &pathInfoTable);
216 
217  // Lower the FIRRTL Class to OM Class.
218  void lowerClassLike(FModuleLike moduleLike, om::ClassLike classLike,
219  const PathInfoTable &pathInfoTable);
220  void lowerClass(om::ClassOp classOp, FModuleLike moduleLike,
221  const PathInfoTable &pathInfoTable);
222  void lowerClassExtern(ClassExternOp classExternOp, FModuleLike moduleLike);
223 
224  // Update Object instantiations in a FIRRTL Module or OM Class.
225  LogicalResult updateInstances(Operation *op, InstanceGraph &instanceGraph,
226  const LoweringState &state,
227  const PathInfoTable &pathInfoTable);
228 
229  // Convert to OM ops and types in Classes or Modules.
230  LogicalResult dialectConversion(
231  Operation *op, const PathInfoTable &pathInfoTable,
232  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable);
233 
234  // State to memoize repeated calls to shouldCreateClass.
235  DenseMap<StringAttr, bool> shouldCreateClassMemo;
236 };
237 
238 struct PathTracker {
239  // An entry point for parallely tracking paths in the circuit and apply
240  // changes to `pathInfoTable`.
241  static LogicalResult
242  run(CircuitOp circuit, InstanceGraph &instanceGraph,
243  hw::InnerSymbolNamespaceCollection &namespaces, HierPathCache &cache,
244  PathInfoTable &pathInfoTable, const SymbolTable &symbolTable,
245  const DenseMap<DistinctAttr, FModuleOp> &owningModules);
246 
247  PathTracker(FModuleLike module,
248  hw::InnerSymbolNamespaceCollection &namespaces,
249  InstanceGraph &instanceGraph, const SymbolTable &symbolTable,
250  const DenseMap<DistinctAttr, FModuleOp> &owningModules)
251  : module(module), moduleNamespace(namespaces[module]),
252  namespaces(namespaces), instanceGraph(instanceGraph),
253  symbolTable(symbolTable), owningModules(owningModules) {}
254 
255 private:
256  struct PathInfoTableEntry {
257  Operation *op;
258  DistinctAttr id;
259  StringAttr altBasePathModule;
260  // This is null if the path has no owning module.
261  ArrayAttr pathAttr;
262  };
263 
264  // Run the main logic.
265  LogicalResult runOnModule();
266 
267  // Return updated annotations for a given AnnoTarget if success.
268  FailureOr<AnnotationSet> processPathTrackers(const AnnoTarget &target);
269 
270  LogicalResult updatePathInfoTable(PathInfoTable &pathInfoTable,
271  HierPathCache &cache) const;
272 
273  // Determine it is necessary to use an alternative base path for `moduleName`
274  // and `owningModule`.
275  FailureOr<bool> getOrComputeNeedsAltBasePath(Location loc,
276  StringAttr moduleName,
277  FModuleOp owningModule);
278  FModuleLike module;
279 
280  // Local data structures.
281  hw::InnerSymbolNamespace &moduleNamespace;
282  hw::InnerSymbolNamespaceCollection &namespaces;
283  DenseMap<std::pair<StringAttr, FModuleOp>, bool> needsAltBasePathCache;
284 
285  // Thread-unsafe global data structure. Don't mutate.
286  InstanceGraph &instanceGraph;
287  const SymbolTable &symbolTable;
288  const DenseMap<DistinctAttr, FModuleOp> &owningModules;
289 
290  // Result.
291  SmallVector<PathInfoTableEntry> entries;
292  SetVector<StringAttr> altBasePathRoots;
293 };
294 
295 } // namespace
296 
297 LogicalResult
298 PathTracker::run(CircuitOp circuit, InstanceGraph &instanceGraph,
299  hw::InnerSymbolNamespaceCollection &namespaces,
300  HierPathCache &cache, PathInfoTable &pathInfoTable,
301  const SymbolTable &symbolTable,
302  const DenseMap<DistinctAttr, FModuleOp> &owningModules) {
303  SmallVector<PathTracker> trackers;
304 
305  // First allocate module namespaces. Don't capture a namespace reference at
306  // this point since they could be invalidated when DenseMap grows.
307  for (auto *node : instanceGraph)
308  if (auto module = node->getModule<FModuleLike>())
309  (void)namespaces.get(module);
310 
311  for (auto *node : instanceGraph)
312  if (auto module = node->getModule<FModuleLike>()) {
313  PathTracker tracker(module, namespaces, instanceGraph, symbolTable,
314  owningModules);
315  if (failed(tracker.runOnModule()))
316  return failure();
317  if (failed(tracker.updatePathInfoTable(pathInfoTable, cache)))
318  return failure();
319  }
320 
321  return success();
322 }
323 
324 LogicalResult PathTracker::runOnModule() {
325  auto processAndUpdateAnnoTarget = [&](AnnoTarget target) -> LogicalResult {
326  auto anno = processPathTrackers(target);
327  if (failed(anno))
328  return failure();
329  target.setAnnotations(*anno);
330  return success();
331  };
332 
333  // Process the module annotations.
334  if (failed(processAndUpdateAnnoTarget(OpAnnoTarget(module))))
335  return failure();
336 
337  // Process module port annotations.
338  SmallVector<Attribute> portAnnotations;
339  portAnnotations.reserve(module.getNumPorts());
340  for (unsigned i = 0, e = module.getNumPorts(); i < e; ++i) {
341  auto annos = processPathTrackers(PortAnnoTarget(module, i));
342  if (failed(annos))
343  return failure();
344  portAnnotations.push_back(annos->getArrayAttr());
345  }
346  // Batch update port annotations.
347  module.setPortAnnotationsAttr(
348  ArrayAttr::get(module.getContext(), portAnnotations));
349 
350  // Process ops in the module body.
351  auto result = module.walk([&](hw::InnerSymbolOpInterface op) {
352  if (failed(processAndUpdateAnnoTarget(OpAnnoTarget(op))))
353  return WalkResult::interrupt();
354  return WalkResult::advance();
355  });
356 
357  if (result.wasInterrupted())
358  return failure();
359 
360  // Process paththrough.
361  return success();
362 }
363 
364 FailureOr<bool>
365 PathTracker::getOrComputeNeedsAltBasePath(Location loc, StringAttr moduleName,
366  FModuleOp owningModule) {
367 
368  auto it = needsAltBasePathCache.find({moduleName, owningModule});
369  if (it != needsAltBasePathCache.end())
370  return it->second;
371  bool needsAltBasePath = false;
372  auto *node = instanceGraph.lookup(moduleName);
373  while (true) {
374  // If the path is rooted at the owning module, we're done.
375  if (node->getModule() == owningModule)
376  break;
377  // If there are no more parents, then the path op lives in a different
378  // hierarchy than the HW object it references, which needs to handled
379  // specially. Flag this, so we know to create an alternative base path
380  // below.
381  if (node->noUses()) {
382  needsAltBasePath = true;
383  break;
384  }
385  // If there is more than one instance of this module, then the path
386  // operation is ambiguous, which is a warning. This should become an error
387  // once user code is properly enforcing single instantiation, but in
388  // practice this generates the same outputs as the original flow for now.
389  // See https://github.com/llvm/circt/issues/7128.
390  if (!node->hasOneUse()) {
391  auto diag = mlir::emitWarning(loc)
392  << "unable to uniquely resolve target due "
393  "to multiple instantiation";
394  for (auto *use : node->uses())
395  diag.attachNote(use->getInstance().getLoc()) << "instance here";
396  }
397  node = (*node->usesBegin())->getParent();
398  }
399  needsAltBasePathCache[{moduleName, owningModule}] = needsAltBasePath;
400  return needsAltBasePath;
401 }
402 
403 FailureOr<AnnotationSet>
404 PathTracker::processPathTrackers(const AnnoTarget &target) {
405  auto error = false;
406  auto annotations = target.getAnnotations();
407  auto *op = target.getOp();
408  annotations.removeAnnotations([&](Annotation anno) {
409  // If there has been an error, just skip this annotation.
410  if (error)
411  return false;
412 
413  // We are looking for OMIR tracker annotations.
414  if (!anno.isClass("circt.tracker"))
415  return false;
416 
417  // The token must have a valid ID.
418  auto id = anno.getMember<DistinctAttr>("id");
419  if (!id) {
420  op->emitError("circt.tracker annotation missing id field");
421  error = true;
422  return false;
423  }
424 
425  // Get the fieldID. If there is none, it is assumed to be 0.
426  uint64_t fieldID = anno.getFieldID();
427 
428  // Attach an inner sym to the operation.
429  Attribute targetSym;
430  if (auto portTarget = dyn_cast<PortAnnoTarget>(target)) {
431  targetSym =
432  getInnerRefTo({portTarget.getPortNo(), portTarget.getOp(), fieldID},
433  [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
434  return moduleNamespace;
435  });
436  } else if (auto module = dyn_cast<FModuleLike>(op)) {
437  assert(!fieldID && "field not valid for modules");
438  targetSym = FlatSymbolRefAttr::get(module.getModuleNameAttr());
439  } else {
440  targetSym =
441  getInnerRefTo({target.getOp(), fieldID},
442  [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
443  return moduleNamespace;
444  });
445  }
446 
447  // Create the hierarchical path.
448  SmallVector<Attribute> path;
449 
450  // Copy the trailing final target part of the path.
451  path.push_back(targetSym);
452 
453  auto moduleName = target.getModule().getModuleNameAttr();
454 
455  // Verify a nonlocal annotation refers to a HierPathOp.
456  hw::HierPathOp hierPathOp;
457  if (auto hierName = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
458  hierPathOp =
459  dyn_cast<hw::HierPathOp>(symbolTable.lookup(hierName.getAttr()));
460  if (!hierPathOp) {
461  op->emitError("annotation does not point at a HierPathOp");
462  error = true;
463  return false;
464  }
465  }
466 
467  // Get the owning module. If there is no owning module, then this
468  // declaration does not have a use, and we can return early.
469  auto owningModule = owningModules.lookup(id);
470  if (!owningModule) {
471  PathInfoTableEntry entry;
472  // This entry is used for checking id uniquness.
473  entry.op = op;
474  entry.id = id;
475  entries.push_back(entry);
476  return true;
477  }
478 
479  // Copy the middle part from the annotation's NLA.
480  if (hierPathOp) {
481  // Get the original path.
482  auto oldPath = hierPathOp.getNamepath().getValue();
483 
484  // Set the moduleName and path based on the hierarchical path. If the
485  // owningModule is in the hierarichal path, start the hierarchical path
486  // there. Otherwise use the top of the hierarchical path.
487  bool pathContainsOwningModule = false;
488  size_t owningModuleIndex = 0;
489  for (auto [idx, pathFramgent] : llvm::enumerate(oldPath)) {
490  if (auto innerRef = dyn_cast<hw::InnerRefAttr>(pathFramgent)) {
491  if (innerRef.getModule() == owningModule.getModuleNameAttr()) {
492  pathContainsOwningModule = true;
493  owningModuleIndex = idx;
494  }
495  } else if (auto symRef = dyn_cast<FlatSymbolRefAttr>(pathFramgent)) {
496  if (symRef.getAttr() == owningModule.getModuleNameAttr()) {
497  pathContainsOwningModule = true;
498  owningModuleIndex = idx;
499  }
500  }
501  }
502 
503  if (pathContainsOwningModule) {
504  // Set the path root module name to the owning module.
505  moduleName = owningModule.getModuleNameAttr();
506 
507  // Copy the old path, dropping the module name and the prefix to the
508  // owning module.
509  llvm::append_range(path, llvm::reverse(oldPath.drop_back().drop_front(
510  owningModuleIndex)));
511  } else {
512  // Set the path root module name to the start of the path.
513  moduleName = cast<hw::InnerRefAttr>(oldPath.front()).getModule();
514 
515  // Copy the old path, dropping the module name.
516  llvm::append_range(path, llvm::reverse(oldPath.drop_back()));
517  }
518  }
519 
520  // Check if we need an alternative base path.
521  auto needsAltBasePath =
522  getOrComputeNeedsAltBasePath(op->getLoc(), moduleName, owningModule);
523  if (failed(needsAltBasePath)) {
524  error = true;
525  return false;
526  }
527 
528  // Copy the leading part of the hierarchical path from the owning module
529  // to the start of the annotation's NLA.
530  InstanceGraphNode *node = instanceGraph.lookup(moduleName);
531  while (true) {
532  // If we get to the owning module or the top, we're done.
533  if (node->getModule() == owningModule || node->noUses())
534  break;
535 
536  // Append the next level of hierarchy to the path. Note that if there
537  // are multiple instances, which we warn about, this is where the
538  // ambiguity manifests. In practice, just picking usesBegin generates
539  // the same output as EmitOMIR would for now.
540  InstanceRecord *inst = *node->usesBegin();
541  path.push_back(
542  OpAnnoTarget(inst->getInstance<InstanceOp>())
543  .getNLAReference(namespaces[inst->getParent()->getModule()]));
544 
545  node = inst->getParent();
546  }
547 
548  // Create the HierPathOp.
549  std::reverse(path.begin(), path.end());
550  auto pathAttr = ArrayAttr::get(op->getContext(), path);
551 
552  // If we need an alternative base path, save the top module from the
553  // path. We will plumb in the basepath from this module.
554  StringAttr altBasePathModule;
555  if (*needsAltBasePath) {
556  altBasePathModule =
557  TypeSwitch<Attribute, StringAttr>(path.front())
558  .Case<FlatSymbolRefAttr>([](auto a) { return a.getAttr(); })
559  .Case<hw::InnerRefAttr>([](auto a) { return a.getModule(); });
560 
561  altBasePathRoots.insert(altBasePathModule);
562  }
563 
564  // Record the path operation associated with the path op.
565  entries.push_back({op, id, altBasePathModule, pathAttr});
566 
567  // Remove this annotation from the operation.
568  return true;
569  });
570 
571  if (error)
572  return {};
573 
574  return annotations;
575 }
576 
577 LogicalResult PathTracker::updatePathInfoTable(PathInfoTable &pathInfoTable,
578  HierPathCache &cache) const {
579  for (auto root : altBasePathRoots)
580  pathInfoTable.addAltBasePathRoot(root);
581 
582  for (const auto &entry : entries) {
583  // Record the path operation associated with the path op.
584  auto [it, inserted] = pathInfoTable.table.try_emplace(entry.id);
585  auto &pathInfo = it->second;
586  if (!inserted) {
587  auto diag =
588  emitError(pathInfo.op->getLoc(), "duplicate identifier found");
589  diag.attachNote(entry.op->getLoc()) << "other identifier here";
590  return failure();
591  }
592 
593  if (entry.pathAttr)
594  pathInfo = {entry.op, cache.getRefFor(entry.pathAttr),
595  entry.altBasePathModule};
596  else
597  pathInfo.op = entry.op;
598  }
599  return success();
600 }
601 
602 /// This pass removes the OMIR tracker annotations from operations, and ensures
603 /// that each thing that was targeted has a hierarchical path targeting it. It
604 /// builds a table which maps the original OMIR tracker annotation IDs to the
605 /// corresponding hierarchical paths. We use this table to convert FIRRTL path
606 /// ops to OM. FIRRTL paths refer to their target using a target ID, while OM
607 /// paths refer to their target using hierarchical paths.
608 LogicalResult LowerClassesPass::processPaths(
609  InstanceGraph &instanceGraph,
610  hw::InnerSymbolNamespaceCollection &namespaces, HierPathCache &cache,
611  PathInfoTable &pathInfoTable, SymbolTable &symbolTable) {
612  auto circuit = getOperation();
613 
614  // Collect the path declarations and owning modules.
615  OwningModuleCache owningModuleCache(instanceGraph);
616  DenseMap<DistinctAttr, FModuleOp> owningModules;
617  std::vector<Operation *> declarations;
618  auto result = circuit.walk([&](Operation *op) {
619  if (auto pathOp = dyn_cast<PathOp>(op)) {
620  // Find the owning module of this path reference.
621  auto owningModule = owningModuleCache.lookup(pathOp);
622  // If this reference does not have a single owning module, it is an error.
623  if (!owningModule) {
624  pathOp->emitError("path does not have a single owning module");
625  return WalkResult::interrupt();
626  }
627  auto target = pathOp.getTargetAttr();
628  auto [it, inserted] = owningModules.try_emplace(target, owningModule);
629  // If this declaration already has a reference, both references must have
630  // the same owning module.
631  if (!inserted && it->second != owningModule) {
632  pathOp->emitError()
633  << "path reference " << target << " has conflicting owning modules "
634  << it->second.getModuleNameAttr() << " and "
635  << owningModule.getModuleNameAttr();
636  return WalkResult::interrupt();
637  }
638  }
639  return WalkResult::advance();
640  });
641 
642  if (result.wasInterrupted())
643  return failure();
644 
645  if (failed(PathTracker::run(circuit, instanceGraph, namespaces, cache,
646  pathInfoTable, symbolTable, owningModules)))
647  return failure();
648 
649  // For each module that will be passing through a base path, compute its
650  // descendants that need this base path passed through.
651  for (auto rootModule : pathInfoTable.getAltBasePathRoots()) {
652  InstanceGraphNode *node = instanceGraph.lookup(rootModule);
653 
654  // Do a depth first traversal of the instance graph from rootModule,
655  // looking for descendants that need to be passed through.
656  auto start = llvm::df_begin(node);
657  auto end = llvm::df_end(node);
658  auto it = start;
659  while (it != end) {
660  // Nothing to do for the root module.
661  if (it == start) {
662  ++it;
663  continue;
664  }
665 
666  // If we aren't creating a class for this child, skip this hierarchy.
667  if (!shouldCreateClass(
668  it->getModule<FModuleLike>().getModuleNameAttr())) {
669  it = it.skipChildren();
670  continue;
671  }
672 
673  // If we are at a leaf, nothing to do.
674  if (it->begin() == it->end()) {
675  ++it;
676  continue;
677  }
678 
679  // Track state for this passthrough.
680  StringAttr passthroughModule = it->getModule().getModuleNameAttr();
681  pathInfoTable.addAltBasePathPassthrough(passthroughModule, rootModule);
682 
683  ++it;
684  }
685  }
686 
687  return success();
688 }
689 
690 /// Lower FIRRTL Class and Object ops to OM Class and Object ops
691 void LowerClassesPass::runOnOperation() {
692  MLIRContext *ctx = &getContext();
693 
694  // Get the CircuitOp.
695  CircuitOp circuit = getOperation();
696 
697  // Get the InstanceGraph and SymbolTable.
698  InstanceGraph &instanceGraph = getAnalysis<InstanceGraph>();
699  SymbolTable &symbolTable = getAnalysis<SymbolTable>();
700 
701  hw::InnerSymbolNamespaceCollection namespaces;
702  HierPathCache cache(circuit, symbolTable);
703 
704  // Fill `shouldCreateClassMemo`.
705  for (auto *node : instanceGraph)
706  if (auto moduleLike = node->getModule<firrtl::FModuleLike>())
707  shouldCreateClassMemo.insert({moduleLike.getModuleNameAttr(), false});
708 
709  parallelForEach(circuit.getContext(), instanceGraph,
710  [&](igraph::InstanceGraphNode *node) {
711  if (auto moduleLike = node->getModule<FModuleLike>())
712  shouldCreateClassMemo[moduleLike.getModuleNameAttr()] =
713  shouldCreateClassImpl(node);
714  });
715 
716  // Rewrite all path annotations into inner symbol targets.
717  PathInfoTable pathInfoTable;
718  if (failed(processPaths(instanceGraph, namespaces, cache, pathInfoTable,
719  symbolTable))) {
720  signalPassFailure();
721  return;
722  }
723 
724  LoweringState loweringState;
725 
726  // Create new OM Class ops serially.
727  DenseMap<StringAttr, firrtl::ClassType> classTypeTable;
728  for (auto *node : instanceGraph) {
729  auto moduleLike = node->getModule<firrtl::FModuleLike>();
730  if (!moduleLike)
731  continue;
732 
733  if (shouldCreateClass(moduleLike.getModuleNameAttr())) {
734  auto omClass = createClass(moduleLike, pathInfoTable);
735  auto &classLoweringState = loweringState.classLoweringStateTable[omClass];
736  classLoweringState.moduleLike = moduleLike;
737 
738  // Find the module instances under the current module with metadata. These
739  // ops will be converted to om objects by this pass. Create a hierarchical
740  // path for each of these instances, which will be used to rebase path
741  // operations. Hierarchical paths must be created serially to ensure their
742  // order in the circuit is deterministc.
743  for (auto *instance : *node) {
744  auto inst = instance->getInstance<firrtl::InstanceOp>();
745  if (!inst)
746  continue;
747  // Get the referenced module.
748  auto module = instance->getTarget()->getModule<FModuleLike>();
749  if (module && shouldCreateClass(module.getModuleNameAttr())) {
750  auto targetSym = getInnerRefTo(
751  {inst, 0}, [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
752  return namespaces[module];
753  });
754  SmallVector<Attribute> path = {targetSym};
755  auto pathAttr = ArrayAttr::get(ctx, path);
756  auto hierPath = cache.getOpFor(pathAttr);
757  classLoweringState.paths.push_back(hierPath);
758  }
759  }
760 
761  if (auto classLike =
762  dyn_cast<firrtl::ClassLike>(moduleLike.getOperation()))
763  classTypeTable[classLike.getNameAttr()] = classLike.getInstanceType();
764  }
765  }
766 
767  // Move ops from FIRRTL Class to OM Class in parallel.
768  mlir::parallelForEach(ctx, loweringState.classLoweringStateTable,
769  [this, &pathInfoTable](auto &entry) {
770  const auto &[classLike, state] = entry;
771  lowerClassLike(state.moduleLike, classLike,
772  pathInfoTable);
773  });
774 
775  // Completely erase Class module-likes, and remove from the InstanceGraph.
776  for (auto &[omClass, state] : loweringState.classLoweringStateTable) {
777  if (isa<firrtl::ClassLike>(state.moduleLike.getOperation())) {
778  InstanceGraphNode *node = instanceGraph.lookup(state.moduleLike);
779  for (auto *use : llvm::make_early_inc_range(node->uses()))
780  use->erase();
781  instanceGraph.erase(node);
782  state.moduleLike.erase();
783  }
784  }
785 
786  // Collect ops where Objects can be instantiated.
787  SmallVector<Operation *> objectContainers;
788  for (auto &op : circuit.getOps())
789  if (isa<FModuleOp, om::ClassLike>(op))
790  objectContainers.push_back(&op);
791 
792  // Update Object creation ops in Classes or Modules in parallel.
793  if (failed(
794  mlir::failableParallelForEach(ctx, objectContainers, [&](auto *op) {
795  return updateInstances(op, instanceGraph, loweringState,
796  pathInfoTable);
797  })))
798  return signalPassFailure();
799 
800  // Convert to OM ops and types in Classes or Modules in parallel.
801  if (failed(
802  mlir::failableParallelForEach(ctx, objectContainers, [&](auto *op) {
803  return dialectConversion(op, pathInfoTable, classTypeTable);
804  })))
805  return signalPassFailure();
806 
807  // We keep the instance graph up to date, so mark that analysis preserved.
808  markAnalysesPreserved<InstanceGraph>();
809 }
810 
811 std::unique_ptr<mlir::Pass> circt::firrtl::createLowerClassesPass() {
812  return std::make_unique<LowerClassesPass>();
813 }
814 
815 // Predicate to check if a module-like needs a Class to be created.
816 bool LowerClassesPass::shouldCreateClass(StringAttr modName) {
817  // Return a memoized result.
818  return shouldCreateClassMemo.at(modName);
819 }
820 
821 // Create an OM Class op from a FIRRTL Class op or Module op with properties.
822 om::ClassLike
823 LowerClassesPass::createClass(FModuleLike moduleLike,
824  const PathInfoTable &pathInfoTable) {
825  // Collect the parameter names from input properties.
826  SmallVector<StringRef> formalParamNames;
827  // Every class gets a base path as its first parameter.
828  formalParamNames.emplace_back("basepath");
829 
830  // If this class is passing through base paths from above, add those.
831  size_t nAltBasePaths =
832  pathInfoTable.getNumAltBasePaths(moduleLike.getModuleNameAttr());
833  for (size_t i = 0; i < nAltBasePaths; ++i)
834  formalParamNames.push_back(StringAttr::get(
835  moduleLike->getContext(), "alt_basepath_" + llvm::Twine(i)));
836 
837  for (auto [index, port] : llvm::enumerate(moduleLike.getPorts()))
838  if (port.isInput() && isa<PropertyType>(port.type))
839  formalParamNames.push_back(port.name);
840 
841  OpBuilder builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
842 
843  // Take the name from the FIRRTL Class or Module to create the OM Class name.
844  StringRef className = moduleLike.getName();
845 
846  // Use the defname for external modules.
847  if (auto externMod = dyn_cast<FExtModuleOp>(moduleLike.getOperation()))
848  if (auto defname = externMod.getDefname())
849  className = defname.value();
850 
851  // If the op is a Module or ExtModule, the OM Class would conflict with the HW
852  // Module, so give it a suffix. There is no formal ABI for this yet.
853  StringRef suffix =
854  isa<FModuleOp, FExtModuleOp>(moduleLike) ? kClassNameSuffix : "";
855 
856  // Construct the OM Class with the FIRRTL Class name and parameter names.
857  om::ClassLike loweredClassOp;
858  if (isa<firrtl::ExtClassOp, firrtl::FExtModuleOp>(moduleLike.getOperation()))
859  loweredClassOp = builder.create<om::ClassExternOp>(
860  moduleLike.getLoc(), className + suffix, formalParamNames);
861  else
862  loweredClassOp = builder.create<om::ClassOp>(
863  moduleLike.getLoc(), className + suffix, formalParamNames);
864 
865  return loweredClassOp;
866 }
867 
868 void LowerClassesPass::lowerClassLike(FModuleLike moduleLike,
869  om::ClassLike classLike,
870  const PathInfoTable &pathInfoTable) {
871 
872  if (auto classOp = dyn_cast<om::ClassOp>(classLike.getOperation())) {
873  return lowerClass(classOp, moduleLike, pathInfoTable);
874  }
875  if (auto classExternOp =
876  dyn_cast<om::ClassExternOp>(classLike.getOperation())) {
877  return lowerClassExtern(classExternOp, moduleLike);
878  }
879  llvm_unreachable("unhandled class-like op");
880 }
881 
882 void LowerClassesPass::lowerClass(om::ClassOp classOp, FModuleLike moduleLike,
883  const PathInfoTable &pathInfoTable) {
884  // Map from Values in the FIRRTL Class to Values in the OM Class.
885  IRMapping mapping;
886 
887  // Collect information about property ports.
888  SmallVector<Property> inputProperties;
889  BitVector portsToErase(moduleLike.getNumPorts());
890  for (auto [index, port] : llvm::enumerate(moduleLike.getPorts())) {
891  // For Module ports that aren't property types, move along.
892  if (!isa<PropertyType>(port.type))
893  continue;
894 
895  // Remember input properties to create the OM Class formal parameters.
896  if (port.isInput())
897  inputProperties.push_back({index, port.name, port.type, port.loc});
898 
899  // In case this is a Module, remember to erase this port.
900  portsToErase.set(index);
901  }
902 
903  // Construct the OM Class body with block arguments for each input property,
904  // updating the mapping to map from the input property to the block argument.
905  Block *classBody = &classOp->getRegion(0).emplaceBlock();
906  // Every class created from a module gets a base path as its first parameter.
907  auto basePathType = BasePathType::get(&getContext());
908  auto unknownLoc = UnknownLoc::get(&getContext());
909  classBody->addArgument(basePathType, unknownLoc);
910 
911  // If this class is passing through base paths from above, add those.
912  size_t nAltBasePaths =
913  pathInfoTable.getNumAltBasePaths(moduleLike.getModuleNameAttr());
914  for (size_t i = 0; i < nAltBasePaths; ++i)
915  classBody->addArgument(basePathType, unknownLoc);
916 
917  for (auto inputProperty : inputProperties) {
918  BlockArgument parameterValue =
919  classBody->addArgument(inputProperty.type, inputProperty.loc);
920  BlockArgument inputValue =
921  moduleLike->getRegion(0).getArgument(inputProperty.index);
922  mapping.map(inputValue, parameterValue);
923  }
924 
925  // Clone the property ops from the FIRRTL Class or Module to the OM Class.
926  SmallVector<Operation *> opsToErase;
927  OpBuilder builder = OpBuilder::atBlockBegin(classOp.getBodyBlock());
928  for (auto &op : moduleLike->getRegion(0).getOps()) {
929  // Check if any operand is a property.
930  auto propertyOperands = llvm::any_of(op.getOperandTypes(), [](Type type) {
931  return isa<PropertyType>(type);
932  });
933  bool needsClone = false;
934  if (auto instance = dyn_cast<InstanceOp>(op))
935  needsClone = shouldCreateClass(instance.getReferencedModuleNameAttr());
936 
937  // Check if any result is a property.
938  auto propertyResults = llvm::any_of(
939  op.getResultTypes(), [](Type type) { return isa<PropertyType>(type); });
940 
941  // If there are no properties here, move along.
942  if (!needsClone && !propertyOperands && !propertyResults)
943  continue;
944 
945  // Actually clone the op over to the OM Class.
946  builder.clone(op, mapping);
947 
948  // In case this is a Module, remember to erase this op, unless it is an
949  // instance. Instances are handled later in updateInstances.
950  if (!isa<InstanceOp>(op))
951  opsToErase.push_back(&op);
952  }
953 
954  // Convert any output property assignments to Field ops.
955  for (auto op : llvm::make_early_inc_range(classOp.getOps<PropAssignOp>())) {
956  // Property assignments will currently be pointing back to the original
957  // FIRRTL Class for output ports.
958  auto outputPort = dyn_cast<BlockArgument>(op.getDest());
959  if (!outputPort)
960  continue;
961 
962  // Get the original port name, create a Field, and erase the propassign.
963  auto name = moduleLike.getPortName(outputPort.getArgNumber());
964  builder.create<ClassFieldOp>(op.getLoc(), name, op.getSrc());
965  op.erase();
966  }
967 
968  // If the module-like is a Class, it will be completely erased later.
969  // Otherwise, erase just the property ports and ops.
970  if (!isa<firrtl::ClassLike>(moduleLike.getOperation())) {
971  // Erase ops in use before def order, thanks to FIRRTL's SSA regions.
972  for (auto *op : llvm::reverse(opsToErase))
973  op->erase();
974 
975  // Erase property typed ports.
976  moduleLike.erasePorts(portsToErase);
977  }
978 }
979 
980 void LowerClassesPass::lowerClassExtern(ClassExternOp classExternOp,
981  FModuleLike moduleLike) {
982  // Construct the OM Class body.
983  // Add a block arguments for each input property.
984  // Add a class.extern.field op for each output.
985  BitVector portsToErase(moduleLike.getNumPorts());
986  Block *classBody = &classExternOp.getRegion().emplaceBlock();
987  OpBuilder builder = OpBuilder::atBlockBegin(classBody);
988 
989  // Every class gets a base path as its first parameter.
990  classBody->addArgument(BasePathType::get(&getContext()),
991  UnknownLoc::get(&getContext()));
992 
993  for (unsigned i = 0, e = moduleLike.getNumPorts(); i < e; ++i) {
994  auto type = moduleLike.getPortType(i);
995  if (!isa<PropertyType>(type))
996  continue;
997 
998  auto loc = moduleLike.getPortLocation(i);
999  auto direction = moduleLike.getPortDirection(i);
1000  if (direction == Direction::In)
1001  classBody->addArgument(type, loc);
1002  else {
1003  auto name = moduleLike.getPortNameAttr(i);
1004  builder.create<om::ClassExternFieldOp>(loc, name, type);
1005  }
1006 
1007  // In case this is a Module, remember to erase this port.
1008  portsToErase.set(i);
1009  }
1010 
1011  // If the module-like is a Class, it will be completely erased later.
1012  // Otherwise, erase just the property ports and ops.
1013  if (!isa<firrtl::ClassLike>(moduleLike.getOperation())) {
1014  // Erase property typed ports.
1015  moduleLike.erasePorts(portsToErase);
1016  }
1017 }
1018 
1019 // Helper to update an Object instantiation. FIRRTL Object instances are
1020 // converted to OM Object instances.
1021 static LogicalResult
1022 updateObjectInClass(firrtl::ObjectOp firrtlObject,
1023  const PathInfoTable &pathInfoTable,
1024  SmallVectorImpl<Operation *> &opsToErase) {
1025  // The 0'th argument is the base path.
1026  auto basePath = firrtlObject->getBlock()->getArgument(0);
1027  // build a table mapping the indices of input ports to their position in the
1028  // om class's parameter list.
1029  auto firrtlClassType = firrtlObject.getType();
1030  auto numElements = firrtlClassType.getNumElements();
1031  llvm::SmallVector<unsigned> argIndexTable;
1032  argIndexTable.resize(numElements);
1033 
1034  // Get any alternative base paths passing through this module.
1035  SmallVector<Value> altBasePaths;
1036  pathInfoTable.collectAltBasePaths(
1037  firrtlObject, firrtlClassType.getNameAttr().getAttr(), altBasePaths);
1038 
1039  // Account for the default base path and any alternatives.
1040  unsigned nextArgIndex = 1 + altBasePaths.size();
1041 
1042  for (unsigned i = 0; i < numElements; ++i) {
1043  auto direction = firrtlClassType.getElement(i).direction;
1044  if (direction == Direction::In)
1045  argIndexTable[i] = nextArgIndex++;
1046  }
1047 
1048  // Collect its input actual parameters by finding any subfield ops that are
1049  // assigned to. Take the source of the assignment as the actual parameter.
1050 
1051  llvm::SmallVector<Value> args;
1052  args.resize(nextArgIndex);
1053  args[0] = basePath;
1054 
1055  // Collect any alternative base paths passing through.
1056  for (auto [i, altBasePath] : llvm::enumerate(altBasePaths))
1057  args[1 + i] = altBasePath; // + 1 to skip default base path
1058 
1059  for (auto *user : llvm::make_early_inc_range(firrtlObject->getUsers())) {
1060  if (auto subfield = dyn_cast<ObjectSubfieldOp>(user)) {
1061  auto index = subfield.getIndex();
1062  auto direction = firrtlClassType.getElement(index).direction;
1063 
1064  // We only lower "writes to input ports" here. Reads from output
1065  // ports will be handled using the conversion framework.
1066  if (direction == Direction::Out)
1067  continue;
1068 
1069  for (auto *subfieldUser :
1070  llvm::make_early_inc_range(subfield->getUsers())) {
1071  if (auto propassign = dyn_cast<PropAssignOp>(subfieldUser)) {
1072  // the operands of the propassign may have already been converted to
1073  // om. Use the generic operand getters to get the operands as
1074  // untyped values.
1075  auto dst = propassign.getOperand(0);
1076  auto src = propassign.getOperand(1);
1077  if (dst == subfield.getResult()) {
1078  args[argIndexTable[index]] = src;
1079  opsToErase.push_back(propassign);
1080  }
1081  }
1082  }
1083 
1084  opsToErase.push_back(subfield);
1085  }
1086  }
1087 
1088  // Check that all input ports have been initialized.
1089  for (unsigned i = 0; i < numElements; ++i) {
1090  auto element = firrtlClassType.getElement(i);
1091  if (element.direction == Direction::Out)
1092  continue;
1093 
1094  auto argIndex = argIndexTable[i];
1095  if (!args[argIndex])
1096  return emitError(firrtlObject.getLoc())
1097  << "uninitialized input port " << element.name;
1098  }
1099 
1100  // Convert the FIRRTL Class type to an OM Class type.
1101  auto className = firrtlObject.getType().getNameAttr();
1102  auto classType = om::ClassType::get(firrtlObject->getContext(), className);
1103 
1104  // Create the new Object op.
1105  OpBuilder builder(firrtlObject);
1106  auto object = builder.create<om::ObjectOp>(
1107  firrtlObject.getLoc(), classType, firrtlObject.getClassNameAttr(), args);
1108 
1109  // Replace uses of the FIRRTL Object with the OM Object. The later dialect
1110  // conversion will take care of converting the types.
1111  firrtlObject.replaceAllUsesWith(object.getResult());
1112 
1113  // Erase the original Object, now that we're done with it.
1114  opsToErase.push_back(firrtlObject);
1115  return success();
1116 }
1117 
1118 // Helper to update a Module instantiation in a Class. Module instances within a
1119 // Class are converted to OM Object instances of the Class derived from the
1120 // Module.
1121 static LogicalResult
1122 updateInstanceInClass(InstanceOp firrtlInstance, hw::HierPathOp hierPath,
1123  InstanceGraph &instanceGraph,
1124  const PathInfoTable &pathInfoTable,
1125  SmallVectorImpl<Operation *> &opsToErase) {
1126 
1127  // Set the insertion point right before the instance op.
1128  OpBuilder builder(firrtlInstance);
1129 
1130  // Collect the FIRRTL instance inputs to form the Object instance actual
1131  // parameters. The order of the SmallVector needs to match the order the
1132  // formal parameters are declared on the corresponding Class.
1133  SmallVector<Value> actualParameters;
1134  // The 0'th argument is the base path.
1135  auto basePath = firrtlInstance->getBlock()->getArgument(0);
1136  auto symRef = FlatSymbolRefAttr::get(hierPath.getSymNameAttr());
1137  auto rebasedPath = builder.create<om::BasePathCreateOp>(
1138  firrtlInstance->getLoc(), basePath, symRef);
1139 
1140  actualParameters.push_back(rebasedPath);
1141 
1142  // Add any alternative base paths passing through this instance.
1143  pathInfoTable.collectAltBasePaths(
1144  firrtlInstance, firrtlInstance.getModuleNameAttr().getAttr(),
1145  actualParameters);
1146 
1147  for (auto result : firrtlInstance.getResults()) {
1148  // If the port is an output, continue.
1149  if (firrtlInstance.getPortDirection(result.getResultNumber()) ==
1150  Direction::Out)
1151  continue;
1152 
1153  // If the port is not a property type, continue.
1154  auto propertyResult = dyn_cast<FIRRTLPropertyValue>(result);
1155  if (!propertyResult)
1156  continue;
1157 
1158  // Get the property assignment to the input, and track the assigned
1159  // Value as an actual parameter to the Object instance.
1160  auto propertyAssignment = getPropertyAssignment(propertyResult);
1161  assert(propertyAssignment && "properties require single assignment");
1162  actualParameters.push_back(propertyAssignment.getSrcMutable().get());
1163 
1164  // Erase the property assignment.
1165  opsToErase.push_back(propertyAssignment);
1166  }
1167 
1168  // Get the referenced module to get its name.
1169  auto referencedModule =
1170  firrtlInstance.getReferencedModule<FModuleLike>(instanceGraph);
1171 
1172  StringRef moduleName = referencedModule.getName();
1173 
1174  // Use the defname for external modules.
1175  if (auto externMod = dyn_cast<FExtModuleOp>(referencedModule.getOperation()))
1176  if (auto defname = externMod.getDefname())
1177  moduleName = defname.value();
1178 
1179  // Convert the FIRRTL Module name to an OM Class type.
1180  auto className = FlatSymbolRefAttr::get(
1181  builder.getStringAttr(moduleName + kClassNameSuffix));
1182 
1183  auto classType = om::ClassType::get(firrtlInstance->getContext(), className);
1184 
1185  // Create the new Object op.
1186  auto object =
1187  builder.create<om::ObjectOp>(firrtlInstance.getLoc(), classType,
1188  className.getAttr(), actualParameters);
1189 
1190  // Replace uses of the FIRRTL instance outputs with field access into
1191  // the OM Object. The later dialect conversion will take care of
1192  // converting the types.
1193  for (auto result : firrtlInstance.getResults()) {
1194  // If the port isn't an output, continue.
1195  if (firrtlInstance.getPortDirection(result.getResultNumber()) !=
1196  Direction::Out)
1197  continue;
1198 
1199  // If the port is not a property type, continue.
1200  if (!isa<PropertyType>(result.getType()))
1201  continue;
1202 
1203  // The path to the field is just this output's name.
1204  auto objectFieldPath = builder.getArrayAttr({FlatSymbolRefAttr::get(
1205  firrtlInstance.getPortName(result.getResultNumber()))});
1206 
1207  // Create the field access.
1208  auto objectField = builder.create<ObjectFieldOp>(
1209  object.getLoc(), result.getType(), object, objectFieldPath);
1210 
1211  result.replaceAllUsesWith(objectField);
1212  }
1213 
1214  // Erase the original instance, now that we're done with it.
1215  opsToErase.push_back(firrtlInstance);
1216  return success();
1217 }
1218 
1219 // Helper to update a Module instantiation in a Module. Module instances within
1220 // a Module are updated to remove the property typed ports.
1221 static LogicalResult
1222 updateInstanceInModule(InstanceOp firrtlInstance, InstanceGraph &instanceGraph,
1223  SmallVectorImpl<Operation *> &opsToErase) {
1224  // Collect property typed ports to erase.
1225  BitVector portsToErase(firrtlInstance.getNumResults());
1226  for (auto result : firrtlInstance.getResults())
1227  if (isa<PropertyType>(result.getType()))
1228  portsToErase.set(result.getResultNumber());
1229 
1230  // If there are none, nothing to do.
1231  if (portsToErase.none())
1232  return success();
1233 
1234  // Create a new instance with the property ports removed.
1235  OpBuilder builder(firrtlInstance);
1236  InstanceOp newInstance = firrtlInstance.erasePorts(builder, portsToErase);
1237 
1238  // Replace the instance in the instance graph. This is called from multiple
1239  // threads, but because the instance graph data structure is not mutated, and
1240  // only one thread ever sets the instance pointer for a given instance, this
1241  // should be safe.
1242  instanceGraph.replaceInstance(firrtlInstance, newInstance);
1243 
1244  // Erase the original instance, which is now replaced.
1245  opsToErase.push_back(firrtlInstance);
1246  return success();
1247 }
1248 
1249 static LogicalResult
1250 updateInstancesInModule(FModuleOp moduleOp, InstanceGraph &instanceGraph,
1251  SmallVectorImpl<Operation *> &opsToErase) {
1252  OpBuilder builder(moduleOp);
1253  for (auto &op : moduleOp->getRegion(0).getOps()) {
1254  if (auto objectOp = dyn_cast<firrtl::ObjectOp>(op)) {
1255  assert(0 && "should be no objects in modules");
1256  } else if (auto instanceOp = dyn_cast<InstanceOp>(op)) {
1257  if (failed(updateInstanceInModule(instanceOp, instanceGraph, opsToErase)))
1258  return failure();
1259  }
1260  }
1261  return success();
1262 }
1263 
1265  om::ClassOp classOp, InstanceGraph &instanceGraph,
1266  const LoweringState &state, const PathInfoTable &pathInfoTable,
1267  SmallVectorImpl<Operation *> &opsToErase) {
1268  OpBuilder builder(classOp);
1269  auto &classState = state.classLoweringStateTable.at(classOp);
1270  auto it = classState.paths.begin();
1271  for (auto &op : classOp->getRegion(0).getOps()) {
1272  if (auto objectOp = dyn_cast<firrtl::ObjectOp>(op)) {
1273  if (failed(updateObjectInClass(objectOp, pathInfoTable, opsToErase)))
1274  return failure();
1275  } else if (auto instanceOp = dyn_cast<InstanceOp>(op)) {
1276  if (failed(updateInstanceInClass(instanceOp, *it++, instanceGraph,
1277  pathInfoTable, opsToErase)))
1278  return failure();
1279  }
1280  }
1281  return success();
1282 }
1283 
1284 // Update Object or Module instantiations in a FIRRTL Module or OM Class.
1285 LogicalResult
1286 LowerClassesPass::updateInstances(Operation *op, InstanceGraph &instanceGraph,
1287  const LoweringState &state,
1288  const PathInfoTable &pathInfoTable) {
1289 
1290  // Track ops to erase at the end. We can't do this eagerly, since we want to
1291  // loop over each op in the container's body, and we may end up removing some
1292  // ops later in the body when we visit instances earlier in the body.
1293  SmallVector<Operation *> opsToErase;
1294  auto result =
1295  TypeSwitch<Operation *, LogicalResult>(op)
1296 
1297  .Case([&](FModuleOp moduleOp) {
1298  // Convert FIRRTL Module instance within a Module to
1299  // remove property ports if necessary.
1300  return updateInstancesInModule(moduleOp, instanceGraph, opsToErase);
1301  })
1302  .Case([&](om::ClassOp classOp) {
1303  // Convert FIRRTL Module instance within a Class to OM
1304  // Object instance.
1306  classOp, instanceGraph, state, pathInfoTable, opsToErase);
1307  })
1308  .Default([](auto *op) { return success(); });
1309  if (failed(result))
1310  return result;
1311  // Erase the ops marked to be erased.
1312  for (auto *op : opsToErase)
1313  op->erase();
1314 
1315  return success();
1316 }
1317 
1318 // Pattern rewriters for dialect conversion.
1319 
1321  : public OpConversionPattern<FIntegerConstantOp> {
1322  using OpConversionPattern::OpConversionPattern;
1323 
1324  LogicalResult
1325  matchAndRewrite(FIntegerConstantOp op, OpAdaptor adaptor,
1326  ConversionPatternRewriter &rewriter) const override {
1327  rewriter.replaceOpWithNewOp<om::ConstantOp>(
1328  op, om::OMIntegerType::get(op.getContext()),
1329  om::IntegerAttr::get(op.getContext(), adaptor.getValueAttr()));
1330  return success();
1331  }
1332 };
1333 
1334 struct BoolConstantOpConversion : public OpConversionPattern<BoolConstantOp> {
1335  using OpConversionPattern::OpConversionPattern;
1336 
1337  LogicalResult
1338  matchAndRewrite(BoolConstantOp op, OpAdaptor adaptor,
1339  ConversionPatternRewriter &rewriter) const override {
1340  rewriter.replaceOpWithNewOp<om::ConstantOp>(
1341  op, rewriter.getBoolAttr(adaptor.getValue()));
1342  return success();
1343  }
1344 };
1345 
1347  : public OpConversionPattern<DoubleConstantOp> {
1348  using OpConversionPattern::OpConversionPattern;
1349 
1350  LogicalResult
1351  matchAndRewrite(DoubleConstantOp op, OpAdaptor adaptor,
1352  ConversionPatternRewriter &rewriter) const override {
1353  rewriter.replaceOpWithNewOp<om::ConstantOp>(op, adaptor.getValue());
1354  return success();
1355  }
1356 };
1357 
1359  : public OpConversionPattern<StringConstantOp> {
1360  using OpConversionPattern::OpConversionPattern;
1361 
1362  LogicalResult
1363  matchAndRewrite(StringConstantOp op, OpAdaptor adaptor,
1364  ConversionPatternRewriter &rewriter) const override {
1365  auto stringType = om::StringType::get(op.getContext());
1366  rewriter.replaceOpWithNewOp<om::ConstantOp>(
1367  op, stringType, StringAttr::get(op.getValue(), stringType));
1368  return success();
1369  }
1370 };
1371 
1373  : public OpConversionPattern<firrtl::ListCreateOp> {
1374  using OpConversionPattern::OpConversionPattern;
1375 
1376  LogicalResult
1377  matchAndRewrite(firrtl::ListCreateOp op, OpAdaptor adaptor,
1378  ConversionPatternRewriter &rewriter) const override {
1379  auto listType = getTypeConverter()->convertType<om::ListType>(op.getType());
1380  if (!listType)
1381  return failure();
1382  rewriter.replaceOpWithNewOp<om::ListCreateOp>(op, listType,
1383  adaptor.getElements());
1384  return success();
1385  }
1386 };
1387 
1389  : public OpConversionPattern<firrtl::IntegerAddOp> {
1390  using OpConversionPattern::OpConversionPattern;
1391 
1392  LogicalResult
1393  matchAndRewrite(firrtl::IntegerAddOp op, OpAdaptor adaptor,
1394  ConversionPatternRewriter &rewriter) const override {
1395  rewriter.replaceOpWithNewOp<om::IntegerAddOp>(op, adaptor.getLhs(),
1396  adaptor.getRhs());
1397  return success();
1398  }
1399 };
1400 
1402  : public OpConversionPattern<firrtl::IntegerMulOp> {
1403  using OpConversionPattern::OpConversionPattern;
1404 
1405  LogicalResult
1406  matchAndRewrite(firrtl::IntegerMulOp op, OpAdaptor adaptor,
1407  ConversionPatternRewriter &rewriter) const override {
1408  rewriter.replaceOpWithNewOp<om::IntegerMulOp>(op, adaptor.getLhs(),
1409  adaptor.getRhs());
1410  return success();
1411  }
1412 };
1413 
1415  : public OpConversionPattern<firrtl::IntegerShrOp> {
1416  using OpConversionPattern::OpConversionPattern;
1417 
1418  LogicalResult
1419  matchAndRewrite(firrtl::IntegerShrOp op, OpAdaptor adaptor,
1420  ConversionPatternRewriter &rewriter) const override {
1421  rewriter.replaceOpWithNewOp<om::IntegerShrOp>(op, adaptor.getLhs(),
1422  adaptor.getRhs());
1423  return success();
1424  }
1425 };
1426 
1427 struct PathOpConversion : public OpConversionPattern<firrtl::PathOp> {
1428 
1429  PathOpConversion(TypeConverter &typeConverter, MLIRContext *context,
1430  const PathInfoTable &pathInfoTable,
1431  PatternBenefit benefit = 1)
1432  : OpConversionPattern(typeConverter, context, benefit),
1433  pathInfoTable(pathInfoTable) {}
1434 
1435  LogicalResult
1436  matchAndRewrite(firrtl::PathOp op, OpAdaptor adaptor,
1437  ConversionPatternRewriter &rewriter) const override {
1438  auto *context = op->getContext();
1439  auto pathType = om::PathType::get(context);
1440  auto pathInfo = pathInfoTable.table.lookup(op.getTarget());
1441 
1442  // The 0'th argument is the base path by default.
1443  auto basePath = op->getBlock()->getArgument(0);
1444 
1445  // If the target was optimized away, then replace the path operation with
1446  // a deleted path.
1447  if (!pathInfo) {
1448  if (op.getTargetKind() == firrtl::TargetKind::DontTouch)
1449  return emitError(op.getLoc(), "DontTouch target was deleted");
1450  if (op.getTargetKind() == firrtl::TargetKind::Instance)
1451  return emitError(op.getLoc(), "Instance target was deleted");
1452  rewriter.replaceOpWithNewOp<om::EmptyPathOp>(op);
1453  return success();
1454  }
1455 
1456  auto symbol = pathInfo.symRef;
1457 
1458  // Convert the target kind to an OMIR target. Member references are updated
1459  // to reflect the current kind of reference.
1460  om::TargetKind targetKind;
1461  switch (op.getTargetKind()) {
1462  case firrtl::TargetKind::DontTouch:
1463  targetKind = om::TargetKind::DontTouch;
1464  break;
1465  case firrtl::TargetKind::Reference:
1466  targetKind = om::TargetKind::Reference;
1467  break;
1468  case firrtl::TargetKind::Instance:
1469  if (!isa<InstanceOp, FModuleLike>(pathInfo.op))
1470  return emitError(op.getLoc(), "invalid target for instance path")
1471  .attachNote(pathInfo.op->getLoc())
1472  << "target not instance or module";
1473  targetKind = om::TargetKind::Instance;
1474  break;
1475  case firrtl::TargetKind::MemberInstance:
1476  case firrtl::TargetKind::MemberReference:
1477  if (isa<InstanceOp, FModuleLike>(pathInfo.op))
1478  targetKind = om::TargetKind::MemberInstance;
1479  else
1480  targetKind = om::TargetKind::MemberReference;
1481  break;
1482  }
1483 
1484  // If we are using an alternative base path for this path, get it from the
1485  // passthrough port on the enclosing class.
1486  if (auto altBasePathModule = pathInfo.altBasePathModule) {
1487  // Get the original name of the parent. At this point both FIRRTL classes
1488  // and modules have been converted to OM classes, but we need to look up
1489  // based on the parent's original name.
1490  auto parent = op->getParentOfType<om::ClassOp>();
1491  auto parentName = parent.getName();
1492  if (parentName.ends_with(kClassNameSuffix))
1493  parentName = parentName.drop_back(kClassNameSuffix.size());
1494  auto originalParentName = StringAttr::get(op->getContext(), parentName);
1495 
1496  // Get the base paths passing through the parent.
1497  auto altBasePaths =
1498  pathInfoTable.getRootsForPassthrough(originalParentName);
1499  assert(!altBasePaths.empty() && "expected passthrough base paths");
1500 
1501  // Find the base path passthrough that was associated with this path.
1502  for (auto [i, altBasePath] : llvm::enumerate(altBasePaths)) {
1503  if (altBasePathModule == altBasePath) {
1504  // + 1 to skip default base path
1505  auto basePathArg = op->getBlock()->getArgument(1 + i);
1506  assert(isa<om::BasePathType>(basePathArg.getType()) &&
1507  "expected a passthrough base path");
1508  basePath = basePathArg;
1509  }
1510  }
1511  }
1512 
1513  rewriter.replaceOpWithNewOp<om::PathCreateOp>(
1514  op, pathType, om::TargetKindAttr::get(op.getContext(), targetKind),
1515  basePath, symbol);
1516  return success();
1517  }
1518 
1519  const PathInfoTable &pathInfoTable;
1520 };
1521 
1522 struct WireOpConversion : public OpConversionPattern<WireOp> {
1523  using OpConversionPattern::OpConversionPattern;
1524 
1525  LogicalResult
1526  matchAndRewrite(WireOp wireOp, OpAdaptor adaptor,
1527  ConversionPatternRewriter &rewriter) const override {
1528  auto wireValue = dyn_cast<FIRRTLPropertyValue>(wireOp.getResult());
1529 
1530  // If the wire isn't a Property, not much we can do here.
1531  if (!wireValue)
1532  return failure();
1533 
1534  // If the wire isn't inside a graph region, we can't trivially remove it. In
1535  // practice, this pattern does run for wires in graph regions, so this check
1536  // should pass and we can proceed with the trivial rewrite.
1537  auto regionKindInterface = wireOp->getParentOfType<RegionKindInterface>();
1538  if (!regionKindInterface)
1539  return failure();
1540  if (regionKindInterface.getRegionKind(0) != RegionKind::Graph)
1541  return failure();
1542 
1543  // Find the assignment to the wire.
1544  PropAssignOp propAssign = getPropertyAssignment(wireValue);
1545 
1546  // Use the source of the assignment instead of the wire.
1547  rewriter.replaceOp(wireOp, propAssign.getSrc());
1548 
1549  // Erase the source of the assignment.
1550  rewriter.eraseOp(propAssign);
1551 
1552  return success();
1553  }
1554 };
1555 
1556 struct AnyCastOpConversion : public OpConversionPattern<ObjectAnyRefCastOp> {
1557  using OpConversionPattern::OpConversionPattern;
1558 
1559  LogicalResult
1560  matchAndRewrite(ObjectAnyRefCastOp op, OpAdaptor adaptor,
1561  ConversionPatternRewriter &rewriter) const override {
1562  rewriter.replaceOpWithNewOp<AnyCastOp>(op, adaptor.getInput());
1563  return success();
1564  }
1565 };
1566 
1568  : public OpConversionPattern<firrtl::ObjectSubfieldOp> {
1569  using OpConversionPattern::OpConversionPattern;
1570 
1572  const TypeConverter &typeConverter, MLIRContext *context,
1573  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable)
1574  : OpConversionPattern(typeConverter, context),
1575  classTypeTable(classTypeTable) {}
1576 
1577  LogicalResult
1578  matchAndRewrite(firrtl::ObjectSubfieldOp op, OpAdaptor adaptor,
1579  ConversionPatternRewriter &rewriter) const override {
1580  auto omClassType = dyn_cast<om::ClassType>(adaptor.getInput().getType());
1581  if (!omClassType)
1582  return failure();
1583 
1584  // Convert the field-index used by the firrtl implementation, to a symbol,
1585  // as used by the om implementation.
1586  auto firrtlClassType =
1587  classTypeTable.lookup(omClassType.getClassName().getAttr());
1588  if (!firrtlClassType)
1589  return failure();
1590 
1591  const auto &element = firrtlClassType.getElement(op.getIndex());
1592  // We cannot convert input ports to fields.
1593  if (element.direction == Direction::In)
1594  return failure();
1595 
1596  auto field = FlatSymbolRefAttr::get(element.name);
1597  auto path = rewriter.getArrayAttr({field});
1598  auto type = typeConverter->convertType(element.type);
1599  rewriter.replaceOpWithNewOp<om::ObjectFieldOp>(op, type, adaptor.getInput(),
1600  path);
1601  return success();
1602  }
1603 
1604  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable;
1605 };
1606 
1607 struct ClassFieldOpConversion : public OpConversionPattern<ClassFieldOp> {
1608  using OpConversionPattern::OpConversionPattern;
1609 
1610  LogicalResult
1611  matchAndRewrite(ClassFieldOp op, OpAdaptor adaptor,
1612  ConversionPatternRewriter &rewriter) const override {
1613  rewriter.replaceOpWithNewOp<ClassFieldOp>(op, adaptor.getNameAttr(),
1614  adaptor.getValue());
1615  return success();
1616  }
1617 };
1618 
1620  : public OpConversionPattern<ClassExternFieldOp> {
1621  using OpConversionPattern::OpConversionPattern;
1622 
1623  LogicalResult
1624  matchAndRewrite(ClassExternFieldOp op, OpAdaptor adaptor,
1625  ConversionPatternRewriter &rewriter) const override {
1626  auto type = typeConverter->convertType(adaptor.getType());
1627  if (!type)
1628  return failure();
1629  rewriter.replaceOpWithNewOp<ClassExternFieldOp>(op, adaptor.getNameAttr(),
1630  type);
1631  return success();
1632  }
1633 };
1634 
1635 struct ObjectOpConversion : public OpConversionPattern<om::ObjectOp> {
1636  using OpConversionPattern::OpConversionPattern;
1637 
1638  LogicalResult
1639  matchAndRewrite(om::ObjectOp objectOp, OpAdaptor adaptor,
1640  ConversionPatternRewriter &rewriter) const override {
1641  // Replace the object with a new object using the converted actual parameter
1642  // types from the adaptor.
1643  rewriter.replaceOpWithNewOp<om::ObjectOp>(objectOp, objectOp.getType(),
1644  adaptor.getClassNameAttr(),
1645  adaptor.getActualParams());
1646  return success();
1647  }
1648 };
1649 
1650 struct ClassOpSignatureConversion : public OpConversionPattern<om::ClassOp> {
1651  using OpConversionPattern::OpConversionPattern;
1652 
1653  LogicalResult
1654  matchAndRewrite(om::ClassOp classOp, OpAdaptor adaptor,
1655  ConversionPatternRewriter &rewriter) const override {
1656  Block *body = classOp.getBodyBlock();
1657  TypeConverter::SignatureConversion result(body->getNumArguments());
1658 
1659  // Convert block argument types.
1660  if (failed(typeConverter->convertSignatureArgs(body->getArgumentTypes(),
1661  result)))
1662  return failure();
1663 
1664  // Convert the body.
1665  if (failed(rewriter.convertRegionTypes(body->getParent(), *typeConverter,
1666  &result)))
1667  return failure();
1668 
1669  rewriter.modifyOpInPlace(classOp, []() {});
1670 
1671  return success();
1672  }
1673 };
1674 
1676  : public OpConversionPattern<om::ClassExternOp> {
1677  using OpConversionPattern::OpConversionPattern;
1678 
1679  LogicalResult
1680  matchAndRewrite(om::ClassExternOp classOp, OpAdaptor adaptor,
1681  ConversionPatternRewriter &rewriter) const override {
1682  Block *body = classOp.getBodyBlock();
1683  TypeConverter::SignatureConversion result(body->getNumArguments());
1684 
1685  // Convert block argument types.
1686  if (failed(typeConverter->convertSignatureArgs(body->getArgumentTypes(),
1687  result)))
1688  return failure();
1689 
1690  // Convert the body.
1691  if (failed(rewriter.convertRegionTypes(body->getParent(), *typeConverter,
1692  &result)))
1693  return failure();
1694 
1695  rewriter.modifyOpInPlace(classOp, []() {});
1696 
1697  return success();
1698  }
1699 };
1700 
1701 struct ObjectFieldOpConversion : public OpConversionPattern<ObjectFieldOp> {
1702  using OpConversionPattern::OpConversionPattern;
1703 
1704  LogicalResult
1705  matchAndRewrite(ObjectFieldOp op, OpAdaptor adaptor,
1706  ConversionPatternRewriter &rewriter) const override {
1707  // Replace the object field with a new object field of the appropriate
1708  // result type based on the type converter.
1709  auto type = typeConverter->convertType(op.getType());
1710  if (!type)
1711  return failure();
1712 
1713  rewriter.replaceOpWithNewOp<ObjectFieldOp>(op, type, adaptor.getObject(),
1714  adaptor.getFieldPathAttr());
1715 
1716  return success();
1717  }
1718 };
1719 
1720 // Helpers for dialect conversion setup.
1721 
1722 static void populateConversionTarget(ConversionTarget &target) {
1723  // FIRRTL dialect operations inside ClassOps or not using only OM types must
1724  // be legalized.
1725  target.addDynamicallyLegalDialect<FIRRTLDialect>(
1726  [](Operation *op) { return !op->getParentOfType<om::ClassLike>(); });
1727 
1728  // OM dialect operations are legal if they don't use FIRRTL types.
1729  target.addDynamicallyLegalDialect<OMDialect>([](Operation *op) {
1730  auto containsFIRRTLType = [](Type type) {
1731  return type
1732  .walk([](Type type) {
1733  return failure(isa<FIRRTLDialect>(type.getDialect()));
1734  })
1735  .wasInterrupted();
1736  };
1737  auto noFIRRTLOperands =
1738  llvm::none_of(op->getOperandTypes(), [&containsFIRRTLType](Type type) {
1739  return containsFIRRTLType(type);
1740  });
1741  auto noFIRRTLResults =
1742  llvm::none_of(op->getResultTypes(), [&containsFIRRTLType](Type type) {
1743  return containsFIRRTLType(type);
1744  });
1745  return noFIRRTLOperands && noFIRRTLResults;
1746  });
1747 
1748  // the OM op class.extern.field doesn't have operands or results, so we must
1749  // check it's type for a firrtl dialect.
1750  target.addDynamicallyLegalOp<ClassExternFieldOp>(
1751  [](ClassExternFieldOp op) { return !isa<FIRRTLType>(op.getType()); });
1752 
1753  // OM Class ops are legal if they don't use FIRRTL types for block arguments.
1754  target.addDynamicallyLegalOp<om::ClassOp, om::ClassExternOp>(
1755  [](Operation *op) -> std::optional<bool> {
1756  auto classLike = dyn_cast<om::ClassLike>(op);
1757  if (!classLike)
1758  return std::nullopt;
1759 
1760  return llvm::none_of(
1761  classLike.getBodyBlock()->getArgumentTypes(),
1762  [](Type type) { return isa<FIRRTLDialect>(type.getDialect()); });
1763  });
1764 }
1765 
1766 static void populateTypeConverter(TypeConverter &converter) {
1767  // Convert FIntegerType to IntegerType.
1768  converter.addConversion(
1769  [](IntegerType type) { return OMIntegerType::get(type.getContext()); });
1770  converter.addConversion([](FIntegerType type) {
1771  // The actual width of the IntegerType doesn't actually get used; it will be
1772  // folded away by the dialect conversion infrastructure to the type of the
1773  // APSIntAttr used in the FIntegerConstantOp.
1774  return OMIntegerType::get(type.getContext());
1775  });
1776 
1777  // Convert FIRRTL StringType to OM StringType.
1778  converter.addConversion([](om::StringType type) { return type; });
1779  converter.addConversion([](firrtl::StringType type) {
1780  return om::StringType::get(type.getContext());
1781  });
1782 
1783  // Convert FIRRTL PathType to OM PathType.
1784  converter.addConversion([](om::PathType type) { return type; });
1785  converter.addConversion([](om::BasePathType type) { return type; });
1786  converter.addConversion([](om::FrozenPathType type) { return type; });
1787  converter.addConversion([](om::FrozenBasePathType type) { return type; });
1788  converter.addConversion([](firrtl::PathType type) {
1789  return om::PathType::get(type.getContext());
1790  });
1791 
1792  // Convert FIRRTL Class type to OM Class type.
1793  converter.addConversion([](om::ClassType type) { return type; });
1794  converter.addConversion([](firrtl::ClassType type) {
1795  return om::ClassType::get(type.getContext(), type.getNameAttr());
1796  });
1797 
1798  // Convert FIRRTL AnyRef type to OM Any type.
1799  converter.addConversion([](om::AnyType type) { return type; });
1800  converter.addConversion([](firrtl::AnyRefType type) {
1801  return om::AnyType::get(type.getContext());
1802  });
1803 
1804  // Convert FIRRTL List type to OM List type.
1805  auto convertListType = [&converter](auto type) -> std::optional<mlir::Type> {
1806  auto elementType = converter.convertType(type.getElementType());
1807  if (!elementType)
1808  return {};
1810  };
1811 
1812  converter.addConversion(
1813  [convertListType](om::ListType type) -> std::optional<mlir::Type> {
1814  // Convert any om.list<firrtl> -> om.list<om>
1815  return convertListType(type);
1816  });
1817 
1818  converter.addConversion(
1819  [convertListType](firrtl::ListType type) -> std::optional<mlir::Type> {
1820  // Convert any firrtl.list<firrtl> -> om.list<om>
1821  return convertListType(type);
1822  });
1823 
1824  // Convert FIRRTL Bool type to OM
1825  converter.addConversion(
1826  [](BoolType type) { return IntegerType::get(type.getContext(), 1); });
1827 
1828  // Convert FIRRTL double type to OM.
1829  converter.addConversion(
1830  [](DoubleType type) { return FloatType::getF64(type.getContext()); });
1831 
1832  // Add a target materialization to fold away unrealized conversion casts.
1833  converter.addTargetMaterialization(
1834  [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
1835  assert(values.size() == 1);
1836  return values[0];
1837  });
1838 
1839  // Add a source materialization to fold away unrealized conversion casts.
1840  converter.addSourceMaterialization(
1841  [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
1842  assert(values.size() == 1);
1843  return values[0];
1844  });
1845 }
1846 
1848  RewritePatternSet &patterns, TypeConverter &converter,
1849  const PathInfoTable &pathInfoTable,
1850  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
1851  patterns.add<FIntegerConstantOpConversion>(converter, patterns.getContext());
1852  patterns.add<StringConstantOpConversion>(converter, patterns.getContext());
1853  patterns.add<PathOpConversion>(converter, patterns.getContext(),
1854  pathInfoTable);
1855  patterns.add<WireOpConversion>(converter, patterns.getContext());
1856  patterns.add<AnyCastOpConversion>(converter, patterns.getContext());
1857  patterns.add<ObjectSubfieldOpConversion>(converter, patterns.getContext(),
1858  classTypeTable);
1859  patterns.add<ClassFieldOpConversion>(converter, patterns.getContext());
1860  patterns.add<ClassExternFieldOpConversion>(converter, patterns.getContext());
1861  patterns.add<ClassOpSignatureConversion>(converter, patterns.getContext());
1863  patterns.getContext());
1864  patterns.add<ObjectOpConversion>(converter, patterns.getContext());
1865  patterns.add<ObjectFieldOpConversion>(converter, patterns.getContext());
1866  patterns.add<ListCreateOpConversion>(converter, patterns.getContext());
1867  patterns.add<BoolConstantOpConversion>(converter, patterns.getContext());
1868  patterns.add<DoubleConstantOpConversion>(converter, patterns.getContext());
1869  patterns.add<IntegerAddOpConversion>(converter, patterns.getContext());
1870  patterns.add<IntegerMulOpConversion>(converter, patterns.getContext());
1871  patterns.add<IntegerShrOpConversion>(converter, patterns.getContext());
1872 }
1873 
1874 // Convert to OM ops and types in Classes or Modules.
1875 LogicalResult LowerClassesPass::dialectConversion(
1876  Operation *op, const PathInfoTable &pathInfoTable,
1877  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
1878  ConversionTarget target(getContext());
1879  populateConversionTarget(target);
1880 
1881  TypeConverter typeConverter;
1882  populateTypeConverter(typeConverter);
1883 
1884  RewritePatternSet patterns(&getContext());
1885  populateRewritePatterns(patterns, typeConverter, pathInfoTable,
1886  classTypeTable);
1887 
1888  return applyPartialConversion(op, target, std::move(patterns));
1889 }
assert(baseType &&"element must be base type")
MlirType uint64_t numElements
Definition: CHIRRTL.cpp:30
MlirType elementType
Definition: CHIRRTL.cpp:29
static LogicalResult updateInstancesInModule(FModuleOp moduleOp, InstanceGraph &instanceGraph, SmallVectorImpl< Operation * > &opsToErase)
static void populateConversionTarget(ConversionTarget &target)
static LogicalResult updateInstanceInClass(InstanceOp firrtlInstance, hw::HierPathOp hierPath, InstanceGraph &instanceGraph, const PathInfoTable &pathInfoTable, SmallVectorImpl< Operation * > &opsToErase)
static void populateTypeConverter(TypeConverter &converter)
static LogicalResult updateInstanceInModule(InstanceOp firrtlInstance, InstanceGraph &instanceGraph, SmallVectorImpl< Operation * > &opsToErase)
static LogicalResult updateObjectsAndInstancesInClass(om::ClassOp classOp, InstanceGraph &instanceGraph, const LoweringState &state, const PathInfoTable &pathInfoTable, SmallVectorImpl< Operation * > &opsToErase)
static LogicalResult updateObjectInClass(firrtl::ObjectOp firrtlObject, const PathInfoTable &pathInfoTable, SmallVectorImpl< Operation * > &opsToErase)
static void populateRewritePatterns(RewritePatternSet &patterns, TypeConverter &converter, const PathInfoTable &pathInfoTable, const DenseMap< StringAttr, firrtl::ClassType > &classTypeTable)
static Block * getBodyBlock(FModuleLike mod)
std::shared_ptr< calyx::CalyxLoweringState > loweringState
This class provides a read-only projection of an annotation.
unsigned getFieldID() const
Get the field id this attribute targets.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
This graph tracks modules and where they are instantiated.
This is a Node in the InstanceGraph.
bool noUses()
Return true if there are no more instances of this module.
auto getModule()
Get the module that this node is tracking.
UseIterator usesBegin()
Iterate the instance records which instantiate this module.
bool hasOneUse()
Return true if this module has exactly one use.
llvm::iterator_range< UseIterator > uses()
virtual void replaceInstance(InstanceOpInterface inst, InstanceOpInterface newInst)
Replaces an instance of a module with another instance.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
This is an edge in the InstanceGraph.
Definition: InstanceGraph.h:55
auto getInstance()
Get the instance-like op that this is tracking.
Definition: InstanceGraph.h:59
InstanceGraphNode * getParent() const
Get the module where the instantiation lives.
Definition: InstanceGraph.h:66
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
hw::InnerRefAttr getInnerRefTo(const hw::InnerSymTarget &target, GetNamespaceCallback getNamespace)
Obtain an inner reference to the target (operation or port), adding an inner symbol as necessary.
PropAssignOp getPropertyAssignment(FIRRTLPropertyValue value)
Return the single assignment to a Property value.
std::unique_ptr< mlir::Pass > createLowerClassesPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
LogicalResult matchAndRewrite(ObjectAnyRefCastOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(BoolConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ClassExternFieldOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(om::ClassExternOp classOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ClassFieldOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(om::ClassOp classOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(DoubleConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(FIntegerConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::IntegerAddOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::IntegerMulOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::IntegerShrOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::ListCreateOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ObjectFieldOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(om::ObjectOp objectOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ObjectSubfieldOpConversion(const TypeConverter &typeConverter, MLIRContext *context, const DenseMap< StringAttr, firrtl::ClassType > &classTypeTable)
const DenseMap< StringAttr, firrtl::ClassType > & classTypeTable
LogicalResult matchAndRewrite(firrtl::ObjectSubfieldOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
PathOpConversion(TypeConverter &typeConverter, MLIRContext *context, const PathInfoTable &pathInfoTable, PatternBenefit benefit=1)
const PathInfoTable & pathInfoTable
LogicalResult matchAndRewrite(firrtl::PathOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(StringConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(WireOp wireOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
An annotation target is used to keep track of something that is targeted by an Annotation.
Operation * getOp() const
FModuleLike getModule() const
Get the parent module of the target.
AnnotationSet getAnnotations() const
Get the annotations associated with the target.
A cache of existing HierPathOps, mostly used to facilitate HierPathOp reuse.
hw::HierPathOp getOpFor(ArrayAttr attr)
FlatSymbolRefAttr getRefFor(ArrayAttr attr)
This represents an annotation targeting a specific operation.
Attribute getNLAReference(hw::InnerSymbolNamespace &moduleNamespace) const
This implements an analysis to determine which module owns a given path operation.
This represents an annotation targeting a specific port of a module, memory, or instance.
This holds the name and type that describes the module's ports.