[llvm] [llvm-lsp] LSP server for LLVM IR (PR #161969)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Jul 19 05:57:52 PDT 2026
Albert =?utf-8?q?Havliček?= <havlialb at fit.cvut.cz>,
Albert =?utf-8?q?Havliček?= <havlialb at fit.cvut.cz>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/161969 at github.com>
https://github.com/Bertik23 updated https://github.com/llvm/llvm-project/pull/161969
>From c992e454ec5221c57fe471c2e9bc42994ecda2bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Albert=20Havli=C4=8Dek?= <havlialb at fit.cvut.cz>
Date: Sun, 19 Jul 2026 14:42:10 +0200
Subject: [PATCH 1/3] [llvm-lsp] Barebones LSP server
---
llvm/tools/llvm-lsp/CMakeLists.txt | 14 +++
llvm/tools/llvm-lsp/LLVMBuild.txt | 29 ++++++
llvm/tools/llvm-lsp/llvm-lsp-server.cpp | 115 ++++++++++++++++++++++++
llvm/tools/llvm-lsp/llvm-lsp-server.h | 64 +++++++++++++
4 files changed, 222 insertions(+)
create mode 100644 llvm/tools/llvm-lsp/CMakeLists.txt
create mode 100644 llvm/tools/llvm-lsp/LLVMBuild.txt
create mode 100755 llvm/tools/llvm-lsp/llvm-lsp-server.cpp
create mode 100644 llvm/tools/llvm-lsp/llvm-lsp-server.h
diff --git a/llvm/tools/llvm-lsp/CMakeLists.txt b/llvm/tools/llvm-lsp/CMakeLists.txt
new file mode 100644
index 0000000000000..0faba540879ce
--- /dev/null
+++ b/llvm/tools/llvm-lsp/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(LLVM_LINK_COMPONENTS
+ Core
+ IRReader
+ Support
+ Analysis
+ Passes
+ SupportLSP
+ TransformUtils
+ AsmParser
+)
+
+add_llvm_tool(llvm-lsp-server
+ llvm-lsp-server.cpp
+)
diff --git a/llvm/tools/llvm-lsp/LLVMBuild.txt b/llvm/tools/llvm-lsp/LLVMBuild.txt
new file mode 100644
index 0000000000000..c29d0099b6b07
--- /dev/null
+++ b/llvm/tools/llvm-lsp/LLVMBuild.txt
@@ -0,0 +1,29 @@
+;===- ./tools/llvm-lsp/LLVMBuild.txt -------------------------*- Conf -*--===;
+;
+; Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+; See https://llvm.org/LICENSE.txt for license information.
+; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+;
+;===------------------------------------------------------------------------===;
+;
+; This is an LLVMBuild description file for the components in this subdirectory.
+;
+; For more information on the LLVMBuild system, please see:
+;
+; http://llvm.org/docs/LLVMBuild.html
+;
+;===------------------------------------------------------------------------===;
+
+[component_0]
+type = Tool
+name = llvm-lsp-server
+parent = Tools
+required_libraries =
+ Core
+ IRReader
+ Support
+ Analysis
+ Passes
+ SupportLSP
+ TransformUtils
+ AsmParser
diff --git a/llvm/tools/llvm-lsp/llvm-lsp-server.cpp b/llvm/tools/llvm-lsp/llvm-lsp-server.cpp
new file mode 100755
index 0000000000000..e917b8f80fab4
--- /dev/null
+++ b/llvm/tools/llvm-lsp/llvm-lsp-server.cpp
@@ -0,0 +1,115 @@
+//===-- llvm-lsp-server.cpp -----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/IR/BasicBlock.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/LSP/Logging.h"
+#include "llvm/Support/Program.h"
+
+#include "llvm-lsp-server.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+
+using namespace llvm;
+
+static cl::OptionCategory LlvmLspServerCategory("llvm-lsp-server options");
+
+static cl::opt<lsp::Logger::Level> LogLevel(
+ "log-level", cl::desc("Log level"), cl::init(lsp::Logger::Level::Info),
+ cl::values(clEnumValN(lsp::Logger::Level::Info, "info", "Info"),
+ clEnumValN(lsp::Logger::Level::Debug, "debug", "Debug"),
+ clEnumValN(lsp::Logger::Level::Error, "error", "Error")),
+ cl::cat(LlvmLspServerCategory));
+
+llvm::Error LspServer::run() {
+ registerMessageHandlers();
+ return Transport.run(MessageHandler);
+}
+
+void LspServer::sendInfo(const std::string &Message) {
+ ShowMessageSender(lsp::ShowMessageParams(lsp::MessageType::Info, Message));
+}
+
+void LspServer::sendError(const std::string &Message) {
+ ShowMessageSender(lsp::ShowMessageParams(lsp::MessageType::Error, Message));
+}
+
+void LspServer::handleRequestInitialize(
+ const lsp::InitializeParams &Params,
+ lsp::Callback<llvm::json::Value> Reply) {
+
+ // clang-format off
+ json::Object ResponseParams{
+ {"capabilities",
+ json::Object{
+ {"textDocumentSync",
+ json::Object{
+ {"openClose", true},
+ {"change", 0}, // We dont want to sync the documents.
+ }
+ },
+ {"referencesProvider", true},
+ {"documentSymbolProvider", true},
+ }
+ }
+ };
+ // clang-format on
+ Reply(json::Value(std::move(ResponseParams)));
+}
+
+void LspServer::handleRequestShutdown(const lsp::NoParams &Params,
+ lsp::Callback<std::nullptr_t> Reply) {
+ // Do cleanup if needed
+ ShutDownRequested = true;
+ Reply(nullptr);
+}
+
+
+bool LspServer::registerMessageHandlers() {
+ MessageHandler.method("initialize", this,
+ &LspServer::handleRequestInitialize);
+
+ // Handle recieving messages
+ MessageHandler.notification(
+ "textDocument/didOpen", this,
+ &LspServer::handleNotificationTextDocumentDidOpen);
+ MessageHandler.method("textDocument/references", this,
+ &LspServer::handleRequestGetReferences);
+ MessageHandler.method("textDocument/documentSymbol", this,
+ &LspServer::handleRequestTextDocumentDocumentSymbol);
+
+ // Setup posting of messages
+ ShowMessageSender =
+ MessageHandler.outgoingNotification<lsp::ShowMessageParams>(
+ "window/showMessage");
+
+ // Return true to indicate handlers were registered successfully
+ return true;
+}
+
+int main(int argc, char **argv) {
+ cl::HideUnrelatedOptions(LlvmLspServerCategory);
+ cl::ParseCommandLineOptions(argc, argv, "LLVM LSP Language Server");
+
+ llvm::sys::ChangeStdinToBinary();
+ lsp::JSONTransport Transport(stdin, llvm::outs());
+
+ LspServer LS(Transport);
+
+ lsp::Logger::setLogLevel(LogLevel);
+
+ auto LSResult = LS.run();
+ if (!LSResult)
+ lsp::Logger::error("Error while running Language Server: {}", LSResult);
+
+ return LS.getExitCode();
+}
diff --git a/llvm/tools/llvm-lsp/llvm-lsp-server.h b/llvm/tools/llvm-lsp/llvm-lsp-server.h
new file mode 100644
index 0000000000000..89c5a43e1fa72
--- /dev/null
+++ b/llvm/tools/llvm-lsp/llvm-lsp-server.h
@@ -0,0 +1,64 @@
+//===-- llvm-lsp-server.h ---------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TOOLS_LLVM_LSP_SERVER_H
+#define LLVM_TOOLS_LLVM_LSP_SERVER_H
+
+#include <sstream>
+
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/LSP/Protocol.h"
+#include "llvm/Support/LSP/Transport.h"
+
+namespace llvm {
+
+class LspServer {
+ lsp::MessageHandler MessageHandler;
+
+ bool ShutDownRequested = false;
+
+public:
+ LspServer(lsp::JSONTransport &Transport)
+ : MessageHandler(Transport), Transport(Transport) {
+ lsp::Logger::info("Starting LLVM LSP Server");
+ }
+
+ // Runs LSP server
+ llvm::Error run();
+
+ // Sends a message to client as INFO notification
+ void sendInfo(const std::string &Message);
+
+ // Sends a message to client as ERROR notification
+ void sendError(const std::string &Message);
+
+ // The process exit code, should be success only if the State is Exitted
+ int getExitCode() { return 1 - ShutDownRequested; }
+
+private:
+ // ---------- Functions to handle various RPC calls -----------------------
+
+ // initialize
+ void handleRequestInitialize(const lsp::InitializeParams &Params,
+ lsp::Callback<llvm::json::Value> Reply);
+
+ // shutdown
+ void handleRequestShutdown(const lsp::NoParams &Params,
+ lsp::Callback<std::nullptr_t> Reply);
+
+ // Identifies RPC Call and dispatches the handling to other methods
+ bool registerMessageHandlers();
+
+ lsp::OutgoingNotification<lsp::ShowMessageParams> ShowMessageSender;
+
+ lsp::JSONTransport &Transport;
+};
+
+} // namespace llvm
+
+#endif // LLVM_TOOLS_LLVM_LSP_SERVER_H
>From 01949bfa75b658275ed638fbe0756dfce977cdb2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Albert=20Havli=C4=8Dek?= <havlialb at fit.cvut.cz>
Date: Sun, 19 Jul 2026 14:43:57 +0200
Subject: [PATCH 2/3] [llvm-lsp] Add simple LSP features
---
llvm/tools/llvm-lsp/IRDocument.h | 73 ++++++++++++++++
llvm/tools/llvm-lsp/llvm-lsp-server.cpp | 111 ++++++++++++++++++++++++
llvm/tools/llvm-lsp/llvm-lsp-server.h | 17 ++++
3 files changed, 201 insertions(+)
create mode 100644 llvm/tools/llvm-lsp/IRDocument.h
diff --git a/llvm/tools/llvm-lsp/IRDocument.h b/llvm/tools/llvm-lsp/IRDocument.h
new file mode 100644
index 0000000000000..f4f35e29ce98b
--- /dev/null
+++ b/llvm/tools/llvm-lsp/IRDocument.h
@@ -0,0 +1,73 @@
+//===-- IRDocument.h --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TOOLS_LLVM_LSP_IRDOCUMENT_H
+#define LLVM_TOOLS_LLVM_LSP_IRDOCUMENT_H
+
+#include "llvm/Analysis/BlockFrequencyInfo.h"
+#include "llvm/Analysis/BranchProbabilityInfo.h"
+#include "llvm/AsmParser/AsmParserContext.h"
+#include "llvm/IR/BasicBlock.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/IRReader/IRReader.h"
+#include "llvm/Passes/PassBuilder.h"
+#include "llvm/Support/LSP/Logging.h"
+#include "llvm/Support/SourceMgr.h"
+
+#include <memory>
+
+namespace llvm {
+// Tracks and Manages the Cache of all Artifacts for a given IR.
+// LSP Server will use this class to query details about the IR file.
+class IRDocument {
+ LLVMContext C;
+ std::unique_ptr<Module> ParsedModule;
+ StringRef Filepath;
+
+public:
+ IRDocument(StringRef PathToIRFile) : Filepath(PathToIRFile) {
+ ParsedModule = loadModuleFromIR(PathToIRFile, C);
+
+ lsp::Logger::info("Finished setting up IR Document: {}",
+ PathToIRFile.str());
+ }
+
+ // ---------------- APIs that the Language Server can use -----------------
+
+ auto &getFunctions() { return ParsedModule->getFunctionList(); }
+
+ Instruction *getInstructionAtLocation(unsigned Line, unsigned Col) {
+ FileLoc FL(Line, Col);
+ if (auto *MaybeI = dyn_cast<Instruction>(
+ ParserContext.getInstructionOrArgumentAtLocation(FL)))
+ return MaybeI;
+ return nullptr;
+ }
+
+ AsmParserContext ParserContext;
+
+private:
+ std::unique_ptr<Module> loadModuleFromIR(StringRef Filepath, LLVMContext &C) {
+ SMDiagnostic Err;
+ // Try to parse as textual IR
+ auto M = parseIRFile(Filepath, Err, C, {}, &ParserContext);
+ if (!M) {
+ // If parsing failed, print the error and crash
+ lsp::Logger::error("Failed parsing IR file: {}", Err.getMessage().str());
+ return nullptr;
+ }
+ return M;
+ }
+};
+
+} // namespace llvm
+
+#endif // LLVM_TOOLS_LLVM_LSP_IRDOCUMENT_H
diff --git a/llvm/tools/llvm-lsp/llvm-lsp-server.cpp b/llvm/tools/llvm-lsp/llvm-lsp-server.cpp
index e917b8f80fab4..549957f3b890a 100755
--- a/llvm/tools/llvm-lsp/llvm-lsp-server.cpp
+++ b/llvm/tools/llvm-lsp/llvm-lsp-server.cpp
@@ -14,6 +14,7 @@
#include "llvm/Support/LSP/Logging.h"
#include "llvm/Support/Program.h"
+#include "IRDocument.h"
#include "llvm-lsp-server.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
@@ -30,6 +31,15 @@ static cl::opt<lsp::Logger::Level> LogLevel(
clEnumValN(lsp::Logger::Level::Error, "error", "Error")),
cl::cat(LlvmLspServerCategory));
+static lsp::Position llvmFileLocToLspPosition(const FileLoc &Pos) {
+ return lsp::Position(Pos.Line, Pos.Col);
+}
+
+static lsp::Range llvmFileLocRangeToLspRange(const FileLocRange &Range) {
+ return lsp::Range(llvmFileLocToLspPosition(Range.Start),
+ llvmFileLocToLspPosition(Range.End));
+}
+
llvm::Error LspServer::run() {
registerMessageHandlers();
return Transport.run(MessageHandler);
@@ -73,6 +83,107 @@ void LspServer::handleRequestShutdown(const lsp::NoParams &Params,
Reply(nullptr);
}
+void LspServer::handleNotificationTextDocumentDidOpen(
+ const lsp::DidOpenTextDocumentParams &Params) {
+ StringRef Filepath = Params.textDocument.uri.file();
+
+ // Prepare IRDocument for Queries
+ lsp::Logger::info("Creating IRDocument for {}", Filepath.str());
+ OpenDocuments[Filepath.str()] = std::make_unique<IRDocument>(Filepath.str());
+}
+
+void LspServer::handleRequestGetReferences(
+ const lsp::ReferenceParams &Params,
+ lsp::Callback<std::vector<lsp::Location>> Reply) {
+ auto Filepath = Params.textDocument.uri.file();
+ auto Line = Params.position.line;
+ auto Character = Params.position.character;
+ assert(Line >= 0);
+ assert(Character >= 0);
+ std::stringstream SS;
+ std::vector<lsp::Location> Result;
+ const auto &Doc = OpenDocuments[Filepath.str()];
+ if (Instruction *MaybeI = Doc->getInstructionAtLocation(Line, Character)) {
+ auto TryAddReference = [&Result, &Params, &Doc](Instruction *I) {
+ auto MaybeInstLocation =
+ Doc->ParserContext.getInstructionOrArgumentLocation(I);
+ if (!MaybeInstLocation)
+ return;
+ Result.emplace_back(
+ lsp::Location(Params.textDocument.uri,
+ llvmFileLocRangeToLspRange(MaybeInstLocation.value())));
+ };
+ TryAddReference(MaybeI);
+ for (User *U : MaybeI->users()) {
+ if (auto *UserInst = dyn_cast<Instruction>(U)) {
+ TryAddReference(UserInst);
+ }
+ }
+ }
+
+ Reply(std::move(Result));
+}
+
+void LspServer::handleRequestTextDocumentDocumentSymbol(
+ const lsp::DocumentSymbolParams &Params,
+ lsp::Callback<std::vector<lsp::DocumentSymbol>> Reply) {
+ if (OpenDocuments.find(Params.textDocument.uri.file().str()) ==
+ OpenDocuments.end()) {
+ lsp::Logger::error(
+ "Document in textDocument/documentSymbol request not open: {}",
+ Params.textDocument.uri.file());
+ return Reply(
+ make_error<lsp::LSPError>(formatv("Did not open file previously {}",
+ Params.textDocument.uri.file()),
+ lsp::ErrorCode::InvalidParams));
+ }
+ auto &Doc = OpenDocuments[Params.textDocument.uri.file().str()];
+ std::vector<lsp::DocumentSymbol> Result;
+ for (const auto &Fn : Doc->getFunctions()) {
+ lsp::DocumentSymbol Func;
+ Func.name = Fn.getNameOrAsOperand();
+ Func.kind = lsp::SymbolKind::Function;
+ auto MaybeLoc = Doc->ParserContext.getFunctionLocation(&Fn);
+ if (!MaybeLoc)
+ continue;
+ Func.range = llvmFileLocRangeToLspRange(*MaybeLoc);
+ // FIXME: Should set the range of the function name in the definition, but
+ // we currently don't know where it is
+ Func.selectionRange = Func.range;
+ for (const auto &BB : Fn) {
+ lsp::DocumentSymbol Block;
+ Block.name = BB.getNameOrAsOperand();
+ // Using namespace as there is no block kind, and namespace is the closest
+ Block.kind = lsp::SymbolKind::Namespace;
+ Block.detail = "basic block";
+ auto MaybeLoc = Doc->ParserContext.getBlockLocation(&BB);
+ if (!MaybeLoc)
+ continue;
+ Block.range = llvmFileLocRangeToLspRange(*MaybeLoc);
+ // FIXME: Should set the range of the basic block label, but we currently
+ // don't know where it is
+ Block.selectionRange = Block.range;
+ for (const auto &I : BB) {
+ lsp::DocumentSymbol Inst;
+ Inst.name = I.getNameOrAsOperand();
+ Inst.kind = lsp::SymbolKind::Variable;
+ {
+ raw_string_ostream Ss(Inst.detail);
+ I.print(Ss);
+ }
+ auto MaybeLoc = Doc->ParserContext.getInstructionOrArgumentLocation(&I);
+ if (!MaybeLoc)
+ continue;
+ Inst.range = llvmFileLocRangeToLspRange(*MaybeLoc);
+ Inst.selectionRange = Inst.range;
+ Block.children.emplace_back(std::move(Inst));
+ }
+ Func.children.emplace_back(std::move(Block));
+ }
+ Result.emplace_back(std::move(Func));
+ }
+ Reply(std::move(Result));
+}
bool LspServer::registerMessageHandlers() {
MessageHandler.method("initialize", this,
diff --git a/llvm/tools/llvm-lsp/llvm-lsp-server.h b/llvm/tools/llvm-lsp/llvm-lsp-server.h
index 89c5a43e1fa72..c1aca0fc3b8e4 100644
--- a/llvm/tools/llvm-lsp/llvm-lsp-server.h
+++ b/llvm/tools/llvm-lsp/llvm-lsp-server.h
@@ -11,6 +11,7 @@
#include <sstream>
+#include "IRDocument.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/LSP/Protocol.h"
#include "llvm/Support/LSP/Transport.h"
@@ -22,6 +23,8 @@ class LspServer {
bool ShutDownRequested = false;
+ std::unordered_map<std::string, std::unique_ptr<IRDocument>> OpenDocuments;
+
public:
LspServer(lsp::JSONTransport &Transport)
: MessageHandler(Transport), Transport(Transport) {
@@ -51,6 +54,20 @@ class LspServer {
void handleRequestShutdown(const lsp::NoParams &Params,
lsp::Callback<std::nullptr_t> Reply);
+ // textDocument/didOpen
+ void handleNotificationTextDocumentDidOpen(
+ const lsp::DidOpenTextDocumentParams &Params);
+
+ // textDocument/references
+ void
+ handleRequestGetReferences(const lsp::ReferenceParams &Params,
+ lsp::Callback<std::vector<lsp::Location>> Reply);
+
+ // textDocument/documentSymbol
+ void handleRequestTextDocumentDocumentSymbol(
+ const lsp::DocumentSymbolParams &Params,
+ lsp::Callback<std::vector<lsp::DocumentSymbol>> Reply);
+
// Identifies RPC Call and dispatches the handling to other methods
bool registerMessageHandlers();
>From 7735aa909deb1d6bc9f1594013eb5c4c69cad6d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Albert=20Havli=C4=8Dek?= <havlialb at fit.cvut.cz>
Date: Sun, 19 Jul 2026 14:44:17 +0200
Subject: [PATCH 3/3] [llvm-lsp] Readme
---
llvm/tools/llvm-lsp/README.md | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
create mode 100644 llvm/tools/llvm-lsp/README.md
diff --git a/llvm/tools/llvm-lsp/README.md b/llvm/tools/llvm-lsp/README.md
new file mode 100644
index 0000000000000..70dee851811b7
--- /dev/null
+++ b/llvm/tools/llvm-lsp/README.md
@@ -0,0 +1,26 @@
+# LLVM LSP server
+
+## Usage
+To use this language server in your favourite editor, please refer to the documentation of that editor.
+
+First party support for this LSP server in VS Code is the LLVM VS Code extension, to use LLVM IR LSP server in VS Code
+install that extension.
+
+
+## Build
+Setup cmake using the tutorial from https://llvm.org/docs/CMake.html#quick-start
+and build the `llvm-lsp-server` target.
+
+## Features
+
+This LSP server is built to the [Language Server Protocol Specification 3.17](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/). It provides several standard features to enhance the development experience.
+
+---
+
+### Standard Capabilities
+
+The server supports the following standard LSP capabilities:
+
+* `textDocumentSync.openClose`: Synchronizes document content with the server.
+* `referencesProvider`: Finds all references to a symbol.
+* `documentsSymbolProvider`: Provides a tree of document symbols, enables breadcrums navigation in the editor.
More information about the llvm-commits
mailing list