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,13 +252,107 @@ 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 std::string removeFileToTemp (const std::string &cmdfileStr,
258+ const std::string &targetAbsStr) {
259+ namespace fs = std::filesystem;
260+
261+ fs::path cmdfile (cmdfileStr);
262+ fs::path targetAbs (targetAbsStr);
263+ const fs::path base = cmdfile.parent_path ();
264+ std::error_code ec;
265+
266+ // Normalize target
267+ fs::path target = fs::weakly_canonical (targetAbs, ec);
268+ if (ec) {
269+ ec.clear ();
270+ target = targetAbs.lexically_normal ();
271+ }
272+
273+ std::ifstream in (cmdfile);
274+ if (!in) {
275+ // return the original path so the caller still has a valid file to use
276+ return cmdfileStr;
277+ }
278+
279+ std::string line, outbuf;
280+ bool removed = false ;
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+ outbuf += line;
291+ outbuf.push_back (' \n ' );
292+ continue ;
293+ }
294+ // Preserve blank lines
295+ if (s.empty ()) {
296+ outbuf.push_back (' \n ' );
297+ continue ;
298+ }
299+
300+ // Treat everything else as a path (relative entries resolved vs. cmdfile
301+ // dir)
302+ fs::path candRel = s;
303+ fs::path candAbs = candRel.is_absolute () ? candRel : (base / candRel);
304+
305+ fs::path cand = fs::weakly_canonical (candAbs, ec);
306+ if (ec) {
307+ ec.clear ();
308+ cand = candAbs.lexically_normal ();
309+ }
310+
311+ if (cand == target) {
312+ removed = true ; // skip writing this line
313+ } else {
314+ outbuf += line;
315+ outbuf.push_back (' \n ' );
316+ }
317+ }
318+
319+ if (!removed) {
320+ // Nothing changed: return the original commandfile path
321+ return cmdfileStr;
322+ }
323+ // Write updated content to a temp file next to the original
324+ fs::path tmp = base / (cmdfile.filename ().string () + " .tmp" );
325+ std::ofstream out (tmp, std::ios::trunc);
326+ out << outbuf;
327+ return tmp.string ();
328+ }
329+
330+ static inline std::string removeFileToTemp (llvm::StringRef cmdfile,
331+ llvm::StringRef target_abs) {
332+ return removeFileToTemp (cmdfile.str (), target_abs.str ());
333+ }
334+
251335VerilogDocument::VerilogDocument (
252336 VerilogServerContext &context, const llvm::lsp::URIForFile &uri,
253337 StringRef contents, std::vector<llvm::lsp::Diagnostic> &diagnostics)
254338 : globalContext(context), index(*this ), uri(uri) {
255339 unsigned int bufferId;
340+
341+ llvm::SmallString<256 > canonPath (uri.file ());
342+ if (std::error_code ec = llvm::sys::fs::real_path (uri.file (), canonPath))
343+ canonPath = uri.file (); // fall back, but try to keep it absolute
344+
345+ // --- Apply project command files (the “-C”s) to this per-buffer driver ---
346+ for (const std::string &cmdFile : context.options .commandFiles ) {
347+ if (!driver.processCommandFiles (removeFileToTemp (cmdFile, canonPath), false ,
348+ true )) {
349+ circt::lsp::Logger::error (Twine (" Failed to open command file " ) +
350+ cmdFile);
351+ }
352+ }
353+
256354 if (auto memBufferOwn =
257- llvm::MemoryBuffer::getMemBufferCopy (contents, uri. file () )) {
355+ llvm::MemoryBuffer::getMemBufferCopy (contents, canonPath )) {
258356
259357 bufferId = sourceMgr.AddNewSourceBuffer (std::move (memBufferOwn), SMLoc ());
260358 } else {
@@ -304,6 +402,18 @@ VerilogDocument::VerilogDocument(
304402 return ;
305403 }
306404
405+ compilation = driver.createCompilation ();
406+ if (failed (compilation))
407+ return ;
408+
409+ std::vector<std::string> topModules;
410+ for (const auto *defs : (*compilation)->getDefinitions ())
411+ topModules.emplace_back (defs->name );
412+
413+ // Make sure that all possible definitions in the file / project scope are
414+ // topModules!
415+ driver.options .topModules = topModules;
416+
307417 compilation = driver.createCompilation ();
308418 if (failed (compilation))
309419 return ;
@@ -322,19 +432,28 @@ VerilogDocument::getLspLocation(slang::SourceLocation loc) const {
322432 auto line = slangSourceManager.getLineNumber (loc) - 1 ;
323433 auto column = slangSourceManager.getColumnNumber (loc) - 1 ;
324434 auto it = bufferIDMap.find (loc.buffer ().getId ());
325- // Check if the current buffer is the main file.
326435 if (it != bufferIDMap.end () && it->second == sourceMgr.getMainFileID ())
327436 return llvm::lsp::Location (uri, llvm::lsp::Range (Position (line, column)));
328437
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- }
438+ llvm::StringRef fileName = slangSourceManager.getFileName (loc);
439+ // Ensure absolute path for LSP:
440+ llvm::SmallString<256 > abs (fileName);
441+ if (!llvm::sys::path::is_absolute (abs)) {
442+ // Try realPath first
443+ if (std::error_code ec = llvm::sys::fs::real_path (fileName, abs)) {
444+ // Fallback: make it absolute relative to the process CWD
445+ llvm::sys::fs::current_path (abs); // abs = CWD
446+ llvm::sys::path::append (abs, fileName);
447+ }
448+ }
337449
450+ if (auto uriOrErr = llvm::lsp::URIForFile::fromFile (abs)) {
451+ return llvm::lsp::Location (*uriOrErr,
452+ llvm::lsp::Range (Position (line, column)));
453+ }
454+ // If we can't form a URI, return empty (avoids crashing)
455+ return llvm::lsp::Location ();
456+ }
338457 return llvm::lsp::Location ();
339458}
340459
@@ -358,8 +477,24 @@ llvm::SMLoc VerilogDocument::getSMLoc(slang::SourceLocation loc) {
358477 auto bufferIDMapIt = bufferIDMap.find (bufferID);
359478 if (bufferIDMapIt == bufferIDMap.end ()) {
360479 // If not, open the source file and add it to the LLVM source manager.
361- auto path = getSlangSourceManager ().getFullPath (loc.buffer ());
362- auto memBuffer = llvm::MemoryBuffer::getFile (path.string ());
480+ const slang::SourceManager &ssm = getSlangSourceManager ();
481+ auto path = ssm.getFullPath (loc.buffer ());
482+
483+ std::string name;
484+ if (path.empty ()) {
485+ // Get filename from slang source manager - might've been loaded already!
486+ llvm::StringRef fname = ssm.getFileName (loc);
487+ llvm::SmallString<256 > realPath;
488+ if (!llvm::sys::fs::real_path (fname, realPath)) {
489+ name = realPath.str ().str ();
490+ } else {
491+ name = fname.str ();
492+ }
493+ } else {
494+ name = path.string ();
495+ }
496+
497+ auto memBuffer = llvm::MemoryBuffer::getFile (name);
363498 if (!memBuffer) {
364499 circt::lsp::Logger::error (
365500 " Failed to open file: " + path.filename ().string () +
@@ -524,6 +659,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
524659 return ;
525660 assert (range.start ().valid () && range.end ().valid () &&
526661 " range must be valid" );
662+
663+ // TODO: This implementation does not handle expanded MACROs. Return
664+ // instead.
665+ if (range.start () >= range.end ()) {
666+ return ;
667+ }
668+
527669 index.insertSymbol (symbol, range, isDefinition);
528670 }
529671
@@ -581,7 +723,30 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
581723 insertSymbol (def, item->package .location (), false );
582724 }
583725 }
726+ visitDefault (expr);
727+ }
584728
729+ void visit (const slang::ast::InstanceSymbol &expr) {
730+ auto *def = &expr.getDefinition ();
731+ if (!def)
732+ return ;
733+
734+ // Add the module definition
735+ insertSymbol (def, def->location , /* isDefinition=*/ true );
736+
737+ // Walk up the syntax tree until we hit the type token;
738+ // Link that token back to the instance declaration.
739+ if (auto *hierInst =
740+ expr.getSyntax ()
741+ ->as_if <slang::syntax::HierarchicalInstanceSyntax>())
742+ if (auto *modInst =
743+ hierInst->parent
744+ ->as_if <slang::syntax::HierarchyInstantiationSyntax>())
745+ if (modInst->type )
746+ insertSymbol (def, modInst->type .location (), false );
747+
748+ // Link the module instance name back to the module definition
749+ insertSymbol (def, expr.location , /* isDefinition=*/ false );
585750 visitDefault (expr);
586751 }
587752
@@ -608,7 +773,13 @@ struct VerilogIndexer : slang::ast::ASTVisitor<VerilogIndexer, true, true> {
608773void VerilogIndex::initialize (slang::ast::Compilation &compilation) {
609774 const auto &root = compilation.getRoot ();
610775 VerilogIndexer visitor (*this );
776+
611777 for (auto *inst : root.topInstances ) {
778+
779+ // Skip all modules, interfaces, etc. that are not defined in this files
780+ if (!(document.getLspLocation (inst->location ).uri == document.getURI ()))
781+ continue ;
782+
612783 // Visit the body of the instance.
613784 inst->body .visit (visitor);
614785
@@ -780,10 +951,17 @@ void circt::lsp::VerilogServer::findReferencesOf(
780951void VerilogIndex::insertSymbol (const slang::ast::Symbol *symbol,
781952 slang::SourceRange from, bool isDefinition) {
782953 assert (from.start ().valid () && from.end ().valid ());
954+
955+ // TODO: Currently doesn't handle expanded macros
956+ if (!from.start ().valid () || !from.end ().valid () ||
957+ from.start () >= from.end ())
958+ return ;
959+
783960 const char *startLoc = getDocument ().getSMLoc (from.start ()).getPointer ();
784961 const char *endLoc = getDocument ().getSMLoc (from.end ()).getPointer () + 1 ;
785- if (!startLoc || !endLoc)
962+ if (!startLoc || !endLoc || startLoc >= endLoc )
786963 return ;
964+
787965 assert (startLoc && endLoc);
788966
789967 if (startLoc != endLoc && !intervalMap.overlaps (startLoc, endLoc)) {
0 commit comments