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