CIRCT 22.0.0git
Loading...
Searching...
No Matches
VerilogIndex.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 "slang/ast/ASTVisitor.h"
10#include "slang/ast/Expression.h"
11#include "slang/ast/statements/MiscStatements.h"
12#include "slang/ast/symbols/CompilationUnitSymbols.h"
13#include "slang/ast/symbols/InstanceSymbols.h"
14#include "slang/ast/symbols/MemberSymbols.h"
15#include "slang/ast/symbols/VariableSymbols.h"
16#include "slang/syntax/AllSyntax.h"
17#include "slang/text/SourceManager.h"
18
19#include "mlir/IR/Location.h"
20#include "mlir/Support/FileUtilities.h"
21
22#include "llvm/ADT/StringExtras.h"
23
24#include "../Utils/LSPUtils.h"
25#include "VerilogIndex.h"
26
27using namespace circt::lsp;
28using namespace llvm;
29
30namespace {
31
32/// Helper function to determine whether an AST node is outside of the main
33/// buffer. If this can't be safely determined, return false.
34template <typename T>
35inline bool definitelyOutsideMainBuffer(const T &t, slang::BufferID bufferId) {
36 if constexpr (requires {
37 t.location;
38 }) { // AST nodes with location, e.g. Symbols
39 auto r = t.location;
40 if (r.valid())
41 return r.buffer() != bufferId;
42 }
43
44 if constexpr (requires {
45 t.sourceRange;
46 }) { // AST nodes with sourceRange, e.g. expressions
47 auto r = t.sourceRange;
48 if (r.start().valid() && r.end().valid())
49 return r.start().buffer() != bufferId && r.end().buffer() != bufferId;
50 }
51
52 if constexpr (requires { t.getSyntax(); }) { // Fallback to syntax range
53 if (auto *syn = t.getSyntax()) {
54 auto r = syn->sourceRange(); // SyntaxNodes always have a source range.
55 if (r.start().valid() && r.end().valid())
56 return r.start().buffer() != bufferId && r.end().buffer() != bufferId;
57 }
58 }
59 return false; // not enough info => don’t prune
60}
61
62// Index the AST to find symbol uses and definitions.
63struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
64 using ASTBase = slang::ast::ASTVisitor<VerilogIndexer, true, true>;
65 VerilogIndexer(VerilogIndex &index) : index(index) {}
66 VerilogIndex &index;
67
68 template <typename T>
69 void recurseIfInMainBuffer(const T &node) {
70 if (definitelyOutsideMainBuffer(node, index.getBufferId()))
71 return;
72 visitDefault(node);
73 }
74
75 void insertSymbol(const slang::ast::Symbol *symbol, slang::SourceRange range,
76 bool isDefinition = true) {
77 if (symbol->name.empty())
78 return;
79 assert(range.start().valid() && range.end().valid() &&
80 "range must be valid");
81
82 // TODO: This implementation does not handle expanded MACROs. Return
83 // instead.
84 if (range.start().offset() >= range.end().offset()) {
85 return;
86 }
87
88 index.insertSymbol(symbol, range, isDefinition);
89 }
90
91 void insertSymbol(const slang::ast::Symbol *symbol,
92 slang::SourceLocation from, bool isDefinition = false) {
93 if (symbol->name.empty())
94 return;
95 assert(from.valid() && "location must be valid");
96 insertSymbol(symbol, slang::SourceRange(from, from + symbol->name.size()),
97 isDefinition);
98 }
99
100 // Handle named values, such as references to declared variables.
101 void visitExpression(const slang::ast::Expression &expr) {
102 auto *symbol = expr.getSymbolReference(true);
103 if (!symbol)
104 return;
105 insertSymbol(symbol, expr.sourceRange, /*isDefinition=*/false);
106 }
107
108 void visitSymbol(const slang::ast::Symbol &symbol) {
109 insertSymbol(&symbol, symbol.location, /*isDefinition=*/true);
110 }
111
112 void visit(const slang::ast::NetSymbol &expr) {
113 insertSymbol(&expr, expr.location, /*isDefinition=*/true);
114 recurseIfInMainBuffer(expr);
115 }
116
117 void visit(const slang::ast::VariableSymbol &expr) {
118 insertSymbol(&expr, expr.location, /*isDefinition=*/true);
119 recurseIfInMainBuffer(expr);
120 }
121
122 void visit(const slang::ast::ExplicitImportSymbol &expr) {
123 auto *def = expr.package();
124 if (!def)
125 return;
126
127 if (auto *syn = expr.getSyntax()) {
128 if (auto *item = syn->as_if<slang::syntax::PackageImportItemSyntax>()) {
129 insertSymbol(def, item->package.location(), /*isDefinition=*/false);
130 }
131 }
132 recurseIfInMainBuffer(expr);
133 }
134
135 void visit(const slang::ast::WildcardImportSymbol &expr) {
136 auto *def = expr.getPackage();
137 if (!def)
138 return;
139
140 if (auto *syn = expr.getSyntax()) {
141 if (auto *item = syn->as_if<slang::syntax::PackageImportItemSyntax>()) {
142 insertSymbol(def, item->package.location(), false);
143 }
144 }
145 recurseIfInMainBuffer(expr);
146 }
147
148 void visit(const slang::ast::InstanceBodySymbol &expr) {
149 // Insert the symbols in the port list.
150 for (const auto *symbol : expr.getPortList())
151 index.insertSymbolDefinition(symbol);
152 recurseIfInMainBuffer(expr);
153 }
154
155 void visit(const slang::ast::InstanceSymbol &expr) {
156 auto *def = &expr.getDefinition();
157 if (!def)
158 return;
159
160 // Add the module definition
161 insertSymbol(def, def->location, /*isDefinition=*/true);
162
163 // Walk up the syntax tree until we hit the type token;
164 // Link that token back to the instance declaration.
165 if (auto *hierInst =
166 expr.getSyntax()
167 ->as_if<slang::syntax::HierarchicalInstanceSyntax>())
168 if (auto *modInst =
169 hierInst->parent
170 ->as_if<slang::syntax::HierarchyInstantiationSyntax>())
171 if (modInst->type)
172 insertSymbol(def, modInst->type.location(), false);
173
174 // Link the module instance name back to the module definition
175 insertSymbol(def, expr.location, /*isDefinition=*/false);
176 recurseIfInMainBuffer(expr);
177 }
178
179 void visit(const slang::ast::VariableDeclStatement &expr) {
180 insertSymbol(&expr.symbol, expr.sourceRange, /*isDefinition=*/true);
181 recurseIfInMainBuffer(expr);
182 }
183
184 template <typename T>
185 void visit(const T &node) {
186 if constexpr (std::is_base_of_v<slang::ast::Expression, T>)
187 visitExpression(node);
188 if constexpr (std::is_base_of_v<slang::ast::Symbol, T>)
189 visitSymbol(node);
190
191 recurseIfInMainBuffer(node);
192 }
193
194 template <typename T>
195 void visitInvalid(const T &t) {}
196};
197} // namespace
198
199void VerilogIndex::initialize(slang::ast::Compilation &compilation) {
200 const auto &root = compilation.getRoot();
201 VerilogIndexer visitor(*this);
202
203 // Index packages defined in the current main buffer
204 for (auto *package : compilation.getPackages()) {
205 if (package->location.buffer() != getBufferId())
206 continue;
207 // Visit the body of the top instance.
208 package->visit(visitor);
209 }
210
211 // Index modules defined in the current main buffer
212 for (auto *inst : root.topInstances) {
213 if (inst->body.location.buffer() != getBufferId())
214 continue;
215 // Visit the body of the top instance.
216 inst->body.visit(visitor);
217 }
218
219 // Parse the source location from the main file.
221}
222
223void VerilogIndex::parseSourceLocation(StringRef toParse) {
224 // No multiline entries.
225 if (toParse.contains('\n'))
226 return;
227
228 StringRef filePath;
229 SmallVector<StringRef, 3> fileLineColStrs;
230
231 // Parse the source location emitted by ExportVerilog, e.g.
232 // @[foo.mlir:1:10, :20:30, bar.mlir:2:{30, 40}]
233 for (auto chunk : llvm::split(toParse, ", ")) {
234 fileLineColStrs.clear();
235 chunk.split(fileLineColStrs, ':');
236 if (fileLineColStrs.size() != 3)
237 continue;
238
239 auto filePathMaybeEmpty = fileLineColStrs[0].trim();
240 // If the file path is empty, use the previous file path.
241 if (!filePathMaybeEmpty.empty())
242 filePath = filePathMaybeEmpty;
243
244 auto line = fileLineColStrs[1].trim();
245 auto column = fileLineColStrs[2].trim();
246
247 uint32_t lineInt;
248 // Line must be always valid.
249 if (line.getAsInteger(10, lineInt))
250 continue;
251
252 // A pair of column and start location. Start location may include filepath
253 // and line string.
254 SmallVector<std::pair<StringRef, const char *>> columns;
255
256 // Column string may contains several columns like `{col1, col2, ...}`.
257 if (column.starts_with('{') && column.ends_with('}')) {
258 bool first = true;
259 for (auto str : llvm::split(column.drop_back().drop_front(), ',')) {
260 columns.emplace_back(str,
261 first ? filePathMaybeEmpty.data() : str.data());
262 first = false;
263 }
264 } else {
265 columns.push_back({column, filePathMaybeEmpty.data()});
266 }
267
268 // Insert the interval into the interval map.
269 for (auto [column, start] : columns) {
270 uint32_t columnInt;
271 if (column.getAsInteger(10, columnInt))
272 continue;
273 auto loc = mlir::FileLineColRange::get(&mlirContext, filePath,
274 lineInt - 1, columnInt - 1,
275 lineInt - 1, columnInt - 1);
276 const char *end = column.end();
277 if (!intervalMap.overlaps(start, end))
278 intervalMap.insert(start, end, loc);
279 }
280 }
281}
282
284 auto &sourceMgr = getSlangSourceManager();
285 auto getMainBuffer = sourceMgr.getSourceText(getBufferId());
286 StringRef text(getMainBuffer);
287
288 // Loop over comments starting with "@[", and parse the source location.
289 // TODO: Consider supporting other location format. This is currently
290 // very specific to `locationInfoStyle=WrapInAtSquareBracket`.
291 while (true) {
292 // Find the source location from the text.
293 StringRef start = "// @[";
294 auto loc = text.find(start);
295 if (loc == StringRef::npos)
296 break;
297
298 text = text.drop_front(loc + start.size());
299 auto endPos = text.find_first_of("]\n");
300 if (endPos == StringRef::npos)
301 break;
302 auto toParse = text.take_front(endPos);
304 parseSourceLocation(toParse);
305 }
306}
307
308void VerilogIndex::insertSymbol(const slang::ast::Symbol *symbol,
309 slang::SourceRange from, bool isDefinition) {
310 assert(from.start().valid() && from.end().valid());
311
312 // TODO: Currently doesn't handle expanded macros
313 if (from.start().offset() >= from.end().offset())
314 return;
315
316 auto lhsBufferId = from.start().buffer();
317 auto rhsBufferId = from.end().buffer();
318 if (lhsBufferId.getId() != rhsBufferId.getId() || !rhsBufferId.valid() ||
319 !lhsBufferId.valid())
320 return;
321
322 auto buffer = getSlangSourceManager().getSourceText(lhsBufferId);
323
324 const auto *lhsBound = buffer.data() + from.start().offset();
325 const auto *rhsBound = buffer.data() + from.end().offset();
326
327 if (lhsBound >= rhsBound)
328 return;
329
330 if (!intervalMap.overlaps(lhsBound, rhsBound)) {
331 intervalMap.insert(lhsBound, rhsBound, symbol);
332 if (!isDefinition)
333 references[symbol].push_back(from);
334 }
335}
336
337void VerilogIndex::insertSymbolDefinition(const slang::ast::Symbol *symbol) {
338 if (!symbol->location)
339 return;
340 auto size = symbol->name.size() ? symbol->name.size() : 1;
341 auto range = slang::SourceRange(symbol->location, symbol->location + size);
342
343 insertSymbol(symbol, range, true);
344}
assert(baseType &&"element must be base type")
static SmallVector< PortInfo > getPortList(ModuleTy &mod)
Definition HWOps.cpp:1428
ReferenceMap references
References of symbols.
void parseSourceLocation()
Parse source location emitted by ExportVerilog.
void insertSymbolDefinition(const slang::ast::Symbol *symbol)
mlir::MLIRContext mlirContext
void insertSymbol(const slang::ast::Symbol *symbol, slang::SourceRange from, bool isDefinition=false)
Register a reference to a symbol symbol from from.
const slang::BufferID & getBufferId() const
MapT intervalMap
An interval map containing a corresponding definition mapped to a source interval.
void initialize(slang::ast::Compilation &compilation)
Initialize the index with the given compilation unit.
const slang::SourceManager & getSlangSourceManager() const
void info(Twine message)
Definition LSPUtils.cpp:20