CIRCT 22.0.0git
Loading...
Searching...
No Matches
VerilogServer.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// This file implements the VerilogServer class, which is responsible for
10// managing the state of the Verilog server. VerilogServer keeps track of the
11// contents of all open text documents, and each document has a slang
12// compilation result.
13//
14//===----------------------------------------------------------------------===//
15#include "VerilogServer.h"
16#include "../Utils/LSPUtils.h"
17
18#include "circt/Support/LLVM.h"
20#include "mlir/IR/Attributes.h"
21#include "mlir/IR/BuiltinAttributes.h"
22#include "mlir/IR/Location.h"
23#include "mlir/IR/MLIRContext.h"
24#include "mlir/Support/FileUtilities.h"
25#include "slang/ast/ASTVisitor.h"
26#include "slang/ast/Compilation.h"
27#include "slang/ast/Scope.h"
28#include "slang/ast/symbols/CompilationUnitSymbols.h"
29#include "slang/diagnostics/DiagnosticClient.h"
30#include "slang/diagnostics/Diagnostics.h"
31#include "slang/driver/Driver.h"
32#include "slang/syntax/AllSyntax.h"
33#include "slang/syntax/SyntaxTree.h"
34#include "slang/text/SourceLocation.h"
35#include "slang/text/SourceManager.h"
36#include "llvm/ADT/IntervalMap.h"
37#include "llvm/ADT/PointerUnion.h"
38#include "llvm/ADT/SmallString.h"
39#include "llvm/ADT/StringExtras.h"
40#include "llvm/ADT/StringMap.h"
41#include "llvm/Support/FileSystem.h"
42#include "llvm/Support/LSP/Logging.h"
43#include "llvm/Support/LSP/Protocol.h"
44#include "llvm/Support/LineIterator.h"
45#include "llvm/Support/MemoryBuffer.h"
46#include "llvm/Support/Path.h"
47#include "llvm/Support/SMLoc.h"
48#include "llvm/Support/SourceMgr.h"
49
50#include <filesystem>
51#include <fstream>
52#include <memory>
53#include <optional>
54#include <string>
55
56using namespace llvm::lsp;
57
58using namespace mlir;
59
60using namespace circt::lsp;
61using namespace circt;
62
63static llvm::lsp::DiagnosticSeverity
64getSeverity(slang::DiagnosticSeverity severity) {
65 switch (severity) {
66 case slang::DiagnosticSeverity::Fatal:
67 case slang::DiagnosticSeverity::Error:
68 return llvm::lsp::DiagnosticSeverity::Error;
69 case slang::DiagnosticSeverity::Warning:
70 return llvm::lsp::DiagnosticSeverity::Warning;
71 case slang::DiagnosticSeverity::Ignored:
72 case slang::DiagnosticSeverity::Note:
73 return llvm::lsp::DiagnosticSeverity::Information;
74 }
75 llvm_unreachable("all slang diagnostic severities should be handled");
76 return llvm::lsp::DiagnosticSeverity::Error;
77}
78namespace {
79
80// A global context carried around by the server.
81struct VerilogServerContext {
82 VerilogServerContext(const VerilogServerOptions &options)
83 : options(options) {}
84 const VerilogServerOptions &options;
85};
86
87class VerilogDocument;
88using VerilogIndexSymbol =
89 llvm::PointerUnion<const slang::ast::Symbol *, mlir::Attribute>;
90
91class VerilogIndex {
92public:
93 VerilogIndex(VerilogDocument &document)
94 : mlirContext(mlir::MLIRContext::Threading::DISABLED),
95 intervalMap(allocator), document(document) {}
96
97 /// Initialize the index with the given compilation unit.
98 void initialize(slang::ast::Compilation &compilation);
99
100 /// Register a reference to a symbol `symbol` from `from`.
101 void insertSymbol(const slang::ast::Symbol *symbol, slang::SourceRange from,
102 bool isDefinition = false);
103 void insertSymbolDefinition(const slang::ast::Symbol *symbol);
104
105 VerilogDocument &getDocument() { return document; }
106
107 /// The type of interval map used to store source references. SMRange is
108 /// half-open, so we also need to use a half-open interval map.
109 using MapT =
110 llvm::IntervalMap<const char *, VerilogIndexSymbol,
111 llvm::IntervalMapImpl::NodeSizer<
112 const char *, VerilogIndexSymbol>::LeafSize,
113 llvm::IntervalMapHalfOpenInfo<const char *>>;
114
115 MapT &getIntervalMap() { return intervalMap; }
116
117 /// A mapping from a symbol to their references.
118 using ReferenceMap = SmallDenseMap<const slang::ast::Symbol *,
119 SmallVector<slang::SourceRange>>;
120 const ReferenceMap &getReferences() const { return references; }
121
122private:
123 /// Parse source location emitted by ExportVerilog.
124 void parseSourceLocation();
125 void parseSourceLocation(StringRef toParse);
126
127 // MLIR context used for generating location attr.
128 mlir::MLIRContext mlirContext;
129
130 /// An allocator for the interval map.
131 MapT::Allocator allocator;
132
133 /// An interval map containing a corresponding definition mapped to a source
134 /// interval.
135 MapT intervalMap;
136
137 /// References of symbols.
138 ReferenceMap references;
139
140 // The parent document.
141 VerilogDocument &document;
142};
143
144//===----------------------------------------------------------------------===//
145// VerilogDocument
146//===----------------------------------------------------------------------===//
147
148/// This class represents all of the information pertaining to a specific
149/// Verilog document.
150class LSPDiagnosticClient;
151class VerilogDocument {
152public:
153 VerilogDocument(VerilogServerContext &globalContext,
154 const llvm::lsp::URIForFile &uri, StringRef contents,
155 std::vector<llvm::lsp::Diagnostic> &diagnostics);
156 VerilogDocument(const VerilogDocument &) = delete;
157 VerilogDocument &operator=(const VerilogDocument &) = delete;
158
159 const llvm::lsp::URIForFile &getURI() const { return uri; }
160
161 llvm::SourceMgr &getSourceMgr() { return sourceMgr; }
163 return bufferIDMap;
164 }
165
166 const slang::SourceManager &getSlangSourceManager() const {
167 return driver.sourceManager;
168 }
169
170 // Return LSP location from slang location.
171 llvm::lsp::Location getLspLocation(slang::SourceLocation loc) const;
172 llvm::lsp::Location getLspLocation(slang::SourceRange range) const;
173
174 // Return SMLoc from slang location.
175 llvm::SMLoc getSMLoc(slang::SourceLocation loc);
176
177 //===--------------------------------------------------------------------===//
178 // Definitions and References
179 //===--------------------------------------------------------------------===//
180
181 void getLocationsOf(const llvm::lsp::URIForFile &uri,
182 const llvm::lsp::Position &defPos,
183 std::vector<llvm::lsp::Location> &locations);
184
185 void findReferencesOf(const llvm::lsp::URIForFile &uri,
186 const llvm::lsp::Position &pos,
187 std::vector<llvm::lsp::Location> &references);
188
189private:
190 std::optional<std::pair<uint32_t, SmallString<128>>>
191 getOrOpenFile(StringRef filePath);
192
193 VerilogServerContext &globalContext;
194
195 // A map from slang buffer ID to the corresponding buffer ID in the LLVM
196 // source manager.
198
199 // A map from a file name to the corresponding buffer ID in the LLVM
200 // source manager.
201 llvm::StringMap<std::pair<uint32_t, SmallString<128>>> filePathMap;
202
203 // The compilation result.
204 FailureOr<std::unique_ptr<slang::ast::Compilation>> compilation;
205
206 // The slang driver.
207 slang::driver::Driver driver;
208
209 // The LLVM source manager.
210 llvm::SourceMgr sourceMgr;
211
212 /// The index of the parsed module.
213 VerilogIndex index;
214
215 // The URI of the document.
216 llvm::lsp::URIForFile uri;
217};
218
219} // namespace
220
221//===----------------------------------------------------------------------===//
222// LSPDiagnosticClient
223//===----------------------------------------------------------------------===//
224
225namespace {
226/// A converter that can be plugged into a slang `DiagnosticEngine` as a
227/// client that will map slang diagnostics to LSP diagnostics.
228class LSPDiagnosticClient : public slang::DiagnosticClient {
229 const VerilogDocument &document;
230 std::vector<llvm::lsp::Diagnostic> &diags;
231
232public:
233 LSPDiagnosticClient(const VerilogDocument &document,
234 std::vector<llvm::lsp::Diagnostic> &diags)
235 : document(document), diags(diags) {}
236
237 void report(const slang::ReportedDiagnostic &slangDiag) override;
238};
239} // namespace
240
241void LSPDiagnosticClient::report(const slang::ReportedDiagnostic &slangDiag) {
242 auto loc = document.getLspLocation(slangDiag.location);
243 // Show only the diagnostics in the current file.
244 if (loc.uri != document.getURI())
245 return;
246 auto &mlirDiag = diags.emplace_back();
247 mlirDiag.severity = getSeverity(slangDiag.severity);
248 mlirDiag.range = loc.range;
249 mlirDiag.source = "slang";
250 mlirDiag.message = slangDiag.formattedMessage;
251}
252
253//===----------------------------------------------------------------------===//
254// VerilogDocument
255//===----------------------------------------------------------------------===//
256
257static std::filesystem::path
258canonicalizeFileName(const std::filesystem::path &file) {
259 std::error_code ec;
260 std::filesystem::path path = std::filesystem::weakly_canonical(file, ec);
261 if (ec)
262 path = std::filesystem::absolute(file).lexically_normal();
263 return path;
264}
265
266// Filter out the main buffer file from the command file list, if it is in
267// there.
268static inline bool
269mainBufferFileInCommandFileList(const std::string &cmdfileStr,
270 const std::string &targetAbsStr) {
271 const std::filesystem::path targetAbs =
272 canonicalizeFileName(std::filesystem::path(targetAbsStr));
273
274 std::string error;
275 auto cmdFile = mlir::openInputFile(cmdfileStr, &error);
276 if (!cmdFile) {
277 circt::lsp::Logger::error(Twine("Failed to open command file ") +
278 cmdfileStr + ": " + error);
279 return false;
280 }
281
282 const std::filesystem::path base =
283 std::filesystem::path(cmdFile->getBufferIdentifier().str()).parent_path();
284
285 // Read line by line, ignoring empty lines and comments.
286 for (llvm::line_iterator i(*cmdFile); !i.is_at_eof(); ++i) {
287 llvm::StringRef line = i->trim();
288
289 if (line.empty())
290 continue;
291
292 static constexpr llvm::StringRef commandPrefixes[] = {"+", "-"};
293 auto isCommand = [&line](llvm::StringRef s) { return line.starts_with(s); };
294 if (llvm::any_of(commandPrefixes, isCommand))
295 continue;
296
297 auto candRel = std::filesystem::path(line.str());
298 auto candAbs = canonicalizeFileName(
299 candRel.is_absolute() ? candRel : (base / candRel));
300
301 if (candAbs == targetAbs)
302 return true;
303 }
304 return false;
305}
306
307VerilogDocument::VerilogDocument(
308 VerilogServerContext &context, const llvm::lsp::URIForFile &uri,
309 StringRef contents, std::vector<llvm::lsp::Diagnostic> &diagnostics)
310 : globalContext(context), index(*this), uri(uri) {
311 bool skipMainBufferSlangImport = false;
312
313 llvm::SmallString<256> canonPath(uri.file());
314 if (std::error_code ec = llvm::sys::fs::real_path(uri.file(), canonPath))
315 canonPath = uri.file(); // fall back, but try to keep it absolute
316
317 // --- Apply project command files (the “-C”s) to this per-buffer driver ---
318 for (const std::string &cmdFile : context.options.commandFiles) {
319 if (!driver.processCommandFiles(cmdFile, false, true)) {
320 circt::lsp::Logger::error(Twine("Failed to open command file ") +
321 cmdFile);
322 }
323 skipMainBufferSlangImport |=
324 mainBufferFileInCommandFileList(cmdFile, canonPath.str().str());
325 }
326
327 auto memBufferOwn =
328 llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file());
329 if (!memBufferOwn) {
331 Twine("Failed to create memory buffer for file ") + uri.file());
332 return;
333 }
334
335 const unsigned int mainBufferId =
336 sourceMgr.AddNewSourceBuffer(std::move(memBufferOwn), SMLoc());
337
338 // Build the set of include directories for this file.
339 llvm::SmallString<32> uriDirectory(uri.file());
340 llvm::sys::path::remove_filename(uriDirectory);
341
342 std::vector<std::string> libDirs;
343 libDirs.push_back(uriDirectory.str().str());
344 libDirs.insert(libDirs.end(), context.options.libDirs.begin(),
345 context.options.libDirs.end());
346
347 // Populate source managers.
348 const llvm::MemoryBuffer *memBuffer = sourceMgr.getMemoryBuffer(mainBufferId);
349
350 // This block compiles the top file to determine all definitions
351 // This is used in a second pass to declare all those definitions
352 // as top modules, so they are elaborated and subsequently indexed.
353 {
354 slang::driver::Driver topDriver;
355
356 auto topSlangBuffer =
357 topDriver.sourceManager.assignText(uri.file(), memBuffer->getBuffer());
358 topDriver.sourceLoader.addBuffer(topSlangBuffer);
359
360 topDriver.options.compilationFlags.emplace(
361 slang::ast::CompilationFlags::LintMode, false);
362 topDriver.options.compilationFlags.emplace(
363 slang::ast::CompilationFlags::DisableInstanceCaching, false);
364
365 if (!topDriver.processOptions()) {
366 return;
367 }
368
369 if (!topDriver.parseAllSources()) {
370 circt::lsp::Logger::error(Twine("Failed to parse Verilog file ") +
371 uri.file());
372 return;
373 }
374
375 FailureOr<std::unique_ptr<slang::ast::Compilation>> topCompilation =
376 topDriver.createCompilation();
377 if (failed(topCompilation))
378 return;
379
380 std::vector<std::string> topModules;
381 for (const auto *defs : (*topCompilation)->getDefinitions())
382 topModules.emplace_back(defs->name);
383
384 // Make sure that all possible definitions in the main buffer are
385 // topModules!
386 driver.options.topModules = std::move(topModules);
387 }
388
389 for (const auto &libDir : libDirs) {
390 driver.sourceLoader.addSearchDirectories(libDir);
391 }
392
393 // If the main buffer is **not** present in a command file, add it into
394 // slang's source manager and bind to llvm source manager.
395 if (!skipMainBufferSlangImport) {
396 auto slangBuffer =
397 driver.sourceManager.assignText(uri.file(), memBuffer->getBuffer());
398 driver.sourceLoader.addBuffer(slangBuffer);
399 bufferIDMap[slangBuffer.id.getId()] = mainBufferId;
400 }
401
402 auto diagClient = std::make_shared<LSPDiagnosticClient>(*this, diagnostics);
403 driver.diagEngine.addClient(diagClient);
404
405 driver.options.compilationFlags.emplace(
406 slang::ast::CompilationFlags::LintMode, false);
407 driver.options.compilationFlags.emplace(
408 slang::ast::CompilationFlags::DisableInstanceCaching, false);
409
410 if (!driver.processOptions()) {
411 return;
412 }
413
414 driver.diagEngine.setIgnoreAllWarnings(false);
415
416 if (!driver.parseAllSources()) {
417 circt::lsp::Logger::error(Twine("Failed to parse Verilog file ") +
418 uri.file());
419 return;
420 }
421
422 compilation = driver.createCompilation();
423 if (failed(compilation))
424 return;
425
426 // If the main buffer is present in a command file, compile it only once
427 // and import directly from the command file; then figure out which buffer id
428 // it was assigned and bind to llvm source manager.
429 llvm::SmallString<256> slangCanonPath;
430 std::unique_ptr<llvm::MemoryBuffer> newBuffer;
431 uint32_t newBufferId;
432
433 // Iterate through all buffers in the slang compilation and set up
434 // a binding to the LLVM Source Manager.
435 auto *sourceManager = (**compilation).getSourceManager();
436 for (auto slangBuffer : sourceManager->getAllBuffers()) {
437 std::string_view slangRawPath = sourceManager->getRawFileName(slangBuffer);
438 if (std::error_code ec =
439 llvm::sys::fs::real_path(slangRawPath, slangCanonPath))
440 continue;
441
442 if (slangCanonPath == canonPath && skipMainBufferSlangImport) {
443 bufferIDMap[slangBuffer.getId()] = mainBufferId;
444 continue;
445 }
446
447 if (slangCanonPath == canonPath && !skipMainBufferSlangImport) {
448 continue;
449 }
450
451 if (!bufferIDMap.contains(slangBuffer.getId())) {
452
453 auto uriOrError = llvm::lsp::URIForFile::fromFile(slangCanonPath);
454 if (auto e = uriOrError.takeError()) {
456 Twine("Failed to get URI from file " + slangCanonPath));
457 continue;
458 }
459
460 newBuffer = llvm::MemoryBuffer::getMemBufferCopy(
461 sourceManager->getSourceText(slangBuffer), uriOrError->file());
462 newBufferId = sourceMgr.AddNewSourceBuffer(std::move(newBuffer), SMLoc());
463 bufferIDMap[slangBuffer.getId()] = newBufferId;
464 continue;
465 }
466 circt::lsp::Logger::error(Twine("Failed to add buffer ID! "));
467 }
468
469 for (auto &diag : (*compilation)->getAllDiagnostics())
470 driver.diagEngine.issue(diag);
471
472 // Populate the index.
473 index.initialize(**compilation);
474}
475
476llvm::lsp::Location
477VerilogDocument::getLspLocation(slang::SourceLocation loc) const {
478 if (loc && loc.buffer() != slang::SourceLocation::NoLocation.buffer()) {
479 const auto &slangSourceManager = getSlangSourceManager();
480 auto line = slangSourceManager.getLineNumber(loc) - 1;
481 auto column = slangSourceManager.getColumnNumber(loc) - 1;
482 auto it = bufferIDMap.find(loc.buffer().getId());
483 if (it != bufferIDMap.end() && it->second == sourceMgr.getMainFileID())
484 return llvm::lsp::Location(uri, llvm::lsp::Range(Position(line, column)));
485
486 llvm::StringRef fileName = slangSourceManager.getFileName(loc);
487 // Ensure absolute path for LSP:
488 llvm::SmallString<256> abs(fileName);
489 if (!llvm::sys::path::is_absolute(abs)) {
490 // Try realPath first
491 if (std::error_code ec = llvm::sys::fs::real_path(fileName, abs)) {
492 // Fallback: make it absolute relative to the process CWD
493 llvm::sys::fs::current_path(abs); // abs = CWD
494 llvm::sys::path::append(abs, fileName);
495 }
496 }
497
498 if (auto uriOrErr = llvm::lsp::URIForFile::fromFile(abs)) {
499 if (auto e = uriOrErr.takeError())
500 return llvm::lsp::Location();
501 return llvm::lsp::Location(*uriOrErr,
502 llvm::lsp::Range(Position(line, column)));
503 }
504 return llvm::lsp::Location();
505 }
506 return llvm::lsp::Location();
507}
508
509llvm::lsp::Location
510VerilogDocument::getLspLocation(slang::SourceRange range) const {
511
512 auto start = getLspLocation(range.start());
513 auto end = getLspLocation(range.end());
514
515 if (start.uri != end.uri)
516 return llvm::lsp::Location();
517
518 return llvm::lsp::Location(
519 start.uri, llvm::lsp::Range(start.range.start, end.range.end));
520}
521
522llvm::SMLoc VerilogDocument::getSMLoc(slang::SourceLocation loc) {
523 auto bufferID = loc.buffer().getId();
524 llvm::SmallString<256> slangCanonPath("");
525
526 // Check if the source is already opened by LLVM source manager.
527 auto bufferIDMapIt = bufferIDMap.find(bufferID);
528 if (bufferIDMapIt == bufferIDMap.end()) {
529 // If not, open the source file and add it to the LLVM source manager.
530 auto path = getSlangSourceManager().getFullPath(loc.buffer());
531
532 // If file is not open yet and not a real path, skip it.
533 if (std::error_code ec =
534 llvm::sys::fs::real_path(path.string(), slangCanonPath))
535 return llvm::SMLoc();
536
537 auto memBuffer = llvm::MemoryBuffer::getFile(slangCanonPath);
538 if (!memBuffer) {
540 "Failed to open file: " + path.filename().string() +
541 memBuffer.getError().message());
542 return llvm::SMLoc();
543 }
544
545 auto id = sourceMgr.AddNewSourceBuffer(std::move(memBuffer.get()), SMLoc());
546 bufferIDMapIt =
547 bufferIDMap.insert({bufferID, static_cast<uint32_t>(id)}).first;
548 }
549
550 const auto *buffer = sourceMgr.getMemoryBuffer(bufferIDMapIt->second);
551
552 return llvm::SMLoc::getFromPointer(buffer->getBufferStart() + loc.offset());
553}
554
555std::optional<std::pair<uint32_t, SmallString<128>>>
556VerilogDocument::getOrOpenFile(StringRef filePath) {
557
558 auto fileInfo = filePathMap.find(filePath);
559 if (fileInfo != filePathMap.end())
560 return fileInfo->second;
561
562 auto getIfExist = [&](StringRef path)
563 -> std::optional<std::pair<uint32_t, SmallString<128>>> {
564 if (llvm::sys::fs::exists(path)) {
565 auto memoryBuffer = llvm::MemoryBuffer::getFile(path);
566 if (!memoryBuffer) {
567 return std::nullopt;
568 }
569 auto id = sourceMgr.AddNewSourceBuffer(std::move(*memoryBuffer), SMLoc());
570
571 fileInfo =
572 filePathMap.insert(std::make_pair(filePath, std::make_pair(id, path)))
573 .first;
574
575 return fileInfo->second;
576 }
577 return std::nullopt;
578 };
579
580 if (llvm::sys::path::is_absolute(filePath))
581 return getIfExist(filePath);
582
583 // Search locations.
584 for (auto &libRoot : globalContext.options.extraSourceLocationDirs) {
585 SmallString<128> lib(libRoot);
586 llvm::sys::path::append(lib, filePath);
587 if (auto fileInfo = getIfExist(lib))
588 return fileInfo;
589 }
590
591 return std::nullopt;
592}
593
594//===----------------------------------------------------------------------===//
595// VerilogTextFile
596//===----------------------------------------------------------------------===//
597
598namespace {
599/// This class represents a text file containing one or more Verilog
600/// documents.
601class VerilogTextFile {
602public:
603 VerilogTextFile(VerilogServerContext &globalContext,
604 const llvm::lsp::URIForFile &uri, StringRef fileContents,
605 int64_t version,
606 std::vector<llvm::lsp::Diagnostic> &diagnostics);
607
608 /// Return the current version of this text file.
609 int64_t getVersion() const { return version; }
610
611 /// Update the file to the new version using the provided set of content
612 /// changes. Returns failure if the update was unsuccessful.
613 LogicalResult
614 update(const llvm::lsp::URIForFile &uri, int64_t newVersion,
615 ArrayRef<llvm::lsp::TextDocumentContentChangeEvent> changes,
616 std::vector<llvm::lsp::Diagnostic> &diagnostics);
617
618 void getLocationsOf(const llvm::lsp::URIForFile &uri,
619 llvm::lsp::Position defPos,
620 std::vector<llvm::lsp::Location> &locations);
621
622 void findReferencesOf(const llvm::lsp::URIForFile &uri,
623 llvm::lsp::Position pos,
624 std::vector<llvm::lsp::Location> &references);
625
626private:
627 /// Initialize the text file from the given file contents.
628 void initialize(const llvm::lsp::URIForFile &uri, int64_t newVersion,
629 std::vector<llvm::lsp::Diagnostic> &diagnostics);
630
631 VerilogServerContext &context;
632
633 /// The full string contents of the file.
634 std::string contents;
635
636 /// The version of this file.
637 int64_t version = 0;
638
639 /// The chunks of this file. The order of these chunks is the order in which
640 /// they appear in the text file.
641 std::unique_ptr<VerilogDocument> document;
642};
643} // namespace
644
645VerilogTextFile::VerilogTextFile(
646 VerilogServerContext &context, const llvm::lsp::URIForFile &uri,
647 StringRef fileContents, int64_t version,
648 std::vector<llvm::lsp::Diagnostic> &diagnostics)
649 : context(context), contents(fileContents.str()) {
650 initialize(uri, version, diagnostics);
651}
652
653LogicalResult VerilogTextFile::update(
654 const llvm::lsp::URIForFile &uri, int64_t newVersion,
655 ArrayRef<llvm::lsp::TextDocumentContentChangeEvent> changes,
656 std::vector<llvm::lsp::Diagnostic> &diagnostics) {
657 if (failed(llvm::lsp::TextDocumentContentChangeEvent::applyTo(changes,
658 contents))) {
659 circt::lsp::Logger::error(Twine("Failed to update contents of ") +
660 uri.file());
661 return failure();
662 }
663
664 // If the file contents were properly changed, reinitialize the text file.
665 initialize(uri, newVersion, diagnostics);
666 return success();
667}
668
669void VerilogTextFile::initialize(
670 const llvm::lsp::URIForFile &uri, int64_t newVersion,
671 std::vector<llvm::lsp::Diagnostic> &diagnostics) {
672 version = newVersion;
673 document =
674 std::make_unique<VerilogDocument>(context, uri, contents, diagnostics);
675}
676
677void VerilogTextFile::getLocationsOf(
678 const llvm::lsp::URIForFile &uri, llvm::lsp::Position defPos,
679 std::vector<llvm::lsp::Location> &locations) {
680 document->getLocationsOf(uri, defPos, locations);
681}
682
683void VerilogTextFile::findReferencesOf(
684 const llvm::lsp::URIForFile &uri, llvm::lsp::Position pos,
685 std::vector<llvm::lsp::Location> &references) {
686 document->findReferencesOf(uri, pos, references);
687}
688
689namespace {
690
691// Index the AST to find symbol uses and definitions.
692struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
693 VerilogIndexer(VerilogIndex &index) : index(index) {}
694 VerilogIndex &index;
695
696 void insertSymbol(const slang::ast::Symbol *symbol, slang::SourceRange range,
697 bool isDefinition = true) {
698 if (symbol->name.empty())
699 return;
700 assert(range.start().valid() && range.end().valid() &&
701 "range must be valid");
702
703 // TODO: This implementation does not handle expanded MACROs. Return
704 // instead.
705 if (range.start() >= range.end()) {
706 return;
707 }
708
709 index.insertSymbol(symbol, range, isDefinition);
710 }
711
712 void insertSymbol(const slang::ast::Symbol *symbol,
713 slang::SourceLocation from, bool isDefinition = false) {
714 if (symbol->name.empty())
715 return;
716 assert(from.valid() && "location must be valid");
717 insertSymbol(symbol, slang::SourceRange(from, from + symbol->name.size()),
718 isDefinition);
719 }
720
721 // Handle named values, such as references to declared variables.
722 void visitExpression(const slang::ast::Expression &expr) {
723 auto *symbol = expr.getSymbolReference(true);
724 if (!symbol)
725 return;
726 insertSymbol(symbol, expr.sourceRange, /*isDefinition=*/false);
727 }
728
729 void visitSymbol(const slang::ast::Symbol &symbol) {
730 insertSymbol(&symbol, symbol.location, /*isDefinition=*/true);
731 }
732
733 void visit(const slang::ast::NetSymbol &expr) {
734 insertSymbol(&expr, expr.location, /*isDefinition=*/true);
735 visitDefault(expr);
736 }
737
738 void visit(const slang::ast::VariableSymbol &expr) {
739 insertSymbol(&expr, expr.location, /*isDefinition=*/true);
740 visitDefault(expr);
741 }
742
743 void visit(const slang::ast::ExplicitImportSymbol &expr) {
744 auto *def = expr.package();
745 if (!def)
746 return;
747
748 if (auto *syn = expr.getSyntax()) {
749 if (auto *item = syn->as_if<slang::syntax::PackageImportItemSyntax>()) {
750 insertSymbol(def, item->package.location(), /*isDefinition=*/false);
751 }
752 }
753 visitDefault(expr);
754 }
755
756 void visit(const slang::ast::WildcardImportSymbol &expr) {
757 auto *def = expr.getPackage();
758 if (!def)
759 return;
760
761 if (auto *syn = expr.getSyntax()) {
762 if (auto *item = syn->as_if<slang::syntax::PackageImportItemSyntax>()) {
763 insertSymbol(def, item->package.location(), false);
764 }
765 }
766 visitDefault(expr);
767 }
768
769 void visit(const slang::ast::InstanceSymbol &expr) {
770 auto *def = &expr.getDefinition();
771 if (!def)
772 return;
773
774 // Add the module definition
775 insertSymbol(def, def->location, /*isDefinition=*/true);
776
777 // Walk up the syntax tree until we hit the type token;
778 // Link that token back to the instance declaration.
779 if (auto *hierInst =
780 expr.getSyntax()
781 ->as_if<slang::syntax::HierarchicalInstanceSyntax>())
782 if (auto *modInst =
783 hierInst->parent
784 ->as_if<slang::syntax::HierarchyInstantiationSyntax>())
785 if (modInst->type)
786 insertSymbol(def, modInst->type.location(), false);
787
788 // Link the module instance name back to the module definition
789 insertSymbol(def, expr.location, /*isDefinition=*/false);
790 visitDefault(expr);
791 }
792
793 void visit(const slang::ast::VariableDeclStatement &expr) {
794 insertSymbol(&expr.symbol, expr.sourceRange, /*isDefinition=*/true);
795 visitDefault(expr);
796 }
797
798 template <typename T>
799 void visit(const T &t) {
800 if constexpr (std::is_base_of_v<slang::ast::Expression, T>)
801 visitExpression(t);
802 if constexpr (std::is_base_of_v<slang::ast::Symbol, T>)
803 visitSymbol(t);
804
805 visitDefault(t);
806 }
807
808 template <typename T>
809 void visitInvalid(const T &t) {}
810};
811} // namespace
812
813void VerilogIndex::initialize(slang::ast::Compilation &compilation) {
814 const auto &root = compilation.getRoot();
815 VerilogIndexer visitor(*this);
816
817 for (auto *inst : root.topInstances) {
818
819 // Skip all modules, interfaces, etc. that are not defined in this files
820 if (!(document.getLspLocation(inst->location).uri == document.getURI()))
821 continue;
822
823 // Visit the body of the instance.
824 inst->body.visit(visitor);
825
826 // Insert the symbols in the port list.
827 for (const auto *symbol : inst->body.getPortList())
828 insertSymbolDefinition(symbol);
829 }
830
831 // Parse the source location from the main file.
832 parseSourceLocation();
833}
834
835void VerilogIndex::parseSourceLocation(StringRef toParse) {
836 // No multiline entries.
837 if (toParse.contains('\n'))
838 return;
839
840 StringRef filePath;
841 SmallVector<StringRef, 3> fileLineColStrs;
842
843 // Parse the source location emitted by ExportVerilog, e.g.
844 // @[foo.mlir:1:10, :20:30, bar.mlir:2:{30, 40}]
845 for (auto chunk : llvm::split(toParse, ", ")) {
846 fileLineColStrs.clear();
847 chunk.split(fileLineColStrs, ':');
848 if (fileLineColStrs.size() != 3)
849 continue;
850
851 auto filePathMaybeEmpty = fileLineColStrs[0].trim();
852 // If the file path is empty, use the previous file path.
853 if (!filePathMaybeEmpty.empty())
854 filePath = filePathMaybeEmpty;
855
856 auto line = fileLineColStrs[1].trim();
857 auto column = fileLineColStrs[2].trim();
858
859 uint32_t lineInt;
860 // Line must be always valid.
861 if (line.getAsInteger(10, lineInt))
862 continue;
863
864 // A pair of column and start location. Start location may include filepath
865 // and line string.
866 SmallVector<std::pair<StringRef, const char *>> columns;
867
868 // Column string may contains several columns like `{col1, col2, ...}`.
869 if (column.starts_with('{') && column.ends_with('}')) {
870 bool first = true;
871 for (auto str : llvm::split(column.drop_back().drop_front(), ',')) {
872 columns.emplace_back(str,
873 first ? filePathMaybeEmpty.data() : str.data());
874 first = false;
875 }
876 } else {
877 columns.push_back({column, filePathMaybeEmpty.data()});
878 }
879
880 // Insert the interval into the interval map.
881 for (auto [column, start] : columns) {
882 uint32_t columnInt;
883 if (column.getAsInteger(10, columnInt))
884 continue;
885 auto loc = mlir::FileLineColRange::get(&mlirContext, filePath,
886 lineInt - 1, columnInt - 1,
887 lineInt - 1, columnInt - 1);
888 const char *end = column.end();
889 if (!intervalMap.overlaps(start, end))
890 intervalMap.insert(start, end, loc);
891 }
892 }
893}
894
895void VerilogIndex::parseSourceLocation() {
896 auto &sourceMgr = getDocument().getSourceMgr();
897 auto *getMainBuffer = sourceMgr.getMemoryBuffer(sourceMgr.getMainFileID());
898 StringRef text(getMainBuffer->getBufferStart(),
899 getMainBuffer->getBufferSize());
900
901 // Loop over comments starting with "@[", and parse the source location.
902 // TODO: Consider supporting other location format. This is currently
903 // very specific to `locationInfoStyle=WrapInAtSquareBracket`.
904 while (true) {
905 // Find the source location from the text.
906 StringRef start = "// @[";
907 auto loc = text.find(start);
908 if (loc == StringRef::npos)
909 break;
910
911 text = text.drop_front(loc + start.size());
912 auto endPos = text.find_first_of("]\n");
913 if (endPos == StringRef::npos)
914 break;
915 auto toParse = text.take_front(endPos);
917 parseSourceLocation(toParse);
918 }
919}
920
921//===----------------------------------------------------------------------===//
922// VerilogServer::Impl
923//===----------------------------------------------------------------------===//
924
926 explicit Impl(const VerilogServerOptions &options) : context(options) {}
927
928 /// The files held by the server, mapped by their URI file name.
929 llvm::StringMap<std::unique_ptr<VerilogTextFile>> files;
930
931 VerilogServerContext context;
932};
933
934//===----------------------------------------------------------------------===//
935// VerilogServer
936//===----------------------------------------------------------------------===//
937
939 : impl(std::make_unique<Impl>(options)) {}
941
943 const URIForFile &uri, StringRef contents, int64_t version,
944 std::vector<llvm::lsp::Diagnostic> &diagnostics) {
945 impl->files[uri.file()] = std::make_unique<VerilogTextFile>(
946 impl->context, uri, contents, version, diagnostics);
947}
948
950 const URIForFile &uri,
951 ArrayRef<llvm::lsp::TextDocumentContentChangeEvent> changes,
952 int64_t version, std::vector<llvm::lsp::Diagnostic> &diagnostics) {
953 // Check that we actually have a document for this uri.
954 auto it = impl->files.find(uri.file());
955 if (it == impl->files.end())
956 return;
957
958 // Try to update the document. If we fail, erase the file from the server. A
959 // failed updated generally means we've fallen out of sync somewhere.
960 if (failed(it->second->update(uri, version, changes, diagnostics)))
961 impl->files.erase(it);
962}
963
964std::optional<int64_t>
966 auto it = impl->files.find(uri.file());
967 if (it == impl->files.end())
968 return std::nullopt;
969
970 int64_t version = it->second->getVersion();
971 impl->files.erase(it);
972 return version;
973}
974
976 const URIForFile &uri, const Position &defPos,
977 std::vector<llvm::lsp::Location> &locations) {
978 auto fileIt = impl->files.find(uri.file());
979 if (fileIt != impl->files.end())
980 fileIt->second->getLocationsOf(uri, defPos, locations);
981}
982
984 const URIForFile &uri, const Position &pos,
985 std::vector<llvm::lsp::Location> &references) {
986 auto fileIt = impl->files.find(uri.file());
987 if (fileIt != impl->files.end())
988 fileIt->second->findReferencesOf(uri, pos, references);
989}
990
991void VerilogIndex::insertSymbol(const slang::ast::Symbol *symbol,
992 slang::SourceRange from, bool isDefinition) {
993 assert(from.start().valid() && from.end().valid());
994
995 // TODO: Currently doesn't handle expanded macros
996 if (!from.start().valid() || !from.end().valid() ||
997 from.start() >= from.end())
998 return;
999
1000 const char *startLoc = getDocument().getSMLoc(from.start()).getPointer();
1001 const char *endLoc = getDocument().getSMLoc(from.end()).getPointer() + 1;
1002 if (!startLoc || !endLoc || startLoc >= endLoc)
1003 return;
1004
1005 assert(startLoc && endLoc);
1006
1007 if (startLoc != endLoc && !intervalMap.overlaps(startLoc, endLoc)) {
1008 intervalMap.insert(startLoc, endLoc, symbol);
1009 if (!isDefinition)
1010 references[symbol].push_back(from);
1011 }
1012}
1013
1014void VerilogIndex::insertSymbolDefinition(const slang::ast::Symbol *symbol) {
1015 if (!symbol->location)
1016 return;
1017 auto size = symbol->name.size() ? symbol->name.size() : 1;
1018 insertSymbol(symbol,
1019 slang::SourceRange(symbol->location, symbol->location + size),
1020 true);
1021}
1022
1023//===----------------------------------------------------------------------===//
1024// VerilogDocument: Definitions and References
1025//===----------------------------------------------------------------------===//
1026
1027static llvm::lsp::Range getRange(const mlir::FileLineColRange &fileLoc) {
1028 return llvm::lsp::Range(
1029 llvm::lsp::Position(fileLoc.getStartLine(), fileLoc.getStartColumn()),
1030 llvm::lsp::Position(fileLoc.getEndLine(), fileLoc.getEndColumn()));
1031}
1032
1033void VerilogDocument::getLocationsOf(
1034 const llvm::lsp::URIForFile &uri, const llvm::lsp::Position &defPos,
1035 std::vector<llvm::lsp::Location> &locations) {
1036 SMLoc posLoc = defPos.getAsSMLoc(sourceMgr);
1037 const auto &intervalMap = index.getIntervalMap();
1038 auto it = intervalMap.find(posLoc.getPointer());
1039
1040 // Found no element in the given index.
1041 if (!it.valid() || posLoc.getPointer() < it.start())
1042 return;
1043
1044 auto element = it.value();
1045 if (auto attr = dyn_cast<Attribute>(element)) {
1046
1047 // Check if the attribute is a FileLineColRange.
1048 if (auto fileLoc = dyn_cast<mlir::FileLineColRange>(attr)) {
1049
1050 // Return URI for the file.
1051 auto fileInfo = getOrOpenFile(fileLoc.getFilename().getValue());
1052 if (!fileInfo)
1053 return;
1054 const auto &[bufferId, filePath] = *fileInfo;
1055 auto uri = llvm::lsp::URIForFile::fromFile(filePath);
1056 if (auto e = uri.takeError()) {
1057 circt::lsp::Logger::error("failed to open file " + filePath);
1058 return;
1059 }
1060
1061 locations.emplace_back(uri.get(), getRange(fileLoc));
1062 }
1063
1064 return;
1065 }
1066
1067 // If the element is verilog symbol, return the definition of the symbol.
1068 const auto *symbol = cast<const slang::ast::Symbol *>(element);
1069
1070 slang::SourceRange range(symbol->location,
1071 symbol->location +
1072 (symbol->name.size() ? symbol->name.size() : 1));
1073 locations.push_back(getLspLocation(range));
1074}
1075
1076void VerilogDocument::findReferencesOf(
1077 const llvm::lsp::URIForFile &uri, const llvm::lsp::Position &pos,
1078 std::vector<llvm::lsp::Location> &references) {
1079 SMLoc posLoc = pos.getAsSMLoc(sourceMgr);
1080 const auto &intervalMap = index.getIntervalMap();
1081 auto intervalIt = intervalMap.find(posLoc.getPointer());
1082 if (!intervalIt.valid() || posLoc.getPointer() < intervalIt.start())
1083 return;
1084
1085 const auto *symbol = dyn_cast<const slang::ast::Symbol *>(intervalIt.value());
1086 if (!symbol)
1087 return;
1088
1089 auto it = index.getReferences().find(symbol);
1090 if (it == index.getReferences().end())
1091 return;
1092 for (auto referenceRange : it->second)
1093 references.push_back(getLspLocation(referenceRange));
1094}
assert(baseType &&"element must be base type")
static SmallVector< PortInfo > getPortList(ModuleTy &mod)
Definition HWOps.cpp:1428
static llvm::lsp::Range getRange(const mlir::FileLineColRange &fileLoc)
static bool mainBufferFileInCommandFileList(const std::string &cmdfileStr, const std::string &targetAbsStr)
static std::filesystem::path canonicalizeFileName(const std::filesystem::path &file)
static llvm::lsp::DiagnosticSeverity getSeverity(slang::DiagnosticSeverity severity)
std::optional< int64_t > removeDocument(const URIForFile &uri)
Remove the document with the given uri.
void updateDocument(const URIForFile &uri, llvm::ArrayRef< TextDocumentContentChangeEvent > changes, int64_t version, std::vector< Diagnostic > &diagnostics)
Update the document, with the provided version, at the given URI.
VerilogServer(const circt::lsp::VerilogServerOptions &options)
void addDocument(const URIForFile &uri, llvm::StringRef contents, int64_t version, std::vector< Diagnostic > &diagnostics)
Add the document, with the provided version, at the given URI.
void getLocationsOf(const URIForFile &uri, const llvm::lsp::Position &defPos, std::vector< llvm::lsp::Location > &locations)
Return the locations of the object pointed at by the given position.
void findReferencesOf(const URIForFile &uri, const llvm::lsp::Position &pos, std::vector< llvm::lsp::Location > &references)
Find all references of the object pointed at by the given position.
void info(Twine message)
Definition LSPUtils.cpp:20
void error(Twine message)
Definition LSPUtils.cpp:16
llvm::lsp::URIForFile URIForFile
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
llvm::StringMap< std::unique_ptr< VerilogTextFile > > files
The files held by the server, mapped by their URI file name.
Impl(const VerilogServerOptions &options)