CIRCT 24.0.0git
Loading...
Searching...
No Matches
VerilogDocument.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// VerilogDocument.cpp
10//
11// This file implements the VerilogDocument class, which represents a single
12// open Verilog/SystemVerilog source file within the CIRCT Verilog LSP server.
13// It acts as the per-buffer bridge between the Language Server Protocol (LSP)
14// and the Slang front-end infrastructure.
15//
16// Responsibilities:
17// * Parse and elaborate a single Verilog source buffer using Slang’s driver.
18// * Integrate with project-wide command files (-C) and include/library search
19// paths supplied by the VerilogServerContext.
20// * Handle main-buffer override semantics: when the buffer is already listed
21// in a command file, it reuses the existing Slang buffer; otherwise it
22// injects an in-memory buffer directly.
23// * Collect and forward diagnostics to the LSP client via
24// LSPDiagnosticClient.
25// * Build and own a VerilogIndex for symbol and location queries.
26// * Provide translation utilities between LSP and Slang coordinates, such as
27// UTF-16 ↔ UTF-8 position mapping and conversion to llvm::lsp::Location.
28//
29// The class is used by VerilogServerContext to maintain open documents,
30// service “go to definition” and “find references” requests, and keep file
31// state synchronized with the editor.
32//
33//===----------------------------------------------------------------------===//
34
35#include "slang/syntax/AllSyntax.h"
36#include "slang/syntax/SyntaxTree.h"
37
38#include "mlir/IR/Attributes.h"
39#include "mlir/IR/BuiltinAttributes.h"
40#include "mlir/IR/Location.h"
41#include "mlir/Support/FileUtilities.h"
42
43#include "llvm/Support/ConvertUTF.h"
44#include "llvm/Support/FileSystem.h"
45#include "llvm/Support/LineIterator.h"
46#include "llvm/Support/Path.h"
47
48#include "../Utils/LSPUtils.h"
49#include "LSPDiagnosticClient.h"
50#include "VerilogDocument.h"
53
54using namespace circt::lsp;
55using namespace llvm::lsp;
56
57static inline void setTopModules(slang::driver::Driver &driver) {
58 // Parse the main buffer
59 if (!driver.parseAllSources()) {
60 circt::lsp::Logger::error(llvm::Twine("Failed to parse main buffer "));
61 return;
62 }
63 // Extract all the top modules in the file directly from the syntax tree
64 std::vector<std::string> topModules;
65 for (auto &t : driver.syntaxTrees) {
66 if (auto *compUnit =
67 t->root().as_if<slang::syntax::CompilationUnitSyntax>()) {
68 for (auto *member : compUnit->members) {
69 // While it's called "ModuleDeclarationSyntax", it also covers
70 // packages
71 if (auto *moduleDecl =
72 member->as_if<slang::syntax::ModuleDeclarationSyntax>()) {
73 topModules.emplace_back(moduleDecl->header->name.valueText());
74 }
75 }
76 }
77 }
78 driver.options.topModules = std::move(topModules);
79}
80
81static inline void
82copyBuffers(slang::driver::Driver &driver,
83 const slang::driver::Driver *const projectDriver,
84 const llvm::SmallString<256> &mainBufferFileName) {
85 for (auto bId : projectDriver->sourceManager.getAllBuffers()) {
86 std::string_view slangRawPath =
87 projectDriver->sourceManager.getRawFileName(bId);
88
89 llvm::SmallString<256> slangCanonPath;
90 if (llvm::sys::fs::real_path(slangRawPath, slangCanonPath))
91 continue;
92
93 if (slangCanonPath ==
94 mainBufferFileName) // skip the file you're already compiling
95 continue;
96
97 bool alreadyLoaded = false;
98 for (auto id : driver.sourceManager.getAllBuffers()) {
99 if (driver.sourceManager.getFullPath(id).string() ==
100 slangCanonPath.str()) {
101 alreadyLoaded = true;
102 break;
103 }
104 }
105 if (alreadyLoaded)
106 continue;
107
108 auto buffer = driver.sourceManager.assignText(
109 slangCanonPath.str(), projectDriver->sourceManager.getSourceText(bId));
110 driver.sourceLoader.addBuffer(buffer);
111 }
112}
113
115 VerilogServerContext &context, const llvm::lsp::URIForFile &uri,
116 StringRef contents, std::vector<llvm::lsp::Diagnostic> &diagnostics,
117 const slang::driver::Driver *const projectDriver,
118 const std::vector<std::string> &projectIncludeDirectories)
119 : globalContext(context), uri(uri) {
120
121 llvm::SmallString<256> canonPath(uri.file());
122 if (std::error_code ec = llvm::sys::fs::real_path(uri.file(), canonPath))
123 canonPath = uri.file(); // fall back, but try to keep it absolute
124
125 // Build the set of include directories for this file.
126 llvm::SmallString<32> uriDirectory(uri.file());
127 llvm::sys::path::remove_filename(uriDirectory);
128
129 std::vector<std::string> libDirs;
130 libDirs.push_back(uriDirectory.str().str());
131 libDirs.insert(libDirs.end(), context.options.libDirs.begin(),
132 context.options.libDirs.end());
133
134 for (const auto &libDir : libDirs)
135 driver.sourceLoader.addSearchDirectories(libDir);
136
137 auto memBuffer = llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file());
138 if (!memBuffer) {
140 Twine("Failed to create memory buffer for file ") + uri.file());
141 return;
142 }
143
144 auto topSlangBuffer =
145 driver.sourceManager.assignText(uri.file(), memBuffer->getBuffer());
146 driver.sourceLoader.addBuffer(topSlangBuffer);
147 mainBufferId = topSlangBuffer.id;
148
149 auto diagClient = std::make_shared<LSPDiagnosticClient>(*this, diagnostics);
150 driver.diagEngine.addClient(diagClient);
151
152 driver.options.compilationFlags.emplace(
153 slang::ast::CompilationFlags::LintMode, false);
154 driver.options.compilationFlags.emplace(
155 slang::ast::CompilationFlags::DisableInstanceCaching, false);
156
157 for (auto &dir : projectIncludeDirectories)
158 (void)driver.sourceManager.addUserDirectories(dir);
159
160 if (!driver.processOptions()) {
161 circt::lsp::Logger::error(Twine("Failed to process slang driver options!"));
162 return;
163 }
164
165 driver.diagEngine.setIgnoreAllWarnings(false);
166
167 // Import dependencies from projectDriver if it exists.
168 if (projectDriver) {
169 // Copy options from project driver
170 driver.options = projectDriver->options;
171 // Set top modules according to main buffer
172 setTopModules(driver);
173 // Copy dependency buffers from project driver
174 copyBuffers(driver, projectDriver, canonPath);
175 }
176
177 if (!driver.parseAllSources()) {
178 circt::lsp::Logger::error(Twine("Failed to parse Verilog file ") +
179 uri.file());
180 return;
181 }
182
183 compilation = driver.createCompilation();
184 if (failed(compilation)) {
185 circt::lsp::Logger::error(Twine("Failed to compile Verilog file ") +
186 uri.file());
187 return;
188 }
189
190 for (auto &diag : (*compilation)->getAllDiagnostics())
191 driver.diagEngine.issue(diag);
192
193 computeLineOffsets(driver.sourceManager.getSourceText(mainBufferId));
194
195 index = std::make_unique<VerilogIndex>(mainBufferId, driver.sourceManager);
196 // Populate the index.
197 index->initialize(**compilation);
198}
199
200llvm::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 = loc.buffer();
207 if (it == mainBufferId)
208 return llvm::lsp::Location(uri, llvm::lsp::Range(Position(line, column)));
209
210 llvm::StringRef fileName = slangSourceManager.getFileName(loc);
211 // Ensure absolute path for LSP:
212 llvm::SmallString<256> abs(fileName);
213 if (!llvm::sys::path::is_absolute(abs)) {
214 // Try realPath first
215 if (std::error_code ec = llvm::sys::fs::real_path(fileName, abs)) {
216 // Fallback: make it absolute relative to the process CWD
217 llvm::sys::fs::current_path(abs); // abs = CWD
218 llvm::sys::path::append(abs, fileName);
219 }
220 }
221
222 if (auto uriOrErr = llvm::lsp::URIForFile::fromFile(abs)) {
223 if (auto e = uriOrErr.takeError())
224 return llvm::lsp::Location();
225 return llvm::lsp::Location(*uriOrErr,
226 llvm::lsp::Range(Position(line, column)));
227 }
228 return llvm::lsp::Location();
229 }
230 return llvm::lsp::Location();
231}
232
233llvm::lsp::Location
234VerilogDocument::getLspLocation(slang::SourceRange range) const {
235
236 auto start = getLspLocation(range.start());
237 auto end = getLspLocation(range.end());
238
239 if (start.uri != end.uri)
240 return llvm::lsp::Location();
241
242 return llvm::lsp::Location(
243 start.uri, llvm::lsp::Range(start.range.start, end.range.end));
244}
245
246std::optional<std::pair<slang::BufferID, llvm::SmallString<128>>>
248
249 auto fileInfo = filePathMap.find(filePath);
250 if (fileInfo != filePathMap.end())
251 return fileInfo->second;
252
253 auto getIfExist = [&](StringRef path)
254 -> std::optional<std::pair<slang::BufferID, SmallString<128>>> {
255 if (llvm::sys::fs::exists(path)) {
256 auto memoryBuffer = llvm::MemoryBuffer::getFile(path);
257 if (!memoryBuffer) {
258 return std::nullopt;
259 }
260
261 auto newSlangBuffer = driver.sourceManager.assignText(
262 path.str(), memoryBuffer.get()->getBufferStart());
263 driver.sourceLoader.addBuffer(newSlangBuffer);
264
265 fileInfo = filePathMap
266 .insert(std::make_pair(
267 filePath, std::make_pair(newSlangBuffer.id, path)))
268 .first;
269
270 return fileInfo->second;
271 }
272 return std::nullopt;
273 };
274
275 if (llvm::sys::path::is_absolute(filePath))
276 return getIfExist(filePath);
277
278 // Search locations.
279 for (auto &libRoot : globalContext.options.extraSourceLocationDirs) {
280 SmallString<128> lib(libRoot);
281 llvm::sys::path::append(lib, filePath);
282 if (auto fileInfo = getIfExist(lib))
283 return fileInfo;
284 }
285
286 return std::nullopt;
287}
288
289static llvm::lsp::Range getRange(const mlir::FileLineColRange &fileLoc) {
290 return llvm::lsp::Range(
291 llvm::lsp::Position(fileLoc.getStartLine(), fileLoc.getStartColumn()),
292 llvm::lsp::Position(fileLoc.getEndLine(), fileLoc.getEndColumn()));
293}
294
295/// Build a vector of line start offsets (0-based).
296void VerilogDocument::computeLineOffsets(std::string_view text) {
297 lineOffsets.clear();
298 lineOffsets.reserve(1024);
299 lineOffsets.push_back(0);
300 for (size_t i = 0; i < text.size(); ++i) {
301 if (text[i] == '\n') {
302 lineOffsets.push_back(static_cast<uint32_t>(i + 1));
303 }
304 }
305}
306
307// LSP (0-based line, UTF-16 character) -> byte offset into UTF-8 buffer.
308std::optional<uint32_t>
309VerilogDocument::lspPositionToOffset(const llvm::lsp::Position &pos) {
310
311 auto &sm = getSlangSourceManager();
312
313 std::string_view text = sm.getSourceText(mainBufferId);
314
315 // Clamp line index
316 if ((unsigned)pos.line >= lineOffsets.size())
317 return std::nullopt;
318
319 size_t lineStart = lineOffsets[pos.line];
320 size_t lineEnd = ((unsigned)(pos.line + 1) < lineOffsets.size())
321 ? lineOffsets[pos.line + 1] - 1
322 : text.size();
323
324 const llvm::UTF8 *src =
325 reinterpret_cast<const llvm::UTF8 *>(text.data() + lineStart);
326 const llvm::UTF8 *srcEnd =
327 reinterpret_cast<const llvm::UTF8 *>(text.data() + lineEnd);
328
329 // Convert up to 'target' UTF-16 code units; stop early if line ends.
330 const uint32_t target = pos.character;
331 if (target == 0)
332 return static_cast<uint32_t>(
333 src - reinterpret_cast<const llvm::UTF8 *>(text.data()));
334
335 std::vector<llvm::UTF16> sink(target);
336 llvm::UTF16 *out = sink.data();
337 llvm::UTF16 *outEnd = out + sink.size();
338
339 (void)llvm::ConvertUTF8toUTF16(&src, srcEnd, &out, outEnd,
340 llvm::lenientConversion);
341
342 return static_cast<uint32_t>(reinterpret_cast<const char *>(src) -
343 text.data());
344}
345
346const char *VerilogDocument::getPointerFor(const llvm::lsp::Position &pos) {
347 auto &sm = getSlangSourceManager();
348 auto slangBufferOffset = lspPositionToOffset(pos);
349
350 if (!slangBufferOffset.has_value())
351 return nullptr;
352
353 uint32_t offset = slangBufferOffset.value();
354 return sm.getSourceText(mainBufferId).data() + offset;
355}
356
358 const llvm::lsp::URIForFile &uri, const llvm::lsp::Position &defPos,
359 std::vector<llvm::lsp::Location> &locations) {
360
361 const auto &slangBufferPointer = getPointerFor(defPos);
362
363 if (!index)
364 return;
365
366 const auto &intervalMap = index->getIntervalMap();
367 auto it = intervalMap.find(slangBufferPointer);
368
369 // Found no element in the given index.
370 if (!it.valid() || slangBufferPointer < it.start())
371 return;
372
373 auto element = it.value();
374 if (auto attr = dyn_cast<Attribute>(element)) {
375
376 // Check if the attribute is a FileLineColRange.
377 if (auto fileLoc = dyn_cast<mlir::FileLineColRange>(attr)) {
378
379 // Return URI for the file.
380 auto fileInfo = getOrOpenFile(fileLoc.getFilename().getValue());
381 if (!fileInfo)
382 return;
383 const auto &[bufferId, filePath] = *fileInfo;
384 auto uri = llvm::lsp::URIForFile::fromFile(filePath);
385 if (auto e = uri.takeError()) {
386 circt::lsp::Logger::error("failed to open file " + filePath);
387 return;
388 }
389 locations.emplace_back(uri.get(), getRange(fileLoc));
390 }
391
392 return;
393 }
394
395 // If the element is verilog symbol, return the definition of the symbol.
396 const auto *symbol = cast<const slang::ast::Symbol *>(element);
397
398 slang::SourceRange range(symbol->location,
399 symbol->location +
400 (symbol->name.size() ? symbol->name.size() : 1));
401 locations.push_back(getLspLocation(range));
402}
403
405 const llvm::lsp::URIForFile &uri, const llvm::lsp::Position &pos,
406 std::vector<llvm::lsp::Location> &references) {
407
408 if (!index)
409 return;
410
411 const auto &slangBufferPointer = getPointerFor(pos);
412 const auto &intervalMap = index->getIntervalMap();
413 auto intervalIt = intervalMap.find(slangBufferPointer);
414
415 if (!intervalIt.valid() || slangBufferPointer < intervalIt.start())
416 return;
417
418 const auto *symbol = dyn_cast<const slang::ast::Symbol *>(intervalIt.value());
419 if (!symbol)
420 return;
421
422 auto it = index->getReferences().find(symbol);
423 if (it == index->getReferences().end())
424 return;
425 for (auto referenceRange : it->second)
426 references.push_back(getLspLocation(referenceRange));
427}
static std::unique_ptr< Context > context
static llvm::lsp::Range getRange(const mlir::FileLineColRange &fileLoc)
static void copyBuffers(slang::driver::Driver &driver, const slang::driver::Driver *const projectDriver, const llvm::SmallString< 256 > &mainBufferFileName)
static void setTopModules(slang::driver::Driver &driver)
std::unique_ptr< circt::lsp::VerilogIndex > index
The index of the parsed module.
const char * getPointerFor(const llvm::lsp::Position &pos)
slang::driver::Driver driver
VerilogDocument(VerilogServerContext &globalContext, const llvm::lsp::URIForFile &uri, llvm::StringRef contents, std::vector< llvm::lsp::Diagnostic > &diagnostics, const slang::driver::Driver *projectDriver=nullptr, const std::vector< std::string > &projectIncludeDirectories={})
VerilogServerContext & globalContext
llvm::lsp::URIForFile uri
void getLocationsOf(const llvm::lsp::URIForFile &uri, const llvm::lsp::Position &defPos, std::vector< llvm::lsp::Location > &locations)
void computeLineOffsets(std::string_view text)
Build a vector of line start offsets (0-based).
void findReferencesOf(const llvm::lsp::URIForFile &uri, const llvm::lsp::Position &pos, std::vector< llvm::lsp::Location > &references)
std::optional< uint32_t > lspPositionToOffset(const llvm::lsp::Position &pos)
const slang::SourceManager & getSlangSourceManager() const
llvm::StringMap< std::pair< slang::BufferID, llvm::SmallString< 128 > > > filePathMap
std::vector< uint32_t > lineOffsets
The precomputed line offsets for faster lookups.
std::optional< std::pair< slang::BufferID, llvm::SmallString< 128 > > > getOrOpenFile(llvm::StringRef filePath)
llvm::lsp::Location getLspLocation(slang::SourceLocation loc) const
void error(Twine message)
Definition LSPUtils.cpp:16