CIRCT 22.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;
56using namespace llvm::lsp;
57
58static std::filesystem::path
59canonicalizeFileName(const std::filesystem::path &file) {
60 std::error_code ec;
61 std::filesystem::path path = std::filesystem::weakly_canonical(file, ec);
62 if (ec)
63 path = std::filesystem::absolute(file).lexically_normal();
64 return path;
65}
66
67// Filter out the main buffer file from the command file list, if it is in
68// there.
69static inline bool
70mainBufferFileInCommandFileList(const std::string &cmdfileStr,
71 const std::string &targetAbsStr) {
72 const std::filesystem::path targetAbs =
73 canonicalizeFileName(std::filesystem::path(targetAbsStr));
74
75 std::string error;
76 auto cmdFile = mlir::openInputFile(cmdfileStr, &error);
77 if (!cmdFile) {
78 circt::lsp::Logger::error(Twine("Failed to open command file ") +
79 cmdfileStr + ": " + error);
80 return false;
81 }
82
83 const std::filesystem::path base =
84 std::filesystem::path(cmdFile->getBufferIdentifier().str()).parent_path();
85
86 // Read line by line, ignoring empty lines and comments.
87 for (llvm::line_iterator i(*cmdFile); !i.is_at_eof(); ++i) {
88 llvm::StringRef line = i->trim();
89
90 if (line.empty())
91 continue;
92
93 static constexpr llvm::StringRef commandPrefixes[] = {"+", "-"};
94 auto isCommand = [&line](llvm::StringRef s) { return line.starts_with(s); };
95 if (llvm::any_of(commandPrefixes, isCommand))
96 continue;
97
98 auto candRel = std::filesystem::path(line.str());
99 auto candAbs = canonicalizeFileName(
100 candRel.is_absolute() ? candRel : (base / candRel));
101
102 if (candAbs == targetAbs)
103 return true;
104 }
105 return false;
106}
107
109 VerilogServerContext &context, const llvm::lsp::URIForFile &uri,
110 StringRef contents, std::vector<llvm::lsp::Diagnostic> &diagnostics)
111 : globalContext(context), uri(uri) {
112 bool skipMainBufferSlangImport = false;
113
114 llvm::SmallString<256> canonPath(uri.file());
115 if (std::error_code ec = llvm::sys::fs::real_path(uri.file(), canonPath))
116 canonPath = uri.file(); // fall back, but try to keep it absolute
117
118 // --- Apply project command files (the “-C”s) to this per-buffer driver ---
119 for (const std::string &cmdFile : context.options.commandFiles) {
120 if (!driver.processCommandFiles(cmdFile, false, true)) {
121 circt::lsp::Logger::error(Twine("Failed to open command file ") +
122 cmdFile);
123 }
124 skipMainBufferSlangImport |=
125 mainBufferFileInCommandFileList(cmdFile, canonPath.str().str());
126 }
127
128 // Build the set of include directories for this file.
129 llvm::SmallString<32> uriDirectory(uri.file());
130 llvm::sys::path::remove_filename(uriDirectory);
131
132 std::vector<std::string> libDirs;
133 libDirs.push_back(uriDirectory.str().str());
134 libDirs.insert(libDirs.end(), context.options.libDirs.begin(),
135 context.options.libDirs.end());
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 // This block parses the top file to determine all definitions
145 // This is used in a second pass to declare all those definitions
146 // as top modules, so they are elaborated and subsequently indexed.
147 {
148 slang::driver::Driver topDriver;
149
150 auto topSlangBuffer =
151 topDriver.sourceManager.assignText(uri.file(), memBuffer->getBuffer());
152 topDriver.sourceLoader.addBuffer(topSlangBuffer);
153
154 topDriver.addStandardArgs();
155
156 if (!topDriver.processOptions()) {
157 return;
158 }
159
160 if (!topDriver.parseAllSources()) {
161 circt::lsp::Logger::error(Twine("Failed to parse Verilog file ") +
162 uri.file());
163 return;
164 }
165
166 // Extract all the top modules in the file directly from the syntax tree
167 std::vector<std::string> topModules;
168 for (auto &t : topDriver.syntaxTrees) {
169 if (auto *compUnit =
170 t->root().as_if<slang::syntax::CompilationUnitSyntax>()) {
171 for (auto *member : compUnit->members) {
172 // While it's called "ModuleDeclarationSyntax", it also covers
173 // packages
174 if (auto *moduleDecl =
175 member->as_if<slang::syntax::ModuleDeclarationSyntax>()) {
176 topModules.emplace_back(moduleDecl->header->name.valueText());
177 }
178 }
179 }
180 }
181 driver.options.topModules = std::move(topModules);
182 }
183
184 for (const auto &libDir : libDirs) {
185 driver.sourceLoader.addSearchDirectories(libDir);
186 }
187
188 // If the main buffer is **not** present in a command file, add it into
189 // slang's source manager.
190 if (!skipMainBufferSlangImport) {
191 auto slangBuffer =
192 driver.sourceManager.assignText(uri.file(), memBuffer->getBuffer());
193 driver.sourceLoader.addBuffer(slangBuffer);
194 mainBufferId = slangBuffer.id;
195 }
196
197 auto diagClient = std::make_shared<LSPDiagnosticClient>(*this, diagnostics);
198 driver.diagEngine.addClient(diagClient);
199
200 driver.options.compilationFlags.emplace(
201 slang::ast::CompilationFlags::LintMode, false);
202 driver.options.compilationFlags.emplace(
203 slang::ast::CompilationFlags::DisableInstanceCaching, false);
204
205 if (!driver.processOptions()) {
206 return;
207 }
208
209 driver.diagEngine.setIgnoreAllWarnings(false);
210
211 if (!driver.parseAllSources()) {
212 circt::lsp::Logger::error(Twine("Failed to parse Verilog file ") +
213 uri.file());
214 return;
215 }
216
217 compilation = driver.createCompilation();
218 if (failed(compilation))
219 return;
220
221 if (skipMainBufferSlangImport) {
222 // If the main buffer is present in a command file, compile it only once
223 // and import directly from the command file; then figure out which buffer
224 // id it was assigned and bind to llvm source manager.
225 llvm::SmallString<256> slangCanonPath;
226 bool mainBufferIdSet = false;
227
228 // Iterate through all buffers in the slang compilation and set up
229 // a binding to the LLVM Source Manager.
230 auto *sourceManager = (**compilation).getSourceManager();
231 for (auto slangBuffer : sourceManager->getAllBuffers()) {
232 std::string_view slangRawPath =
233 sourceManager->getRawFileName(slangBuffer);
234 if (std::error_code ec =
235 llvm::sys::fs::real_path(slangRawPath, slangCanonPath))
236 continue;
237
238 if (slangCanonPath == canonPath) {
239 mainBufferId = slangBuffer;
240 mainBufferIdSet = true;
241 break;
242 }
243 }
244
245 if (!mainBufferIdSet)
247 Twine("Failed to set main buffer id after compilation! "));
248 }
249
250 for (auto &diag : (*compilation)->getAllDiagnostics())
251 driver.diagEngine.issue(diag);
252
253 computeLineOffsets(driver.sourceManager.getSourceText(mainBufferId));
254
255 index = std::make_unique<VerilogIndex>(mainBufferId, driver.sourceManager);
256
257 // Populate the index.
258 index->initialize(**compilation);
259}
260
261llvm::lsp::Location
262VerilogDocument::getLspLocation(slang::SourceLocation loc) const {
263 if (loc && loc.buffer() != slang::SourceLocation::NoLocation.buffer()) {
264 const auto &slangSourceManager = getSlangSourceManager();
265 auto line = slangSourceManager.getLineNumber(loc) - 1;
266 auto column = slangSourceManager.getColumnNumber(loc) - 1;
267 auto it = loc.buffer();
268 if (it == mainBufferId)
269 return llvm::lsp::Location(uri, llvm::lsp::Range(Position(line, column)));
270
271 llvm::StringRef fileName = slangSourceManager.getFileName(loc);
272 // Ensure absolute path for LSP:
273 llvm::SmallString<256> abs(fileName);
274 if (!llvm::sys::path::is_absolute(abs)) {
275 // Try realPath first
276 if (std::error_code ec = llvm::sys::fs::real_path(fileName, abs)) {
277 // Fallback: make it absolute relative to the process CWD
278 llvm::sys::fs::current_path(abs); // abs = CWD
279 llvm::sys::path::append(abs, fileName);
280 }
281 }
282
283 if (auto uriOrErr = llvm::lsp::URIForFile::fromFile(abs)) {
284 if (auto e = uriOrErr.takeError())
285 return llvm::lsp::Location();
286 return llvm::lsp::Location(*uriOrErr,
287 llvm::lsp::Range(Position(line, column)));
288 }
289 return llvm::lsp::Location();
290 }
291 return llvm::lsp::Location();
292}
293
294llvm::lsp::Location
295VerilogDocument::getLspLocation(slang::SourceRange range) const {
296
297 auto start = getLspLocation(range.start());
298 auto end = getLspLocation(range.end());
299
300 if (start.uri != end.uri)
301 return llvm::lsp::Location();
302
303 return llvm::lsp::Location(
304 start.uri, llvm::lsp::Range(start.range.start, end.range.end));
305}
306
307std::optional<std::pair<slang::BufferID, SmallString<128>>>
309
310 auto fileInfo = filePathMap.find(filePath);
311 if (fileInfo != filePathMap.end())
312 return fileInfo->second;
313
314 auto getIfExist = [&](StringRef path)
315 -> std::optional<std::pair<slang::BufferID, SmallString<128>>> {
316 if (llvm::sys::fs::exists(path)) {
317 auto memoryBuffer = llvm::MemoryBuffer::getFile(path);
318 if (!memoryBuffer) {
319 return std::nullopt;
320 }
321
322 auto newSlangBuffer = driver.sourceManager.assignText(
323 path.str(), memoryBuffer.get()->getBufferStart());
324 driver.sourceLoader.addBuffer(newSlangBuffer);
325
326 fileInfo = filePathMap
327 .insert(std::make_pair(
328 filePath, std::make_pair(newSlangBuffer.id, path)))
329 .first;
330
331 return fileInfo->second;
332 }
333 return std::nullopt;
334 };
335
336 if (llvm::sys::path::is_absolute(filePath))
337 return getIfExist(filePath);
338
339 // Search locations.
340 for (auto &libRoot : globalContext.options.extraSourceLocationDirs) {
341 SmallString<128> lib(libRoot);
342 llvm::sys::path::append(lib, filePath);
343 if (auto fileInfo = getIfExist(lib))
344 return fileInfo;
345 }
346
347 return std::nullopt;
348}
349
350static llvm::lsp::Range getRange(const mlir::FileLineColRange &fileLoc) {
351 return llvm::lsp::Range(
352 llvm::lsp::Position(fileLoc.getStartLine(), fileLoc.getStartColumn()),
353 llvm::lsp::Position(fileLoc.getEndLine(), fileLoc.getEndColumn()));
354}
355
356/// Build a vector of line start offsets (0-based).
357void VerilogDocument::computeLineOffsets(std::string_view text) {
358 lineOffsets.clear();
359 lineOffsets.reserve(1024);
360 lineOffsets.push_back(0);
361 for (size_t i = 0; i < text.size(); ++i) {
362 if (text[i] == '\n') {
363 lineOffsets.push_back(static_cast<uint32_t>(i + 1));
364 }
365 }
366}
367
368// LSP (0-based line, UTF-16 character) -> byte offset into UTF-8 buffer.
369std::optional<uint32_t>
370VerilogDocument::lspPositionToOffset(const llvm::lsp::Position &pos) {
371
372 auto &sm = getSlangSourceManager();
373
374 std::string_view text = sm.getSourceText(mainBufferId);
375
376 // Clamp line index
377 if ((unsigned)pos.line >= lineOffsets.size())
378 return std::nullopt;
379
380 size_t lineStart = lineOffsets[pos.line];
381 size_t lineEnd = ((unsigned)(pos.line + 1) < lineOffsets.size())
382 ? lineOffsets[pos.line + 1] - 1
383 : text.size();
384
385 const llvm::UTF8 *src =
386 reinterpret_cast<const llvm::UTF8 *>(text.data() + lineStart);
387 const llvm::UTF8 *srcEnd =
388 reinterpret_cast<const llvm::UTF8 *>(text.data() + lineEnd);
389
390 // Convert up to 'target' UTF-16 code units; stop early if line ends.
391 const uint32_t target = pos.character;
392 if (target == 0)
393 return static_cast<uint32_t>(
394 src - reinterpret_cast<const llvm::UTF8 *>(text.data()));
395
396 std::vector<llvm::UTF16> sink(target);
397 llvm::UTF16 *out = sink.data();
398 llvm::UTF16 *outEnd = out + sink.size();
399
400 (void)llvm::ConvertUTF8toUTF16(&src, srcEnd, &out, outEnd,
401 llvm::lenientConversion);
402
403 return static_cast<uint32_t>(reinterpret_cast<const char *>(src) -
404 text.data());
405}
406
407const char *VerilogDocument::getPointerFor(const llvm::lsp::Position &pos) {
408 auto &sm = getSlangSourceManager();
409 auto slangBufferOffset = lspPositionToOffset(pos);
410
411 if (!slangBufferOffset.has_value())
412 return nullptr;
413
414 uint32_t offset = slangBufferOffset.value();
415 return sm.getSourceText(mainBufferId).data() + offset;
416}
417
419 const llvm::lsp::URIForFile &uri, const llvm::lsp::Position &defPos,
420 std::vector<llvm::lsp::Location> &locations) {
421
422 const auto &slangBufferPointer = getPointerFor(defPos);
423
424 if (!index)
425 return;
426
427 const auto &intervalMap = index->getIntervalMap();
428 auto it = intervalMap.find(slangBufferPointer);
429
430 // Found no element in the given index.
431 if (!it.valid() || slangBufferPointer < it.start())
432 return;
433
434 auto element = it.value();
435 if (auto attr = dyn_cast<Attribute>(element)) {
436
437 // Check if the attribute is a FileLineColRange.
438 if (auto fileLoc = dyn_cast<mlir::FileLineColRange>(attr)) {
439
440 // Return URI for the file.
441 auto fileInfo = getOrOpenFile(fileLoc.getFilename().getValue());
442 if (!fileInfo)
443 return;
444 const auto &[bufferId, filePath] = *fileInfo;
445 auto uri = llvm::lsp::URIForFile::fromFile(filePath);
446 if (auto e = uri.takeError()) {
447 circt::lsp::Logger::error("failed to open file " + filePath);
448 return;
449 }
450 locations.emplace_back(uri.get(), getRange(fileLoc));
451 }
452
453 return;
454 }
455
456 // If the element is verilog symbol, return the definition of the symbol.
457 const auto *symbol = cast<const slang::ast::Symbol *>(element);
458
459 slang::SourceRange range(symbol->location,
460 symbol->location +
461 (symbol->name.size() ? symbol->name.size() : 1));
462 locations.push_back(getLspLocation(range));
463}
464
466 const llvm::lsp::URIForFile &uri, const llvm::lsp::Position &pos,
467 std::vector<llvm::lsp::Location> &references) {
468
469 if (!index)
470 return;
471
472 const auto &slangBufferPointer = getPointerFor(pos);
473 const auto &intervalMap = index->getIntervalMap();
474 auto intervalIt = intervalMap.find(slangBufferPointer);
475
476 if (!intervalIt.valid() || slangBufferPointer < intervalIt.start())
477 return;
478
479 const auto *symbol = dyn_cast<const slang::ast::Symbol *>(intervalIt.value());
480 if (!symbol)
481 return;
482
483 auto it = index->getReferences().find(symbol);
484 if (it == index->getReferences().end())
485 return;
486 for (auto referenceRange : it->second)
487 references.push_back(getLspLocation(referenceRange));
488}
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)
std::unique_ptr< circt::lsp::VerilogIndex > index
The index of the parsed module.
VerilogDocument(VerilogServerContext &globalContext, const llvm::lsp::URIForFile &uri, llvm::StringRef contents, std::vector< llvm::lsp::Diagnostic > &diagnostics)
const char * getPointerFor(const llvm::lsp::Position &pos)
slang::driver::Driver driver
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).
std::optional< std::pair< slang::BufferID, llvm::SmallString< 128 > > > getOrOpenFile(llvm::StringRef filePath)
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.
llvm::lsp::Location getLspLocation(slang::SourceLocation loc) const
void error(Twine message)
Definition LSPUtils.cpp:16
const circt::lsp::VerilogServerOptions & options
const std::vector< std::string > & libDirs
Additional list of RTL directories to search.
const std::vector< std::string > & extraSourceLocationDirs
Additional list of external source directories to search.