2323#include  " mlir/IR/MLIRContext.h" 
2424#include  " slang/ast/ASTVisitor.h" 
2525#include  " slang/ast/Compilation.h" 
26+ #include  " slang/ast/Scope.h" 
27+ #include  " slang/ast/symbols/CompilationUnitSymbols.h" 
2628#include  " slang/diagnostics/DiagnosticClient.h" 
2729#include  " slang/diagnostics/Diagnostics.h" 
2830#include  " slang/driver/Driver.h" 
4345#include  " llvm/Support/SMLoc.h" 
4446#include  " llvm/Support/SourceMgr.h" 
4547
48+ #include  < filesystem> 
49+ #include  < fstream> 
4650#include  < memory> 
4751#include  < optional> 
4852#include  < string> 
@@ -248,15 +252,87 @@ void LSPDiagnosticClient::report(const slang::ReportedDiagnostic &slangDiag) {
248252//  VerilogDocument
249253// ===----------------------------------------------------------------------===//
250254
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+ 
251310VerilogDocument::VerilogDocument (
252311    VerilogServerContext &context, const  llvm::lsp::URIForFile &uri,
253312    StringRef contents, std::vector<llvm::lsp::Diagnostic> &diagnostics)
254313    : 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+ 
256331  if  (auto  memBufferOwn =
257332          llvm::MemoryBuffer::getMemBufferCopy (contents, uri.file ())) {
258333
259-     bufferId = sourceMgr.AddNewSourceBuffer (std::move (memBufferOwn), SMLoc ());
334+     mainBufferId =
335+         sourceMgr.AddNewSourceBuffer (std::move (memBufferOwn), SMLoc ());
260336  } else  {
261337    circt::lsp::Logger::error (
262338        Twine (" Failed to create memory buffer for file "  ) + uri.file ());
@@ -273,17 +349,58 @@ VerilogDocument::VerilogDocument(
273349                 context.options .libDirs .end ());
274350
275351  //  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;
277390
278391  for  (const  auto  &libDir : libDirs) {
279392    driver.sourceLoader .addSearchDirectories (libDir);
280393  }
281394
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+ 
287404  auto  diagClient = std::make_shared<LSPDiagnosticClient>(*this , diagnostics);
288405  driver.diagEngine .addClient (diagClient);
289406
@@ -308,6 +425,50 @@ VerilogDocument::VerilogDocument(
308425  if  (failed (compilation))
309426    return ;
310427
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+ 
311472  for  (auto  &diag : (*compilation)->getAllDiagnostics ())
312473    driver.diagEngine .issue (diag);
313474
@@ -322,19 +483,29 @@ VerilogDocument::getLspLocation(slang::SourceLocation loc) const {
322483    auto  line = slangSourceManager.getLineNumber (loc) - 1 ;
323484    auto  column = slangSourceManager.getColumnNumber (loc) - 1 ;
324485    auto  it = bufferIDMap.find (loc.buffer ().getId ());
325-     //  Check if the current buffer is the main file.
326486    if  (it != bufferIDMap.end () && it->second  == sourceMgr.getMainFileID ())
327487      return  llvm::lsp::Location (uri, llvm::lsp::Range (Position (line, column)));
328488
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+     }
337500
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+   }
338509  return  llvm::lsp::Location ();
339510}
340511
@@ -353,13 +524,19 @@ VerilogDocument::getLspLocation(slang::SourceRange range) const {
353524
354525llvm::SMLoc VerilogDocument::getSMLoc (slang::SourceLocation loc) {
355526  auto  bufferID = loc.buffer ().getId ();
527+   llvm::SmallString<256 > slangCanonPath (" "  );
356528
357529  //  Check if the source is already opened by LLVM source manager.
358530  auto  bufferIDMapIt = bufferIDMap.find (bufferID);
359531  if  (bufferIDMapIt == bufferIDMap.end ()) {
360532    //  If not, open the source file and add it to the LLVM source manager.
361533    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 = llvm::sys::fs::real_path (path.string (), slangCanonPath))
537+       return  llvm::SMLoc ();
538+ 
539+     auto  memBuffer = llvm::MemoryBuffer::getFile (slangCanonPath);
363540    if  (!memBuffer) {
364541      circt::lsp::Logger::error (
365542          " Failed to open file: "   + path.filename ().string () +
@@ -524,6 +701,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
524701      return ;
525702    assert (range.start ().valid () && range.end ().valid () &&
526703           " range must be valid"  );
704+ 
705+     //  TODO: This implementation does not handle expanded MACROs. Return
706+     //  instead.
707+     if  (range.start () >= range.end ()) {
708+       return ;
709+     }
710+ 
527711    index.insertSymbol (symbol, range, isDefinition);
528712  }
529713
@@ -581,7 +765,30 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
581765        insertSymbol (def, item->package .location (), false );
582766      }
583767    }
768+     visitDefault (expr);
769+   }
584770
771+   void  visit (const  slang::ast::InstanceSymbol &expr) {
772+     auto  *def = &expr.getDefinition ();
773+     if  (!def)
774+       return ;
775+ 
776+     //  Add the module definition
777+     insertSymbol (def, def->location , /* isDefinition=*/ true );
778+ 
779+     //  Walk up the syntax tree until we hit the type token;
780+     //  Link that token back to the instance declaration.
781+     if  (auto  *hierInst =
782+             expr.getSyntax ()
783+                 ->as_if <slang::syntax::HierarchicalInstanceSyntax>())
784+       if  (auto  *modInst =
785+               hierInst->parent 
786+                   ->as_if <slang::syntax::HierarchyInstantiationSyntax>())
787+         if  (modInst->type )
788+           insertSymbol (def, modInst->type .location (), false );
789+ 
790+     //  Link the module instance name back to the module definition
791+     insertSymbol (def, expr.location , /* isDefinition=*/ false );
585792    visitDefault (expr);
586793  }
587794
@@ -608,7 +815,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
608815void  VerilogIndex::initialize (slang::ast::Compilation &compilation) {
609816  const  auto  &root = compilation.getRoot ();
610817  VerilogIndexer visitor (*this );
818+ 
611819  for  (auto  *inst : root.topInstances ) {
820+ 
821+     //  Skip all modules, interfaces, etc. that are not defined in this files
822+     if  (!(document.getLspLocation (inst->location ).uri  == document.getURI ()))
823+       continue ;
824+ 
612825    //  Visit the body of the instance.
613826    inst->body .visit (visitor);
614827
@@ -780,10 +993,17 @@ void circt::lsp::VerilogServer::findReferencesOf(
780993void  VerilogIndex::insertSymbol (const  slang::ast::Symbol *symbol,
781994                                slang::SourceRange from, bool  isDefinition) {
782995  assert (from.start ().valid () && from.end ().valid ());
996+ 
997+   //  TODO: Currently doesn't handle expanded macros
998+   if  (!from.start ().valid () || !from.end ().valid () ||
999+       from.start () >= from.end ())
1000+     return ;
1001+ 
7831002  const  char  *startLoc = getDocument ().getSMLoc (from.start ()).getPointer ();
7841003  const  char  *endLoc = getDocument ().getSMLoc (from.end ()).getPointer () + 1 ;
785-   if  (!startLoc || !endLoc)
1004+   if  (!startLoc || !endLoc || startLoc >= endLoc )
7861005    return ;
1006+ 
7871007  assert (startLoc && endLoc);
7881008
7891009  if  (startLoc != endLoc && !intervalMap.overlaps (startLoc, endLoc)) {
0 commit comments