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