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,20 @@ 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 =
537+ llvm::sys::fs::real_path (path.string (), slangCanonPath))
538+ return llvm::SMLoc ();
539+
540+ auto memBuffer = llvm::MemoryBuffer::getFile (slangCanonPath);
363541 if (!memBuffer) {
364542 circt::lsp::Logger::error (
365543 " Failed to open file: " + path.filename ().string () +
@@ -524,6 +702,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
524702 return ;
525703 assert (range.start ().valid () && range.end ().valid () &&
526704 " 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+
527712 index.insertSymbol (symbol, range, isDefinition);
528713 }
529714
@@ -581,7 +766,30 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
581766 insertSymbol (def, item->package .location (), false );
582767 }
583768 }
769+ visitDefault (expr);
770+ }
584771
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 );
585793 visitDefault (expr);
586794 }
587795
@@ -608,7 +816,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
608816void VerilogIndex::initialize (slang::ast::Compilation &compilation) {
609817 const auto &root = compilation.getRoot ();
610818 VerilogIndexer visitor (*this );
819+
611820 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+
612826 // Visit the body of the instance.
613827 inst->body .visit (visitor);
614828
@@ -780,10 +994,17 @@ void circt::lsp::VerilogServer::findReferencesOf(
780994void VerilogIndex::insertSymbol (const slang::ast::Symbol *symbol,
781995 slang::SourceRange from, bool isDefinition) {
782996 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+
7831003 const char *startLoc = getDocument ().getSMLoc (from.start ()).getPointer ();
7841004 const char *endLoc = getDocument ().getSMLoc (from.end ()).getPointer () + 1 ;
785- if (!startLoc || !endLoc)
1005+ if (!startLoc || !endLoc || startLoc >= endLoc )
7861006 return ;
1007+
7871008 assert (startLoc && endLoc);
7881009
7891010 if (startLoc != endLoc && !intervalMap.overlaps (startLoc, endLoc)) {
0 commit comments