CIRCT 24.0.0git
Loading...
Searching...
No Matches
InferContext.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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 pass resolves arc.inferred_context operations to their closest
10// provided context. Context providers are:
11// - arc.model operations
12// - arc.sim.instantiate operations
13// - Operations implementing FunctionOpInterface with a context-typed argument
14//
15// Any InferredContextOp nested under a context provider is resolved directly
16// to the provided context.
17// If an InferredContextOp is found in a private function that does not
18// provide a context, a new context argument is added and it is recursively
19// resolved at its call sites.
20//
21//===----------------------------------------------------------------------===//
22
27#include "circt/Support/LLVM.h"
28#include "mlir/IR/OpDefinition.h"
29#include "mlir/IR/Operation.h"
30#include "mlir/IR/SymbolTable.h"
31#include "mlir/IR/Threading.h"
32#include "mlir/Interfaces/CallInterfaces.h"
33#include "mlir/Interfaces/FunctionInterfaces.h"
34#include "mlir/Support/LLVM.h"
35#include "mlir/Support/WalkResult.h"
36#include "llvm/ADT/DenseMap.h"
37#include "llvm/ADT/DenseSet.h"
38#include "llvm/ADT/STLExtras.h"
39#include "llvm/ADT/SmallVector.h"
40#include "llvm/Support/Casting.h"
41#include "llvm/Support/Debug.h"
42
43#include <mutex>
44
45#define DEBUG_TYPE "arc-infer-context"
46
47namespace circt {
48namespace arc {
49#define GEN_PASS_DEF_INFERCONTEXT
50#include "circt/Dialect/Arc/ArcPasses.h.inc"
51} // namespace arc
52} // namespace circt
53
54using namespace mlir;
55using namespace circt;
56using namespace arc;
57
58namespace {
59
60struct CallerGraphNode {
61 CallerGraphNode(FunctionOpInterface &fnOp) : fnOp(fnOp) {}
62 FunctionOpInterface fnOp;
64};
65
66struct InferContextPass : public arc::impl::InferContextBase<InferContextPass> {
67 void runOnOperation() override;
68
69private:
70 /// Build the graph pointing from callees to their callers.
71 void buildCallerGraph(ArrayRef<FunctionOpInterface> functions,
72 SymbolTableCollection &symbolTable);
73 /// Add functions to `needsContextSet` that can reach a function that is
74 /// already in the set.
75 void backPropagateNeedsContext();
76 /// Walk the region and wire-in the inferred context.
77 void updateRegion(Region *region, SymbolTableCollection &symbolTable);
78
79 /// Functions providing context.
80 llvm::SmallDenseSet<FunctionOpInterface> hasContextSet;
81 /// Functions needing a new context argument.
82 llvm::SmallDenseSet<FunctionOpInterface> needsContextSet;
83 /// Callee-to-caller graph; must remain const after creation to not invalidate
84 /// pointers.
85 DenseMap<FunctionOpInterface, CallerGraphNode> callerGraph;
86};
87
88void InferContextPass::updateRegion(Region *region,
89 SymbolTableCollection &symbolTable) {
90 assert(!region->empty());
91 IRRewriter rewriter(region->getContext());
92 rewriter.setInsertionPointToStart(&region->front());
93 FunctionOpInterface containingFn =
94 dyn_cast<FunctionOpInterface>(region->getParentOp());
95
96 // Obtain the inferred context value.
97 Value inferredContextVal = {};
98 if (auto modelOp = llvm::dyn_cast<ModelOp>(region->getParentOp())) {
99 // Arc model op body: Context derived from the storage argument.
100 auto storageArg = region->getArgument(0);
101 inferredContextVal =
102 AsContextOp::create(rewriter, modelOp->getLoc(), storageArg);
103 LLVM_DEBUG(auto fnName = modelOp.getSymName();
104 llvm::dbgs()
105 << "Updating body of model \"" << fnName << "\"\n";);
106 } else if (auto instantiateOp =
107 llvm::dyn_cast<SimInstantiateOp>(region->getParentOp())) {
108 // Instance op body: Context derived from the instance handle.
109 auto instanceArg = region->getArgument(0);
110 inferredContextVal =
111 AsContextOp::create(rewriter, instantiateOp->getLoc(), instanceArg);
112 LLVM_DEBUG(llvm::dbgs() << "Updating body of instance\n";);
113 } else if (containingFn && (needsContextSet.contains(containingFn) ||
114 hasContextSet.contains(containingFn))) {
115 // A function that has a new or pre-existing context argument.
116 auto *ctxtArg = llvm::find_if(region->getArguments(), [](Value arg) {
117 return isa<ContextType>(arg.getType());
118 });
119 assert(ctxtArg && "Expected function to have a context argument");
120 inferredContextVal = *ctxtArg;
121 LLVM_DEBUG(auto fnName = cast<FunctionOpInterface>(region->getParentOp())
122 .getNameAttr()
123 .getValue();
124 llvm::dbgs()
125 << "Updating body of function \"" << fnName << "\"\n";);
126 } else {
127 // A function that has no context argument, but may contain instances that
128 // we recurse into.
129 LLVM_DEBUG(auto fnName = cast<FunctionOpInterface>(region->getParentOp())
130 .getNameAttr()
131 .getValue();
132 llvm::dbgs()
133 << "Traversing body of function \"" << fnName << "\"\n";);
134 }
135
136 // Do the update walk.
137 region->walk<WalkOrder::PreOrder>([&](Operation *op) -> WalkResult {
138 if (op->getNumRegions() > 0) {
139 // Recurse into instances
140 if (auto instOp = dyn_cast<SimInstantiateOp>(op)) {
141 updateRegion(&instOp.getBody(), symbolTable);
142 return WalkResult::skip();
143 }
144 if (op->hasTrait<OpTrait::IsIsolatedFromAbove>())
145 return WalkResult::skip();
146 }
147 // Replace InferredContextOps
148 if (auto ctxtOp = dyn_cast<arc::InferredContextOp>(op)) {
149 assert(inferredContextVal && "No context to propagate");
150 rewriter.replaceOp(ctxtOp, inferredContextVal);
151 return WalkResult::skip();
152 }
153
154 // Update calls to callees that need context, if any.
155 if (needsContextSet.empty())
156 return WalkResult::advance();
157 auto callOp = dyn_cast<CallOpInterface>(op);
158 if (!callOp)
159 return WalkResult::advance();
160 auto callee = dyn_cast_or_null<FunctionOpInterface>(
161 callOp.resolveCallableInTable(&symbolTable));
162 if (!callee || !needsContextSet.contains(callee))
163 return WalkResult::advance();
164 assert(inferredContextVal && "No context to propagate");
165 callOp.getArgOperandsMutable().append({inferredContextVal});
166 return WalkResult::advance();
167 });
168}
169
170} // namespace
171
172void InferContextPass::buildCallerGraph(ArrayRef<FunctionOpInterface> functions,
173 SymbolTableCollection &symbolTable) {
174 // Allocate the nodes for the caller graph. Nodes point to each other, so
175 // we must not mutate the map afterwards.
176 callerGraph.reserve(functions.size());
177 for (auto fn : functions)
178 if (!fn.getFunctionBody().empty())
179 callerGraph.emplace_or_assign(fn, CallerGraphNode(fn));
180
181 // Find all callees in the function bodies and add the reversed edges to the
182 // graph.
183 for (auto fn : functions) {
184 if (fn.getFunctionBody().empty())
185 continue;
186 auto *caller = &callerGraph.at(fn);
187 fn.getFunctionBody().walk<WalkOrder::PreOrder>(
188 [&](Operation *op) -> WalkResult {
189 if (op->getNumRegions() > 0) {
190 // SimInstantiateOps provide a context on their own, so we ignore
191 // calls nested under them.
192 if (auto instOp = dyn_cast<SimInstantiateOp>(op))
193 return WalkResult::skip();
194 if (op->hasTrait<OpTrait::IsIsolatedFromAbove>())
195 return WalkResult::skip();
196 }
197 auto callOp = dyn_cast<CallOpInterface>(op);
198 if (!callOp)
199 return WalkResult::advance();
200 auto callee = llvm::dyn_cast_or_null<FunctionOpInterface>(
201 callOp.resolveCallableInTable(&symbolTable));
202 if (callee) {
203 auto calleeIt = callerGraph.find(callee);
204 if (calleeIt != callerGraph.end())
205 calleeIt->second.callers.insert(caller);
206 }
207 return WalkResult::advance();
208 });
209 }
210}
211
212void InferContextPass::backPropagateNeedsContext() {
213
214 // Propagate "needsContext" to callers.
215 struct DFSFrame {
216 DFSFrame(CallerGraphNode *node) : node(node) {}
217 CallerGraphNode *const node;
218 unsigned index = 0;
219 bool isFinished() const { return index >= node->callers.size(); }
220 DFSFrame getNext() {
221 assert(!isFinished());
222 return DFSFrame(node->callers[index++]);
223 }
224 };
225
226 // Seed the DFS with the already marked functions.
227 SmallVector<DFSFrame> dfsStack;
228 for (auto seed : needsContextSet) {
229 LLVM_DEBUG(auto fnName = seed.getNameAttr().getValue();
230 llvm::dbgs()
231 << "Seeding needsContext with function \"" << fnName << "\"\n";);
232 auto fnNode = callerGraph.find(seed);
233 assert(fnNode != callerGraph.end() && "Function not in caller graph");
234 dfsStack.emplace_back(&fnNode->second);
235 }
236
237 // Mark functions reached on the inverted call graph.
238 while (!dfsStack.empty()) {
239 if (dfsStack.back().isFinished()) {
240 dfsStack.pop_back();
241 continue;
242 }
243 auto next = dfsStack.back().getNext();
244 // Stop propagation at context providers
245 if (hasContextSet.contains(next.node->fnOp))
246 continue;
247 if (needsContextSet.insert(next.node->fnOp).second) {
248 LLVM_DEBUG(auto fnName = next.node->fnOp.getNameAttr().getValue();
249 llvm::dbgs() << "Propagating needsContext to function \""
250 << fnName << "\"\n";);
251 dfsStack.push_back(next);
252 }
253 }
254}
255
256void InferContextPass::runOnOperation() {
257 SymbolTableCollection symbolTable;
258 ModuleOp moduleOp = getOperation();
259
260 // Functions containing instances.
261 llvm::SmallDenseSet<FunctionOpInterface> hasInstancesSet;
262
263 hasContextSet.clear();
264 needsContextSet.clear();
265 callerGraph.clear();
266
267 // Guards hasContextSet, needsContextSet and hasInstancesSet.
268 std::mutex setMutex;
269
270 // Collect all interesting functions. A function is interesting if it
271 // contains any instances or any InferredContextOps or provides a context.
272 SmallVector<FunctionOpInterface> funcOps =
273 llvm::to_vector(moduleOp.getOps<FunctionOpInterface>());
274
275 parallelForEach(
276 moduleOp.getContext(), funcOps, [&](FunctionOpInterface funcOp) {
277 bool needsContext = false;
278 bool hasInstances = false;
279
280 bool hasContext = llvm::any_of(funcOp.getArgumentTypes(), [](Type ty) {
281 return isa<ContextType>(ty);
282 });
283
284 funcOp.getFunctionBody().walk<WalkOrder::PreOrder>(
285 [&](Operation *op) -> WalkResult {
286 if (op->getNumRegions() > 0) {
287 if (isa<SimInstantiateOp>(op)) {
288 hasInstances = true;
289 return WalkResult::skip();
290 }
291 // TODO: Should we handle nested IsolatedFromAbove ops?
292 if (op->hasTrait<OpTrait::IsIsolatedFromAbove>())
293 return WalkResult::skip();
294 } else if (!hasContext) {
295 needsContext |= isa<InferredContextOp>(op);
296 }
297 // Early out
298 if ((hasContext || needsContext) && hasInstances)
299 return WalkResult::interrupt();
300 return WalkResult::advance();
301 });
302 assert(!(hasContext && needsContext));
303 if (hasContext || needsContext || hasInstances) {
304 std::lock_guard<std::mutex> lock(setMutex);
305 if (hasContext)
306 hasContextSet.insert(funcOp);
307 if (needsContext)
308 needsContextSet.insert(funcOp);
309 if (hasInstances)
310 hasInstancesSet.insert(funcOp);
311 }
312 });
313
314 SmallPtrSet<Region *, 4> regionsToUpdate;
315
316 if (!needsContextSet.empty()) {
317 // Find functions that we have to thread the context into.
318 buildCallerGraph(funcOps, symbolTable);
319 backPropagateNeedsContext();
320
321 // Now we know which functions need a context argument. Add it to their
322 // signature.
323 auto ctxtType = arc::ContextType::get(getOperation()->getContext());
324 bool anyFailed = false;
325 for (auto fn : needsContextSet) {
326 if (fn.isPublic()) {
327 fn.emitError("Cannot infer an Arc context through a public function. A "
328 "context argument must be provided explicitly.");
329 anyFailed = true;
330 continue;
331 }
332 if (failed(fn.insertArgument(fn.getNumArguments(), ctxtType,
333 /*argAttrs=*/{}, fn.getLoc()))) {
334 fn.emitError("Failed to add context argument to function.");
335 anyFailed = true;
336 }
337 regionsToUpdate.insert(&fn.getFunctionBody());
338 }
339
340 if (anyFailed) {
341 signalPassFailure();
342 return;
343 }
344 } else {
345 // If there are none, we only have to replace InferredContextOps in
346 // the body of context providers.
347 LLVM_DEBUG(llvm::dbgs() << "No function needs a context argument.\n");
348 }
349
350 // Traverse the interesting regions to replace `arc.inferred_context` ops and
351 // propagate the context to callees in `needsContextSet`.
352 for (auto instFn : hasInstancesSet)
353 regionsToUpdate.insert(&instFn.getFunctionBody());
354 for (auto fnOp : hasContextSet)
355 regionsToUpdate.insert(&fnOp.getFunctionBody());
356 for (auto modelOp : moduleOp.getOps<ModelOp>())
357 regionsToUpdate.insert(&modelOp.getBody());
358
359 for (Region *region : regionsToUpdate)
360 updateRegion(region, symbolTable);
361
362 markAnalysesPreserved<ModelInfoAnalysis>();
363}
assert(baseType &&"element must be base type")
static InstancePath empty
Definition arc.py:1
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.