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