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,98 @@ 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
+ namespace fs = std::filesystem;
261
+
262
+ fs::path cmdfile (cmdfileStr);
263
+ fs::path targetAbs (targetAbsStr);
264
+ const fs::path base = cmdfile.parent_path ();
265
+ std::error_code ec;
266
+
267
+ // Normalize target
268
+ fs::path target = fs::weakly_canonical (targetAbs, ec);
269
+ if (ec) {
270
+ ec.clear ();
271
+ target = targetAbs.lexically_normal ();
272
+ }
273
+
274
+ std::ifstream in (cmdfile);
275
+ if (!in) {
276
+ // return the original path so the caller still has a valid file to use
277
+ return false ;
278
+ }
279
+
280
+ std::string line;
281
+
282
+ while (std::getline (in, line)) {
283
+ std::string s = line;
284
+ s.erase (0 , s.find_first_not_of (" \t\r\n " )); // left-trim
285
+
286
+ // Keep plusargs
287
+ if (!s.empty () &&
288
+ (s.rfind (" +incdir+" , 0 ) == 0 || s.rfind (" +define+" , 0 ) == 0 ||
289
+ s.rfind (" -I" , 0 ) == 0 || s.rfind (" -D" , 0 ) == 0 )) {
290
+ continue ;
291
+ }
292
+ // Preserve blank lines
293
+ if (s.empty ()) {
294
+ continue ;
295
+ }
296
+
297
+ // Treat everything else as a path (relative entries resolved vs. cmdfile
298
+ // dir)
299
+ fs::path candRel = s;
300
+ fs::path candAbs = candRel.is_absolute () ? candRel : (base / candRel);
301
+
302
+ fs::path cand = fs::weakly_canonical (candAbs, ec);
303
+ if (ec) {
304
+ ec.clear ();
305
+ cand = candAbs.lexically_normal ();
306
+ }
307
+
308
+ if (cand == target) {
309
+ return true ;
310
+ }
311
+ }
312
+
313
+ return false ;
314
+ }
315
+
316
+ static inline bool mainBufferFileInCommandFileList (llvm::StringRef cmdfile,
317
+ llvm::StringRef target_abs) {
318
+ return mainBufferFileInCommandFileList (cmdfile.str (), target_abs.str ());
319
+ }
320
+
251
321
VerilogDocument::VerilogDocument (
252
322
VerilogServerContext &context, const llvm::lsp::URIForFile &uri,
253
323
StringRef contents, std::vector<llvm::lsp::Diagnostic> &diagnostics)
254
324
: globalContext(context), index(*this ), uri(uri) {
255
- unsigned int bufferId;
325
+ unsigned int mainBufferId;
326
+ bool skipMainBufferSlangImport = false ;
327
+
328
+ llvm::SmallString<256 > canonPath (uri.file ());
329
+ if (std::error_code ec = llvm::sys::fs::real_path (uri.file (), canonPath))
330
+ canonPath = uri.file (); // fall back, but try to keep it absolute
331
+
332
+ // --- Apply project command files (the “-C”s) to this per-buffer driver ---
333
+ for (const std::string &cmdFile : context.options .commandFiles ) {
334
+ if (!driver.processCommandFiles (cmdFile, false , true )) {
335
+ circt::lsp::Logger::error (Twine (" Failed to open command file " ) +
336
+ cmdFile);
337
+ }
338
+ skipMainBufferSlangImport |=
339
+ mainBufferFileInCommandFileList (cmdFile, canonPath);
340
+ }
341
+
256
342
if (auto memBufferOwn =
257
343
llvm::MemoryBuffer::getMemBufferCopy (contents, uri.file ())) {
258
344
259
- bufferId = sourceMgr.AddNewSourceBuffer (std::move (memBufferOwn), SMLoc ());
345
+ mainBufferId =
346
+ sourceMgr.AddNewSourceBuffer (std::move (memBufferOwn), SMLoc ());
260
347
} else {
261
348
circt::lsp::Logger::error (
262
349
Twine (" Failed to create memory buffer for file " ) + uri.file ());
@@ -273,17 +360,58 @@ VerilogDocument::VerilogDocument(
273
360
context.options .libDirs .end ());
274
361
275
362
// Populate source managers.
276
- const llvm::MemoryBuffer *memBuffer = sourceMgr.getMemoryBuffer (bufferId);
363
+ const llvm::MemoryBuffer *memBuffer = sourceMgr.getMemoryBuffer (mainBufferId);
364
+
365
+ // This block compiles the top file to determine all definitions
366
+ // This is used in a second pass to declare all those definitions
367
+ // as top modules, so they are elaborated and subsequently indexed.
368
+ slang::driver::Driver topDriver;
369
+
370
+ auto topSlangBuffer =
371
+ topDriver.sourceManager .assignText (uri.file (), memBuffer->getBuffer ());
372
+ topDriver.sourceLoader .addBuffer (topSlangBuffer);
373
+
374
+ topDriver.options .compilationFlags .emplace (
375
+ slang::ast::CompilationFlags::LintMode, false );
376
+ topDriver.options .compilationFlags .emplace (
377
+ slang::ast::CompilationFlags::DisableInstanceCaching, false );
378
+
379
+ if (!topDriver.processOptions ()) {
380
+ return ;
381
+ }
382
+
383
+ if (!topDriver.parseAllSources ()) {
384
+ circt::lsp::Logger::error (Twine (" Failed to parse Verilog file " ) +
385
+ uri.file ());
386
+ return ;
387
+ }
388
+
389
+ FailureOr<std::unique_ptr<slang::ast::Compilation>> topCompilation =
390
+ topDriver.createCompilation ();
391
+ if (failed (topCompilation))
392
+ return ;
393
+
394
+ std::vector<std::string> topModules;
395
+ for (const auto *defs : (*topCompilation)->getDefinitions ())
396
+ topModules.emplace_back (defs->name );
397
+
398
+ // Make sure that all possible definitions in the main buffer are
399
+ // topModules!
400
+ driver.options .topModules = topModules;
277
401
278
402
for (const auto &libDir : libDirs) {
279
403
driver.sourceLoader .addSearchDirectories (libDir);
280
404
}
281
405
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;
406
+ // If the main buffer is **not** present in a command file, add it into
407
+ // slang's source manager and bind to llvm source manager.
408
+ if (!skipMainBufferSlangImport) {
409
+ auto slangBuffer =
410
+ driver.sourceManager .assignText (uri.file (), memBuffer->getBuffer ());
411
+ driver.sourceLoader .addBuffer (slangBuffer);
412
+ bufferIDMap[slangBuffer.id .getId ()] = mainBufferId;
413
+ }
414
+
287
415
auto diagClient = std::make_shared<LSPDiagnosticClient>(*this , diagnostics);
288
416
driver.diagEngine .addClient (diagClient);
289
417
@@ -308,6 +436,49 @@ VerilogDocument::VerilogDocument(
308
436
if (failed (compilation))
309
437
return ;
310
438
439
+ // If the main buffer is present in a command file, compile it only once
440
+ // and import directly from the command file; then figure out which buffer id
441
+ // it was assigned and bind to llvm source manager.
442
+ llvm::SmallString<256 > slangCanonPath (" " );
443
+ std::string slangRawPath;
444
+ std::unique_ptr<llvm::MemoryBuffer> newBuffer;
445
+ uint32_t newBufferId;
446
+
447
+ // Iterate through all buffers in the slang compilation and set up
448
+ // a binding to the LLVM Source Manager.
449
+ auto *sourceManager = (**compilation).getSourceManager ();
450
+ for (auto slangBuffer : sourceManager->getAllBuffers ()) {
451
+ slangRawPath = sourceManager->getRawFileName (slangBuffer);
452
+ if (std::error_code ec =
453
+ llvm::sys::fs::real_path (slangRawPath, slangCanonPath))
454
+ slangCanonPath = slangRawPath;
455
+
456
+ if (slangCanonPath == canonPath && skipMainBufferSlangImport) {
457
+ bufferIDMap[slangBuffer.getId ()] = mainBufferId;
458
+ continue ;
459
+ }
460
+
461
+ if (slangCanonPath == canonPath && !skipMainBufferSlangImport) {
462
+ continue ;
463
+ }
464
+
465
+ if (!bufferIDMap.contains (slangBuffer.getId ())) {
466
+
467
+ auto uriOrError = llvm::lsp::URIForFile::fromFile (slangCanonPath);
468
+ if (auto e = uriOrError.takeError ()) {
469
+ circt::lsp::Logger::error (Twine (" Failed to get URI from file " + slangCanonPath));
470
+ continue ;
471
+ }
472
+
473
+ newBuffer = llvm::MemoryBuffer::getMemBufferCopy (
474
+ sourceManager->getSourceText (slangBuffer), uriOrError->file ());
475
+ newBufferId = sourceMgr.AddNewSourceBuffer (std::move (newBuffer), SMLoc ());
476
+ bufferIDMap[slangBuffer.getId ()] = newBufferId;
477
+ continue ;
478
+ }
479
+ circt::lsp::Logger::error (Twine (" Failed to add buffer ID! " ));
480
+ }
481
+
311
482
for (auto &diag : (*compilation)->getAllDiagnostics ())
312
483
driver.diagEngine .issue (diag);
313
484
@@ -322,19 +493,29 @@ VerilogDocument::getLspLocation(slang::SourceLocation loc) const {
322
493
auto line = slangSourceManager.getLineNumber (loc) - 1 ;
323
494
auto column = slangSourceManager.getColumnNumber (loc) - 1 ;
324
495
auto it = bufferIDMap.find (loc.buffer ().getId ());
325
- // Check if the current buffer is the main file.
326
496
if (it != bufferIDMap.end () && it->second == sourceMgr.getMainFileID ())
327
497
return llvm::lsp::Location (uri, llvm::lsp::Range (Position (line, column)));
328
498
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
- }
499
+ llvm::StringRef fileName = slangSourceManager.getFileName (loc);
500
+ // Ensure absolute path for LSP:
501
+ llvm::SmallString<256 > abs (fileName);
502
+ if (!llvm::sys::path::is_absolute (abs)) {
503
+ // Try realPath first
504
+ if (std::error_code ec = llvm::sys::fs::real_path (fileName, abs)) {
505
+ // Fallback: make it absolute relative to the process CWD
506
+ llvm::sys::fs::current_path (abs); // abs = CWD
507
+ llvm::sys::path::append (abs, fileName);
508
+ }
509
+ }
337
510
511
+ if (auto uriOrErr = llvm::lsp::URIForFile::fromFile (abs)) {
512
+ if (auto e = uriOrErr.takeError ())
513
+ return llvm::lsp::Location ();
514
+ return llvm::lsp::Location (*uriOrErr,
515
+ llvm::lsp::Range (Position (line, column)));
516
+ }
517
+ return llvm::lsp::Location ();
518
+ }
338
519
return llvm::lsp::Location ();
339
520
}
340
521
@@ -524,6 +705,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
524
705
return ;
525
706
assert (range.start ().valid () && range.end ().valid () &&
526
707
" range must be valid" );
708
+
709
+ // TODO: This implementation does not handle expanded MACROs. Return
710
+ // instead.
711
+ if (range.start () >= range.end ()) {
712
+ return ;
713
+ }
714
+
527
715
index.insertSymbol (symbol, range, isDefinition);
528
716
}
529
717
@@ -581,7 +769,30 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
581
769
insertSymbol (def, item->package .location (), false );
582
770
}
583
771
}
772
+ visitDefault (expr);
773
+ }
774
+
775
+ void visit (const slang::ast::InstanceSymbol &expr) {
776
+ auto *def = &expr.getDefinition ();
777
+ if (!def)
778
+ return ;
584
779
780
+ // Add the module definition
781
+ insertSymbol (def, def->location , /* isDefinition=*/ true );
782
+
783
+ // Walk up the syntax tree until we hit the type token;
784
+ // Link that token back to the instance declaration.
785
+ if (auto *hierInst =
786
+ expr.getSyntax ()
787
+ ->as_if <slang::syntax::HierarchicalInstanceSyntax>())
788
+ if (auto *modInst =
789
+ hierInst->parent
790
+ ->as_if <slang::syntax::HierarchyInstantiationSyntax>())
791
+ if (modInst->type )
792
+ insertSymbol (def, modInst->type .location (), false );
793
+
794
+ // Link the module instance name back to the module definition
795
+ insertSymbol (def, expr.location , /* isDefinition=*/ false );
585
796
visitDefault (expr);
586
797
}
587
798
@@ -608,7 +819,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
608
819
void VerilogIndex::initialize (slang::ast::Compilation &compilation) {
609
820
const auto &root = compilation.getRoot ();
610
821
VerilogIndexer visitor (*this );
822
+
611
823
for (auto *inst : root.topInstances ) {
824
+
825
+ // Skip all modules, interfaces, etc. that are not defined in this files
826
+ if (!(document.getLspLocation (inst->location ).uri == document.getURI ()))
827
+ continue ;
828
+
612
829
// Visit the body of the instance.
613
830
inst->body .visit (visitor);
614
831
@@ -780,10 +997,17 @@ void circt::lsp::VerilogServer::findReferencesOf(
780
997
void VerilogIndex::insertSymbol (const slang::ast::Symbol *symbol,
781
998
slang::SourceRange from, bool isDefinition) {
782
999
assert (from.start ().valid () && from.end ().valid ());
1000
+
1001
+ // TODO: Currently doesn't handle expanded macros
1002
+ if (!from.start ().valid () || !from.end ().valid () ||
1003
+ from.start () >= from.end ())
1004
+ return ;
1005
+
783
1006
const char *startLoc = getDocument ().getSMLoc (from.start ()).getPointer ();
784
1007
const char *endLoc = getDocument ().getSMLoc (from.end ()).getPointer () + 1 ;
785
- if (!startLoc || !endLoc)
1008
+ if (!startLoc || !endLoc || startLoc >= endLoc )
786
1009
return ;
1010
+
787
1011
assert (startLoc && endLoc);
788
1012
789
1013
if (startLoc != endLoc && !intervalMap.overlaps (startLoc, endLoc)) {
0 commit comments