23
23
#include " mlir/IR/MLIRContext.h"
24
24
#include " slang/ast/ASTVisitor.h"
25
25
#include " slang/ast/Compilation.h"
26
+ #include " slang/ast/Scope.h"
27
+ #include " slang/ast/symbols/CompilationUnitSymbols.h"
26
28
#include " slang/diagnostics/DiagnosticClient.h"
27
29
#include " slang/diagnostics/Diagnostics.h"
28
30
#include " slang/driver/Driver.h"
43
45
#include " llvm/Support/SMLoc.h"
44
46
#include " llvm/Support/SourceMgr.h"
45
47
48
+ #include < filesystem>
49
+ #include < fstream>
46
50
#include < memory>
47
51
#include < optional>
48
52
#include < string>
@@ -248,15 +252,87 @@ void LSPDiagnosticClient::report(const slang::ReportedDiagnostic &slangDiag) {
248
252
// VerilogDocument
249
253
// ===----------------------------------------------------------------------===//
250
254
255
+ // Filter out the main buffer file from the command file list, if it is in
256
+ // there.
257
+ static inline bool
258
+ mainBufferFileInCommandFileList (const std::string &cmdfileStr,
259
+ const std::string &targetAbsStr) {
260
+
261
+ std::filesystem::path cmdfile (cmdfileStr);
262
+ std::filesystem::path targetAbs (targetAbsStr);
263
+ const std::filesystem::path base = cmdfile.parent_path ();
264
+ std::error_code ec;
265
+ // Normalize target
266
+ std::filesystem::path target =
267
+ std::filesystem::weakly_canonical (targetAbs, ec);
268
+ if (ec) {
269
+ ec.clear ();
270
+ target = targetAbs.lexically_normal ();
271
+ }
272
+ std::ifstream in (cmdfile);
273
+ if (!in)
274
+ return false ;
275
+
276
+ std::string line;
277
+ while (std::getline (in, line)) {
278
+ std::string s = line;
279
+ s.erase (0 , s.find_first_not_of (" \t\r\n " )); // left-trim
280
+ if (!s.empty () &&
281
+ (s.rfind (" +incdir+" , 0 ) == 0 || s.rfind (" +define+" , 0 ) == 0 ||
282
+ s.rfind (" -I" , 0 ) == 0 || s.rfind (" -D" , 0 ) == 0 )) {
283
+ continue ;
284
+ }
285
+ if (s.empty ()) {
286
+ continue ;
287
+ }
288
+ // Treat everything else as a path (relative entries resolved vs. cmdfile
289
+ // dir)
290
+ std::filesystem::path candRel = s;
291
+ std::filesystem::path candAbs =
292
+ candRel.is_absolute () ? candRel : (base / candRel);
293
+ std::filesystem::path cand = std::filesystem::weakly_canonical (candAbs, ec);
294
+ if (ec) {
295
+ ec.clear ();
296
+ cand = candAbs.lexically_normal ();
297
+ }
298
+
299
+ if (cand == target)
300
+ return true ;
301
+ }
302
+ return false ;
303
+ }
304
+
305
+ static inline bool mainBufferFileInCommandFileList (llvm::StringRef cmdfile,
306
+ llvm::StringRef target_abs) {
307
+ return mainBufferFileInCommandFileList (cmdfile.str (), target_abs.str ());
308
+ }
309
+
251
310
VerilogDocument::VerilogDocument (
252
311
VerilogServerContext &context, const llvm::lsp::URIForFile &uri,
253
312
StringRef contents, std::vector<llvm::lsp::Diagnostic> &diagnostics)
254
313
: globalContext(context), index(*this ), uri(uri) {
255
- unsigned int bufferId;
314
+ unsigned int mainBufferId;
315
+ bool skipMainBufferSlangImport = false ;
316
+
317
+ llvm::SmallString<256 > canonPath (uri.file ());
318
+ if (std::error_code ec = llvm::sys::fs::real_path (uri.file (), canonPath))
319
+ canonPath = uri.file (); // fall back, but try to keep it absolute
320
+
321
+ // --- Apply project command files (the “-C”s) to this per-buffer driver ---
322
+ for (const std::string &cmdFile : context.options .commandFiles ) {
323
+ if (!driver.processCommandFiles (cmdFile, false , true )) {
324
+ circt::lsp::Logger::error (Twine (" Failed to open command file " ) +
325
+ cmdFile);
326
+ }
327
+ skipMainBufferSlangImport |=
328
+ mainBufferFileInCommandFileList (cmdFile, canonPath);
329
+ }
330
+
256
331
if (auto memBufferOwn =
257
332
llvm::MemoryBuffer::getMemBufferCopy (contents, uri.file ())) {
258
333
259
- bufferId = sourceMgr.AddNewSourceBuffer (std::move (memBufferOwn), SMLoc ());
334
+ mainBufferId =
335
+ sourceMgr.AddNewSourceBuffer (std::move (memBufferOwn), SMLoc ());
260
336
} else {
261
337
circt::lsp::Logger::error (
262
338
Twine (" Failed to create memory buffer for file " ) + uri.file ());
@@ -273,17 +349,58 @@ VerilogDocument::VerilogDocument(
273
349
context.options .libDirs .end ());
274
350
275
351
// Populate source managers.
276
- const llvm::MemoryBuffer *memBuffer = sourceMgr.getMemoryBuffer (bufferId);
352
+ const llvm::MemoryBuffer *memBuffer = sourceMgr.getMemoryBuffer (mainBufferId);
353
+
354
+ // This block compiles the top file to determine all definitions
355
+ // This is used in a second pass to declare all those definitions
356
+ // as top modules, so they are elaborated and subsequently indexed.
357
+ slang::driver::Driver topDriver;
358
+
359
+ auto topSlangBuffer =
360
+ topDriver.sourceManager .assignText (uri.file (), memBuffer->getBuffer ());
361
+ topDriver.sourceLoader .addBuffer (topSlangBuffer);
362
+
363
+ topDriver.options .compilationFlags .emplace (
364
+ slang::ast::CompilationFlags::LintMode, false );
365
+ topDriver.options .compilationFlags .emplace (
366
+ slang::ast::CompilationFlags::DisableInstanceCaching, false );
367
+
368
+ if (!topDriver.processOptions ()) {
369
+ return ;
370
+ }
371
+
372
+ if (!topDriver.parseAllSources ()) {
373
+ circt::lsp::Logger::error (Twine (" Failed to parse Verilog file " ) +
374
+ uri.file ());
375
+ return ;
376
+ }
377
+
378
+ FailureOr<std::unique_ptr<slang::ast::Compilation>> topCompilation =
379
+ topDriver.createCompilation ();
380
+ if (failed (topCompilation))
381
+ return ;
382
+
383
+ std::vector<std::string> topModules;
384
+ for (const auto *defs : (*topCompilation)->getDefinitions ())
385
+ topModules.emplace_back (defs->name );
386
+
387
+ // Make sure that all possible definitions in the main buffer are
388
+ // topModules!
389
+ driver.options .topModules = topModules;
277
390
278
391
for (const auto &libDir : libDirs) {
279
392
driver.sourceLoader .addSearchDirectories (libDir);
280
393
}
281
394
282
- // Assign text to slang.
283
- auto slangBuffer =
284
- driver.sourceManager .assignText (uri.file (), memBuffer->getBuffer ());
285
- driver.sourceLoader .addBuffer (slangBuffer);
286
- bufferIDMap[slangBuffer.id .getId ()] = bufferId;
395
+ // If the main buffer is **not** present in a command file, add it into
396
+ // slang's source manager and bind to llvm source manager.
397
+ if (!skipMainBufferSlangImport) {
398
+ auto slangBuffer =
399
+ driver.sourceManager .assignText (uri.file (), memBuffer->getBuffer ());
400
+ driver.sourceLoader .addBuffer (slangBuffer);
401
+ bufferIDMap[slangBuffer.id .getId ()] = mainBufferId;
402
+ }
403
+
287
404
auto diagClient = std::make_shared<LSPDiagnosticClient>(*this , diagnostics);
288
405
driver.diagEngine .addClient (diagClient);
289
406
@@ -308,6 +425,50 @@ VerilogDocument::VerilogDocument(
308
425
if (failed (compilation))
309
426
return ;
310
427
428
+ // If the main buffer is present in a command file, compile it only once
429
+ // and import directly from the command file; then figure out which buffer id
430
+ // it was assigned and bind to llvm source manager.
431
+ llvm::SmallString<256 > slangCanonPath (" " );
432
+ std::string slangRawPath;
433
+ std::unique_ptr<llvm::MemoryBuffer> newBuffer;
434
+ uint32_t newBufferId;
435
+
436
+ // Iterate through all buffers in the slang compilation and set up
437
+ // a binding to the LLVM Source Manager.
438
+ auto *sourceManager = (**compilation).getSourceManager ();
439
+ for (auto slangBuffer : sourceManager->getAllBuffers ()) {
440
+ slangRawPath = sourceManager->getRawFileName (slangBuffer);
441
+ if (std::error_code ec =
442
+ llvm::sys::fs::real_path (slangRawPath, slangCanonPath))
443
+ continue ;
444
+
445
+ if (slangCanonPath == canonPath && skipMainBufferSlangImport) {
446
+ bufferIDMap[slangBuffer.getId ()] = mainBufferId;
447
+ continue ;
448
+ }
449
+
450
+ if (slangCanonPath == canonPath && !skipMainBufferSlangImport) {
451
+ continue ;
452
+ }
453
+
454
+ if (!bufferIDMap.contains (slangBuffer.getId ())) {
455
+
456
+ auto uriOrError = llvm::lsp::URIForFile::fromFile (slangCanonPath);
457
+ if (auto e = uriOrError.takeError ()) {
458
+ circt::lsp::Logger::error (
459
+ Twine (" Failed to get URI from file " + slangCanonPath));
460
+ continue ;
461
+ }
462
+
463
+ newBuffer = llvm::MemoryBuffer::getMemBufferCopy (
464
+ sourceManager->getSourceText (slangBuffer), uriOrError->file ());
465
+ newBufferId = sourceMgr.AddNewSourceBuffer (std::move (newBuffer), SMLoc ());
466
+ bufferIDMap[slangBuffer.getId ()] = newBufferId;
467
+ continue ;
468
+ }
469
+ circt::lsp::Logger::error (Twine (" Failed to add buffer ID! " ));
470
+ }
471
+
311
472
for (auto &diag : (*compilation)->getAllDiagnostics ())
312
473
driver.diagEngine .issue (diag);
313
474
@@ -322,19 +483,29 @@ VerilogDocument::getLspLocation(slang::SourceLocation loc) const {
322
483
auto line = slangSourceManager.getLineNumber (loc) - 1 ;
323
484
auto column = slangSourceManager.getColumnNumber (loc) - 1 ;
324
485
auto it = bufferIDMap.find (loc.buffer ().getId ());
325
- // Check if the current buffer is the main file.
326
486
if (it != bufferIDMap.end () && it->second == sourceMgr.getMainFileID ())
327
487
return llvm::lsp::Location (uri, llvm::lsp::Range (Position (line, column)));
328
488
329
- // Otherwise, construct URI from slang source manager.
330
- auto fileName = slangSourceManager.getFileName (loc);
331
- auto loc = llvm::lsp::URIForFile::fromFile (fileName);
332
- if (auto e = loc.takeError ())
333
- return llvm::lsp::Location ();
334
- return llvm::lsp::Location (loc.get (),
335
- llvm::lsp::Range (Position (line, column)));
336
- }
489
+ llvm::StringRef fileName = slangSourceManager.getFileName (loc);
490
+ // Ensure absolute path for LSP:
491
+ llvm::SmallString<256 > abs (fileName);
492
+ if (!llvm::sys::path::is_absolute (abs)) {
493
+ // Try realPath first
494
+ if (std::error_code ec = llvm::sys::fs::real_path (fileName, abs)) {
495
+ // Fallback: make it absolute relative to the process CWD
496
+ llvm::sys::fs::current_path (abs); // abs = CWD
497
+ llvm::sys::path::append (abs, fileName);
498
+ }
499
+ }
337
500
501
+ if (auto uriOrErr = llvm::lsp::URIForFile::fromFile (abs)) {
502
+ if (auto e = uriOrErr.takeError ())
503
+ return llvm::lsp::Location ();
504
+ return llvm::lsp::Location (*uriOrErr,
505
+ llvm::lsp::Range (Position (line, column)));
506
+ }
507
+ return llvm::lsp::Location ();
508
+ }
338
509
return llvm::lsp::Location ();
339
510
}
340
511
@@ -353,13 +524,20 @@ VerilogDocument::getLspLocation(slang::SourceRange range) const {
353
524
354
525
llvm::SMLoc VerilogDocument::getSMLoc (slang::SourceLocation loc) {
355
526
auto bufferID = loc.buffer ().getId ();
527
+ llvm::SmallString<256 > slangCanonPath (" " );
356
528
357
529
// Check if the source is already opened by LLVM source manager.
358
530
auto bufferIDMapIt = bufferIDMap.find (bufferID);
359
531
if (bufferIDMapIt == bufferIDMap.end ()) {
360
532
// If not, open the source file and add it to the LLVM source manager.
361
533
auto path = getSlangSourceManager ().getFullPath (loc.buffer ());
362
- auto memBuffer = llvm::MemoryBuffer::getFile (path.string ());
534
+
535
+ // If file is not open yet and not a real path, skip it.
536
+ if (std::error_code ec =
537
+ llvm::sys::fs::real_path (path.string (), slangCanonPath))
538
+ return llvm::SMLoc ();
539
+
540
+ auto memBuffer = llvm::MemoryBuffer::getFile (slangCanonPath);
363
541
if (!memBuffer) {
364
542
circt::lsp::Logger::error (
365
543
" Failed to open file: " + path.filename ().string () +
@@ -524,6 +702,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
524
702
return ;
525
703
assert (range.start ().valid () && range.end ().valid () &&
526
704
" range must be valid" );
705
+
706
+ // TODO: This implementation does not handle expanded MACROs. Return
707
+ // instead.
708
+ if (range.start () >= range.end ()) {
709
+ return ;
710
+ }
711
+
527
712
index.insertSymbol (symbol, range, isDefinition);
528
713
}
529
714
@@ -581,7 +766,30 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
581
766
insertSymbol (def, item->package .location (), false );
582
767
}
583
768
}
769
+ visitDefault (expr);
770
+ }
584
771
772
+ void visit (const slang::ast::InstanceSymbol &expr) {
773
+ auto *def = &expr.getDefinition ();
774
+ if (!def)
775
+ return ;
776
+
777
+ // Add the module definition
778
+ insertSymbol (def, def->location , /* isDefinition=*/ true );
779
+
780
+ // Walk up the syntax tree until we hit the type token;
781
+ // Link that token back to the instance declaration.
782
+ if (auto *hierInst =
783
+ expr.getSyntax ()
784
+ ->as_if <slang::syntax::HierarchicalInstanceSyntax>())
785
+ if (auto *modInst =
786
+ hierInst->parent
787
+ ->as_if <slang::syntax::HierarchyInstantiationSyntax>())
788
+ if (modInst->type )
789
+ insertSymbol (def, modInst->type .location (), false );
790
+
791
+ // Link the module instance name back to the module definition
792
+ insertSymbol (def, expr.location , /* isDefinition=*/ false );
585
793
visitDefault (expr);
586
794
}
587
795
@@ -608,7 +816,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
608
816
void VerilogIndex::initialize (slang::ast::Compilation &compilation) {
609
817
const auto &root = compilation.getRoot ();
610
818
VerilogIndexer visitor (*this );
819
+
611
820
for (auto *inst : root.topInstances ) {
821
+
822
+ // Skip all modules, interfaces, etc. that are not defined in this files
823
+ if (!(document.getLspLocation (inst->location ).uri == document.getURI ()))
824
+ continue ;
825
+
612
826
// Visit the body of the instance.
613
827
inst->body .visit (visitor);
614
828
@@ -780,10 +994,17 @@ void circt::lsp::VerilogServer::findReferencesOf(
780
994
void VerilogIndex::insertSymbol (const slang::ast::Symbol *symbol,
781
995
slang::SourceRange from, bool isDefinition) {
782
996
assert (from.start ().valid () && from.end ().valid ());
997
+
998
+ // TODO: Currently doesn't handle expanded macros
999
+ if (!from.start ().valid () || !from.end ().valid () ||
1000
+ from.start () >= from.end ())
1001
+ return ;
1002
+
783
1003
const char *startLoc = getDocument ().getSMLoc (from.start ()).getPointer ();
784
1004
const char *endLoc = getDocument ().getSMLoc (from.end ()).getPointer () + 1 ;
785
- if (!startLoc || !endLoc)
1005
+ if (!startLoc || !endLoc || startLoc >= endLoc )
786
1006
return ;
1007
+
787
1008
assert (startLoc && endLoc);
788
1009
789
1010
if (startLoc != endLoc && !intervalMap.overlaps (startLoc, endLoc)) {
0 commit comments