Skip to content

Commit e45c7f8

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. - **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 e45c7f8

File tree

8 files changed

+526
-19
lines changed

8 files changed

+526
-19
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: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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/diagnostics/Diagnostics.h"
22+
#include "slang/syntax/AllSyntax.h"
23+
#include "slang/syntax/SyntaxTree.h"
24+
#include "slang/text/SourceLocation.h"
25+
#include "slang/text/SourceManager.h"
26+
#include "llvm/Support/LSP/Protocol.h"
27+
#include "llvm/Support/SourceMgr.h"
28+
29+
using namespace slang::syntax;
30+
31+
static bool isLocationInRange(slang::SourceRange r, slang::SourceLocation loc) {
32+
if (!r.start() || !r.end())
33+
return false;
34+
auto o = loc.offset();
35+
return r.start().offset() <= o && o < r.end().offset();
36+
}
37+
38+
slang::SourceLocation
39+
SlangProjectLookup::lspPositionToSlangLocation(slang::BufferID buf,
40+
const llvm::lsp::Position &pos) {
41+
std::string_view text = sourceManager.getSourceText(buf);
42+
size_t line = (size_t)pos.line, col = (size_t)pos.character;
43+
44+
size_t off = 0;
45+
for (size_t i = 0; i < line && off < text.size(); ++i) {
46+
size_t nl = text.find('\n', off);
47+
if (nl == std::string_view::npos) {
48+
off = text.size();
49+
break;
50+
}
51+
off = nl + 1;
52+
}
53+
size_t eol = text.find('\n', off);
54+
if (eol == std::string_view::npos)
55+
eol = text.size();
56+
size_t len =
57+
(eol > off && text[eol - 1] == '\r') ? (eol - off - 1) : (eol - off);
58+
if (col > len)
59+
col = len;
60+
return slang::SourceLocation(buf, off + col);
61+
}
62+
63+
const SyntaxNode *
64+
SlangProjectLookup::findSmallestNodeAt(const SyntaxNode &root,
65+
slang::SourceLocation loc) {
66+
67+
if (!isLocationInRange(root.sourceRange(), loc))
68+
return nullptr;
69+
70+
const SyntaxNode *cur = &root;
71+
72+
while (true) {
73+
const SyntaxNode *bestChild = nullptr;
74+
uint32_t bestSpan = std::numeric_limits<uint32_t>::max();
75+
76+
// Scan all children; pick the *tightest* range that still contains 'loc'.
77+
for (size_t i = 0, e = cur->getChildCount(); i < e; ++i) {
78+
const SyntaxNode *ch = cur->childNode(i);
79+
if (!ch)
80+
continue;
81+
82+
auto r = ch->sourceRange();
83+
if (!r.start() || !r.end())
84+
continue;
85+
if (!isLocationInRange(r, loc))
86+
continue;
87+
88+
uint32_t span = r.end().offset() - r.start().offset();
89+
if (span < bestSpan) {
90+
bestSpan = span;
91+
bestChild = ch;
92+
}
93+
}
94+
95+
if (!bestChild)
96+
return cur; // no deeper child contains 'loc' => 'cur' is the smallest
97+
98+
cur = bestChild;
99+
}
100+
}
101+
102+
llvm::FailureOr<const SyntaxNode *>
103+
SlangProjectLookup::getSyntaxNodeAt(slang::BufferID mainBufferId,
104+
slang::SourceLocation loc) {
105+
106+
// Find the syntax tree that owns this buffer.
107+
const SyntaxTree *tree = nullptr;
108+
for (const auto &t : compilation.getSyntaxTrees()) {
109+
auto r = t->root().sourceRange();
110+
if (r.start().buffer() == mainBufferId) {
111+
tree = t.get();
112+
break;
113+
}
114+
}
115+
116+
if (!tree)
117+
return llvm::failure();
118+
119+
const auto &root = tree->root();
120+
// Climb down the tree to get the smallest node matching our location
121+
return findSmallestNodeAt(root, loc);
122+
}
123+
124+
static bool looksLikeDefNameContext(const SyntaxNode *n) {
125+
for (auto *p = n ? n->parent : nullptr; p; p = p->parent) {
126+
switch (p->kind) {
127+
// HierarchyInstantiation corresponds to a module, interface or class
128+
// instance.
129+
case SyntaxKind::HierarchyInstantiation:
130+
// Declaration headers
131+
case SyntaxKind::ModuleDeclaration:
132+
case SyntaxKind::InterfaceDeclaration:
133+
case SyntaxKind::ProgramDeclaration:
134+
return true;
135+
136+
default:
137+
break;
138+
}
139+
}
140+
return false;
141+
}
142+
143+
llvm::FailureOr<const slang::SourceLocation>
144+
SlangProjectLookup::tryFindDefinitionAt(slang::BufferID mainBufferId,
145+
slang::SourceLocation loc) {
146+
147+
const SyntaxNode *node;
148+
llvm::FailureOr<const SyntaxNode *> n = getSyntaxNodeAt(mainBufferId, loc);
149+
if (failed(n))
150+
return llvm::failure();
151+
node = n.value();
152+
153+
// See if this node "looks like" an instance or declaration -> global lookup
154+
if (looksLikeDefNameContext(node)) {
155+
if (const auto *hierInst = node->as_if<HierarchyInstantiationSyntax>()) {
156+
auto result =
157+
compilation.tryGetDefinition(hierInst->type.valueText(), rootSymbol);
158+
if (!result.definition)
159+
llvm::dbgs() << "No result definition :(\n";
160+
else {
161+
return result.definition->location;
162+
}
163+
}
164+
}
165+
166+
// See if this node is a name of an instance -> global lookup
167+
if (const auto *instNode = node->as_if<InstanceNameSyntax>()) {
168+
llvm::dbgs() << "Can interpret as name of an instance! "
169+
<< instNode->name.toString();
170+
return llvm::failure();
171+
}
172+
173+
// See if this node "looks like" a name -> local lookup
174+
for (const SyntaxNode *cur = node; cur; cur = cur->parent) {
175+
if (auto *s = cur->as_if<NameSyntax>()) {
176+
// TODO: Lookup name in local scope
177+
return llvm::failure();
178+
}
179+
}
180+
181+
// Don't really know what this node is :/
182+
return llvm::failure();
183+
}

0 commit comments

Comments
 (0)