CIRCT  19.0.0git
EmitOMIR.cpp
Go to the documentation of this file.
1 //===- EmitOMIR.cpp ---------------------------------------------*- 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 defines the EmitOMIR pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "PassDetails.h"
26 #include "circt/Dialect/SV/SVOps.h"
27 #include "mlir/IR/ImplicitLocOpBuilder.h"
28 #include "llvm/ADT/TypeSwitch.h"
29 #include "llvm/Support/Debug.h"
30 #include "llvm/Support/JSON.h"
31 #include <functional>
32 
33 #define DEBUG_TYPE "omir"
34 
35 using namespace circt;
36 using namespace firrtl;
37 using mlir::LocationAttr;
38 using mlir::UnitAttr;
39 
40 //===----------------------------------------------------------------------===//
41 // Utilities
42 //===----------------------------------------------------------------------===//
43 
44 namespace {
45 /// Information concerning a tracker in the IR.
46 struct Tracker {
47  /// The unique ID of this tracker.
48  IntegerAttr id;
49  /// The operation onto which this tracker was annotated.
50  Operation *op;
51  /// If this tracker is non-local, this is the corresponding anchor.
52  hw::HierPathOp nla;
53  /// If this is a port, then set the portIdx, else initialized to -1.
54  int portNo = -1;
55  /// If this is a field, the ID will be greater than 0, else it will be 0.
56  unsigned fieldID;
57 
58  // Returns true if the tracker has a non-zero field ID.
59  bool hasFieldID() { return fieldID > 0; }
60 };
61 
62 class EmitOMIRPass : public EmitOMIRBase<EmitOMIRPass> {
63 public:
64  using EmitOMIRBase::outputFilename;
65 
66 private:
67  void runOnOperation() override;
68  void makeTrackerAbsolute(Tracker &tracker);
69 
70  void emitSourceInfo(Location input, SmallString<64> &into);
71  void emitOMNode(Attribute node, llvm::json::OStream &jsonStream);
72  void emitOMField(StringAttr fieldName, DictionaryAttr field,
73  llvm::json::OStream &jsonStream);
74  void emitOptionalRTLPorts(DictionaryAttr node,
75  llvm::json::OStream &jsonStream);
76  void emitValue(Attribute node, llvm::json::OStream &jsonStream,
77  bool dutInstance);
78  void emitTrackedTarget(DictionaryAttr node, llvm::json::OStream &jsonStream,
79  bool dutInstance);
80 
81  SmallString<8> addSymbolImpl(Attribute symbol) {
82  unsigned id;
83  auto it = symbolIndices.find(symbol);
84  if (it != symbolIndices.end()) {
85  id = it->second;
86  } else {
87  id = symbols.size();
88  symbols.push_back(symbol);
89  symbolIndices.insert({symbol, id});
90  }
91  SmallString<8> str;
92  ("{{" + Twine(id) + "}}").toVector(str);
93  return str;
94  }
95  SmallString<8> addSymbol(hw::InnerRefAttr symbol) {
96  return addSymbolImpl(symbol);
97  }
98  SmallString<8> addSymbol(FlatSymbolRefAttr symbol) {
99  return addSymbolImpl(symbol);
100  }
101  SmallString<8> addSymbol(StringAttr symbolName) {
102  return addSymbol(FlatSymbolRefAttr::get(symbolName));
103  }
104  SmallString<8> addSymbol(Operation *op) {
105  return addSymbol(SymbolTable::getSymbolName(op));
106  }
107 
108  /// Obtain an inner reference to an operation, possibly adding an `inner_sym`
109  /// to that operation.
110  hw::InnerRefAttr getInnerRefTo(Operation *op);
111  /// Obtain an inner reference to a module port, possibly adding an `inner_sym`
112  /// to that port.
113  hw::InnerRefAttr getInnerRefTo(FModuleLike module, size_t portIdx);
114 
115  // Obtain the result type of an Operation.
116  FIRRTLType getTypeOf(Operation *op);
117  // Obtain the type of a module port.
118  FIRRTLType getTypeOf(FModuleLike mod, size_t portIdx);
119 
120  // Constructs a reference to a field from a FIRRTLType with a fieldID.
121  void addFieldID(FIRRTLType type, unsigned fieldID,
122  SmallVectorImpl<char> &result);
123 
124  /// Get the cached namespace for a module.
125  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
126  return moduleNamespaces.try_emplace(module, module).first->second;
127  }
128 
129  /// Whether any errors have occurred in the current `runOnOperation`.
130  bool anyFailures;
131  CircuitNamespace *circuitNamespace;
132  InstanceGraph *instanceGraph;
133  InstancePathCache *instancePaths;
134  /// OMIR target trackers gathered in the current operation, by tracker ID.
135  DenseMap<Attribute, Tracker> trackers;
136  /// The list of symbols to be interpolated in the verbatim JSON. This gets
137  /// populated as the JSON is constructed and module and instance names are
138  /// collected.
139  SmallVector<Attribute> symbols;
141  /// Temporary `firrtl.hierpath` operations to be deleted at the end of the
142  /// pass. Vector elements are unique.
143  SmallVector<hw::HierPathOp> removeTempNLAs;
144  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
145  /// Lookup table of instances by name and parent module.
146  DenseMap<hw::InnerRefAttr, InstanceOp> instancesByName;
147  /// Record to remove any temporary symbols added to instances.
148  DenseSet<Operation *> tempSymInstances;
149  /// The Design Under Test module.
150  StringAttr dutModuleName;
151 
152  /// Cached NLA table analysis.
153  NLATable *nlaTable;
154 };
155 } // namespace
156 
157 /// Check if an `OMNode` is an `OMSRAM` and requires special treatment of its
158 /// instance path field. This returns the ID of the tracker stored in the
159 /// `instancePath` or `finalPath` field if the node has an array field `omType`
160 /// that contains a `OMString:OMSRAM` entry.
161 static IntegerAttr isOMSRAM(Attribute &node) {
162  auto dict = dyn_cast<DictionaryAttr>(node);
163  if (!dict)
164  return {};
165  auto idAttr = dict.getAs<StringAttr>("id");
166  if (!idAttr)
167  return {};
168  IntegerAttr id;
169  if (auto infoAttr = dict.getAs<DictionaryAttr>("fields")) {
170  auto finalPath = infoAttr.getAs<DictionaryAttr>("finalPath");
171  // The following is used prior to an upstream bump in Chisel.
172  if (!finalPath)
173  finalPath = infoAttr.getAs<DictionaryAttr>("instancePath");
174  if (finalPath)
175  if (auto v = finalPath.getAs<DictionaryAttr>("value"))
176  if (v.getAs<UnitAttr>("omir.tracker"))
177  id = v.getAs<IntegerAttr>("id");
178  if (auto omTy = infoAttr.getAs<DictionaryAttr>("omType"))
179  if (auto valueArr = omTy.getAs<ArrayAttr>("value"))
180  for (auto attr : valueArr)
181  if (auto str = dyn_cast<StringAttr>(attr))
182  if (str.getValue().equals("OMString:OMSRAM"))
183  return id;
184  }
185  return {};
186 }
187 
188 //===----------------------------------------------------------------------===//
189 // Code related to handling OMIR annotations
190 //===----------------------------------------------------------------------===//
191 
192 /// Recursively walk Object Model IR and convert FIRRTL targets to identifiers
193 /// while scattering trackers into the newAnnotations argument.
194 ///
195 /// Object Model IR consists of a type hierarchy built around recursive arrays
196 /// and dictionaries whose leaves are "string-encoded types". This is an Object
197 /// Model-specific construct that puts type information alongside a value.
198 /// Concretely, these look like:
199 ///
200 /// 'OM' type ':' value
201 ///
202 /// This function is only concerned with unpacking types whose values are FIRRTL
203 /// targets. This is because these need to be kept up-to-date with
204 /// modifications made to the circuit whereas other types are just passing
205 /// through CIRCT.
206 ///
207 /// At a later time this understanding may be expanded or Object Model IR may
208 /// become its own Dialect. At this time, this function is trying to do as
209 /// minimal work as possible to just validate that the OMIR looks okay without
210 /// doing lots of unnecessary unpacking/repacking of string-encoded types.
211 static std::optional<Attribute> scatterOMIR(Attribute original,
212  ApplyState &state) {
213  auto *ctx = original.getContext();
214 
215  // Convert a string-encoded type to a dictionary that includes the type
216  // information and an identifier derived from the current annotationID. Then
217  // increment the annotationID. Return the constructed dictionary.
218  auto addID = [&](StringRef tpe, StringRef path,
219  IntegerAttr id) -> DictionaryAttr {
220  NamedAttrList fields;
221  fields.append("id", id);
222  fields.append("omir.tracker", UnitAttr::get(ctx));
223  fields.append("path", StringAttr::get(ctx, path));
224  fields.append("type", StringAttr::get(ctx, tpe));
225  return DictionaryAttr::getWithSorted(ctx, fields);
226  };
227 
228  return TypeSwitch<Attribute, std::optional<Attribute>>(original)
229  // Most strings in the Object Model are actually string-encoded types.
230  // These are types which look like: "<type>:<value>". This code will
231  // examine all strings, parse them into type and value, and then either
232  // store them in their unpacked state (and possibly scatter trackers into
233  // the circuit), store them in their packed state (because CIRCT is not
234  // expected to care about them right now), or error if we see them
235  // (because they should not exist and are expected to serialize to a
236  // different format).
237  .Case<StringAttr>([&](StringAttr str) -> std::optional<Attribute> {
238  // Unpack the string into type and value.
239  StringRef tpe, value;
240  std::tie(tpe, value) = str.getValue().split(":");
241 
242  // These are string-encoded types that are targets in the circuit.
243  // These require annotations to be scattered for them. Replace their
244  // target with an ID and scatter a tracker.
245  if (tpe == "OMReferenceTarget" || tpe == "OMMemberReferenceTarget" ||
246  tpe == "OMMemberInstanceTarget" || tpe == "OMInstanceTarget" ||
247  tpe == "OMDontTouchedReferenceTarget") {
248  auto idAttr = state.newID();
249  NamedAttrList tracker;
250  tracker.append("class", StringAttr::get(ctx, omirTrackerAnnoClass));
251  tracker.append("id", idAttr);
252  tracker.append("target", StringAttr::get(ctx, value));
253  tracker.append("type", StringAttr::get(ctx, tpe));
254 
255  state.addToWorklistFn(DictionaryAttr::get(ctx, tracker));
256 
257  return addID(tpe, value, idAttr);
258  }
259 
260  // The following are types that may exist, but we do not unbox them. At
261  // a later time, we may want to change this behavior and unbox these if
262  // we wind up building out an Object Model dialect:
264  return str;
265 
266  // The following types are not expected to exist because they have
267  // serializations to JSON types or are removed during serialization.
268  // Hence, any of the following types are NOT expected to exist and we
269  // error if we see them. These are explicitly specified as opposed to
270  // being handled in the "unknown" catch-all case below because we want
271  // to provide a good error message that a user may be doing something
272  // very weird.
273  if (tpe == "OMMap" || tpe == "OMArray" || tpe == "OMBoolean" ||
274  tpe == "OMInt" || tpe == "OMDouble" || tpe == "OMFrozenTarget") {
275  auto diag =
276  mlir::emitError(state.circuit.getLoc())
277  << "found known string-encoded OMIR type \"" << tpe
278  << "\", but this type should not be seen as it has a defined "
279  "serialization format that does NOT use a string-encoded type";
280  diag.attachNote()
281  << "the problematic OMIR is reproduced here: " << original;
282  return std::nullopt;
283  }
284 
285  // This is a catch-all for any unknown types.
286  auto diag = mlir::emitError(state.circuit.getLoc())
287  << "found unknown string-encoded OMIR type \"" << tpe
288  << "\" (Did you misspell it? Is CIRCT missing an Object "
289  "Model OMIR type?)";
290  diag.attachNote() << "the problematic OMIR is reproduced here: "
291  << original;
292  return std::nullopt;
293  })
294  // For an array, just recurse into each element and rewrite the array with
295  // the results.
296  .Case<ArrayAttr>([&](ArrayAttr arr) -> std::optional<Attribute> {
297  SmallVector<Attribute> newArr;
298  for (auto element : arr) {
299  auto newElement = scatterOMIR(element, state);
300  if (!newElement)
301  return std::nullopt;
302  newArr.push_back(*newElement);
303  }
304  return ArrayAttr::get(ctx, newArr);
305  })
306  // For a dictionary, recurse into each value and rewrite the key/value
307  // pairs.
308  .Case<DictionaryAttr>(
309  [&](DictionaryAttr dict) -> std::optional<Attribute> {
310  NamedAttrList newAttrs;
311  for (auto pairs : dict) {
312  auto maybeValue = scatterOMIR(pairs.getValue(), state);
313  if (!maybeValue)
314  return std::nullopt;
315  newAttrs.append(pairs.getName(), *maybeValue);
316  }
317  return DictionaryAttr::get(ctx, newAttrs);
318  })
319  // These attributes are all expected. They are OMIR types, but do not
320  // have string-encodings (hence why these should error if we see them as
321  // strings).
322  .Case</* OMBoolean */ BoolAttr, /* OMDouble */ FloatAttr,
323  /* OMInt */ IntegerAttr>(
324  [](auto passThrough) { return passThrough; })
325  // Error if we see anything else.
326  .Default([&](auto) -> std::optional<Attribute> {
327  auto diag = mlir::emitError(state.circuit.getLoc())
328  << "found unexpected MLIR attribute \"" << original
329  << "\" while trying to scatter OMIR";
330  return std::nullopt;
331  });
332 }
333 
334 /// Convert an Object Model Field into an optional pair of a string key and a
335 /// dictionary attribute. Expand internal source locator strings to location
336 /// attributes. Scatter any FIRRTL targets into the circuit. If this is an
337 /// illegal Object Model Field return None.
338 ///
339 /// Each Object Model Field consists of three mandatory members with
340 /// the following names and types:
341 ///
342 /// - "info": Source Locator String
343 /// - "name": String
344 /// - "value": Object Model IR
345 ///
346 /// The key is the "name" and the dictionary consists of the "info" and "value"
347 /// members. Each value is recursively traversed to scatter any FIRRTL targets
348 /// that may be used inside it.
349 ///
350 /// This conversion from an object (dictionary) to key--value pair is safe
351 /// because each Object Model Field in an Object Model Node must have a unique
352 /// "name". Anything else is illegal Object Model.
353 static std::optional<std::pair<StringRef, DictionaryAttr>>
354 scatterOMField(Attribute original, const Attribute root, unsigned index,
355  ApplyState &state) {
356  // The input attribute must be a dictionary.
357  DictionaryAttr dict = dyn_cast<DictionaryAttr>(original);
358  if (!dict) {
359  llvm::errs() << "OMField is not a dictionary, but should be: " << original
360  << "\n";
361  return std::nullopt;
362  }
363 
364  auto loc = state.circuit.getLoc();
365  auto *ctx = state.circuit.getContext();
366 
367  // Generate an arbitrary identifier to use for caching when using
368  // `maybeStringToLocation`.
369  StringAttr locatorFilenameCache = StringAttr::get(ctx, ".");
370  FileLineColLoc fileLineColLocCache;
371 
372  // Convert location from a string to a location attribute.
373  auto infoAttr = tryGetAs<StringAttr>(dict, root, "info", loc, omirAnnoClass);
374  if (!infoAttr)
375  return std::nullopt;
376  auto maybeLoc =
377  maybeStringToLocation(infoAttr.getValue(), false, locatorFilenameCache,
378  fileLineColLocCache, ctx);
379  mlir::LocationAttr infoLoc;
380  if (maybeLoc.first)
381  infoLoc = *maybeLoc.second;
382  else
383  infoLoc = UnknownLoc::get(ctx);
384 
385  // Extract the name attribute.
386  auto nameAttr = tryGetAs<StringAttr>(dict, root, "name", loc, omirAnnoClass);
387  if (!nameAttr)
388  return std::nullopt;
389 
390  // The value attribute is unstructured and just copied over.
391  auto valueAttr = tryGetAs<Attribute>(dict, root, "value", loc, omirAnnoClass);
392  if (!valueAttr)
393  return std::nullopt;
394  auto newValue = scatterOMIR(valueAttr, state);
395  if (!newValue)
396  return std::nullopt;
397 
398  NamedAttrList values;
399  // We add the index if one was provided. This can be used later to
400  // reconstruct the order of the original array.
401  values.append("index", IntegerAttr::get(IntegerType::get(ctx, 64), index));
402  values.append("info", infoLoc);
403  values.append("value", *newValue);
404 
405  return {{nameAttr.getValue(), DictionaryAttr::getWithSorted(ctx, values)}};
406 }
407 
408 /// Convert an Object Model Node to an optional dictionary, convert source
409 /// locator strings to location attributes, and scatter FIRRTL targets into the
410 /// circuit. If this is an illegal Object Model Node, then return None.
411 ///
412 /// An Object Model Node is expected to look like:
413 ///
414 /// - "info": Source Locator String
415 /// - "id": String-encoded integer ('OMID' ':' Integer)
416 /// - "fields": Array<Object>
417 ///
418 /// The "fields" member may be absent. If so, then construct an empty array.
419 static std::optional<DictionaryAttr>
420 scatterOMNode(Attribute original, const Attribute root, ApplyState &state) {
421 
422  auto loc = state.circuit.getLoc();
423 
424  /// The input attribute must be a dictionary.
425  DictionaryAttr dict = dyn_cast<DictionaryAttr>(original);
426  if (!dict) {
427  llvm::errs() << "OMNode is not a dictionary, but should be: " << original
428  << "\n";
429  return std::nullopt;
430  }
431 
432  NamedAttrList omnode;
433  auto *ctx = state.circuit.getContext();
434 
435  // Generate an arbitrary identifier to use for caching when using
436  // `maybeStringToLocation`.
437  StringAttr locatorFilenameCache = StringAttr::get(ctx, ".");
438  FileLineColLoc fileLineColLocCache;
439 
440  // Convert the location from a string to a location attribute.
441  auto infoAttr = tryGetAs<StringAttr>(dict, root, "info", loc, omirAnnoClass);
442  if (!infoAttr)
443  return std::nullopt;
444  auto maybeLoc =
445  maybeStringToLocation(infoAttr.getValue(), false, locatorFilenameCache,
446  fileLineColLocCache, ctx);
447  mlir::LocationAttr infoLoc;
448  if (maybeLoc.first)
449  infoLoc = *maybeLoc.second;
450  else
451  infoLoc = UnknownLoc::get(ctx);
452 
453  // Extract the OMID. Don't parse this, just leave it as a string.
454  auto idAttr = tryGetAs<StringAttr>(dict, root, "id", loc, omirAnnoClass);
455  if (!idAttr)
456  return std::nullopt;
457 
458  // Convert the fields from an ArrayAttr to a DictionaryAttr keyed by their
459  // "name". If no fields member exists, then just create an empty dictionary.
460  // Note that this is safe to construct because all fields must have unique
461  // "name" members relative to each other.
462  auto maybeFields = dict.getAs<ArrayAttr>("fields");
463  DictionaryAttr fields;
464  if (!maybeFields)
465  fields = DictionaryAttr::get(ctx);
466  else {
467  auto fieldAttr = maybeFields.getValue();
468  NamedAttrList fieldAttrs;
469  for (size_t i = 0, e = fieldAttr.size(); i != e; ++i) {
470  auto field = fieldAttr[i];
471  if (auto newField = scatterOMField(field, root, i, state)) {
472  fieldAttrs.append(newField->first, newField->second);
473  continue;
474  }
475  return std::nullopt;
476  }
477  fields = DictionaryAttr::get(ctx, fieldAttrs);
478  }
479 
480  omnode.append("fields", fields);
481  omnode.append("id", idAttr);
482  omnode.append("info", infoLoc);
483 
484  return DictionaryAttr::getWithSorted(ctx, omnode);
485 }
486 
487 /// Main entry point to handle scattering of an OMIRAnnotation. Return the
488 /// modified optional attribute on success and None on failure. Any scattered
489 /// annotations will be added to the reference argument `newAnnotations`.
490 LogicalResult circt::firrtl::applyOMIR(const AnnoPathValue &target,
491  DictionaryAttr anno, ApplyState &state) {
492 
493  auto loc = state.circuit.getLoc();
494 
495  auto nodes = tryGetAs<ArrayAttr>(anno, anno, "nodes", loc, omirAnnoClass);
496  if (!nodes)
497  return failure();
498 
499  SmallVector<Attribute> newNodes;
500  for (auto node : nodes) {
501  auto newNode = scatterOMNode(node, anno, state);
502  if (!newNode)
503  return failure();
504  newNodes.push_back(*newNode);
505  }
506 
507  auto *ctx = state.circuit.getContext();
508 
509  NamedAttrList newAnnotation;
510  newAnnotation.append("class", StringAttr::get(ctx, omirAnnoClass));
511  newAnnotation.append("nodes", ArrayAttr::get(ctx, newNodes));
512 
513  AnnotationSet annotations(state.circuit);
514  annotations.addAnnotations(DictionaryAttr::get(ctx, newAnnotation));
515  annotations.applyToOperation(state.circuit);
516 
517  return success();
518 }
519 
520 //===----------------------------------------------------------------------===//
521 // Pass Implementation
522 //===----------------------------------------------------------------------===//
523 
524 void EmitOMIRPass::runOnOperation() {
525  anyFailures = false;
526  circuitNamespace = nullptr;
527  instanceGraph = nullptr;
528  instancePaths = nullptr;
529  trackers.clear();
530  symbols.clear();
531  symbolIndices.clear();
532  removeTempNLAs.clear();
533  moduleNamespaces.clear();
534  instancesByName.clear();
535  CircuitOp circuitOp = getOperation();
536 
537  // Gather the relevant annotations from the circuit. On the one hand these are
538  // all the actual `OMIRAnnotation`s that need processing and emission, as well
539  // as an optional `OMIRFileAnnotation` that overrides the default OMIR output
540  // file. Also while we're at it, keep track of all OMIR nodes that qualify as
541  // an SRAM and that require their trackers to be turned into NLAs starting at
542  // the root of the hierarchy.
543  SmallVector<ArrayRef<Attribute>> annoNodes;
544  DenseSet<Attribute> sramIDs;
545  std::optional<StringRef> outputFilename;
546 
547  AnnotationSet::removeAnnotations(circuitOp, [&](Annotation anno) {
548  if (anno.isClass(omirFileAnnoClass)) {
549  auto pathAttr = anno.getMember<StringAttr>("filename");
550  if (!pathAttr) {
551  circuitOp.emitError(omirFileAnnoClass)
552  << " annotation missing `filename` string attribute";
553  anyFailures = true;
554  return true;
555  }
556  LLVM_DEBUG(llvm::dbgs() << "- OMIR path: " << pathAttr << "\n");
557  outputFilename = pathAttr.getValue();
558  return true;
559  }
560  if (anno.isClass(omirAnnoClass)) {
561  auto nodesAttr = anno.getMember<ArrayAttr>("nodes");
562  if (!nodesAttr) {
563  circuitOp.emitError(omirAnnoClass)
564  << " annotation missing `nodes` array attribute";
565  anyFailures = true;
566  return true;
567  }
568  LLVM_DEBUG(llvm::dbgs() << "- OMIR: " << nodesAttr << "\n");
569  annoNodes.push_back(nodesAttr.getValue());
570  for (auto node : nodesAttr) {
571  if (auto id = isOMSRAM(node)) {
572  LLVM_DEBUG(llvm::dbgs() << " - is SRAM with tracker " << id << "\n");
573  sramIDs.insert(id);
574  }
575  }
576  return true;
577  }
578  return false;
579  });
580  if (anyFailures)
581  return signalPassFailure();
582 
583  // If an OMIR output filename has been specified as a pass parameter, override
584  // whatever the annotations have configured. If neither are specified we just
585  // bail.
586  if (!this->outputFilename.empty())
587  outputFilename = this->outputFilename;
588  if (!outputFilename) {
589  LLVM_DEBUG(llvm::dbgs() << "Not emitting OMIR because no annotation or "
590  "pass parameter specified an output file\n");
591  markAllAnalysesPreserved();
592  return;
593  }
594  // Establish some of the analyses we need throughout the pass.
595  CircuitNamespace currentCircuitNamespace(circuitOp);
596  InstanceGraph &currentInstanceGraph = getAnalysis<InstanceGraph>();
597  nlaTable = &getAnalysis<NLATable>();
598  InstancePathCache currentInstancePaths(currentInstanceGraph);
599  circuitNamespace = &currentCircuitNamespace;
600  instanceGraph = &currentInstanceGraph;
601  instancePaths = &currentInstancePaths;
602  dutModuleName = {};
603 
604  // Traverse the IR and collect all tracker annotations that were previously
605  // scattered into the circuit.
606  circuitOp.walk([&](Operation *op) {
607  if (auto instOp = dyn_cast<InstanceOp>(op)) {
608  // This instance does not have a symbol, but we are adding one. Remove it
609  // after the pass.
611  tempSymInstances.insert(instOp);
612 
613  instancesByName.insert({getInnerRefTo(op), instOp});
614  }
615  auto setTracker = [&](int portNo, Annotation anno) {
616  if (!anno.isClass(omirTrackerAnnoClass))
617  return false;
618  Tracker tracker;
619  tracker.op = op;
620  tracker.id = anno.getMember<IntegerAttr>("id");
621  tracker.portNo = portNo;
622  tracker.fieldID = anno.getFieldID();
623  if (!tracker.id) {
624  op->emitError(omirTrackerAnnoClass)
625  << " annotation missing `id` integer attribute";
626  anyFailures = true;
627  return true;
628  }
629  if (auto nlaSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
630  auto tmp = nlaTable->getNLA(nlaSym.getAttr());
631  if (!tmp) {
632  op->emitError("missing annotation ") << nlaSym.getValue();
633  anyFailures = true;
634  return true;
635  }
636  tracker.nla = cast<hw::HierPathOp>(tmp);
637  }
638  if (sramIDs.erase(tracker.id))
639  makeTrackerAbsolute(tracker);
640  if (auto [it, inserted] = trackers.try_emplace(tracker.id, tracker);
641  !inserted) {
642  auto diag = op->emitError(omirTrackerAnnoClass)
643  << " annotation with same ID already found, must resolve "
644  "to single target";
645  diag.attachNote(it->second.op->getLoc())
646  << "tracker with same ID already found here";
647  anyFailures = true;
648  return true;
649  }
650  return true;
651  };
652  AnnotationSet::removePortAnnotations(op, setTracker);
653  AnnotationSet::removeAnnotations(
654  op, std::bind(setTracker, -1, std::placeholders::_1));
655  if (auto modOp = dyn_cast<FModuleOp>(op)) {
656  AnnotationSet annos(modOp.getAnnotations());
657  if (!annos.hasAnnotation(dutAnnoClass))
658  return;
659  dutModuleName = modOp.getNameAttr();
660  }
661  });
662 
663  // Build the output JSON.
664  std::string jsonBuffer;
665  llvm::raw_string_ostream jsonOs(jsonBuffer);
666  llvm::json::OStream json(jsonOs, 2);
667  json.array([&] {
668  for (auto nodes : annoNodes) {
669  for (auto node : nodes) {
670  emitOMNode(node, json);
671  if (anyFailures)
672  return;
673  }
674  }
675  });
676  if (anyFailures)
677  return signalPassFailure();
678 
679  // Drop temporary (and sometimes invalid) NLA's created during the pass:
680  for (auto nla : removeTempNLAs) {
681  LLVM_DEBUG(llvm::dbgs() << "Removing '" << nla << "'\n");
682  nlaTable->erase(nla);
683  nla.erase();
684  }
685  removeTempNLAs.clear();
686 
687  // Remove the temp symbol from instances.
688  for (auto *op : tempSymInstances)
689  cast<InstanceOp>(op).setInnerSymbolAttr({});
690  tempSymInstances.clear();
691 
692  // Emit the OMIR JSON as a verbatim op.
693  auto builder = circuitOp.getBodyBuilder();
694  auto loc = builder.getUnknownLoc();
695  builder.create<emit::FileOp>(loc, *outputFilename, [&] {
696  builder.create<sv::VerbatimOp>(builder.getUnknownLoc(), jsonBuffer,
697  ValueRange{}, builder.getArrayAttr(symbols));
698  });
699 
700  markAnalysesPreserved<NLATable>();
701 }
702 
703 /// Make a tracker absolute by adding an NLA to it which starts at the root
704 /// module of the circuit. Generates an error if any module along the path is
705 /// instantiated multiple times.
706 void EmitOMIRPass::makeTrackerAbsolute(Tracker &tracker) {
707  auto builder = OpBuilder::atBlockBegin(getOperation().getBodyBlock());
708 
709  // Pick a name for the NLA that doesn't collide with anything.
710  StringAttr opName;
711  if (auto module = dyn_cast<FModuleLike>(tracker.op))
712  opName = module.getModuleNameAttr();
713  else
714  opName = tracker.op->getAttrOfType<StringAttr>("name");
715  auto nlaName = circuitNamespace->newName("omir_nla_" + opName.getValue());
716 
717  // Get all the paths instantiating this module. If there is an NLA already
718  // attached to this tracker, we use it as a base to disambiguate the path to
719  // the memory.
720  igraph::ModuleOpInterface mod;
721  if (tracker.nla)
722  mod = instanceGraph->lookup(tracker.nla.root())
723  ->getModule<igraph::ModuleOpInterface>();
724  else
725  mod = tracker.op->getParentOfType<FModuleOp>();
726 
727  // Get all the paths instantiating this module.
728  auto paths = instancePaths->getAbsolutePaths(mod);
729  if (paths.empty()) {
730  tracker.op->emitError("OMIR node targets uninstantiated component `")
731  << opName.getValue() << "`";
732  anyFailures = true;
733  return;
734  }
735  if (paths.size() > 1) {
736  auto diag = tracker.op->emitError("OMIR node targets ambiguous component `")
737  << opName.getValue() << "`";
738  diag.attachNote(tracker.op->getLoc())
739  << "may refer to the following paths:";
740  for (auto path : paths) {
741  std::string pathStr;
742  llvm::raw_string_ostream os(pathStr);
743  os << "- " << path;
744  diag.attachNote(tracker.op->getLoc()) << pathStr;
745  }
746  anyFailures = true;
747  return;
748  }
749 
750  // Assemble the module and name path for the NLA. Also attach an NLA reference
751  // annotation to each instance participating in the path.
752  SmallVector<Attribute> namepath;
753  auto addToPath = [&](Operation *op) {
754  namepath.push_back(getInnerRefTo(op));
755  };
756  // Add the path up to where the NLA starts.
757  for (auto inst : paths[0])
758  addToPath(inst);
759  // Add the path from the NLA to the op.
760  if (tracker.nla) {
761  auto path = tracker.nla.getNamepath().getValue();
762  for (auto attr : path.drop_back()) {
763  auto ref = attr.cast<hw::InnerRefAttr>();
764  // Find the instance referenced by the NLA.
765  auto *node = instanceGraph->lookup(ref.getModule());
766  auto it = llvm::find_if(*node, [&](igraph::InstanceRecord *record) {
767  return getInnerSymName(record->getInstance<InstanceOp>()) ==
768  ref.getName();
769  });
770  assert(it != node->end() &&
771  "Instance referenced by NLA does not exist in module");
772  addToPath((*it)->getInstance());
773  }
774  }
775 
776  // TODO: Don't create NLA if namepath is empty
777  // (care needed to ensure this will be handled correctly elsewhere)
778 
779  // Add the op itself.
780  if (auto module = dyn_cast<FModuleLike>(tracker.op))
781  namepath.push_back(FlatSymbolRefAttr::get(module.getModuleNameAttr()));
782  else
783  namepath.push_back(getInnerRefTo(tracker.op));
784 
785  // Add the NLA to the tracker and mark it to be deleted later.
786  tracker.nla = builder.create<hw::HierPathOp>(builder.getUnknownLoc(),
787  builder.getStringAttr(nlaName),
788  builder.getArrayAttr(namepath));
789  nlaTable->addNLA(tracker.nla);
790 
791  removeTempNLAs.push_back(tracker.nla);
792 }
793 
794 /// Emit a source locator into a string, for inclusion in the `info` field of
795 /// `OMNode` and `OMField`.
796 void EmitOMIRPass::emitSourceInfo(Location input, SmallString<64> &into) {
797  into.clear();
798  input->walk([&](Location loc) {
799  if (FileLineColLoc fileLoc = dyn_cast<FileLineColLoc>(loc)) {
800  into.append(into.empty() ? "@[" : " ");
801  (Twine(fileLoc.getFilename()) + " " + Twine(fileLoc.getLine()) + ":" +
802  Twine(fileLoc.getColumn()))
803  .toVector(into);
804  }
805  return WalkResult::advance();
806  });
807  if (!into.empty())
808  into.append("]");
809  else
810  into.append("UnlocatableSourceInfo");
811 }
812 
813 /// Emit an entire `OMNode` as JSON.
814 void EmitOMIRPass::emitOMNode(Attribute node, llvm::json::OStream &jsonStream) {
815  auto dict = dyn_cast<DictionaryAttr>(node);
816  if (!dict) {
817  getOperation()
818  .emitError("OMNode must be a dictionary")
819  .attachNote(getOperation().getLoc())
820  << node;
821  anyFailures = true;
822  return;
823  }
824 
825  // Extract the `info` field and serialize the location.
826  SmallString<64> info;
827  if (auto infoAttr = dict.getAs<LocationAttr>("info"))
828  emitSourceInfo(infoAttr, info);
829  if (anyFailures)
830  return;
831 
832  // Extract the `id` field.
833  auto idAttr = dict.getAs<StringAttr>("id");
834  if (!idAttr) {
835  getOperation()
836  .emitError("OMNode missing `id` string field")
837  .attachNote(getOperation().getLoc())
838  << dict;
839  anyFailures = true;
840  return;
841  }
842 
843  // Extract and order the fields of this node.
844  SmallVector<std::tuple<unsigned, StringAttr, DictionaryAttr>> orderedFields;
845  auto fieldsDict = dict.getAs<DictionaryAttr>("fields");
846  if (fieldsDict) {
847  for (auto nameAndField : fieldsDict.getValue()) {
848  auto fieldDict = dyn_cast<DictionaryAttr>(nameAndField.getValue());
849  if (!fieldDict) {
850  getOperation()
851  .emitError("OMField must be a dictionary")
852  .attachNote(getOperation().getLoc())
853  << nameAndField.getValue();
854  anyFailures = true;
855  return;
856  }
857 
858  unsigned index = 0;
859  if (auto indexAttr = fieldDict.getAs<IntegerAttr>("index"))
860  index = indexAttr.getValue().getLimitedValue();
861 
862  orderedFields.push_back({index, nameAndField.getName(), fieldDict});
863  }
864  llvm::sort(orderedFields,
865  [](auto a, auto b) { return std::get<0>(a) < std::get<0>(b); });
866  }
867 
868  jsonStream.object([&] {
869  jsonStream.attribute("info", info);
870  jsonStream.attribute("id", idAttr.getValue());
871  jsonStream.attributeArray("fields", [&] {
872  for (auto &orderedField : orderedFields) {
873  emitOMField(std::get<1>(orderedField), std::get<2>(orderedField),
874  jsonStream);
875  if (anyFailures)
876  return;
877  }
878  if (auto node = fieldsDict.getAs<DictionaryAttr>("containingModule"))
879  if (auto value = node.getAs<DictionaryAttr>("value"))
880  emitOptionalRTLPorts(value, jsonStream);
881  });
882  });
883 }
884 
885 /// Emit a single `OMField` as JSON. This expects the field's name to be
886 /// provided from the outside, for example as the field name that this attribute
887 /// has in the surrounding dictionary.
888 void EmitOMIRPass::emitOMField(StringAttr fieldName, DictionaryAttr field,
889  llvm::json::OStream &jsonStream) {
890  // Extract the `info` field and serialize the location.
891  auto infoAttr = field.getAs<LocationAttr>("info");
892  SmallString<64> info;
893  if (infoAttr)
894  emitSourceInfo(infoAttr, info);
895  if (anyFailures)
896  return;
897 
898  jsonStream.object([&] {
899  jsonStream.attribute("info", info);
900  jsonStream.attribute("name", fieldName.strref());
901  jsonStream.attributeBegin("value");
902  emitValue(field.get("value"), jsonStream,
903  fieldName.strref().equals("dutInstance"));
904  jsonStream.attributeEnd();
905  });
906 }
907 
908 // If the given `node` refers to a valid tracker in the IR, gather the
909 // additional port metadata of the module it refers to. Then emit this port
910 // metadata as a `ports` array field for the surrounding `OMNode`.
911 void EmitOMIRPass::emitOptionalRTLPorts(DictionaryAttr node,
912  llvm::json::OStream &jsonStream) {
913  // First make sure we actually have a valid tracker. If not, just silently
914  // abort and don't emit any port metadata.
915  auto idAttr = node.getAs<IntegerAttr>("id");
916  auto trackerIt = trackers.find(idAttr);
917  if (!idAttr || !node.getAs<UnitAttr>("omir.tracker") ||
918  trackerIt == trackers.end())
919  return;
920  auto tracker = trackerIt->second;
921 
922  // Lookup the module the tracker refers to. If it points at something *within*
923  // a module, go dig up the surrounding module. This is roughly what
924  // `Target.referringModule(...)` does on the Scala side.
925  auto module = dyn_cast<FModuleLike>(tracker.op);
926  if (!module)
927  module = tracker.op->getParentOfType<FModuleLike>();
928  if (!module) {
929  LLVM_DEBUG(llvm::dbgs() << "Not emitting RTL ports since tracked operation "
930  "does not have a FModuleLike parent: "
931  << *tracker.op << "\n");
932  return;
933  }
934  LLVM_DEBUG(llvm::dbgs() << "Emitting RTL ports for module `"
935  << module.getModuleName() << "`\n");
936 
937  // Emit the JSON.
938  SmallString<64> buf;
939  jsonStream.object([&] {
940  buf.clear();
941  emitSourceInfo(module.getLoc(), buf);
942  jsonStream.attribute("info", buf);
943  jsonStream.attribute("name", "ports");
944  jsonStream.attributeArray("value", [&] {
945  for (const auto &port : llvm::enumerate(module.getPorts())) {
946  auto portType = type_dyn_cast<FIRRTLBaseType>(port.value().type);
947  if (!portType || portType.getBitWidthOrSentinel() == 0)
948  continue;
949  jsonStream.object([&] {
950  // Emit the `ref` field.
951  buf.assign("OMDontTouchedReferenceTarget:~");
952  if (module.getModuleNameAttr() == dutModuleName) {
953  // If module is DUT, then root the target relative to the DUT.
954  buf.append(module.getModuleName());
955  } else {
956  buf.append(getOperation().getName());
957  }
958  buf.push_back('|');
959  buf.append(addSymbol(module));
960  buf.push_back('>');
961  buf.append(addSymbol(getInnerRefTo(module, port.index())));
962  jsonStream.attribute("ref", buf);
963 
964  // Emit the `direction` field.
965  buf.assign("OMString:");
966  buf.append(port.value().isOutput() ? "Output" : "Input");
967  jsonStream.attribute("direction", buf);
968 
969  // Emit the `width` field.
970  buf.assign("OMBigInt:");
971  Twine::utohexstr(portType.getBitWidthOrSentinel()).toVector(buf);
972  jsonStream.attribute("width", buf);
973  });
974  }
975  });
976  });
977 }
978 
979 void EmitOMIRPass::emitValue(Attribute node, llvm::json::OStream &jsonStream,
980  bool dutInstance) {
981  // Handle the null case.
982  if (!node || isa<UnitAttr>(node))
983  return jsonStream.value(nullptr);
984 
985  // Handle the trivial cases where the OMIR serialization simply uses the
986  // builtin JSON types.
987  if (auto attr = dyn_cast<BoolAttr>(node))
988  return jsonStream.value(attr.getValue()); // OMBoolean
989  if (auto attr = dyn_cast<IntegerAttr>(node)) {
990  // CAVEAT: We expect these integers to come from an OMIR file that is
991  // initially read in from JSON, where they are i32 or i64, so this should
992  // yield a valid value. However, a user could cook up an arbitrary precision
993  // integer attr in MLIR input and then subtly break the JSON spec.
994  SmallString<16> val;
995  attr.getValue().toStringSigned(val);
996  return jsonStream.rawValue(val); // OMInt
997  }
998  if (auto attr = dyn_cast<FloatAttr>(node)) {
999  // CAVEAT: We expect these floats to come from an OMIR file that is
1000  // initially read in from JSON, where they are f32 or f64, so this should
1001  // yield a valid value. However, a user could cook up an arbitrary precision
1002  // float attr in MLIR input and then subtly break the JSON spec.
1003  SmallString<16> val;
1004  attr.getValue().toString(val);
1005  return jsonStream.rawValue(val); // OMDouble
1006  }
1007 
1008  // Handle aggregate types.
1009  if (auto attr = dyn_cast<ArrayAttr>(node)) {
1010  jsonStream.array([&] {
1011  for (auto element : attr.getValue()) {
1012  emitValue(element, jsonStream, dutInstance);
1013  if (anyFailures)
1014  return;
1015  }
1016  });
1017  return;
1018  }
1019  if (auto attr = dyn_cast<DictionaryAttr>(node)) {
1020  // Handle targets that have a corresponding tracker annotation in the IR.
1021  if (attr.getAs<UnitAttr>("omir.tracker"))
1022  return emitTrackedTarget(attr, jsonStream, dutInstance);
1023 
1024  // Handle regular dictionaries.
1025  jsonStream.object([&] {
1026  for (auto field : attr.getValue()) {
1027  jsonStream.attributeBegin(field.getName());
1028  emitValue(field.getValue(), jsonStream, dutInstance);
1029  jsonStream.attributeEnd();
1030  if (anyFailures)
1031  return;
1032  }
1033  });
1034  return;
1035  }
1036 
1037  // The remaining types are all simple string-encoded pass-through cases.
1038  if (auto attr = dyn_cast<StringAttr>(node)) {
1039  StringRef val = attr.getValue();
1040  if (isOMIRStringEncodedPassthrough(val.split(":").first))
1041  return jsonStream.value(val);
1042  }
1043 
1044  // If we get here, we don't know how to serialize the given MLIR attribute as
1045  // a OMIR value.
1046  jsonStream.value("<unsupported value>");
1047  getOperation().emitError("unsupported attribute for OMIR serialization: `")
1048  << node << "`";
1049  anyFailures = true;
1050 }
1051 
1052 void EmitOMIRPass::emitTrackedTarget(DictionaryAttr node,
1053  llvm::json::OStream &jsonStream,
1054  bool dutInstance) {
1055  // Extract the `id` field.
1056  auto idAttr = node.getAs<IntegerAttr>("id");
1057  if (!idAttr) {
1058  getOperation()
1059  .emitError("tracked OMIR target missing `id` string field")
1060  .attachNote(getOperation().getLoc())
1061  << node;
1062  anyFailures = true;
1063  return jsonStream.value("<error>");
1064  }
1065 
1066  // Extract the `type` field.
1067  auto typeAttr = node.getAs<StringAttr>("type");
1068  if (!typeAttr) {
1069  getOperation()
1070  .emitError("tracked OMIR target missing `type` string field")
1071  .attachNote(getOperation().getLoc())
1072  << node;
1073  anyFailures = true;
1074  return jsonStream.value("<error>");
1075  }
1076  StringRef type = typeAttr.getValue();
1077 
1078  // Find the tracker for this target, and handle the case where the tracker has
1079  // been deleted.
1080  auto trackerIt = trackers.find(idAttr);
1081  if (trackerIt == trackers.end()) {
1082  // Some of the target types indicate removal of the target through an
1083  // `OMDeleted` node.
1084  if (type == "OMReferenceTarget" || type == "OMMemberReferenceTarget" ||
1085  type == "OMMemberInstanceTarget")
1086  return jsonStream.value("OMDeleted:");
1087 
1088  // The remaining types produce an error upon removal of the target.
1089  auto diag = getOperation().emitError("tracked OMIR target of type `")
1090  << type << "` was deleted";
1091  diag.attachNote(getOperation().getLoc())
1092  << "`" << type << "` should never be deleted";
1093  if (auto path = node.getAs<StringAttr>("path"))
1094  diag.attachNote(getOperation().getLoc())
1095  << "original path: `" << path.getValue() << "`";
1096  anyFailures = true;
1097  return jsonStream.value("<error>");
1098  }
1099  auto tracker = trackerIt->second;
1100 
1101  // In case this is an `OMMemberTarget`, handle the case where the component
1102  // used to be a "reference target" (wire, register, memory, node) when the
1103  // OMIR was read in, but has been change to an "instance target" during the
1104  // execution of the compiler. This mainly occurs during mapping of
1105  // `firrtl.mem` operations to a corresponding `firrtl.instance`.
1106  if (type == "OMMemberReferenceTarget" && isa<InstanceOp, MemOp>(tracker.op))
1107  type = "OMMemberInstanceTarget";
1108 
1109  // Serialize the target circuit first.
1110  SmallString<64> target(type);
1111  target.append(":~");
1112  target.append(getOperation().getName());
1113  target.push_back('|');
1114 
1115  // Serialize the local or non-local module/instance hierarchy path.
1116  if (tracker.nla) {
1117  bool notFirst = false;
1118  hw::InnerRefAttr instName;
1119  for (auto nameRef : tracker.nla.getNamepath()) {
1120  StringAttr modName;
1121  if (auto innerRef = nameRef.dyn_cast<hw::InnerRefAttr>())
1122  modName = innerRef.getModule();
1123  else if (auto ref = dyn_cast<FlatSymbolRefAttr>(nameRef))
1124  modName = ref.getAttr();
1125  if (!dutInstance && modName == dutModuleName) {
1126  // Check if the DUT module occurs in the instance path.
1127  // Print the path relative to the DUT, if the nla is inside the DUT.
1128  // Keep the path for dutInstance relative to test harness. (SFC
1129  // implementation in TestHarnessOMPhase.scala)
1130  target = type;
1131  target.append(":~");
1132  target.append(dutModuleName);
1133  target.push_back('|');
1134  notFirst = false;
1135  instName = {};
1136  }
1137 
1138  Operation *module = nlaTable->getModule(modName);
1139  assert(module);
1140  if (notFirst)
1141  target.push_back('/');
1142  notFirst = true;
1143  if (instName) {
1144  target.append(addSymbol(instName));
1145  target.push_back(':');
1146  }
1147  target.append(addSymbol(module));
1148 
1149  if (auto innerRef = nameRef.dyn_cast<hw::InnerRefAttr>()) {
1150  // Find an instance with the given name in this module. Ensure it has a
1151  // symbol that we can refer to.
1152  auto instOp = instancesByName.lookup(innerRef);
1153  if (!instOp)
1154  continue;
1155  LLVM_DEBUG(llvm::dbgs() << "Marking NLA-participating instance "
1156  << innerRef.getName() << " in module "
1157  << modName << " as dont-touch\n");
1158  tempSymInstances.erase(instOp);
1159  instName = getInnerRefTo(instOp);
1160  }
1161  }
1162  } else {
1163  FModuleOp module = dyn_cast<FModuleOp>(tracker.op);
1164  if (!module)
1165  module = tracker.op->getParentOfType<FModuleOp>();
1166  assert(module);
1167  if (module.getNameAttr() == dutModuleName) {
1168  // If module is DUT, then root the target relative to the DUT.
1169  target = type;
1170  target.append(":~");
1171  target.append(dutModuleName);
1172  target.push_back('|');
1173  }
1174  target.append(addSymbol(module));
1175  }
1176 
1177  // Serialize any potential component *inside* the module that this target may
1178  // specifically refer to.
1179  hw::InnerRefAttr componentName;
1180  FIRRTLType componentType;
1181  if (isa<WireOp, RegOp, RegResetOp, InstanceOp, NodeOp, MemOp>(tracker.op)) {
1182  tempSymInstances.erase(tracker.op);
1183  componentName = getInnerRefTo(tracker.op);
1184  LLVM_DEBUG(llvm::dbgs() << "Marking OMIR-targeted " << componentName
1185  << " as dont-touch\n");
1186 
1187  // If the target refers to a field, get the type of the component so we can
1188  // extract the field, or fail if we don't know how to get the type.
1189  if (tracker.hasFieldID()) {
1190  if (isa<WireOp, RegOp, RegResetOp, NodeOp>(tracker.op)) {
1191  componentType = getTypeOf(tracker.op);
1192  } else {
1193  tracker.op->emitError("does not support OMIR targeting fields");
1194  anyFailures = true;
1195  return jsonStream.value("<error>");
1196  }
1197  }
1198  } else if (auto mod = dyn_cast<FModuleLike>(tracker.op)) {
1199  if (tracker.portNo >= 0) {
1200  componentName = getInnerRefTo(mod, tracker.portNo);
1201 
1202  // If the target refers to a field, get the type of the port.
1203  if (tracker.hasFieldID())
1204  componentType = getTypeOf(mod, tracker.portNo);
1205  }
1206  } else if (!isa<FModuleLike>(tracker.op)) {
1207  tracker.op->emitError("invalid target for `") << type << "` OMIR";
1208  anyFailures = true;
1209  return jsonStream.value("<error>");
1210  }
1211  if (componentName) {
1212  // Check if the targeted component is going to be emitted as an instance.
1213  // This is trivially the case for `InstanceOp`s, but also for `MemOp`s that
1214  // get converted to an instance during lowering to HW dialect and generator
1215  // callout.
1216  [&] {
1217  if (type == "OMMemberInstanceTarget") {
1218  if (auto instOp = dyn_cast<InstanceOp>(tracker.op)) {
1219  target.push_back('/');
1220  target.append(addSymbol(componentName));
1221  target.push_back(':');
1222  target.append(addSymbol(instOp.getModuleNameAttr()));
1223  return;
1224  }
1225  if (auto memOp = dyn_cast<MemOp>(tracker.op)) {
1226  target.push_back('/');
1227  target.append(addSymbol(componentName));
1228  target.push_back(':');
1229  target.append(memOp.getSummary().getFirMemoryName());
1230  return;
1231  }
1232  }
1233  target.push_back('>');
1234  target.append(addSymbol(componentName));
1235  addFieldID(componentType, tracker.fieldID, target);
1236  }();
1237  }
1238 
1239  jsonStream.value(target);
1240 }
1241 
1242 hw::InnerRefAttr EmitOMIRPass::getInnerRefTo(Operation *op) {
1244  [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
1245  return getModuleNamespace(module);
1246  });
1247 }
1248 
1249 hw::InnerRefAttr EmitOMIRPass::getInnerRefTo(FModuleLike module,
1250  size_t portIdx) {
1251  return ::getInnerRefTo(module, portIdx,
1252  [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
1253  return getModuleNamespace(mod);
1254  });
1255 }
1256 
1257 FIRRTLType EmitOMIRPass::getTypeOf(Operation *op) {
1258  if (auto fop = dyn_cast<Forceable>(op))
1259  return fop.getDataType();
1260  assert(op->getNumResults() == 1 &&
1261  isa<FIRRTLType>(op->getResult(0).getType()) &&
1262  "op must have a single FIRRTLType result");
1263  return type_cast<FIRRTLType>(op->getResult(0).getType());
1264 }
1265 
1266 FIRRTLType EmitOMIRPass::getTypeOf(FModuleLike mod, size_t portIdx) {
1267  Type portType = mod.getPortType(portIdx);
1268  assert(isa<FIRRTLType>(portType) && "port must have a FIRRTLType");
1269  return type_cast<FIRRTLType>(portType);
1270 }
1271 
1272 // Constructs a reference to a field of an aggregate FIRRTLType with a fieldID,
1273 // and appends it to result. If fieldID is 0, meaning it does not reference a
1274 // field of an aggregate FIRRTLType, this is a no-op.
1275 void EmitOMIRPass::addFieldID(FIRRTLType type, unsigned fieldID,
1276  SmallVectorImpl<char> &result) {
1277  while (fieldID)
1279  .Case<FVectorType>([&](FVectorType vector) {
1280  size_t index = vector.getIndexForFieldID(fieldID);
1281  type = vector.getElementType();
1282  fieldID -= vector.getFieldID(index);
1283  result.push_back('[');
1284  Twine(index).toVector(result);
1285  result.push_back(']');
1286  })
1287  .Case<BundleType>([&](BundleType bundle) {
1288  size_t index = bundle.getIndexForFieldID(fieldID);
1289  StringRef name = bundle.getElement(index).name;
1290  type = bundle.getElementType(index);
1291  fieldID -= bundle.getFieldID(index);
1292  result.push_back('.');
1293  result.append(name.begin(), name.end());
1294  })
1295  .Default([](auto val) { llvm::report_fatal_error("invalid fieldID"); });
1296 }
1297 
1298 //===----------------------------------------------------------------------===//
1299 // Pass Infrastructure
1300 //===----------------------------------------------------------------------===//
1301 
1302 std::unique_ptr<mlir::Pass>
1303 circt::firrtl::createEmitOMIRPass(StringRef outputFilename) {
1304  auto pass = std::make_unique<EmitOMIRPass>();
1305  if (!outputFilename.empty())
1306  pass->outputFilename = outputFilename.str();
1307  return pass;
1308 }
assert(baseType &&"element must be base type")
static std::optional< DictionaryAttr > scatterOMNode(Attribute original, const Attribute root, ApplyState &state)
Convert an Object Model Node to an optional dictionary, convert source locator strings to location at...
Definition: EmitOMIR.cpp:420
static IntegerAttr isOMSRAM(Attribute &node)
Check if an OMNode is an OMSRAM and requires special treatment of its instance path field.
Definition: EmitOMIR.cpp:161
static std::optional< std::pair< StringRef, DictionaryAttr > > scatterOMField(Attribute original, const Attribute root, unsigned index, ApplyState &state)
Convert an Object Model Field into an optional pair of a string key and a dictionary attribute.
Definition: EmitOMIR.cpp:354
static std::optional< Attribute > scatterOMIR(Attribute original, ApplyState &state)
Recursively walk Object Model IR and convert FIRRTL targets to identifiers while scattering trackers ...
Definition: EmitOMIR.cpp:211
static std::vector< mlir::Value > toVector(mlir::ValueRange range)
Builder builder
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
bool applyToOperation(Operation *op) const
Store the annotations in this set in an operation's annotations attribute, overwriting any existing a...
void addAnnotations(ArrayRef< Annotation > annotations)
Add more annotations to this annotation set.
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 class implements the same functionality as TypeSwitch except that it uses firrtl::type_dyn_cast ...
Definition: FIRRTLTypes.h:518
FIRRTLTypeSwitch< T, ResultT > & Case(CallableT &&caseFn)
Add a case on the given type.
Definition: FIRRTLTypes.h:528
This graph tracks modules and where they are instantiated.
This table tracks nlas and what modules participate in them.
Definition: NLATable.h:29
static StringRef getInnerSymbolAttrName()
Return the name of the attribute used for inner symbol names.
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
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
LogicalResult applyOMIR(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
Main entry point to handle scattering of an OMIRAnnotation.
Definition: EmitOMIR.cpp:490
std::unique_ptr< mlir::Pass > createEmitOMIRPass(mlir::StringRef outputFilename="")
std::pair< bool, std::optional< mlir::LocationAttr > > maybeStringToLocation(llvm::StringRef spelling, bool skipParsing, mlir::StringAttr &locatorFilenameCache, FileLineColLoc &fileLineColLocCache, MLIRContext *context)
constexpr const char * dutAnnoClass
constexpr const char * omirAnnoClass
constexpr const char * omirTrackerAnnoClass
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.
bool isOMIRStringEncodedPassthrough(StringRef type)
Check if an OMIR type is a string-encoded value that the FIRRTL dialect simply passes through as a st...
constexpr const char * omirFileAnnoClass
StringAttr getInnerSymName(Operation *op)
Return the StringAttr for the inner_sym name, if it exists.
Definition: FIRRTLOps.h:107
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
State threaded through functions for resolving and applying annotations.
The namespace of a CircuitOp, generally inhabited by modules.
Definition: Namespace.h:24
A data structure that caches and provides absolute paths to module instances in the IR.