CIRCT  20.0.0git
InstanceGraph.cpp
Go to the documentation of this file.
1 //===- InstanceGraph.cpp - Instance Graph -----------------------*- 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 
10 #include "mlir/IR/BuiltinOps.h"
11 #include "mlir/IR/Threading.h"
12 
13 using namespace circt;
14 using namespace igraph;
15 
17  // Update the prev node to point to the next node.
18  if (prevUse)
20  else
22  // Update the next node to point to the prev node.
23  if (nextUse)
25  getParent()->instances.erase(this);
26 }
27 
28 InstanceRecord *InstanceGraphNode::addInstance(InstanceOpInterface instance,
29  InstanceGraphNode *target) {
30  auto *instanceRecord = new InstanceRecord(this, instance, target);
31  target->recordUse(instanceRecord);
32  instances.push_back(instanceRecord);
33  return instanceRecord;
34 }
35 
37  record->nextUse = firstUse;
38  if (firstUse)
39  firstUse->prevUse = record;
40  firstUse = record;
41 }
42 
44  // Try to insert an InstanceGraphNode. If its not inserted, it returns
45  // an iterator pointing to the node.
46  auto *&node = nodeMap[name];
47  if (!node) {
48  node = new InstanceGraphNode();
49  nodes.push_back(node);
50  }
51  return node;
52 }
53 
54 InstanceGraph::InstanceGraph(Operation *parent) : parent(parent) {
55  assert(parent->hasTrait<mlir::OpTrait::SingleBlock>() &&
56  "top-level operation must have a single block");
57  SmallVector<std::pair<ModuleOpInterface, SmallVector<InstanceOpInterface>>>
58  moduleToInstances;
59  // First accumulate modules inside the parent op.
60  for (auto module :
61  parent->getRegion(0).front().getOps<igraph::ModuleOpInterface>())
62  moduleToInstances.push_back({module, {}});
63 
64  // Populate instances in the module parallelly.
65  mlir::parallelFor(parent->getContext(), 0, moduleToInstances.size(),
66  [&](size_t idx) {
67  auto module = moduleToInstances[idx].first;
68  auto &instances = moduleToInstances[idx].second;
69  // Find all instance operations in the module body.
70  module.walk([&](InstanceOpInterface instanceOp) {
71  instances.push_back(instanceOp);
72  });
73  });
74 
75  // Construct an instance graph sequentially.
76  for (auto &[module, instances] : moduleToInstances) {
77  auto name = module.getModuleNameAttr();
78  auto *currentNode = getOrAddNode(name);
79  currentNode->module = module;
80  for (auto instanceOp : instances) {
81  // Add an edge to indicate that this module instantiates the target.
82  for (auto targetNameAttr : instanceOp.getReferencedModuleNamesAttr()) {
83  auto *targetNode = getOrAddNode(cast<StringAttr>(targetNameAttr));
84  currentNode->addInstance(instanceOp, targetNode);
85  }
86  }
87  }
88 }
89 
90 InstanceGraphNode *InstanceGraph::addModule(ModuleOpInterface module) {
91  assert(!nodeMap.count(module.getModuleNameAttr()) && "module already added");
92  auto *node = new InstanceGraphNode();
93  node->module = module;
94  nodeMap[module.getModuleNameAttr()] = node;
95  nodes.push_back(node);
96  return node;
97 }
98 
100  assert(node->noUses() &&
101  "all instances of this module must have been erased.");
102  // Erase all instances inside this module.
103  for (auto *instance : llvm::make_early_inc_range(*node))
104  instance->erase();
105  nodeMap.erase(node->getModule().getModuleNameAttr());
106  nodes.erase(node);
107 }
108 
110  auto it = nodeMap.find(name);
111  assert(it != nodeMap.end() && "Module not in InstanceGraph!");
112  return it->second;
113 }
114 
115 InstanceGraphNode *InstanceGraph::lookup(ModuleOpInterface op) {
116  return lookup(cast<ModuleOpInterface>(op).getModuleNameAttr());
117 }
118 
119 void InstanceGraph::replaceInstance(InstanceOpInterface inst,
120  InstanceOpInterface newInst) {
121  assert(inst.getReferencedModuleNamesAttr() ==
122  newInst.getReferencedModuleNamesAttr() &&
123  "Both instances must be targeting the same modules");
124 
125  // Replace all edges between the module of the instance and all targets.
126  for (Attribute targetNameAttr : inst.getReferencedModuleNamesAttr()) {
127  // Find the instance record of this instance.
128  auto *node = lookup(cast<StringAttr>(targetNameAttr));
129  for (InstanceRecord *record : node->uses()) {
130  if (record->getInstance() == inst) {
131  // We can just replace the instance op in the InstanceRecord without
132  // updating any instance lists.
133  record->instance = newInst;
134  }
135  }
136  }
137 }
138 
140  ModuleOpInterface child, ModuleOpInterface parent,
141  llvm::function_ref<bool(InstanceRecord *)> skipInstance) {
142  DenseSet<InstanceGraphNode *> seen;
143  SmallVector<InstanceGraphNode *> worklist;
144  auto *cn = lookup(child);
145  worklist.push_back(cn);
146  seen.insert(cn);
147  while (!worklist.empty()) {
148  auto *node = worklist.back();
149  worklist.pop_back();
150  if (node->getModule() == parent)
151  return true;
152  for (auto *use : node->uses()) {
153  if (skipInstance(use))
154  continue;
155  auto *mod = use->getParent();
156  if (!seen.count(mod)) {
157  seen.insert(mod);
158  worklist.push_back(mod);
159  }
160  }
161  }
162  return false;
163 }
164 
165 FailureOr<llvm::ArrayRef<InstanceGraphNode *>>
167  if (!inferredTopLevelNodes.empty())
168  return {inferredTopLevelNodes};
169 
170  /// Topologically sort the instance graph.
171  llvm::SetVector<InstanceGraphNode *> visited, marked;
172  llvm::SetVector<InstanceGraphNode *> candidateTopLevels(this->begin(),
173  this->end());
174  SmallVector<InstanceGraphNode *> cycleTrace;
175 
176  // Recursion function; returns true if a cycle was detected.
177  std::function<bool(InstanceGraphNode *, SmallVector<InstanceGraphNode *>)>
178  cycleUtil =
179  [&](InstanceGraphNode *node, SmallVector<InstanceGraphNode *> trace) {
180  if (visited.contains(node))
181  return false;
182  trace.push_back(node);
183  if (marked.contains(node)) {
184  // Cycle detected.
185  cycleTrace = trace;
186  return true;
187  }
188  marked.insert(node);
189  for (auto use : *node) {
190  InstanceGraphNode *targetModule = use->getTarget();
191  candidateTopLevels.remove(targetModule);
192  if (cycleUtil(targetModule, trace))
193  return true; // Cycle detected.
194  }
195  marked.remove(node);
196  visited.insert(node);
197  return false;
198  };
199 
200  bool cyclic = false;
201  for (auto moduleIt : *this) {
202  if (visited.contains(moduleIt))
203  continue;
204 
205  cyclic |= cycleUtil(moduleIt, {});
206  if (cyclic)
207  break;
208  }
209 
210  if (cyclic) {
211  auto err = getParent()->emitOpError();
212  err << "cannot deduce top level module - cycle "
213  "detected in instance graph (";
214  llvm::interleave(
215  cycleTrace, err,
216  [&](auto node) { err << node->getModule().getModuleName(); }, "->");
217  err << ").";
218  return err;
219  }
220  assert(!candidateTopLevels.empty() &&
221  "if non-cyclic, there should be at least 1 candidate top level");
222 
223  inferredTopLevelNodes = llvm::SmallVector<InstanceGraphNode *>(
224  candidateTopLevels.begin(), candidateTopLevels.end());
225  return {inferredTopLevelNodes};
226 }
227 
229 
230 ArrayRef<InstancePath>
231 InstancePathCache::getAbsolutePaths(ModuleOpInterface op) {
232  return getAbsolutePaths(op, instanceGraph.getTopLevelNode());
233 }
234 
235 // NOLINTBEGIN(misc-no-recursion)
236 ArrayRef<InstancePath>
237 InstancePathCache::getAbsolutePaths(ModuleOpInterface op,
238  InstanceGraphNode *top) {
239  InstanceGraphNode *node = instanceGraph[op];
240 
241  if (node == top) {
242  return empty;
243  }
244 
245  // Fast path: hit the cache.
246  auto cached = absolutePathsCache.find(op);
247  if (cached != absolutePathsCache.end())
248  return cached->second;
249 
250  // For each instance, collect the instance paths to its parent and append the
251  // instance itself to each.
252  SmallVector<InstancePath, 8> extendedPaths;
253  for (auto *inst : node->uses()) {
254  if (auto module = inst->getParent()->getModule()) {
255  auto instPaths = getAbsolutePaths(module);
256  extendedPaths.reserve(instPaths.size());
257  for (auto path : instPaths) {
258  extendedPaths.push_back(appendInstance(
259  path, cast<InstanceOpInterface>(*inst->getInstance())));
260  }
261  } else {
262  extendedPaths.emplace_back(empty);
263  }
264  }
265 
266  // Move the list of paths into the bump allocator for later quick retrieval.
267  ArrayRef<InstancePath> pathList;
268  if (!extendedPaths.empty()) {
269  auto *paths = allocator.Allocate<InstancePath>(extendedPaths.size());
270  std::copy(extendedPaths.begin(), extendedPaths.end(), paths);
271  pathList = ArrayRef<InstancePath>(paths, extendedPaths.size());
272  }
273  absolutePathsCache.insert({op, pathList});
274  return pathList;
275 }
276 // NOLINTEND(misc-no-recursion)
277 
278 void InstancePath::print(llvm::raw_ostream &into) const {
279  into << "$root";
280  for (unsigned i = 0, n = path.size(); i < n; ++i) {
281  auto inst = path[i];
282 
283  into << "/" << inst.getInstanceName() << ":";
284  auto names = inst.getReferencedModuleNamesAttr();
285  if (names.size() == 1) {
286  // If there is a unique target, print it.
287  into << cast<StringAttr>(names[0]).getValue();
288  } else {
289  if (i + 1 < n) {
290  // If this is not a leaf node, the target module should be the
291  // parent of the next instance operation in the path.
292  into << path[i + 1]
293  ->getParentOfType<ModuleOpInterface>()
294  .getModuleName();
295  } else {
296  // Otherwise, print the whole set of targets.
297  into << "{";
298  llvm::interleaveComma(names, into, [&](Attribute name) {
299  into << cast<StringAttr>(name).getValue();
300  });
301  into << "}";
302  }
303  }
304  }
305 }
306 
307 InstancePath InstancePathCache::appendInstance(InstancePath path,
308  InstanceOpInterface inst) {
309  size_t n = path.size() + 1;
310  auto *newPath = allocator.Allocate<InstanceOpInterface>(n);
311  std::copy(path.begin(), path.end(), newPath);
312  newPath[path.size()] = inst;
313  return InstancePath(ArrayRef(newPath, n));
314 }
315 
316 InstancePath InstancePathCache::prependInstance(InstanceOpInterface inst,
317  InstancePath path) {
318  size_t n = path.size() + 1;
319  auto *newPath = allocator.Allocate<InstanceOpInterface>(n);
320  std::copy(path.begin(), path.end(), newPath + 1);
321  newPath[0] = inst;
322  return InstancePath(ArrayRef(newPath, n));
323 }
324 
325 void InstancePathCache::replaceInstance(InstanceOpInterface oldOp,
326  InstanceOpInterface newOp) {
327 
328  instanceGraph.replaceInstance(oldOp, newOp);
329 
330  // Iterate over all the paths, and search for the old InstanceOpInterface. If
331  // found, then replace it with the new InstanceOpInterface, and create a new
332  // copy of the paths and update the cache.
333  auto instanceExists = [&](const ArrayRef<InstancePath> &paths) -> bool {
334  return llvm::any_of(
335  paths, [&](InstancePath p) { return llvm::is_contained(p, oldOp); });
336  };
337 
338  for (auto &iter : absolutePathsCache) {
339  if (!instanceExists(iter.getSecond()))
340  continue;
341  SmallVector<InstancePath, 8> updatedPaths;
342  for (auto path : iter.getSecond()) {
343  const auto *iter = llvm::find(path, oldOp);
344  if (iter == path.end()) {
345  // path does not contain the oldOp, just copy it as is.
346  updatedPaths.push_back(path);
347  continue;
348  }
349  auto *newPath = allocator.Allocate<InstanceOpInterface>(path.size());
350  llvm::copy(path, newPath);
351  newPath[iter - path.begin()] = newOp;
352  updatedPaths.push_back(InstancePath(ArrayRef(newPath, path.size())));
353  }
354  // Move the list of paths into the bump allocator for later quick
355  // retrieval.
356  auto *paths = allocator.Allocate<InstancePath>(updatedPaths.size());
357  llvm::copy(updatedPaths, paths);
358  iter.getSecond() = ArrayRef<InstancePath>(paths, updatedPaths.size());
359  }
360 }
361 
362 #include "circt/Support/InstanceGraphInterface.cpp.inc"
assert(baseType &&"element must be base type")
static InstancePath empty
void erase(igraph::InstanceGraphNode *node) override
Erases a module, updating links to entry.
This is a Node in the InstanceGraph.
InstanceRecord * firstUse
List of instances which instantiate this module.
bool noUses()
Return true if there are no more instances of this module.
InstanceRecord * addInstance(InstanceOpInterface instance, InstanceGraphNode *target)
Record a new instance op in the body of this module.
auto getModule()
Get the module that this node is tracking.
InstanceList instances
List of instance operations in this module.
llvm::iterator_range< UseIterator > uses()
void recordUse(InstanceRecord *record)
Record that a module instantiates this module.
virtual InstanceGraphNode * addModule(ModuleOpInterface module)
Add a newly created module to the instance graph.
Operation * parent
The node under which all modules are nested.
llvm::DenseMap< Attribute, InstanceGraphNode * > nodeMap
This maps each operation to its graph node.
virtual void replaceInstance(InstanceOpInterface inst, InstanceOpInterface newInst)
Replaces an instance of a module with another instance.
FailureOr< llvm::ArrayRef< InstanceGraphNode * > > getInferredTopLevelNodes()
Get the nodes corresponding to the inferred top-level modules of a circuit.
llvm::SmallVector< InstanceGraphNode * > inferredTopLevelNodes
A caching of the inferred top level module(s).
NodeList nodes
The storage for graph nodes, with deterministic iteration.
bool isAncestor(ModuleOpInterface child, ModuleOpInterface parent, llvm::function_ref< bool(InstanceRecord *)> skipInstance=[](InstanceRecord *_) { return false;})
Check if child is instantiated by a parent.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
Operation * getParent()
Return the parent under which all nodes are nested.
InstanceGraphNode * getOrAddNode(StringAttr name)
Get the node corresponding to the module.
InstanceGraph(Operation *parent)
Create a new module graph of a circuit.
An instance path composed of a series of instances.
ArrayRef< InstanceOpInterface >::iterator end() const
ArrayRef< InstanceOpInterface >::iterator begin() const
This is an edge in the InstanceGraph.
Definition: InstanceGraph.h:55
void erase()
Erase this instance record, removing it from the parent module and the target's use-list.
InstanceGraphNode * target
This is the module which the instance-like is instantiating.
Definition: InstanceGraph.h:91
InstanceRecord * nextUse
Intrusive linked list for other uses.
Definition: InstanceGraph.h:93
InstanceGraphNode * getParent() const
Get the module where the instantiation lives.
Definition: InstanceGraph.h:66
igraph::InstanceGraphNode InstanceGraphNode
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21