CIRCT  19.0.0git
ResolveTraces.cpp
Go to the documentation of this file.
1 //===- ResolveTraces.cpp - Resolve TraceAnnotations -------------*- 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 // Find any TraceAnnotations in the design, update their targets, and write the
9 // annotations out to an output annotation file.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "PassDetails.h"
22 #include "circt/Dialect/HW/HWOps.h"
23 #include "circt/Dialect/SV/SVOps.h"
24 #include "mlir/IR/ImplicitLocOpBuilder.h"
25 #include "llvm/ADT/APSInt.h"
26 #include "llvm/ADT/TypeSwitch.h"
27 #include "llvm/Support/Debug.h"
28 #include "llvm/Support/JSON.h"
29 
30 #define DEBUG_TYPE "firrtl-resolve-traces"
31 
32 using namespace circt;
33 using namespace firrtl;
34 
35 /// Expand a TraceNameAnnotation (which has don't touch semantics) into a
36 /// TraceAnnotation (which does NOT have don't touch semantics) and separate
37 /// DontTouchAnnotations for targets that are not modules, external modules, or
38 /// instances (as these targets are not valid for a don't touch).
39 LogicalResult circt::firrtl::applyTraceName(const AnnoPathValue &target,
40  DictionaryAttr anno,
41  ApplyState &state) {
42 
43  auto *context = anno.getContext();
44 
45  NamedAttrList trace, dontTouch;
46  for (auto namedAttr : anno.getValue()) {
47  if (namedAttr.getName() == "class") {
48  trace.append("class", StringAttr::get(context, traceAnnoClass));
49  dontTouch.append("class", StringAttr::get(context, dontTouchAnnoClass));
50  continue;
51  }
52  trace.append(namedAttr);
53 
54  // When we see the "target", check to see if this is not targeting a module,
55  // extmodule, or instance (as these are invalid "don't touch" targets). If
56  // it is not, then add a DontTouchAnnotation.
57  if (namedAttr.getName() == "target" &&
58  !target.isOpOfType<FModuleOp, FExtModuleOp, InstanceOp>()) {
59  dontTouch.append(namedAttr);
60  state.addToWorklistFn(DictionaryAttr::getWithSorted(context, dontTouch));
61  }
62  }
63 
64  state.addToWorklistFn(DictionaryAttr::getWithSorted(context, trace));
65 
66  return success();
67 }
68 
69 struct ResolveTracesPass : public ResolveTracesBase<ResolveTracesPass> {
70  using ResolveTracesBase::outputAnnotationFilename;
71 
72  void runOnOperation() override;
73 
74 private:
75  /// Stores a pointer to an NLA Table. This is populated during
76  /// runOnOperation.
78 
79  /// Stores a pointer to an inner symbol table collection.
80  hw::InnerSymbolTableCollection *istc;
81 
82  /// Global symbol index used for substitutions, e.g., "{{42}}". This value is
83  /// the _next_ index that will be used.
84  unsigned symbolIdx = 0;
85 
86  /// Map of symbol to symbol index. This is used to reuse symbol
87  /// substitutions.
88  DenseMap<Attribute, unsigned> symbolMap;
89 
90  /// Symbol substitutions for the JSON verbatim op.
91  SmallVector<Attribute> symbols;
92 
93  /// Get a symbol index and update symbol datastructures.
94  unsigned getSymbolIndex(Attribute attr) {
95  auto iterator = symbolMap.find(attr);
96  if (iterator != symbolMap.end())
97  return iterator->getSecond();
98 
99  auto idx = symbolIdx++;
100  symbolMap.insert({attr, idx});
101  symbols.push_back(attr);
102 
103  return idx;
104  }
105 
106  /// Convert an annotation path to a string with symbol substitutions.
107  void buildTarget(AnnoPathValue &path, SmallString<64> &newTarget) {
108 
109  auto addSymbol = [&](Attribute attr) -> void {
110  newTarget.append("{{");
111  Twine(getSymbolIndex(attr)).toVector(newTarget);
112  newTarget.append("}}");
113  };
114 
115  newTarget.append("~");
116  newTarget.append(
117  path.ref.getModule()->getParentOfType<CircuitOp>().getName());
118  newTarget.append("|");
119 
120  if (path.isLocal()) {
121  addSymbol(
122  FlatSymbolRefAttr::get(path.ref.getModule().getModuleNameAttr()));
123  } else {
124  addSymbol(FlatSymbolRefAttr::get(path.instances.front()
125  ->getParentOfType<FModuleLike>()
126  .getModuleNameAttr()));
127  }
128 
129  for (auto inst : path.instances) {
130  newTarget.append("/");
131  addSymbol(hw::InnerRefAttr::get(
132  inst->getParentOfType<FModuleLike>().getModuleNameAttr(),
133  inst.getInnerSymAttr().getSymName()));
134  newTarget.append(":");
135  addSymbol(inst.getModuleNameAttr());
136  }
137 
138  // If this targets a module or an instance, then we're done. There is no
139  // "reference" part of the FIRRTL target.
140  if (path.ref.isa<OpAnnoTarget>() &&
141  path.isOpOfType<FModuleOp, FExtModuleOp, InstanceOp>())
142  return;
143 
144  newTarget.append(">");
145  auto innerSymStr =
146  TypeSwitch<AnnoTarget, StringAttr>(path.ref)
147  .Case<PortAnnoTarget>([&](PortAnnoTarget portTarget) {
148  return hw::InnerSymbolTable::getInnerSymbol(hw::InnerSymTarget(
149  portTarget.getPortNo(), portTarget.getModule(), 0));
150  })
151  .Case<OpAnnoTarget>([&](OpAnnoTarget opTarget) {
152  return hw::InnerSymbolTable::getInnerSymbol(opTarget.getOp());
153  })
154  .Default([](auto) {
155  assert(false && "unexpected annotation target type");
156  return StringAttr{};
157  });
158  addSymbol(hw::InnerRefAttr::get(path.ref.getModule().getModuleNameAttr(),
159  innerSymStr));
160 
161  auto type = dyn_cast<FIRRTLBaseType>(path.ref.getType());
162  assert(type && "expected a FIRRTLBaseType");
163  auto targetFieldID = path.fieldIdx;
164  while (targetFieldID) {
166  .Case<FVectorType>([&](FVectorType vector) {
167  auto index = vector.getIndexForFieldID(targetFieldID);
168  newTarget.append("[");
169  Twine(index).toVector(newTarget);
170  newTarget.append("]");
171  type = vector.getElementType();
172  targetFieldID -= vector.getFieldID(index);
173  })
174  .template Case<BundleType>([&](BundleType bundle) {
175  auto index = bundle.getIndexForFieldID(targetFieldID);
176  newTarget.append(".");
177  newTarget.append(bundle.getElementName(index));
178  type = bundle.getElementType(index);
179  targetFieldID -= bundle.getFieldID(index);
180  })
181  .Default([&](auto) { targetFieldID = 0; });
182  }
183  }
184 
185  /// Internal implementation that updates an Annotation to add a "target" field
186  /// based on the current location of the annotation in the circuit. The value
187  /// of the "target" will be a local target if the Annotation is local and a
188  /// non-local target if the Annotation is non-local.
189  AnnoPathValue updateTargetImpl(Annotation &anno, FModuleLike &module,
190  FIRRTLBaseType type, hw::InnerRefAttr name,
191  AnnoTarget target) {
192  SmallString<64> newTarget("~");
193  newTarget.append(module->getParentOfType<CircuitOp>().getName());
194  newTarget.append("|");
195 
196  SmallVector<InstanceOp> instances;
197 
198  if (auto nla = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
199  hw::HierPathOp path = nlaTable->getNLA(nla.getAttr());
200  for (auto part : path.getNamepath().getValue().drop_back()) {
201  auto inst = cast<hw::InnerRefAttr>(part);
202  instances.push_back(dyn_cast<InstanceOp>(
203  istc->getInnerSymbolTable(nlaTable->getModule(inst.getModule()))
204  .lookupOp(inst.getName())));
205  }
206  }
207 
208  AnnoPathValue path(instances, target, anno.getFieldID());
209 
210  return path;
211  }
212 
213  /// Add a "target" field to a port Annotation that indicates the current
214  /// location of the port in the circuit.
215  std::optional<AnnoPathValue> updatePortTarget(FModuleLike &module,
216  Annotation &anno,
217  unsigned portIdx,
218  hw::InnerRefAttr innerRef) {
219  auto type = getBaseType(type_cast<FIRRTLType>(module.getPortType(portIdx)));
220  return updateTargetImpl(anno, module, type, innerRef,
221  PortAnnoTarget(module, portIdx));
222  }
223 
224  /// Add a "target" field to an Annotation that indicates the current location
225  /// of a component in the circuit.
226  std::optional<AnnoPathValue> updateTarget(FModuleLike &module, Operation *op,
227  Annotation &anno,
228  hw::InnerRefAttr innerRef) {
229 
230  // Get the type of the operation either by checking for the
231  // result targeted by symbols on it (which are used to track the op)
232  // or by inspecting its single result.
233  auto is = dyn_cast<hw::InnerSymbolOpInterface>(op);
234  Type type;
235  if (is && is.getTargetResult())
236  type = is.getTargetResult().getType();
237  else {
238  if (op->getNumResults() != 1)
239  return std::nullopt;
240  type = op->getResultTypes().front();
241  }
242 
243  auto baseType = getBaseType(type_cast<FIRRTLType>(type));
244  return updateTargetImpl(anno, module, baseType, innerRef, OpAnnoTarget(op));
245  }
246 
247  /// Add a "target" field to an Annotation on a Module that indicates the
248  /// current location of the module. This will be local or non-local depending
249  /// on the Annotation.
250  std::optional<AnnoPathValue> updateModuleTarget(FModuleLike &module,
251  Annotation &anno) {
252  SmallVector<InstanceOp> instances;
253 
254  if (auto nla = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
255  hw::HierPathOp path = nlaTable->getNLA(nla.getAttr());
256  for (auto part : path.getNamepath().getValue().drop_back()) {
257  auto inst = cast<hw::InnerRefAttr>(part);
258  instances.push_back(cast<InstanceOp>(
259  istc->getInnerSymbolTable(nlaTable->getModule(inst.getModule()))
260  .lookupOp(inst.getName())));
261  }
262  }
263 
264  AnnoPathValue path(instances, OpAnnoTarget(module), 0);
265 
266  return path;
267  }
268 };
269 
271  LLVM_DEBUG(
272  llvm::dbgs() << "==----- Running ResolveTraces "
273  "-----------------------------------------------===\n"
274  << "Annotation Modifications:\n");
275 
276  // Grab the circuit (as this is used a few times below).
277  CircuitOp circuit = getOperation();
278  MLIRContext *context = circuit.getContext();
279 
280  // Populate pointer datastructures.
281  nlaTable = &getAnalysis<NLATable>();
282  istc = &getAnalysis<hw::InnerSymbolTableCollection>();
283 
284  // Function to find all Trace Annotations in the circuit, add a "target" field
285  // to them indicating the current local/non-local target of the operation/port
286  // the Annotation is attached to, copy the annotation into an
287  // "outputAnnotations" return vector, and delete the original Annotation. If
288  // a component or port is targeted by a Trace Annotation it will be given a
289  // symbol to prevent the output Trace Annotation from being made invalid by a
290  // later optimization.
291  auto onModule = [&](FModuleLike moduleLike) {
292  // Output Trace Annotations from this module only.
293  SmallVector<std::pair<Annotation, AnnoPathValue>> outputAnnotations;
294 
295  // A lazily constructed module namespace.
296  std::optional<hw::InnerSymbolNamespace> moduleNamespace;
297 
298  // Return a cached module namespace, lazily constructing it if needed.
299  auto getNamespace = [&](auto module) -> hw::InnerSymbolNamespace & {
300  if (!moduleNamespace)
301  moduleNamespace = hw::InnerSymbolNamespace(module);
302  return *moduleNamespace;
303  };
304 
305  // Visit the module.
306  AnnotationSet::removeAnnotations(moduleLike, [&](Annotation anno) {
307  if (!anno.isClass(traceAnnoClass))
308  return false;
309 
310  auto path = updateModuleTarget(moduleLike, anno);
311  if (!path)
312  return false;
313 
314  outputAnnotations.push_back({anno, *path});
315  return true;
316  });
317 
318  // Visit port annotations.
320  moduleLike, [&](unsigned portIdx, Annotation anno) {
321  if (!anno.isClass(traceAnnoClass))
322  return false;
323 
324  hw::InnerRefAttr innerRef =
325  getInnerRefTo(moduleLike, portIdx, getNamespace);
326  auto path = updatePortTarget(moduleLike, anno, portIdx, innerRef);
327  if (!path)
328  return false;
329 
330  outputAnnotations.push_back({anno, *path});
331  return true;
332  });
333 
334  // Visit component annotations.
335  moduleLike.walk([&](Operation *component) {
336  AnnotationSet::removeAnnotations(component, [&](Annotation anno) {
337  if (!anno.isClass(traceAnnoClass))
338  return false;
339 
340  hw::InnerRefAttr innerRef = getInnerRefTo(component, getNamespace);
341  auto path = updateTarget(moduleLike, component, anno, innerRef);
342  if (!path)
343  return false;
344 
345  outputAnnotations.push_back({anno, *path});
346  return true;
347  });
348  });
349 
350  return outputAnnotations;
351  };
352 
353  // Function to append one vector after another. This is used to merge results
354  // from parallel executions of "onModule".
355  auto appendVecs = [](auto &&a, auto &&b) {
356  a.append(b.begin(), b.end());
357  return std::forward<decltype(a)>(a);
358  };
359 
360  // Process all the modules in parallel or serially, depending on the
361  // multithreading context.
362  SmallVector<FModuleLike, 0> mods(circuit.getOps<FModuleLike>());
363  auto outputAnnotations = transformReduce(
364  context, mods, SmallVector<std::pair<Annotation, AnnoPathValue>>{},
365  appendVecs, onModule);
366 
367  // Do not generate an output Annotation file if no Annotations exist.
368  if (outputAnnotations.empty())
369  return markAllAnalysesPreserved();
370 
371  // Write out all the Trace Annotations to a JSON buffer.
372  std::string jsonBuffer;
373  llvm::raw_string_ostream jsonStream(jsonBuffer);
374  llvm::json::OStream json(jsonStream, /*IndentSize=*/2);
375  json.arrayBegin();
376  for (auto &[anno, path] : outputAnnotations) {
377  json.objectBegin();
378  json.attribute("class", anno.getClass());
379  SmallString<64> targetStr;
380  buildTarget(path, targetStr);
381  LLVM_DEBUG({
382  llvm::dbgs()
383  << " - chiselTarget: "
384  << anno.getDict().getAs<StringAttr>("chiselTarget").getValue() << "\n"
385  << " target: " << targetStr << "\n"
386  << " translated: " << path << "\n";
387  });
388  json.attribute("target", targetStr);
389  json.attribute("chiselTarget",
390  anno.getMember<StringAttr>("chiselTarget").getValue());
391  json.objectEnd();
392  }
393  json.arrayEnd();
394 
395  LLVM_DEBUG({
396  llvm::dbgs() << "Symbols:\n";
397  for (auto [id, symbol] : llvm::enumerate(symbols))
398  llvm::errs() << " - " << id << ": " << symbol << "\n";
399  });
400 
401  // Write the JSON-encoded Trace Annotation to a file called
402  // "$circuitName.anno.json".
403  auto builder = ImplicitLocOpBuilder::atBlockBegin(UnknownLoc::get(context),
404  circuit.getBodyBlock());
405 
406  StringAttr fileAttr;
407  if (this->outputAnnotationFilename.empty())
408  fileAttr = builder.getStringAttr(circuit.getName() + ".anno.json");
409  else
410  fileAttr = builder.getStringAttr(outputAnnotationFilename);
411 
412  builder.create<emit::FileOp>(fileAttr, [&] {
413  builder.create<sv::VerbatimOp>(jsonBuffer, ValueRange{},
414  builder.getArrayAttr(symbols));
415  });
416 
417  return markAllAnalysesPreserved();
418 }
419 
420 std::unique_ptr<mlir::Pass>
421 circt::firrtl::createResolveTracesPass(StringRef outputAnnotationFilename) {
422  auto pass = std::make_unique<ResolveTracesPass>();
423  if (!outputAnnotationFilename.empty())
424  pass->outputAnnotationFilename = outputAnnotationFilename.str();
425  return pass;
426 }
assert(baseType &&"element must be base type")
Builder builder
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
static bool removePortAnnotations(Operation *module, llvm::function_ref< bool(unsigned, Annotation)> predicate)
Remove all port annotations from a module or extmodule for which predicate returns true.
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 table tracks nlas and what modules participate in them.
Definition: NLATable.h:29
hw::HierPathOp getNLA(StringAttr name)
Resolve a symbol to an NLA.
Definition: NLATable.cpp:48
FModuleLike getModule(StringAttr name)
Resolve a symbol to a Module.
Definition: NLATable.cpp:53
static StringAttr getInnerSymbol(Operation *op)
Get InnerSymbol for an operation.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
Definition: FIRRTLUtils.h:217
std::unique_ptr< mlir::Pass > createResolveTracesPass(mlir::StringRef outputAnnotationFilename="")
constexpr const char * traceAnnoClass
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.
LogicalResult applyTraceName(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
Expand a TraceNameAnnotation (which has don't touch semantics) into a TraceAnnotation (which does NOT...
static ResultTy transformReduce(MLIRContext *context, IterTy begin, IterTy end, ResultTy init, ReduceFuncTy reduce, TransformFuncTy transform)
Wrapper for llvm::parallelTransformReduce that performs the transform_reduce serially when MLIR multi...
Definition: FIRRTLUtils.h:289
constexpr const char * dontTouchAnnoClass
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
void runOnOperation() override
AnnoPathValue updateTargetImpl(Annotation &anno, FModuleLike &module, FIRRTLBaseType type, hw::InnerRefAttr name, AnnoTarget target)
Internal implementation that updates an Annotation to add a "target" field based on the current locat...
std::optional< AnnoPathValue > updateTarget(FModuleLike &module, Operation *op, Annotation &anno, hw::InnerRefAttr innerRef)
Add a "target" field to an Annotation that indicates the current location of a component in the circu...
NLATable * nlaTable
Stores a pointer to an NLA Table.
hw::InnerSymbolTableCollection * istc
Stores a pointer to an inner symbol table collection.
std::optional< AnnoPathValue > updateModuleTarget(FModuleLike &module, Annotation &anno)
Add a "target" field to an Annotation on a Module that indicates the current location of the module.
unsigned getSymbolIndex(Attribute attr)
Get a symbol index and update symbol datastructures.
std::optional< AnnoPathValue > updatePortTarget(FModuleLike &module, Annotation &anno, unsigned portIdx, hw::InnerRefAttr innerRef)
Add a "target" field to a port Annotation that indicates the current location of the port in the circ...
void buildTarget(AnnoPathValue &path, SmallString< 64 > &newTarget)
Convert an annotation path to a string with symbol substitutions.
SmallVector< Attribute > symbols
Symbol substitutions for the JSON verbatim op.
DenseMap< Attribute, unsigned > symbolMap
Map of symbol to symbol index.
SmallVector< InstanceOp > instances
An annotation target is used to keep track of something that is targeted by an Annotation.
FIRRTLType getType() const
Get the type of the target.
Operation * getOp() const
FModuleLike getModule() const
Get the parent module of the target.
State threaded through functions for resolving and applying annotations.
This represents an annotation targeting a specific operation.
This represents an annotation targeting a specific port of a module, memory, or instance.