CIRCT  19.0.0git
ResolvePaths.cpp
Go to the documentation of this file.
1 //===- ResolvePaths.cpp - Resolve path operations ---------------*- 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 contains the ResolvePathsPass.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "PassDetails.h"
16 #include "mlir/IR/ImplicitLocOpBuilder.h"
17 
18 using namespace circt;
19 using namespace firrtl;
20 
21 namespace {
22 struct PathResolver {
23  PathResolver(CircuitOp circuit, InstanceGraph &instanceGraph)
24  : circuit(circuit), symbolTable(circuit), instanceGraph(instanceGraph),
25  instancePathCache(instanceGraph), hierPathCache(circuit, symbolTable),
26  builder(OpBuilder::atBlockBegin(circuit->getBlock())) {}
27 
28  /// This function will find the operation targeted and create a hierarchical
29  /// path operation if needed. If the target is resolved, the op will either
30  /// be a reference to the HierPathOp, or null if no HierPathOp was needed.
31  LogicalResult resolveHierPath(Location loc, FModuleOp owningModule,
32  const AnnoPathValue &target,
33  bool canDisambiguate,
34  SmallVectorImpl<FlatSymbolRefAttr> &results) {
35 
36  // We want to root this path at the top level module, or in the case of an
37  // unreachable module, we settle for as high as we can get.
38  auto module = target.ref.getModule();
39  if (!target.instances.empty())
40  module = target.instances.front()->getParentOfType<FModuleLike>();
41  auto *node = instanceGraph[module];
42  bool needsDisambiguation = false;
43  while (true) {
44  // If the path is rooted at the owning module, we're done.
45  if (node->getModule() == owningModule)
46  break;
47  // If there are no more parents, then the path op lives in a different
48  // hierarchy than the HW object it references, which is an error.
49  if (node->noUses())
50  return emitError(loc)
51  << "unable to resolve path relative to owning module "
52  << owningModule.getModuleNameAttr();
53  // If there is more than one instance of this module, then the path
54  // operation is ambiguous.
55  if (!node->hasOneUse()) {
56  // If we can disambiguate by duplicating paths, stop here, otherwise
57  // this is an error.
58  if (canDisambiguate) {
59  needsDisambiguation = true;
60  break;
61  }
62 
63  auto diag = emitError(loc) << "unable to uniquely resolve target due "
64  "to multiple instantiation";
65  for (auto *use : node->uses())
66  diag.attachNote(use->getInstance().getLoc()) << "instance here";
67  return diag;
68  }
69  node = (*node->usesBegin())->getParent();
70  }
71 
72  // Find the minimal uniquely-identifying path to the operation. We scan
73  // through the list of instances looking for the first module which is
74  // multiply instantiated. We will start our HierPathOp at this instance.
75  auto *it = llvm::find_if(target.instances, [&](InstanceOp instance) {
76  auto *node = instanceGraph.lookup(instance.getReferencedModuleNameAttr());
77  return !node->hasOneUse();
78  });
79 
80  // If the path is empty and we don't need to disambiguate, then this is a
81  // local reference and we should not construct a HierPathOp.
82  auto pathLength = std::distance(it, target.instances.end());
83  if (pathLength == 0 && !needsDisambiguation) {
84  return success();
85  }
86 
87  // Transform the instances into a list of FlatSymbolRefs.
88  SmallVector<Attribute> insts;
89  insts.reserve(pathLength);
90  std::transform(it, target.instances.end(), std::back_inserter(insts),
91  [&](InstanceOp instance) {
92  return OpAnnoTarget(instance).getNLAReference(
93  namespaces[instance->getParentOfType<FModuleLike>()]);
94  });
95 
96  // Push a reference to the current module.
97  insts.push_back(
98  FlatSymbolRefAttr::get(target.ref.getModule().getModuleNameAttr()));
99 
100  // If we need to disambiguate this path, create new paths with every unique
101  // prefix from the owning module to the point where the path suffix starts.
102  if (needsDisambiguation) {
103  ArrayRef<igraph::InstancePath> instancePaths =
104  instancePathCache.getAbsolutePaths(node->getModule(),
105  instanceGraph[owningModule]);
106  for (auto instancePath : instancePaths) {
107  // For each unique prefix from the owning module, build up a new path.
108  SmallVector<Attribute> fullInsts;
109  fullInsts.reserve(instancePath.size() + target.instances.size() + 1);
110  std::transform(
111  instancePath.begin(), instancePath.end(),
112  std::back_inserter(fullInsts),
113  [&](igraph::InstanceOpInterface inst) {
114  return OpAnnoTarget(cast<InstanceOp>(inst))
115  .getNLAReference(
116  namespaces[inst->getParentOfType<FModuleLike>()]);
117  });
118 
119  // If present, include the start of the target's instance path to tie
120  // the prefix to the suffix.
121  if (!target.instances.empty())
122  for (InstanceOp inst : target.instances)
123  fullInsts.push_back(OpAnnoTarget(inst).getNLAReference(
124  namespaces[inst->getParentOfType<FModuleOp>()]));
125 
126  // Include the suffix, starting from the module which was multiply
127  // instantiated.
128  fullInsts.append(insts);
129 
130  // Return the disambiguated hierchical path.
131  auto instAttr = ArrayAttr::get(circuit.getContext(), fullInsts);
132  results.push_back(hierPathCache.getRefFor(instAttr));
133  }
134  } else {
135  // Return the one hierchical path.
136  auto instAttr = ArrayAttr::get(circuit.getContext(), insts);
137  results.push_back(hierPathCache.getRefFor(instAttr));
138  }
139 
140  return success();
141  }
142 
143  LogicalResult resolve(OwningModuleCache &cache, UnresolvedPathOp unresolved) {
144  auto loc = unresolved.getLoc();
145  ImplicitLocOpBuilder b(loc, unresolved);
146  auto *context = b.getContext();
147 
148  /// Spelling takes the form
149  /// "OMReferenceTarget:~Circuit|Foo/bar:Bar>member".
150  auto target = unresolved.getTarget();
151 
152  // OMDeleted nodes do not have a target, so it is impossible to resolve
153  // them to a real path. We create a special constant for these path
154  // values.
155  if (target.consume_front("OMDeleted:")) {
156  if (!target.empty())
157  return emitError(loc, "OMDeleted references can not have targets");
158  // Deleted targets are turned into OMReference targets with a dangling
159  // id
160  // - i.e. the id is not attached to any target.
161  auto targetKind = TargetKindAttr::get(context, TargetKind::Reference);
162  auto id = DistinctAttr::create(UnitAttr::get(context));
163  auto resolved = b.create<PathOp>(targetKind, id);
164  unresolved->replaceAllUsesWith(resolved);
165  unresolved.erase();
166  return success();
167  }
168 
169  // Parse the OM target kind.
170  TargetKind targetKind;
171  if (target.consume_front("OMDontTouchedReferenceTarget")) {
172  targetKind = TargetKind::DontTouch;
173  } else if (target.consume_front("OMInstanceTarget")) {
174  targetKind = TargetKind::Instance;
175  } else if (target.consume_front("OMMemberInstanceTarget")) {
176  targetKind = TargetKind::MemberInstance;
177  } else if (target.consume_front("OMMemberReferenceTarget")) {
178  targetKind = TargetKind::MemberReference;
179  } else if (target.consume_front("OMReferenceTarget")) {
180  targetKind = TargetKind::Reference;
181  } else {
182  return emitError(loc)
183  << "unknown or missing OM reference type in target string: \""
184  << target << "\"";
185  }
186  auto targetKindAttr = TargetKindAttr::get(context, targetKind);
187 
188  // Parse the target.
189  if (!target.consume_front(":"))
190  return emitError(loc, "expected ':' in target string");
191 
192  auto token = tokenizePath(target);
193  if (!token)
194  return emitError(loc)
195  << "cannot tokenize annotation path \"" << target << "\"";
196 
197  // Resolve the target to a target.
198  auto path = resolveEntities(*token, circuit, symbolTable, targetCache);
199  if (!path)
200  return failure();
201 
202  // Make sure that we are targeting a leaf of the operation. That way lower
203  // types can't split a single reference into many, and cause ambiguity. If
204  // we are targeting a module, the type will be null.
205  if (Type targetType = path->ref.getType()) {
206  auto fieldId = path->fieldIdx;
207  auto baseType = dyn_cast<FIRRTLBaseType>(targetType);
208  if (!baseType)
209  return emitError(loc, "unable to target non-hardware type ")
210  << targetType;
211  targetType = hw::FieldIdImpl::getFinalTypeByFieldID(baseType, fieldId);
212  if (isa<BundleType, FVectorType>(targetType))
213  return emitError(loc, "unable to target aggregate type ") << targetType;
214  }
215 
216  auto owningModule = cache.lookup(unresolved);
217  StringRef moduleName = "nullptr";
218  if (owningModule)
219  moduleName = owningModule.getModuleName();
220  if (!owningModule)
221  return unresolved->emitError("path does not have a single owning module");
222 
223  // If the UnresolvedPathOp is ambiguous due to multiple instantiation, we
224  // can disambiguate by expanding it into multiple unambiguous paths, but
225  // only if the UnresolvedPathOp is already being used in a list.
226  bool canDisambiguate =
227  !unresolved->use_empty() &&
228  llvm::all_of(unresolved->getUsers(),
229  [](Operation *user) { return isa<ListCreateOp>(user); });
230 
231  // Resolve a unique path to the operation in question, or multiple paths if
232  // necessary and it is legal to disambiguate.
233  SmallVector<FlatSymbolRefAttr> hierPathNames;
234  if (failed(resolveHierPath(loc, owningModule, *path, canDisambiguate,
235  hierPathNames)))
236  return failure();
237 
238  auto createAnnotation = [&](std::optional<FlatSymbolRefAttr> hierPathName) {
239  // Create a unique ID.
240  auto id = DistinctAttr::create(UnitAttr::get(context));
241 
242  // Create the annotation.
243  NamedAttrList fields;
244  fields.append("id", id);
245  fields.append("class", StringAttr::get(context, "circt.tracker"));
246  if (hierPathName)
247  fields.append("circt.nonlocal", hierPathName.value());
248  if (path->fieldIdx != 0)
249  fields.append("circt.fieldID", b.getI64IntegerAttr(path->fieldIdx));
250 
251  return DictionaryAttr::get(context, fields);
252  };
253 
254  // Create the annotation(s).
255  SmallVector<Attribute> annotations;
256  if (hierPathNames.empty()) {
257  annotations.push_back(createAnnotation(std::nullopt));
258  } else {
259  for (auto hierPathName : hierPathNames)
260  annotations.push_back(createAnnotation(hierPathName));
261  }
262 
263  // Attach the annotation(s) to the target.
264  auto annoTarget = path->ref;
265  auto targetAnnotations = annoTarget.getAnnotations();
266  targetAnnotations.addAnnotations(annotations);
267  if (targetKindAttr.getValue() == TargetKind::DontTouch)
268  targetAnnotations.addDontTouch();
269  annoTarget.setAnnotations(targetAnnotations);
270 
271  // Create the path operation(s).
272  size_t lastAnnotationIdx = annotations.size() - 1;
273  for (auto [i, annotation] : llvm::enumerate(annotations)) {
274  // Create a PathOp using the id in the annotation we added to the target.
275  auto dictAttr = cast<DictionaryAttr>(annotation);
276  auto id = cast<DistinctAttr>(dictAttr.get("id"));
277  auto resolved = b.create<PathOp>(targetKindAttr, id);
278 
279  if (!canDisambiguate || i == lastAnnotationIdx) {
280  // If we didn't need to disambiguate, always just replace the unresolved
281  // path with the resolved path. If we did disambiguate, and this is the
282  // last path, replace the unresolved path. In the case of multiple
283  // paths, we save the final replacement for last so the other paths can
284  // find the uses to update.
285  unresolved->replaceAllUsesWith(resolved);
286  unresolved.erase();
287  } else {
288  // If we are handling one of several paths after disambiguation, insert
289  // a use of the new path next to where the unresolved path was used.
290  for (auto &use : unresolved->getUses()) {
291  assert(isa<ListCreateOp>(use.getOwner()));
292  use.getOwner()->insertOperands(use.getOperandNumber(),
293  resolved.getResult());
294  }
295  }
296  }
297  return success();
298  }
299 
300  CircuitOp circuit;
301  SymbolTable symbolTable;
302  CircuitTargetCache targetCache;
303  InstanceGraph &instanceGraph;
304  InstancePathCache instancePathCache;
305  hw::InnerSymbolNamespaceCollection namespaces;
306  HierPathCache hierPathCache;
307  OpBuilder builder;
308 };
309 } // end anonymous namespace
310 
311 //===----------------------------------------------------------------------===//
312 // Pass Infrastructure
313 //===----------------------------------------------------------------------===//
314 
315 namespace {
316 struct ResolvePathsPass : public ResolvePathsBase<ResolvePathsPass> {
317  void runOnOperation() override;
318 };
319 } // end anonymous namespace
320 
321 void ResolvePathsPass::runOnOperation() {
322  auto circuit = getOperation();
323  auto &instanceGraph = getAnalysis<InstanceGraph>();
324  PathResolver resolver(circuit, instanceGraph);
325  OwningModuleCache cache(instanceGraph);
326  auto result = circuit.walk([&](UnresolvedPathOp unresolved) {
327  if (failed(resolver.resolve(cache, unresolved))) {
328  signalPassFailure();
329  return WalkResult::interrupt();
330  }
331  return WalkResult::advance();
332  });
333  if (result.wasInterrupted())
334  signalPassFailure();
335  markAnalysesPreserved<InstanceGraph>();
336 }
337 
338 std::unique_ptr<mlir::Pass> circt::firrtl::createResolvePathsPass() {
339  return std::make_unique<ResolvePathsPass>();
340 }
assert(baseType &&"element must be base type")
Builder builder
This graph tracks modules and where they are instantiated.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
std::unique_ptr< mlir::Pass > createResolvePathsPass()
std::optional< AnnoPathValue > resolveEntities(TokenAnnoTarget path, CircuitOp circuit, SymbolTable &symTbl, CircuitTargetCache &cache)
Convert a parsed target string to a resolved target structure.
std::optional< TokenAnnoTarget > tokenizePath(StringRef origTarget)
Parse a FIRRTL annotation path into its constituent parts.
::mlir::Type getFinalTypeByFieldID(Type type, uint64_t fieldID)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
SmallVector< InstanceOp > instances
FModuleLike getModule() const
Get the parent module of the target.
Cache AnnoTargets for a circuit's modules, walked as needed.
A cache of existing HierPathOps, mostly used to facilitate HierPathOp reuse.
This represents an annotation targeting a specific operation.
This implements an analysis to determine which module owns a given path operation.
FModuleOp lookup(ClassOp classOp)
Return this operation's owning module.
A data structure that caches and provides absolute paths to module instances in the IR.