CIRCT 23.0.0git
Loading...
Searching...
No Matches
CaptureAnalysis.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#include "CaptureAnalysis.h"
10#include "slang/ast/ASTVisitor.h"
11#include "llvm/ADT/MapVector.h"
12#include "llvm/Support/SaveAndRestore.h"
13
14using namespace slang::ast;
15using namespace circt;
16using namespace circt::ImportVerilog;
17
18/// Check whether `var` is local to `func`. Walk up from the variable's parent
19/// scope; if we reach `func` before hitting another function boundary, the
20/// variable is local.
21static bool isLocalToFunction(const ValueSymbol &var,
22 const SubroutineSymbol &func) {
23 for (const Scope *scope = var.getParentScope(); scope;
24 scope = scope->asSymbol().getParentScope()) {
25 if (&scope->asSymbol() == &func)
26 return true;
27 if (scope->asSymbol().kind == SymbolKind::Subroutine)
28 return false;
29 }
30 return false;
31}
32
33/// Workaround for a slang deficiency: when accessing a member of a virtual
34/// interface (e.g., `vif.data`), slang resolves the entire dotted path during
35/// name lookup and produces a `NamedValueExpression` that directly references
36/// the signal symbol inside the interface's `InstanceBody`. Unlike struct
37/// fields and class properties, which produce a `MemberAccessExpression`, there
38/// is no syntactic indication on the expression that this was a member
39/// projection.
40///
41/// This check matches variables that live inside an interface instance body.
42/// A `NamedValueExpression` referencing such a symbol is the result of slang's
43/// virtual interface member resolution, not a genuine variable capture. This is
44/// expected to be fixed upstream in slang.
45///
46/// See https://github.com/MikePopoloski/slang/discussions/1770
47static bool isVirtualInterfaceMemberAccess(const ValueSymbol &var) {
48 // Walk up from the variable to find the nearest enclosing InstanceBody.
49 for (const Scope *scope = var.getParentScope(); scope;
50 scope = scope->asSymbol().getParentScope()) {
51 auto *body = scope->asSymbol().as_if<InstanceBodySymbol>();
52 if (!body)
53 continue;
54 return body->getDefinition().definitionKind == DefinitionKind::Interface;
55 }
56 return false;
57}
58
59/// Check whether `var` is a global variable. Walk up from the variable's parent
60/// scope; if we hit a function or instance body, it's not global. Otherwise
61/// (package, compilation unit, root) it is.
62static bool isGlobalVariable(const ValueSymbol &var) {
63 for (const Scope *scope = var.getParentScope(); scope;
64 scope = scope->asSymbol().getParentScope()) {
65 switch (scope->asSymbol().kind) {
66 case SymbolKind::Subroutine:
67 case SymbolKind::InstanceBody:
68 return false;
69 default:
70 break;
71 }
72 }
73 return true;
74}
75
77 return kind == SymbolKind::Parameter || kind == SymbolKind::EnumValue ||
78 kind == SymbolKind::Genvar || kind == SymbolKind::Specparam;
79}
80
81const InstanceSymbol *
82circt::ImportVerilog::getRootInstance(const HierarchicalReference &ref) {
83 for (auto &elem : ref.path)
84 if (auto *inst = elem.symbol->as_if<InstanceSymbol>())
85 return inst;
86 return nullptr;
87}
88
89namespace {
90
91/// Walk the entire AST to collect captured variables and the call graph for
92/// each function. Uses slang's `ASTVisitor` with both statement and expression
93/// visiting enabled so that we recurse into all function bodies.
94struct CaptureWalker
95 : public ASTVisitor<CaptureWalker, VisitFlags::AllCanonical> {
96
97 /// The function whose body we are currently inside, or nullptr if we are at
98 /// a scope outside any function.
99 const SubroutineSymbol *currentFunc = nullptr;
100
101 /// Captured variables per function.
102 CaptureMap capturedVars;
103
104 mlir::DenseSet<const InstanceBodySymbol *> visitedInstanceBodies;
105
106 /// Inverse call graph: maps each callee to the set of callers that call it.
107 /// Used to propagate captures from callees to their callers. Uses MapVector
108 /// for deterministic iteration order during propagation.
109 MapVector<const SubroutineSymbol *,
111 callers;
112
113 /// For each (function, captured symbol) pair, the instance through wich
114 /// the hierarchical reference was first reached. Used to detect captures
115 /// that resolve to one symbol through several instances.
116 DenseMap<std::pair<const SubroutineSymbol *, const ValueSymbol *>,
117 const InstanceSymbol *>
118 hierCaptureRootInstance;
119
120 /// Captures that were reached through more than one instance reported as
121 /// errors since they cannot be wired to a single instance.
122 SmallVector<AmbiguousHierCapture> ambiguousHierCaptures;
123
124 /// Track which root instance each hierarchical capture was reached through;
125 /// the same symbol seen through two different instances is ambiguous.
126 void noteHierCapture(const SubroutineSymbol &func, const ValueSymbol &var,
127 const InstanceSymbol *rootInst) {
128 if (!rootInst)
129 return;
130 auto key = std::make_pair(&func, &var);
131 auto [it, inserted] = hierCaptureRootInstance.try_emplace(key, rootInst);
132 if (!inserted && it->second != rootInst)
133 ambiguousHierCaptures.push_back({&func, &var});
134 }
135
136 /// When we enter a function body, record it as the current function and
137 /// recurse into its members and body statements.
138 void handle(const SubroutineSymbol &func) {
139 llvm::SaveAndRestore guard(currentFunc, &func);
140 visitDefault(func);
141 }
142
143 /// When we see a named value reference inside a function, check if it needs
144 /// to be captured.
145 void handle(const NamedValueExpression &expr) {
146 if (!currentFunc)
147 return;
148
149 auto &var = expr.symbol;
150
151 // Class properties are accessed through `this`, not captured.
152 if (var.kind == SymbolKind::ClassProperty)
153 return;
154
155 // Function arguments are local by definition.
156 if (var.kind == SymbolKind::FormalArgument)
157 return;
158
159 if (isCompileTimeConstant(var.kind))
160 return;
161
162 // Only capture variables that are non-local and non-global.
163 if (isLocalToFunction(var, *currentFunc) || isGlobalVariable(var))
164 return;
165
166 // Work around a slang deficiency where virtual interface member accesses
167 // are resolved to NamedValueExpressions referencing symbols inside the
168 // interface's instance body, indistinguishable from direct variable
169 // references. See isVirtualInterfaceMemberAccess for details.
171 return;
172
173 capturedVars[currentFunc].insert(&var);
174 }
175
176 /// Hierarchical references (`inst.var`) are captured like ordinary
177 /// non-local variables. Interface-port references are excluded: they are
178 /// handled by the interface lowering machinery, not as captures.
179 void handle(const HierarchicalValueExpression &expr) {
180 if (!currentFunc)
181 return;
182 if (expr.ref.isViaIfacePort())
183 return;
184 auto &var = expr.symbol;
185 if (isCompileTimeConstant(var.kind))
186 return;
187 noteHierCapture(*currentFunc, var, getRootInstance(expr.ref));
188 capturedVars[currentFunc].insert(&var);
189 }
190
191 /// Record call graph edges when we see a function call.
192 void handle(const CallExpression &expr) {
193 if (currentFunc)
194 if (auto *const *callee =
195 std::get_if<const SubroutineSymbol *>(&expr.subroutine))
196 callers[*callee].insert(currentFunc);
197 visitDefault(expr);
198 }
199
200 /// `VisitCanonical` is set above, so this visits the canonical instance body
201 /// if there is one. If there is and we've already visited it via some
202 /// other instance, don't process it again.
203 void handle(const InstanceBodySymbol &instance) {
204 if (visitedInstanceBodies.insert(&instance).second) {
205 visitDefault(instance);
206 }
207 }
208
209 /// Propagate captures transitively through the call graph. For each callee
210 /// that has captures, push each captured variable upward through all
211 /// transitive callers using a worklist. A captured variable is only
212 /// propagated to a caller if it is not local to that caller.
213 void propagateCaptures() {
214 using WorkItem = std::pair<const SubroutineSymbol *, const ValueSymbol *>;
216
217 for (auto &[func, _] : callers) {
218 // Check if this function captures any variables. Nothing to do if it
219 // doesn't.
220 auto it = capturedVars.find(func);
221 if (it == capturedVars.end())
222 continue;
223
224 // Prime the worklist with the captured variables.
225 for (auto *var : it->second)
226 worklist.insert({func, var});
227
228 // Push each captured variables to the func's callers transitively.
229 while (!worklist.empty()) {
230 auto [func, cap] = worklist.pop_back_val();
231 auto callersIt = callers.find(func);
232 if (callersIt == callers.end())
233 continue;
234 for (auto *caller : callersIt->second) {
235 if (!isLocalToFunction(*cap, *caller)) {
236 noteHierCapture(*caller, *cap,
237 hierCaptureRootInstance.lookup({func, cap}));
238 if (capturedVars[caller].insert(cap))
239 worklist.insert({caller, cap});
240 }
241 }
242 }
243 }
244 }
245};
246
247} // namespace
248
250 const RootSymbol &root, SmallVectorImpl<AmbiguousHierCapture> &ambiguous) {
251 CaptureWalker walker;
252 root.visit(walker);
253 walker.propagateCaptures();
254 ambiguous.assign(walker.ambiguousHierCaptures.begin(),
255 walker.ambiguousHierCaptures.end());
256 return std::move(walker.capturedVars);
257}
static bool isLocalToFunction(const ValueSymbol &var, const SubroutineSymbol &func)
Check whether var is local to func.
static bool isGlobalVariable(const ValueSymbol &var)
Check whether var is a global variable.
static bool isVirtualInterfaceMemberAccess(const ValueSymbol &var)
Workaround for a slang deficiency: when accessing a member of a virtual interface (e....
const slang::ast::InstanceSymbol * getRootInstance(const slang::ast::HierarchicalReference &ref)
Return the first instance on a hierarchical reference path, i.e.
CaptureMap analyzeFunctionCaptures(const slang::ast::RootSymbol &root, SmallVectorImpl< AmbiguousHierCapture > &ambiguous)
Analyze the AST rooted at root to determine which variables each function captures: symbols reference...
bool isCompileTimeConstant(slang::ast::SymbolKind kind)
Return true if symbols of this kind are elaboration-time constants.
DenseMap< const slang::ast::SubroutineSymbol *, SmallSetVector< const slang::ast::ValueSymbol *, 4 > > CaptureMap
The result of capture analysis: for each function, the set of non-local, non-global variable symbols ...
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.