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,98 @@ 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+ 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+
251321VerilogDocument::VerilogDocument (
252322 VerilogServerContext &context, const llvm::lsp::URIForFile &uri,
253323 StringRef contents, std::vector<llvm::lsp::Diagnostic> &diagnostics)
254324 : 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+
256342 if (auto memBufferOwn =
257- llvm::MemoryBuffer::getMemBufferCopy (contents, uri. file () )) {
343+ llvm::MemoryBuffer::getMemBufferCopy (contents, canonPath )) {
258344
259- bufferId = sourceMgr.AddNewSourceBuffer (std::move (memBufferOwn), SMLoc ());
345+ mainBufferId =
346+ sourceMgr.AddNewSourceBuffer (std::move (memBufferOwn), SMLoc ());
260347 } else {
261348 circt::lsp::Logger::error (
262349 Twine (" Failed to create memory buffer for file " ) + uri.file ());
@@ -273,17 +360,57 @@ VerilogDocument::VerilogDocument(
273360 context.options .libDirs .end ());
274361
275362 // 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;
277401
278402 for (const auto &libDir : libDirs) {
279403 driver.sourceLoader .addSearchDirectories (libDir);
280404 }
281405
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+ }
413+
287414 auto diagClient = std::make_shared<LSPDiagnosticClient>(*this , diagnostics);
288415 driver.diagEngine .addClient (diagClient);
289416
@@ -308,6 +435,38 @@ VerilogDocument::VerilogDocument(
308435 if (failed (compilation))
309436 return ;
310437
438+ // If the main buffer is present in a command file, compile it only once
439+ // and import directly from the command file; then figure out which buffer id
440+ // it was assigned and bind to llvm source manager.
441+ llvm::SmallString<256 > slangCanonPath (" " );
442+ std::string slangRawPath;
443+ std::unique_ptr<llvm::MemoryBuffer> newBuffer;
444+ uint32_t newBufferId;
445+
446+ // Iterate through all buffers in the slang compilation and set up
447+ // a binding to the LLVM Source Manager.
448+ auto *sourceManager = (**compilation).getSourceManager ();
449+ for (auto slangBuffer : sourceManager->getAllBuffers ()) {
450+ slangRawPath = sourceManager->getRawFileName (slangBuffer);
451+ if (std::error_code ec =
452+ llvm::sys::fs::real_path (slangRawPath, slangCanonPath))
453+ slangCanonPath = slangRawPath;
454+
455+ if (slangCanonPath == canonPath) {
456+ bufferIDMap[slangBuffer.getId ()] = mainBufferId;
457+ continue ;
458+ }
459+
460+ if (!bufferIDMap.contains (slangBuffer.getId ())) {
461+ newBuffer = llvm::MemoryBuffer::getMemBufferCopy (
462+ sourceManager->getSourceText (slangBuffer), slangCanonPath);
463+ newBufferId = sourceMgr.AddNewSourceBuffer (std::move (newBuffer), SMLoc ());
464+ bufferIDMap[slangBuffer.getId ()] = newBufferId;
465+ continue ;
466+ }
467+ circt::lsp::Logger::error (Twine (" Failed to add buffer ID! " ));
468+ }
469+
311470 for (auto &diag : (*compilation)->getAllDiagnostics ())
312471 driver.diagEngine .issue (diag);
313472
@@ -322,19 +481,27 @@ VerilogDocument::getLspLocation(slang::SourceLocation loc) const {
322481 auto line = slangSourceManager.getLineNumber (loc) - 1 ;
323482 auto column = slangSourceManager.getColumnNumber (loc) - 1 ;
324483 auto it = bufferIDMap.find (loc.buffer ().getId ());
325- // Check if the current buffer is the main file.
326484 if (it != bufferIDMap.end () && it->second == sourceMgr.getMainFileID ())
327485 return llvm::lsp::Location (uri, llvm::lsp::Range (Position (line, column)));
328486
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- }
487+ llvm::StringRef fileName = slangSourceManager.getFileName (loc);
488+ // Ensure absolute path for LSP:
489+ llvm::SmallString<256 > abs (fileName);
490+ if (!llvm::sys::path::is_absolute (abs)) {
491+ // Try realPath first
492+ if (std::error_code ec = llvm::sys::fs::real_path (fileName, abs)) {
493+ // Fallback: make it absolute relative to the process CWD
494+ llvm::sys::fs::current_path (abs); // abs = CWD
495+ llvm::sys::path::append (abs, fileName);
496+ }
497+ }
337498
499+ if (auto uriOrErr = llvm::lsp::URIForFile::fromFile (abs)) {
500+ return llvm::lsp::Location (*uriOrErr,
501+ llvm::lsp::Range (Position (line, column)));
502+ }
503+ return llvm::lsp::Location ();
504+ }
338505 return llvm::lsp::Location ();
339506}
340507
@@ -524,6 +691,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
524691 return ;
525692 assert (range.start ().valid () && range.end ().valid () &&
526693 " range must be valid" );
694+
695+ // TODO: This implementation does not handle expanded MACROs. Return
696+ // instead.
697+ if (range.start () >= range.end ()) {
698+ return ;
699+ }
700+
527701 index.insertSymbol (symbol, range, isDefinition);
528702 }
529703
@@ -581,7 +755,30 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
581755 insertSymbol (def, item->package .location (), false );
582756 }
583757 }
758+ visitDefault (expr);
759+ }
760+
761+ void visit (const slang::ast::InstanceSymbol &expr) {
762+ auto *def = &expr.getDefinition ();
763+ if (!def)
764+ return ;
584765
766+ // Add the module definition
767+ insertSymbol (def, def->location , /* isDefinition=*/ true );
768+
769+ // Walk up the syntax tree until we hit the type token;
770+ // Link that token back to the instance declaration.
771+ if (auto *hierInst =
772+ expr.getSyntax ()
773+ ->as_if <slang::syntax::HierarchicalInstanceSyntax>())
774+ if (auto *modInst =
775+ hierInst->parent
776+ ->as_if <slang::syntax::HierarchyInstantiationSyntax>())
777+ if (modInst->type )
778+ insertSymbol (def, modInst->type .location (), false );
779+
780+ // Link the module instance name back to the module definition
781+ insertSymbol (def, expr.location , /* isDefinition=*/ false );
585782 visitDefault (expr);
586783 }
587784
@@ -608,7 +805,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
608805void VerilogIndex::initialize (slang::ast::Compilation &compilation) {
609806 const auto &root = compilation.getRoot ();
610807 VerilogIndexer visitor (*this );
808+
611809 for (auto *inst : root.topInstances ) {
810+
811+ // Skip all modules, interfaces, etc. that are not defined in this files
812+ if (!(document.getLspLocation (inst->location ).uri == document.getURI ()))
813+ continue ;
814+
612815 // Visit the body of the instance.
613816 inst->body .visit (visitor);
614817
@@ -780,10 +983,17 @@ void circt::lsp::VerilogServer::findReferencesOf(
780983void VerilogIndex::insertSymbol (const slang::ast::Symbol *symbol,
781984 slang::SourceRange from, bool isDefinition) {
782985 assert (from.start ().valid () && from.end ().valid ());
986+
987+ // TODO: Currently doesn't handle expanded macros
988+ if (!from.start ().valid () || !from.end ().valid () ||
989+ from.start () >= from.end ())
990+ return ;
991+
783992 const char *startLoc = getDocument ().getSMLoc (from.start ()).getPointer ();
784993 const char *endLoc = getDocument ().getSMLoc (from.end ()).getPointer () + 1 ;
785- if (!startLoc || !endLoc)
994+ if (!startLoc || !endLoc || startLoc >= endLoc )
786995 return ;
996+
787997 assert (startLoc && endLoc);
788998
789999 if (startLoc != endLoc && !intervalMap.overlaps (startLoc, endLoc)) {
0 commit comments