Skip to content

Commit 9b82fcf

Browse files
committed
[circt-verilog-lsp] Add -C option command files, gotoDefinition
This patch adds **"textDocument/definition"** support to the Verilog LSP server using Slang. Currently only project-level module definition lookups work, but helpers are provided to add buffer-local lookups as well. - **New `SlangProjectLookup`** - Map LSP positions => Slang `SourceLocation` - Walk syntax tree to find the smallest node - Resolve definitions via `Compilation::tryFindDefinitionAt` - **Server integration** - Advertise `"definitionProvider": true` - Handle `"textDocument/definition"` requests - Return single `Location` or `null` - **Command file support** - Add `-C/--commandFiles` option - Preload command files into per-buffer drivers - **Other changes** - Absolute path normalization for URIs and command files - New `getDefinitionAt` in `VerilogDocument` - `VerilogServer::findDefinition` API
1 parent ffd1d5e commit 9b82fcf

File tree

9 files changed

+616
-20
lines changed

9 files changed

+616
-20
lines changed

include/circt/Tools/circt-verilog-lsp-server/CirctVerilogLspServerMain.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ class JSONTransport;
3434
namespace circt {
3535
namespace lsp {
3636
struct VerilogServerOptions {
37-
VerilogServerOptions(const std::vector<std::string> &libDirs)
38-
: libDirs(libDirs) {}
39-
/// Additional list of RTL directories to search.
37+
VerilogServerOptions(const std::vector<std::string> &libDirs,
38+
std::vector<std::string> &commandFiles)
39+
: libDirs(libDirs), commandFiles(commandFiles) {}
4040
const std::vector<std::string> &libDirs;
41+
const std::vector<std::string> &commandFiles;
4142
};
4243
// namespace lsp
4344

lib/Tools/circt-verilog-lsp-server/LSPServer.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "LSPServer.h"
1010
#include "VerilogServerImpl/VerilogServer.h"
11+
#include "llvm/Support/JSON.h"
1112
#include "llvm/Support/LSP/Protocol.h"
1213
#include "llvm/Support/LSP/Transport.h"
1314
#include <optional>
@@ -43,6 +44,13 @@ struct LSPServer {
4344
void onDocumentDidClose(const DidCloseTextDocumentParams &params);
4445
void onDocumentDidChange(const DidChangeTextDocumentParams &params);
4546

47+
//===--------------------------------------------------------------------===//
48+
// Definition
49+
//===--------------------------------------------------------------------===//
50+
51+
void onDocumentDefinition(const TextDocumentPositionParams &params,
52+
Callback<llvm::json::Value> reply);
53+
4654
//===--------------------------------------------------------------------===//
4755
// Fields
4856
//===--------------------------------------------------------------------===//
@@ -73,7 +81,8 @@ void LSPServer::onInitialize(const InitializeParams &params,
7381
{"openClose", true},
7482
{"change", (int)TextDocumentSyncKind::Incremental},
7583
{"save", true},
76-
}}};
84+
}},
85+
{"definitionProvider", true}};
7786

7887
json::Object result{
7988
{{"serverInfo", json::Object{{"name", "circt-verilog-lsp-server"},
@@ -124,6 +133,21 @@ void LSPServer::onDocumentDidChange(const DidChangeTextDocumentParams &params) {
124133
publishDiagnostics(diagParams);
125134
}
126135

136+
//===----------------------------------------------------------------------===//
137+
// Definition
138+
//===----------------------------------------------------------------------===//
139+
140+
void LSPServer::onDocumentDefinition(
141+
const llvm::lsp::TextDocumentPositionParams &params,
142+
Callback<llvm::json::Value> reply) {
143+
auto loc = server.findDefinition(params.textDocument.uri, params.position);
144+
if (loc) {
145+
reply(*loc); // return a single Location
146+
} else {
147+
reply(llvm::json::Value(nullptr));
148+
}
149+
}
150+
127151
//===----------------------------------------------------------------------===//
128152
// Entry Point
129153
//===----------------------------------------------------------------------===//
@@ -147,6 +171,10 @@ LogicalResult circt::lsp::runVerilogLSPServer(VerilogServer &server,
147171
messageHandler.notification("textDocument/didChange", &lspServer,
148172
&LSPServer::onDocumentDidChange);
149173

174+
// Definition
175+
messageHandler.method("textDocument/definition", &lspServer,
176+
&LSPServer::onDocumentDefinition);
177+
150178
// Diagnostics
151179
lspServer.publishDiagnostics =
152180
messageHandler.outgoingNotification<PublishDiagnosticsParams>(

lib/Tools/circt-verilog-lsp-server/VerilogServerImpl/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
include(SlangCompilerOptions)
33

44
add_circt_library(CIRCTVerilogLspServerImpl
5+
SlangProjectLookup.cpp
56
VerilogServer.cpp
67

78
ADDITIONAL_HEADER_DIRS
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file implements the SlangProjectLookup class, which is responsible
10+
// for trying to resolve definitions within one compile unit, as specified
11+
// by the `-C` argument.
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
#include "SlangProjectLookup.h"
16+
17+
#include "slang/ast/Compilation.h"
18+
#include "slang/ast/Lookup.h"
19+
#include "slang/ast/Scope.h"
20+
#include "slang/ast/Symbol.h"
21+
#include "slang/ast/Scope.h"
22+
#include "slang/ast/symbols/CompilationUnitSymbols.h"
23+
#include "slang/diagnostics/Diagnostics.h"
24+
#include "slang/syntax/AllSyntax.h"
25+
#include "slang/syntax/SyntaxFacts.h"
26+
#include "slang/syntax/SyntaxKind.h"
27+
#include "slang/syntax/SyntaxNode.h"
28+
#include "slang/syntax/SyntaxTree.h"
29+
#include "slang/syntax/SyntaxVisitor.h"
30+
#include "slang/text/SourceLocation.h"
31+
#include "slang/text/SourceManager.h"
32+
#include "llvm/Support/LSP/Protocol.h"
33+
34+
using namespace slang;
35+
using namespace slang::syntax;
36+
using namespace slang::ast;
37+
38+
struct DefIndex {
39+
// For now: name -> location (first hit wins). Upgrade to multimap if needed.
40+
std::unordered_map<std::string, slang::SourceLocation> byName;
41+
};
42+
43+
static void indexDeclarators(const slang::syntax::SyntaxNode &node,
44+
DefIndex &out) {
45+
using namespace slang::syntax;
46+
47+
switch (node.kind) {
48+
case SyntaxKind::Declarator:
49+
if (auto *decl = node.as_if<DeclaratorSyntax>()) {
50+
auto nameText = decl->name.valueText(); // strips trivia
51+
auto loc = decl->name.location();
52+
if (!nameText.empty() && loc)
53+
out.byName.emplace(std::string(nameText), loc);
54+
}
55+
break;
56+
default:
57+
break;
58+
}
59+
}
60+
61+
/// Walk the syntax tree from its root down to collect all declarators;
62+
/// declarators contain symbol definitions we can lookup later.
63+
static void walk(const slang::syntax::SyntaxNode &root, DefIndex &out) {
64+
llvm::SmallVector<const slang::syntax::SyntaxNode *, 32> worklist;
65+
worklist.push_back(&root);
66+
67+
while (!worklist.empty()) {
68+
const slang::syntax::SyntaxNode *node = worklist.pop_back_val();
69+
70+
// Collect declarators at this node.
71+
indexDeclarators(*node, out);
72+
73+
// Push children to the stack.
74+
for (size_t i = 0, e = node->getChildCount(); i < e; ++i) {
75+
if (auto *c = node->childNode(i))
76+
worklist.push_back(c);
77+
}
78+
}
79+
}
80+
81+
static bool isLocationInRange(slang::SourceRange r, slang::SourceLocation loc) {
82+
if (!r.start() || !r.end())
83+
return false;
84+
auto o = loc.offset();
85+
return r.start().offset() <= o && o < r.end().offset();
86+
}
87+
88+
namespace {
89+
/// Visitor that tries to produce a definition `SourceLocation` for a node.
90+
struct DefinitionVisitor : public slang::syntax::SyntaxVisitor<
91+
std::optional<slang::SourceLocation>> {
92+
using Base = SyntaxVisitor<std::optional<slang::SourceLocation>>;
93+
using Base::visit;
94+
95+
DefinitionVisitor(const slang::ast::Compilation &comp,
96+
const slang::ast::Scope &rootScope,
97+
slang::SourceLocation queryLoc, const DefIndex &defIndex)
98+
: comp(comp), rootScope(rootScope), queryLoc(queryLoc),
99+
defIndex(defIndex) {}
100+
101+
// Hierarchy instantiation -> resolve the *type* globally.
102+
std::optional<slang::SourceLocation>
103+
visit(const HierarchyInstantiationSyntax &n) {
104+
auto res = comp.tryGetDefinition(n.type.valueText(), rootScope);
105+
if (res.definition)
106+
return res.definition->location;
107+
return std::nullopt;
108+
}
109+
110+
// Declaration name (e.g., "logic x;") — treat cursor as the def.
111+
std::optional<slang::SourceLocation> visit(const DeclaratorSyntax &) {
112+
return queryLoc;
113+
}
114+
115+
// Module header (cursor on module name) — treat cursor as the def.
116+
std::optional<slang::SourceLocation> visit(const ModuleHeaderSyntax &) {
117+
return queryLoc;
118+
}
119+
120+
// Instance name (u0) — TODO (not implemented).
121+
std::optional<slang::SourceLocation> visit(const InstanceNameSyntax &) {
122+
return std::nullopt;
123+
}
124+
125+
// Generic name — TODO: local scope lookup.
126+
std::optional<slang::SourceLocation> visit(const IdentifierNameSyntax &node) {
127+
auto it = defIndex.byName.find(std::string(node.identifier.valueText()));
128+
if (it == defIndex.byName.end())
129+
return std::nullopt;
130+
return it->second;
131+
}
132+
133+
// Fallback for all other node kinds.
134+
template <typename T>
135+
std::optional<slang::SourceLocation> visit(const T &) {
136+
return std::nullopt;
137+
}
138+
139+
const slang::ast::Compilation &comp;
140+
const slang::ast::Scope &rootScope;
141+
slang::SourceLocation queryLoc;
142+
const DefIndex &defIndex;
143+
};
144+
145+
} // namespace
146+
147+
slang::SourceLocation
148+
SlangProjectLookup::lspPositionToSlangLocation(slang::BufferID buf,
149+
const llvm::lsp::Position &pos) {
150+
std::string_view text = sourceManager.getSourceText(buf);
151+
size_t line = (size_t)pos.line, col = (size_t)pos.character;
152+
153+
size_t off = 0;
154+
for (size_t i = 0; i < line && off < text.size(); ++i) {
155+
size_t nl = text.find('\n', off);
156+
if (nl == std::string_view::npos) {
157+
off = text.size();
158+
break;
159+
}
160+
off = nl + 1;
161+
}
162+
size_t eol = text.find('\n', off);
163+
if (eol == std::string_view::npos)
164+
eol = text.size();
165+
size_t len =
166+
(eol > off && text[eol - 1] == '\r') ? (eol - off - 1) : (eol - off);
167+
if (col > len)
168+
col = len;
169+
return slang::SourceLocation(buf, off + col);
170+
}
171+
172+
const SyntaxNode *
173+
SlangProjectLookup::findSmallestNodeAt(const SyntaxNode &root,
174+
slang::SourceLocation loc) {
175+
176+
if (!isLocationInRange(root.sourceRange(), loc))
177+
return nullptr;
178+
179+
const SyntaxNode *cur = &root;
180+
181+
while (true) {
182+
const SyntaxNode *bestChild = nullptr;
183+
uint32_t bestSpan = std::numeric_limits<uint32_t>::max();
184+
185+
// Scan all children; pick the *tightest* range that still contains 'loc'.
186+
for (size_t i = 0, e = cur->getChildCount(); i < e; ++i) {
187+
const SyntaxNode *ch = cur->childNode(i);
188+
if (!ch)
189+
continue;
190+
191+
auto r = ch->sourceRange();
192+
if (!r.start() || !r.end())
193+
continue;
194+
if (!isLocationInRange(r, loc))
195+
continue;
196+
197+
uint32_t span = r.end().offset() - r.start().offset();
198+
if (span < bestSpan) {
199+
bestSpan = span;
200+
bestChild = ch;
201+
}
202+
}
203+
204+
if (!bestChild)
205+
return cur; // no deeper child contains 'loc' => 'cur' is the smallest
206+
207+
cur = bestChild;
208+
}
209+
}
210+
211+
llvm::FailureOr<const SyntaxNode *>
212+
SlangProjectLookup::getSyntaxNodeAt(slang::BufferID mainBufferId,
213+
slang::SourceLocation loc) {
214+
215+
// Find the syntax tree that owns this buffer.
216+
const SyntaxTree *tree = nullptr;
217+
for (const auto &t : compilation.getSyntaxTrees()) {
218+
auto r = t->root().sourceRange();
219+
if (r.start().buffer() == mainBufferId) {
220+
tree = t.get();
221+
break;
222+
}
223+
}
224+
225+
if (!tree)
226+
return llvm::failure();
227+
228+
const auto &root = tree->root();
229+
// Climb down the tree to get the smallest node matching our location
230+
return findSmallestNodeAt(root, loc);
231+
}
232+
233+
llvm::FailureOr<const slang::SourceLocation>
234+
SlangProjectLookup::tryFindDefinitionAt(slang::BufferID mainBufferId,
235+
slang::SourceLocation loc) {
236+
237+
const SyntaxNode *node;
238+
llvm::FailureOr<const SyntaxNode *> n = getSyntaxNodeAt(mainBufferId, loc);
239+
240+
if (failed(n))
241+
return llvm::failure();
242+
node = n.value();
243+
244+
// Find the root (topmost) syntax node of this tree.
245+
const SyntaxNode *root = node;
246+
for (const SyntaxNode *cur = node; cur; cur = cur->parent)
247+
root = cur; // root ends up as the last non-null
248+
249+
DefIndex d;
250+
walk(*root, d);
251+
252+
DefinitionVisitor v{compilation, rootSymbol, loc, d};
253+
254+
// Try at the node first…
255+
if (auto hit = node->visit(v))
256+
return *hit;
257+
258+
// …then walk up parents to broaden the context.
259+
for (const SyntaxNode *p = node->parent; p; p = p->parent) {
260+
if (auto hit = p->visit(v))
261+
return *hit;
262+
}
263+
264+
// Don't really know what this node is :/
265+
return llvm::failure();
266+
}

0 commit comments

Comments
 (0)