CIRCT 21.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/Tools/lsp-server-support/Logging.h"
21#include "mlir/Tools/lsp-server-support/Protocol.h"
22#include "slang/ast/Compilation.h"
23#include "slang/diagnostics/DiagnosticClient.h"
24#include "slang/diagnostics/Diagnostics.h"
25#include "slang/driver/Driver.h"
26#include "slang/syntax/SyntaxTree.h"
27#include "slang/text/SourceLocation.h"
28#include "slang/text/SourceManager.h"
29#include "llvm/ADT/SmallString.h"
30#include "llvm/ADT/StringMap.h"
31#include "llvm/Support/MemoryBuffer.h"
32#include "llvm/Support/Path.h"
33#include "llvm/Support/SourceMgr.h"
34
35#include <memory>
36#include <optional>
37
38using namespace mlir;
39using namespace mlir::lsp;
40
41using namespace circt::lsp;
42using namespace circt;
43
44static mlir::lsp::DiagnosticSeverity
45getSeverity(slang::DiagnosticSeverity severity) {
46 switch (severity) {
47 case slang::DiagnosticSeverity::Fatal:
48 case slang::DiagnosticSeverity::Error:
49 return mlir::lsp::DiagnosticSeverity::Error;
50 case slang::DiagnosticSeverity::Warning:
51 return mlir::lsp::DiagnosticSeverity::Warning;
52 case slang::DiagnosticSeverity::Ignored:
53 case slang::DiagnosticSeverity::Note:
54 return mlir::lsp::DiagnosticSeverity::Information;
55 }
56 llvm_unreachable("all slang diagnostic severities should be handled");
57 return mlir::lsp::DiagnosticSeverity::Error;
58}
59namespace {
60
61// A global context carried around by the server.
62struct VerilogServerContext {
63 VerilogServerContext(const VerilogServerOptions &options)
64 : options(options) {}
65 const VerilogServerOptions &options;
66};
67
68//===----------------------------------------------------------------------===//
69// VerilogDocument
70//===----------------------------------------------------------------------===//
71
72/// This class represents all of the information pertaining to a specific
73/// Verilog document.
74class LSPDiagnosticClient;
75struct VerilogDocument {
76 VerilogDocument(VerilogServerContext &globalContext,
77 const mlir::lsp::URIForFile &uri, StringRef contents,
78 std::vector<mlir::lsp::Diagnostic> &diagnostics);
79 VerilogDocument(const VerilogDocument &) = delete;
80 VerilogDocument &operator=(const VerilogDocument &) = delete;
81
82 const mlir::lsp::URIForFile &getURI() const { return uri; }
83
84 llvm::SourceMgr &getSourceMgr() { return sourceMgr; }
86 return bufferIDMap;
87 }
88
89 const slang::SourceManager &getSlangSourceManager() const {
90 return driver.sourceManager;
91 }
92
93 // Return LSP location from slang location.
94 mlir::lsp::Location getLspLocation(slang::SourceLocation loc) const;
95
96private:
97 // A map from slang buffer ID to the corresponding buffer ID in the LLVM
98 // source manager.
100
101 // The compilation result.
102 FailureOr<std::unique_ptr<slang::ast::Compilation>> compilation;
103
104 // The slang driver.
105 slang::driver::Driver driver;
106
107 // The LLVM source manager.
108 llvm::SourceMgr sourceMgr;
109
110 // The URI of the document.
111 mlir::lsp::URIForFile uri;
112};
113
114} // namespace
115
116//===----------------------------------------------------------------------===//
117// LSPDiagnosticClient
118//===----------------------------------------------------------------------===//
119
120namespace {
121/// A converter that can be plugged into a slang `DiagnosticEngine` as a
122/// client that will map slang diagnostics to LSP diagnostics.
123class LSPDiagnosticClient : public slang::DiagnosticClient {
124 const VerilogDocument &document;
125 std::vector<mlir::lsp::Diagnostic> &diags;
126
127public:
128 LSPDiagnosticClient(const VerilogDocument &document,
129 std::vector<mlir::lsp::Diagnostic> &diags)
130 : document(document), diags(diags) {}
131
132 void report(const slang::ReportedDiagnostic &slangDiag) override;
133};
134} // namespace
135
136void LSPDiagnosticClient::report(const slang::ReportedDiagnostic &slangDiag) {
137 auto loc = document.getLspLocation(slangDiag.location);
138 // Show only the diagnostics in the current file.
139 if (loc.uri != document.getURI())
140 return;
141 auto &mlirDiag = diags.emplace_back();
142 mlirDiag.severity = getSeverity(slangDiag.severity);
143 mlirDiag.range = loc.range;
144 mlirDiag.source = "slang";
145 mlirDiag.message = slangDiag.formattedMessage;
146}
147
148//===----------------------------------------------------------------------===//
149// VerilogDocument
150//===----------------------------------------------------------------------===//
151
152VerilogDocument::VerilogDocument(
153 VerilogServerContext &context, const mlir::lsp::URIForFile &uri,
154 StringRef contents, std::vector<mlir::lsp::Diagnostic> &diagnostics)
155 : uri(uri) {
156 unsigned int bufferId;
157 if (auto memBufferOwn =
158 llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file())) {
159
160 bufferId = sourceMgr.AddNewSourceBuffer(std::move(memBufferOwn), SMLoc());
161 } else {
163 Twine("Failed to create memory buffer for file ") + uri.file());
164 return;
165 }
166
167 // Build the set of include directories for this file.
168 llvm::SmallString<32> uriDirectory(uri.file());
169 llvm::sys::path::remove_filename(uriDirectory);
170
171 std::vector<std::string> libDirs;
172 libDirs.push_back(uriDirectory.str().str());
173 libDirs.insert(libDirs.end(), context.options.libDirs.begin(),
174 context.options.libDirs.end());
175
176 // Populate source managers.
177 const llvm::MemoryBuffer *memBuffer = sourceMgr.getMemoryBuffer(bufferId);
178
179 driver.options.libDirs = libDirs;
180 // Assign text to slang.
181 auto slangBuffer =
182 driver.sourceManager.assignText(uri.file(), memBuffer->getBuffer());
183 driver.buffers.push_back(slangBuffer);
184 bufferIDMap[slangBuffer.id.getId()] = bufferId;
185
186 auto diagClient = std::make_shared<LSPDiagnosticClient>(*this, diagnostics);
187 driver.diagEngine.addClient(diagClient);
188
189 if (!driver.parseAllSources()) {
190 circt::lsp::Logger::error(Twine("Failed to parse Verilog file ") +
191 uri.file());
192 return;
193 }
194
195 compilation = driver.createCompilation();
196 for (auto &diag : (*compilation)->getAllDiagnostics())
197 driver.diagEngine.issue(diag);
198}
199
200mlir::lsp::Location
201VerilogDocument::getLspLocation(slang::SourceLocation loc) const {
202 if (loc && loc.buffer() != slang::SourceLocation::NoLocation.buffer()) {
203 const auto &slangSourceManager = getSlangSourceManager();
204 auto line = slangSourceManager.getLineNumber(loc) - 1;
205 auto column = slangSourceManager.getColumnNumber(loc) - 1;
206 auto it = bufferIDMap.find(loc.buffer().getId());
207 // Check if the current buffer is the main file.
208 if (it != bufferIDMap.end() && it->second == sourceMgr.getMainFileID())
209 return mlir::lsp::Location(uri, mlir::lsp::Range(Position(line, column)));
210
211 // Otherwise, construct URI from slang source manager.
212 auto fileName = slangSourceManager.getFileName(loc);
213 auto loc = mlir::lsp::URIForFile::fromFile(
214 slangSourceManager.makeAbsolutePath(fileName));
215 if (auto e = loc.takeError())
216 return mlir::lsp::Location();
217 return mlir::lsp::Location(loc.get(),
218 mlir::lsp::Range(Position(line, column)));
219 }
220
221 return mlir::lsp::Location();
222}
223
224//===----------------------------------------------------------------------===//
225// VerilogTextFile
226//===----------------------------------------------------------------------===//
227
228namespace {
229/// This class represents a text file containing one or more Verilog
230/// documents.
231class VerilogTextFile {
232public:
233 VerilogTextFile(VerilogServerContext &globalContext,
234 const mlir::lsp::URIForFile &uri, StringRef fileContents,
235 int64_t version,
236 std::vector<mlir::lsp::Diagnostic> &diagnostics);
237
238 /// Return the current version of this text file.
239 int64_t getVersion() const { return version; }
240
241 /// Update the file to the new version using the provided set of content
242 /// changes. Returns failure if the update was unsuccessful.
243 LogicalResult
244 update(const mlir::lsp::URIForFile &uri, int64_t newVersion,
245 ArrayRef<mlir::lsp::TextDocumentContentChangeEvent> changes,
246 std::vector<mlir::lsp::Diagnostic> &diagnostics);
247
248private:
249 /// Initialize the text file from the given file contents.
250 void initialize(const mlir::lsp::URIForFile &uri, int64_t newVersion,
251 std::vector<mlir::lsp::Diagnostic> &diagnostics);
252
253 VerilogServerContext &context;
254
255 /// The full string contents of the file.
256 std::string contents;
257
258 /// The version of this file.
259 int64_t version = 0;
260
261 /// The chunks of this file. The order of these chunks is the order in which
262 /// they appear in the text file.
263 std::unique_ptr<VerilogDocument> document;
264};
265} // namespace
266
267VerilogTextFile::VerilogTextFile(
268 VerilogServerContext &context, const mlir::lsp::URIForFile &uri,
269 StringRef fileContents, int64_t version,
270 std::vector<mlir::lsp::Diagnostic> &diagnostics)
271 : context(context), contents(fileContents.str()) {
272 initialize(uri, version, diagnostics);
273}
274
275LogicalResult VerilogTextFile::update(
276 const mlir::lsp::URIForFile &uri, int64_t newVersion,
277 ArrayRef<mlir::lsp::TextDocumentContentChangeEvent> changes,
278 std::vector<mlir::lsp::Diagnostic> &diagnostics) {
279 if (failed(mlir::lsp::TextDocumentContentChangeEvent::applyTo(changes,
280 contents))) {
281 circt::lsp::Logger::error(Twine("Failed to update contents of ") +
282 uri.file());
283 return failure();
284 }
285
286 // If the file contents were properly changed, reinitialize the text file.
287 initialize(uri, newVersion, diagnostics);
288 return success();
289}
290
291void VerilogTextFile::initialize(
292 const mlir::lsp::URIForFile &uri, int64_t newVersion,
293 std::vector<mlir::lsp::Diagnostic> &diagnostics) {
294 version = newVersion;
295 document =
296 std::make_unique<VerilogDocument>(context, uri, contents, diagnostics);
297}
298
299//===----------------------------------------------------------------------===//
300// VerilogServer::Impl
301//===----------------------------------------------------------------------===//
302
304 explicit Impl(const VerilogServerOptions &options) : context(options) {}
305
306 /// The files held by the server, mapped by their URI file name.
307 llvm::StringMap<std::unique_ptr<VerilogTextFile>> files;
308
309 VerilogServerContext context;
310};
311
312//===----------------------------------------------------------------------===//
313// VerilogServer
314//===----------------------------------------------------------------------===//
315
317 : impl(std::make_unique<Impl>(options)) {}
319
321 const URIForFile &uri, StringRef contents, int64_t version,
322 std::vector<mlir::lsp::Diagnostic> &diagnostics) {
323 impl->files[uri.file()] = std::make_unique<VerilogTextFile>(
324 impl->context, uri, contents, version, diagnostics);
325}
326
328 const URIForFile &uri,
329 ArrayRef<mlir::lsp::TextDocumentContentChangeEvent> changes,
330 int64_t version, std::vector<mlir::lsp::Diagnostic> &diagnostics) {
331 // Check that we actually have a document for this uri.
332 auto it = impl->files.find(uri.file());
333 if (it == impl->files.end())
334 return;
335
336 // Try to update the document. If we fail, erase the file from the server. A
337 // failed updated generally means we've fallen out of sync somewhere.
338 if (failed(it->second->update(uri, version, changes, diagnostics)))
339 impl->files.erase(it);
340}
341
342std::optional<int64_t>
344 auto it = impl->files.find(uri.file());
345 if (it == impl->files.end())
346 return std::nullopt;
347
348 int64_t version = it->second->getVersion();
349 impl->files.erase(it);
350 return version;
351}
static mlir::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 error(Twine message)
Definition LSPUtils.cpp:16
mlir::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)