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
76namespace {
77
78/// Walk the entire AST to collect captured variables and the call graph for
79/// each function. Uses slang's `ASTVisitor` with both statement and expression
80/// visiting enabled so that we recurse into all function bodies.
81struct CaptureWalker
82 : public ASTVisitor<CaptureWalker, /*VisitStatements=*/true,
83 /*VisitExpressions=*/true> {
84
85 /// The function whose body we are currently inside, or nullptr if we are at
86 /// a scope outside any function.
87 const SubroutineSymbol *currentFunc = nullptr;
88
89 /// Captured variables per function.
90 CaptureMap capturedVars;
91
92 /// Inverse call graph: maps each callee to the set of callers that call it.
93 /// Used to propagate captures from callees to their callers. Uses MapVector
94 /// for deterministic iteration order during propagation.
95 MapVector<const SubroutineSymbol *,
97 callers;
98
99 /// When we enter a function body, record it as the current function and
100 /// recurse into its members and body statements.
101 void handle(const SubroutineSymbol &func) {
102 llvm::SaveAndRestore guard(currentFunc, &func);
103 visitDefault(func);
104 }
105
106 /// When we see a named value reference inside a function, check if it needs
107 /// to be captured.
108 void handle(const NamedValueExpression &expr) {
109 if (!currentFunc)
110 return;
111
112 auto &var = expr.symbol;
113
114 // Class properties are accessed through `this`, not captured.
115 if (var.kind == SymbolKind::ClassProperty)
116 return;
117
118 // Function arguments are local by definition.
119 if (var.kind == SymbolKind::FormalArgument)
120 return;
121
122 // Compile-time constants are materialized inline and don't need capturing.
123 // Slang monomorphizes modules and classes per parameterization, so within
124 // any given elaborated scope these are fixed values.
125 if (var.kind == SymbolKind::Parameter ||
126 var.kind == SymbolKind::EnumValue || var.kind == SymbolKind::Genvar ||
127 var.kind == SymbolKind::Specparam)
128 return;
129
130 // Only capture variables that are non-local and non-global.
131 if (isLocalToFunction(var, *currentFunc) || isGlobalVariable(var))
132 return;
133
134 // Work around a slang deficiency where virtual interface member accesses
135 // are resolved to NamedValueExpressions referencing symbols inside the
136 // interface's instance body, indistinguishable from direct variable
137 // references. See isVirtualInterfaceMemberAccess for details.
139 return;
140
141 capturedVars[currentFunc].insert(&var);
142 }
143
144 /// Record call graph edges when we see a function call.
145 void handle(const CallExpression &expr) {
146 if (currentFunc)
147 if (auto *const *callee =
148 std::get_if<const SubroutineSymbol *>(&expr.subroutine))
149 callers[*callee].insert(currentFunc);
150 visitDefault(expr);
151 }
152
153 /// Propagate captures transitively through the call graph. For each callee
154 /// that has captures, push each captured variable upward through all
155 /// transitive callers using a worklist. A captured variable is only
156 /// propagated to a caller if it is not local to that caller.
157 void propagateCaptures() {
158 using WorkItem = std::pair<const SubroutineSymbol *, const ValueSymbol *>;
160
161 for (auto &[func, _] : callers) {
162 // Check if this function captures any variables. Nothing to do if it
163 // doesn't.
164 auto it = capturedVars.find(func);
165 if (it == capturedVars.end())
166 continue;
167
168 // Prime the worklist with the captured variables.
169 for (auto *var : it->second)
170 worklist.insert({func, var});
171
172 // Push each captured variables to the func's callers transitively.
173 while (!worklist.empty()) {
174 auto [func, cap] = worklist.pop_back_val();
175 auto callersIt = callers.find(func);
176 if (callersIt == callers.end())
177 continue;
178 for (auto *caller : callersIt->second)
179 if (!isLocalToFunction(*cap, *caller))
180 if (capturedVars[caller].insert(cap))
181 worklist.insert({caller, cap});
182 }
183 }
184 }
185};
186
187} // namespace
188
190 CaptureWalker walker;
191 root.visit(walker);
192 walker.propagateCaptures();
193 return std::move(walker.capturedVars);
194}
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....
CaptureMap analyzeFunctionCaptures(const slang::ast::RootSymbol &root)
Analyze the AST rooted at root to determine which variables each function captures.
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.