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 
17 #include "mlir/IR/ImplicitLocOpBuilder.h"
18 #include "mlir/Pass/Pass.h"
19 
20 namespace circt {
21 namespace firrtl {
22 #define GEN_PASS_DEF_RESOLVEPATHS
23 #include "circt/Dialect/FIRRTL/Passes.h.inc"
24 } // namespace firrtl
25 } // namespace circt
26 
27 using namespace circt;
28 using namespace firrtl;
29 
30 namespace {
31 struct PathResolver {
32  PathResolver(CircuitOp circuit, InstanceGraph &instanceGraph)
33  : circuit(circuit), symbolTable(circuit), instanceGraph(instanceGraph),
34  instancePathCache(instanceGraph), hierPathCache(circuit, symbolTable),
35  builder(OpBuilder::atBlockBegin(circuit->getBlock())) {}
36 
37  /// This function will find the operation targeted and create a hierarchical
38  /// path operation if needed. If the target is resolved, the op will either
39  /// be a reference to the HierPathOp, or null if no HierPathOp was needed.
40  LogicalResult resolveHierPath(Location loc, FModuleOp owningModule,
41  const AnnoPathValue &target,
42  FlatSymbolRefAttr &result) {
43 
44  // We want to root this path at the top level module, or in the case of an
45  // unreachable module, we settle for as high as we can get.
46  auto module = target.ref.getModule();
47  if (!target.instances.empty())
48  module = target.instances.front()->getParentOfType<FModuleLike>();
49  auto *node = instanceGraph[module];
50  while (true) {
51  // If the path is rooted at the owning module, we're done.
52  if (node->getModule() == owningModule)
53  break;
54  // If there are no more parents, then the path op lives in a different
55  // hierarchy than the HW object it references, which is an error.
56  if (node->noUses())
57  return emitError(loc)
58  << "unable to resolve path relative to owning module "
59  << owningModule.getModuleNameAttr();
60  // If there is more than one instance of this module, then the path
61  // operation is ambiguous, which is a warning. This should become an error
62  // once user code is properly enforcing single instantiation, but in
63  // practice this generates the same outputs as the original flow for now.
64  // See https://github.com/llvm/circt/issues/7128.
65  if (!node->hasOneUse()) {
66  auto diag = emitWarning(loc) << "unable to uniquely resolve target due "
67  "to multiple instantiation";
68  for (auto *use : node->uses())
69  diag.attachNote(use->getInstance().getLoc()) << "instance here";
70  }
71  node = (*node->usesBegin())->getParent();
72  }
73 
74  // If the path is empty then this is a local reference and we should not
75  // construct a HierPathOp.
76  if (target.instances.empty()) {
77  return success();
78  }
79 
80  // Transform the instances into a list of FlatSymbolRefs.
81  SmallVector<Attribute> insts;
82  insts.reserve(target.instances.size());
83  std::transform(target.instances.begin(), target.instances.end(),
84  std::back_inserter(insts), [&](InstanceOp instance) {
85  return OpAnnoTarget(instance).getNLAReference(
86  namespaces[instance->getParentOfType<FModuleLike>()]);
87  });
88 
89  // Push a reference to the current module.
90  insts.push_back(
91  FlatSymbolRefAttr::get(target.ref.getModule().getModuleNameAttr()));
92 
93  // Return the hierchical path.
94  auto instAttr = ArrayAttr::get(circuit.getContext(), insts);
95 
96  result = hierPathCache.getRefFor(instAttr);
97 
98  return success();
99  }
100 
101  LogicalResult resolve(OwningModuleCache &cache, UnresolvedPathOp unresolved) {
102  auto loc = unresolved.getLoc();
103  ImplicitLocOpBuilder b(loc, unresolved);
104  auto *context = b.getContext();
105 
106  /// Spelling takes the form
107  /// "OMReferenceTarget:~Circuit|Foo/bar:Bar>member".
108  auto target = unresolved.getTarget();
109 
110  // OMDeleted nodes do not have a target, so it is impossible to resolve
111  // them to a real path. We create a special constant for these path
112  // values.
113  if (target.consume_front("OMDeleted:")) {
114  if (!target.empty())
115  return emitError(loc, "OMDeleted references can not have targets");
116  // Deleted targets are turned into OMReference targets with a dangling
117  // id
118  // - i.e. the id is not attached to any target.
119  auto targetKind = TargetKindAttr::get(context, TargetKind::Reference);
120  auto id = DistinctAttr::create(UnitAttr::get(context));
121  auto resolved = b.create<PathOp>(targetKind, id);
122  unresolved->replaceAllUsesWith(resolved);
123  unresolved.erase();
124  return success();
125  }
126 
127  // Parse the OM target kind.
128  TargetKind targetKind;
129  if (target.consume_front("OMDontTouchedReferenceTarget")) {
130  targetKind = TargetKind::DontTouch;
131  } else if (target.consume_front("OMInstanceTarget")) {
132  targetKind = TargetKind::Instance;
133  } else if (target.consume_front("OMMemberInstanceTarget")) {
134  targetKind = TargetKind::MemberInstance;
135  } else if (target.consume_front("OMMemberReferenceTarget")) {
136  targetKind = TargetKind::MemberReference;
137  } else if (target.consume_front("OMReferenceTarget")) {
138  targetKind = TargetKind::Reference;
139  } else {
140  return emitError(loc)
141  << "unknown or missing OM reference type in target string: \""
142  << target << "\"";
143  }
144  auto targetKindAttr = TargetKindAttr::get(context, targetKind);
145 
146  // Parse the target.
147  if (!target.consume_front(":"))
148  return emitError(loc, "expected ':' in target string");
149 
150  auto token = tokenizePath(target);
151  if (!token)
152  return emitError(loc)
153  << "cannot tokenize annotation path \"" << target << "\"";
154 
155  // Resolve the target to a target.
156  auto path = resolveEntities(*token, circuit, symbolTable, targetCache);
157  if (!path)
158  return failure();
159 
160  // Make sure that we are targeting a leaf of the operation. That way lower
161  // types can't split a single reference into many, and cause ambiguity. If
162  // we are targeting a module, the type will be null.
163  if (Type targetType = path->ref.getType()) {
164  auto fieldId = path->fieldIdx;
165  auto baseType = type_dyn_cast<FIRRTLBaseType>(targetType);
166  if (!baseType)
167  return emitError(loc, "unable to target non-hardware type ")
168  << targetType;
169  targetType = hw::FieldIdImpl::getFinalTypeByFieldID(baseType, fieldId);
170  if (type_isa<BundleType, FVectorType>(targetType))
171  return emitError(loc, "unable to target aggregate type ") << targetType;
172  }
173 
174  auto owningModule = cache.lookup(unresolved);
175  StringRef moduleName = "nullptr";
176  if (owningModule)
177  moduleName = owningModule.getModuleName();
178  if (!owningModule)
179  return unresolved->emitError("path does not have a single owning module");
180 
181  // Resolve a path to the operation in question.
182  FlatSymbolRefAttr hierPathName;
183  if (failed(resolveHierPath(loc, owningModule, *path, hierPathName)))
184  return failure();
185 
186  auto createAnnotation = [&](FlatSymbolRefAttr hierPathName) {
187  // Create a unique ID.
188  auto id = DistinctAttr::create(UnitAttr::get(context));
189 
190  // Create the annotation.
191  NamedAttrList fields;
192  fields.append("id", id);
193  fields.append("class", StringAttr::get(context, "circt.tracker"));
194  if (hierPathName)
195  fields.append("circt.nonlocal", hierPathName);
196  if (path->fieldIdx != 0)
197  fields.append("circt.fieldID", b.getI64IntegerAttr(path->fieldIdx));
198 
199  return DictionaryAttr::get(context, fields);
200  };
201 
202  // Create the annotation(s).
203  Attribute annotation = createAnnotation(hierPathName);
204 
205  // Attach the annotation(s) to the target.
206  auto annoTarget = path->ref;
207  auto targetAnnotations = annoTarget.getAnnotations();
208  targetAnnotations.addAnnotations({annotation});
209  if (targetKindAttr.getValue() == TargetKind::DontTouch)
210  targetAnnotations.addDontTouch();
211  annoTarget.setAnnotations(targetAnnotations);
212 
213  // Create a PathOp using the id in the annotation we added to the target.
214  auto dictAttr = cast<DictionaryAttr>(annotation);
215  auto id = cast<DistinctAttr>(dictAttr.get("id"));
216  auto resolved = b.create<PathOp>(targetKindAttr, id);
217 
218  // Replace the unresolved path with the PathOp.
219  unresolved->replaceAllUsesWith(resolved);
220  unresolved.erase();
221 
222  return success();
223  }
224 
225  CircuitOp circuit;
226  SymbolTable symbolTable;
227  CircuitTargetCache targetCache;
228  InstanceGraph &instanceGraph;
229  InstancePathCache instancePathCache;
230  hw::InnerSymbolNamespaceCollection namespaces;
231  HierPathCache hierPathCache;
232  OpBuilder builder;
233 };
234 } // end anonymous namespace
235 
236 //===----------------------------------------------------------------------===//
237 // Pass Infrastructure
238 //===----------------------------------------------------------------------===//
239 
240 namespace {
241 struct ResolvePathsPass
242  : public circt::firrtl::impl::ResolvePathsBase<ResolvePathsPass> {
243  void runOnOperation() override;
244 };
245 } // end anonymous namespace
246 
247 void ResolvePathsPass::runOnOperation() {
248  auto circuit = getOperation();
249  auto &instanceGraph = getAnalysis<InstanceGraph>();
250  PathResolver resolver(circuit, instanceGraph);
251  OwningModuleCache cache(instanceGraph);
252  auto result = circuit.walk([&](UnresolvedPathOp unresolved) {
253  if (failed(resolver.resolve(cache, unresolved))) {
254  signalPassFailure();
255  return WalkResult::interrupt();
256  }
257  return WalkResult::advance();
258  });
259  if (result.wasInterrupted())
260  signalPassFailure();
261  markAnalysesPreserved<InstanceGraph>();
262 }
263 
264 std::unique_ptr<mlir::Pass> circt::firrtl::createResolvePathsPass() {
265  return std::make_unique<ResolvePathsPass>();
266 }
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 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.