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