[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