Loading [MathJax]/extensions/tex2jax.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
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
28InstanceRecord *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
54InstanceGraph::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
90InstanceGraphNode *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 if (it == nodeMap.end())
112 return nullptr;
113 return it->second;
114}
115
116void InstanceGraph::replaceInstance(InstanceOpInterface inst,
117 InstanceOpInterface newInst) {
118 assert(inst.getReferencedModuleNamesAttr() ==
119 newInst.getReferencedModuleNamesAttr() &&
120 "Both instances must be targeting the same modules");
121
122 // Replace all edges between the module of the instance and all targets.
123 for (Attribute targetNameAttr : inst.getReferencedModuleNamesAttr()) {
124 // Find the instance record of this instance.
125 auto *node = lookup(cast<StringAttr>(targetNameAttr));
126 for (InstanceRecord *record : node->uses()) {
127 if (record->getInstance() == inst) {
128 // We can just replace the instance op in the InstanceRecord without
129 // updating any instance lists.
130 record->instance = newInst;
131 }
132 }
133 }
134}
135
137 ModuleOpInterface child, ModuleOpInterface parent,
138 llvm::function_ref<bool(InstanceRecord *)> skipInstance) {
139 DenseSet<InstanceGraphNode *> seen;
140 SmallVector<InstanceGraphNode *> worklist;
141 auto *cn = lookup(child);
142 worklist.push_back(cn);
143 seen.insert(cn);
144 while (!worklist.empty()) {
145 auto *node = worklist.back();
146 worklist.pop_back();
147 if (node->getModule() == parent)
148 return true;
149 for (auto *use : node->uses()) {
150 if (skipInstance(use))
151 continue;
152 auto *mod = use->getParent();
153 if (!seen.count(mod)) {
154 seen.insert(mod);
155 worklist.push_back(mod);
156 }
157 }
158 }
159 return false;
160}
161
162FailureOr<llvm::ArrayRef<InstanceGraphNode *>>
164 if (!inferredTopLevelNodes.empty())
165 return {inferredTopLevelNodes};
166
167 /// Topologically sort the instance graph.
168 llvm::SetVector<InstanceGraphNode *> visited, marked;
169 llvm::SetVector<InstanceGraphNode *> candidateTopLevels(this->begin(),
170 this->end());
171 SmallVector<InstanceGraphNode *> cycleTrace;
172
173 // Recursion function; returns true if a cycle was detected.
174 std::function<bool(InstanceGraphNode *, SmallVector<InstanceGraphNode *>)>
175 cycleUtil =
176 [&](InstanceGraphNode *node, SmallVector<InstanceGraphNode *> trace) {
177 if (visited.contains(node))
178 return false;
179 trace.push_back(node);
180 if (marked.contains(node)) {
181 // Cycle detected.
182 cycleTrace = trace;
183 return true;
184 }
185 marked.insert(node);
186 for (auto use : *node) {
187 InstanceGraphNode *targetModule = use->getTarget();
188 candidateTopLevels.remove(targetModule);
189 if (cycleUtil(targetModule, trace))
190 return true; // Cycle detected.
191 }
192 marked.remove(node);
193 visited.insert(node);
194 return false;
195 };
196
197 bool cyclic = false;
198 for (auto moduleIt : *this) {
199 if (visited.contains(moduleIt))
200 continue;
201
202 cyclic |= cycleUtil(moduleIt, {});
203 if (cyclic)
204 break;
205 }
206
207 if (cyclic) {
208 auto err = getParent()->emitOpError();
209 err << "cannot deduce top level module - cycle "
210 "detected in instance graph (";
211 llvm::interleave(
212 cycleTrace, err,
213 [&](auto node) { err << node->getModule().getModuleName(); }, "->");
214 err << ").";
215 return err;
216 }
217 assert(!candidateTopLevels.empty() &&
218 "if non-cyclic, there should be at least 1 candidate top level");
219
220 inferredTopLevelNodes = llvm::SmallVector<InstanceGraphNode *>(
221 candidateTopLevels.begin(), candidateTopLevels.end());
222 return {inferredTopLevelNodes};
223}
224
226
227ArrayRef<InstancePath>
231
232// NOLINTBEGIN(misc-no-recursion)
233ArrayRef<InstancePath>
235 InstanceGraphNode *top) {
237
238 if (node == top) {
239 return empty;
240 }
241
242 // Fast path: hit the cache.
243 auto cached = absolutePathsCache.find(op);
244 if (cached != absolutePathsCache.end())
245 return cached->second;
246
247 // For each instance, collect the instance paths to its parent and append the
248 // instance itself to each.
249 SmallVector<InstancePath, 8> extendedPaths;
250 for (auto *inst : node->uses()) {
251 if (auto module = inst->getParent()->getModule()) {
252 auto instPaths = getAbsolutePaths(module);
253 extendedPaths.reserve(instPaths.size());
254 for (auto path : instPaths) {
255 extendedPaths.push_back(appendInstance(
256 path, cast<InstanceOpInterface>(*inst->getInstance())));
257 }
258 } else {
259 extendedPaths.emplace_back(empty);
260 }
261 }
262
263 // Move the list of paths into the bump allocator for later quick retrieval.
264 ArrayRef<InstancePath> pathList;
265 if (!extendedPaths.empty()) {
266 auto *paths = allocator.Allocate<InstancePath>(extendedPaths.size());
267 std::copy(extendedPaths.begin(), extendedPaths.end(), paths);
268 pathList = ArrayRef<InstancePath>(paths, extendedPaths.size());
269 }
270 absolutePathsCache.insert({op, pathList});
271 return pathList;
272}
273// NOLINTEND(misc-no-recursion)
274
275void InstancePath::print(llvm::raw_ostream &into) const {
276 into << "$root";
277 for (unsigned i = 0, n = path.size(); i < n; ++i) {
278 auto inst = path[i];
279
280 into << "/" << inst.getInstanceName() << ":";
281 auto names = inst.getReferencedModuleNamesAttr();
282 if (names.size() == 1) {
283 // If there is a unique target, print it.
284 into << cast<StringAttr>(names[0]).getValue();
285 } else {
286 if (i + 1 < n) {
287 // If this is not a leaf node, the target module should be the
288 // parent of the next instance operation in the path.
289 into << path[i + 1]
290 ->getParentOfType<ModuleOpInterface>()
291 .getModuleName();
292 } else {
293 // Otherwise, print the whole set of targets.
294 into << "{";
295 llvm::interleaveComma(names, into, [&](Attribute name) {
296 into << cast<StringAttr>(name).getValue();
297 });
298 into << "}";
299 }
300 }
301 }
302}
303
305 InstanceOpInterface inst) {
306 size_t n = path.size() + 1;
307 auto *newPath = allocator.Allocate<InstanceOpInterface>(n);
308 std::copy(path.begin(), path.end(), newPath);
309 newPath[path.size()] = inst;
310 return InstancePath(ArrayRef(newPath, n));
311}
312
314 InstancePath path) {
315 size_t n = path.size() + 1;
316 auto *newPath = allocator.Allocate<InstanceOpInterface>(n);
317 std::copy(path.begin(), path.end(), newPath + 1);
318 newPath[0] = inst;
319 return InstancePath(ArrayRef(newPath, n));
320}
321
322void InstancePathCache::replaceInstance(InstanceOpInterface oldOp,
323 InstanceOpInterface newOp) {
324
325 instanceGraph.replaceInstance(oldOp, newOp);
326
327 // Iterate over all the paths, and search for the old InstanceOpInterface. If
328 // found, then replace it with the new InstanceOpInterface, and create a new
329 // copy of the paths and update the cache.
330 auto instanceExists = [&](const ArrayRef<InstancePath> &paths) -> bool {
331 return llvm::any_of(
332 paths, [&](InstancePath p) { return llvm::is_contained(p, oldOp); });
333 };
334
335 for (auto &iter : absolutePathsCache) {
336 if (!instanceExists(iter.getSecond()))
337 continue;
338 SmallVector<InstancePath, 8> updatedPaths;
339 for (auto path : iter.getSecond()) {
340 const auto *iter = llvm::find(path, oldOp);
341 if (iter == path.end()) {
342 // path does not contain the oldOp, just copy it as is.
343 updatedPaths.push_back(path);
344 continue;
345 }
346 auto *newPath = allocator.Allocate<InstanceOpInterface>(path.size());
347 llvm::copy(path, newPath);
348 newPath[iter - path.begin()] = newOp;
349 updatedPaths.push_back(InstancePath(ArrayRef(newPath, path.size())));
350 }
351 // Move the list of paths into the bump allocator for later quick
352 // retrieval.
353 auto *paths = allocator.Allocate<InstancePath>(updatedPaths.size());
354 llvm::copy(updatedPaths, paths);
355 iter.getSecond() = ArrayRef<InstancePath>(paths, updatedPaths.size());
356 }
357}
358
359#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 > getAbsolutePaths(ModuleOpInterface op)
void replaceInstance(InstanceOpInterface oldOp, InstanceOpInterface newOp)
Replace an InstanceOp. This is required to keep the cache updated.
DenseMap< Operation *, ArrayRef< InstancePath > > absolutePathsCache
Cached absolute instance paths.
InstancePath prependInstance(InstanceOpInterface inst, InstancePath path)
Prepend an instance to a path.
InstancePath appendInstance(InstancePath path, InstanceOpInterface inst)
Append an instance to a path.
llvm::BumpPtrAllocator allocator
An allocator for individual instance paths and entire path lists.
InstanceGraph & instanceGraph
The instance graph of the IR.