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