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