Skip to content

Commit 34ecdfc

Browse files
fabianschuikiclaude
andcommitted
[ImportVerilog] Add capture analysis pre-pass for functions
Add a pre-pass over the slang AST that determines which non-local, non-global variables each function captures, either directly or transitively through calls. This walks the entire AST, collecting variable references and call graph edges, then propagates captures through the inverse call graph using a worklist until stable. This analysis is a prerequisite for switching ImportVerilog to a two-phase declare-then-define approach for functions. Currently, function bodies are converted eagerly during package/module member iteration, which can fail when a function transitively references a variable that hasn't been declared yet (e.g., UVM's `m_uvm_core_state`). With the capture sets known upfront, function declarations can include the correct capture parameters from the start, and body conversion can be deferred. Also add `MapVector`, `SmallMapVector`, and `SmallSetVector` re-exports. The analysis is not yet directly used in ImportVerilog. This will be done in a follow-up commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 56e2170 commit 34ecdfc

File tree

8 files changed

+457
-2
lines changed

8 files changed

+457
-2
lines changed

include/circt/Support/LLVM.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,26 @@ using mlir::TypeSwitch; // NOLINT(misc-unused-using-decls)
7272
// Forward declarations of LLVM classes to be imported in to the circt
7373
// namespace.
7474
namespace llvm {
75+
template <typename KeyT, typename ValueT, typename MapType, typename VectorType>
76+
class MapVector;
7577
template <typename KeyT, typename ValueT, unsigned InlineBuckets,
7678
typename KeyInfoT, typename BucketT>
7779
class SmallDenseMap;
80+
template <typename KeyT, typename ValueT, unsigned N>
81+
struct SmallMapVector;
7882
template <typename T, unsigned N, typename C>
7983
class SmallSet;
84+
template <typename T, unsigned N>
85+
class SmallSetVector;
8086
} // namespace llvm
8187

8288
// Import things we want into our namespace.
8389
namespace circt {
84-
using llvm::SmallDenseMap; // NOLINT(misc-unused-using-decls)
85-
using llvm::SmallSet; // NOLINT(misc-unused-using-decls)
90+
using llvm::MapVector; // NOLINT(misc-unused-using-decls)
91+
using llvm::SmallDenseMap; // NOLINT(misc-unused-using-decls)
92+
using llvm::SmallMapVector; // NOLINT(misc-unused-using-decls)
93+
using llvm::SmallSet; // NOLINT(misc-unused-using-decls)
94+
using llvm::SmallSetVector; // NOLINT(misc-unused-using-decls)
8695
} // namespace circt
8796

8897
// Forward declarations of classes to be imported in to the circt namespace.

lib/Conversion/ImportVerilog/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ include(SlangCompilerOptions)
22

33
add_circt_translation_library(CIRCTImportVerilog
44
AssertionExpr.cpp
5+
CaptureAnalysis.cpp
56
Expressions.cpp
67
FormatStrings.cpp
78
HierarchicalNames.cpp
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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+
14+
using namespace slang::ast;
15+
using namespace circt;
16+
using 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.
21+
static 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+
/// Check whether `var` is a global variable. Walk up from the variable's parent
34+
/// scope; if we hit a function or instance body, it's not global. Otherwise
35+
/// (package, compilation unit, root) it is.
36+
static bool isGlobalVariable(const ValueSymbol &var) {
37+
for (const Scope *scope = var.getParentScope(); scope;
38+
scope = scope->asSymbol().getParentScope()) {
39+
switch (scope->asSymbol().kind) {
40+
case SymbolKind::Subroutine:
41+
case SymbolKind::InstanceBody:
42+
return false;
43+
default:
44+
break;
45+
}
46+
}
47+
return true;
48+
}
49+
50+
namespace {
51+
52+
/// Walk the entire AST to collect captured variables and the call graph for
53+
/// each function. Uses slang's `ASTVisitor` with both statement and expression
54+
/// visiting enabled so that we recurse into all function bodies.
55+
struct CaptureWalker
56+
: public ASTVisitor<CaptureWalker, /*VisitStatements=*/true,
57+
/*VisitExpressions=*/true> {
58+
59+
/// The function whose body we are currently inside, or nullptr if we are at
60+
/// a scope outside any function.
61+
const SubroutineSymbol *currentFunc = nullptr;
62+
63+
/// Captured variables per function.
64+
CaptureMap capturedVars;
65+
66+
/// Inverse call graph: maps each callee to the set of callers that call it.
67+
/// Used to propagate captures from callees to their callers. Uses MapVector
68+
/// for deterministic iteration order during propagation.
69+
MapVector<const SubroutineSymbol *,
70+
SmallSetVector<const SubroutineSymbol *, 4>>
71+
callers;
72+
73+
/// When we enter a function body, record it as the current function and
74+
/// recurse into its members and body statements.
75+
void handle(const SubroutineSymbol &func) {
76+
llvm::SaveAndRestore guard(currentFunc, &func);
77+
visitDefault(func);
78+
}
79+
80+
/// When we see a named value reference inside a function, check if it needs
81+
/// to be captured.
82+
void handle(const NamedValueExpression &expr) {
83+
if (!currentFunc)
84+
return;
85+
86+
auto &var = expr.symbol;
87+
88+
// Class properties are accessed through `this`, not captured.
89+
if (var.kind == SymbolKind::ClassProperty)
90+
return;
91+
92+
// Function arguments are local by definition.
93+
if (var.kind == SymbolKind::FormalArgument)
94+
return;
95+
96+
// Only capture variables that are non-local and non-global.
97+
if (isLocalToFunction(var, *currentFunc) || isGlobalVariable(var))
98+
return;
99+
100+
capturedVars[currentFunc].insert(&var);
101+
}
102+
103+
/// Record call graph edges when we see a function call.
104+
void handle(const CallExpression &expr) {
105+
if (currentFunc)
106+
if (auto *const *callee =
107+
std::get_if<const SubroutineSymbol *>(&expr.subroutine))
108+
callers[*callee].insert(currentFunc);
109+
visitDefault(expr);
110+
}
111+
112+
/// Propagate captures transitively through the call graph. For each callee
113+
/// that has captures, push each captured variable upward through all
114+
/// transitive callers using a worklist. A captured variable is only
115+
/// propagated to a caller if it is not local to that caller.
116+
void propagateCaptures() {
117+
using WorkItem = std::pair<const SubroutineSymbol *, const ValueSymbol *>;
118+
SmallSetVector<WorkItem, 16> worklist;
119+
120+
for (auto &[func, _] : callers) {
121+
// Check if this function captures any variables. Nothing to do if it
122+
// doesn't.
123+
auto it = capturedVars.find(func);
124+
if (it == capturedVars.end())
125+
continue;
126+
127+
// Prime the worklist with the captured variables.
128+
for (auto *var : it->second)
129+
worklist.insert({func, var});
130+
131+
// Push each captured variables to the func's callers transitively.
132+
while (!worklist.empty()) {
133+
auto [func, cap] = worklist.pop_back_val();
134+
auto callersIt = callers.find(func);
135+
if (callersIt == callers.end())
136+
continue;
137+
for (auto *caller : callersIt->second)
138+
if (!isLocalToFunction(*cap, *caller))
139+
if (capturedVars[caller].insert(cap))
140+
worklist.insert({caller, cap});
141+
}
142+
}
143+
}
144+
};
145+
146+
} // namespace
147+
148+
CaptureMap ImportVerilog::analyzeFunctionCaptures(const RootSymbol &root) {
149+
CaptureWalker walker;
150+
root.visit(walker);
151+
walker.propagateCaptures();
152+
return std::move(walker.capturedVars);
153+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
// Pre-pass over the slang AST to determine which non-local, non-global
10+
// variables each function captures, either directly or transitively through
11+
// calls to other functions.
12+
//
13+
// This information is needed before any MLIR conversion happens so that
14+
// function declarations can be created with the correct signature (including
15+
// extra capture parameters) upfront, enabling a clean two-phase
16+
// declare-then-define approach for functions.
17+
//
18+
//===----------------------------------------------------------------------===//
19+
20+
// NOLINTNEXTLINE(llvm-header-guard)
21+
#ifndef CONVERSION_IMPORTVERILOG_CAPTUREANALYSIS_H
22+
#define CONVERSION_IMPORTVERILOG_CAPTUREANALYSIS_H
23+
24+
#include "circt/Support/LLVM.h"
25+
#include "llvm/ADT/SetVector.h"
26+
27+
namespace slang {
28+
namespace ast {
29+
class RootSymbol;
30+
class SubroutineSymbol;
31+
class ValueSymbol;
32+
} // namespace ast
33+
} // namespace slang
34+
35+
namespace circt {
36+
namespace ImportVerilog {
37+
38+
/// The result of capture analysis: for each function, the set of non-local,
39+
/// non-global variable symbols that the function captures directly or
40+
/// transitively through calls.
41+
using CaptureMap = DenseMap<const slang::ast::SubroutineSymbol *,
42+
SmallSetVector<const slang::ast::ValueSymbol *, 4>>;
43+
44+
/// Analyze the AST rooted at `root` to determine which variables each function
45+
/// captures. A variable is considered captured by a function if it is
46+
/// referenced inside the function's body (or transitively through called
47+
/// functions) and is neither local to the function nor a global variable
48+
/// (package-scope or compilation-unit-scope variables that are lowered via
49+
/// `get_global_signal`).
50+
CaptureMap analyzeFunctionCaptures(const slang::ast::RootSymbol &root);
51+
52+
} // namespace ImportVerilog
53+
} // namespace circt
54+
55+
#endif // CONVERSION_IMPORTVERILOG_CAPTUREANALYSIS_H

unittests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ function(add_circt_unittest test_dirname)
99
add_unittest(CIRCTUnitTests ${test_dirname} ${ARGN})
1010
endfunction()
1111

12+
add_subdirectory(Conversion)
1213
add_subdirectory(Dialect)
1314
add_subdirectory(Support)
1415
add_subdirectory(Tools)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
if(CIRCT_SLANG_FRONTEND_ENABLED)
2+
add_subdirectory(ImportVerilog)
3+
endif()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
include(SlangCompilerOptions)
2+
3+
add_circt_unittest(CIRCTImportVerilogTests
4+
CaptureAnalysisTest.cpp
5+
)
6+
7+
target_include_directories(CIRCTImportVerilogTests
8+
PRIVATE
9+
${CIRCT_MAIN_SRC_DIR}
10+
)
11+
12+
target_link_libraries(CIRCTImportVerilogTests
13+
PRIVATE
14+
CIRCTImportVerilog
15+
LLVMSupport
16+
slang_slang
17+
)

0 commit comments

Comments
 (0)