Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenCL Language Server Refactoring #20

Merged
merged 32 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2969e70
Refactor jsonrpc
Galarius Jul 23, 2023
dbaa694
Fix clang-format toggle
Galarius Jul 23, 2023
f188d12
Update gitignore
Galarius Jul 23, 2023
31b865b
Define IJsonRPC interface and hide JsonRPC class impl
Galarius Jul 24, 2023
b9074bd
Make `diagnostics` and `jrpc` instances injectable into `lsp`
Galarius Jul 24, 2023
a2c35e7
Move json-rpc tests to a separate file
Galarius Jul 25, 2023
aab26e8
Refactor LSPServer for better testability
Galarius Jul 25, 2023
c1af853
Refactor LSPServerEventsHandler::OnInitialize and add tests
Galarius Jul 29, 2023
d11aa7d
Refactor OnInitialized, optimize id generation, add tests
Galarius Jul 30, 2023
c3c3c2c
Add test for LSPServerEventsHandler::BuildDiagnosticsRespond
Galarius Jul 31, 2023
03b4126
Add test for throwing LSPServerEventsHandler::BuildDiagnosticsRespond
Galarius Aug 1, 2023
aeffd69
Add test for LSPServerEventsHandler::OnTextOpen and OnTextChanged
Galarius Aug 3, 2023
d2a79a7
Update LSPServerEventsHandler::OnConfiguration and add test
Galarius Aug 3, 2023
a77cb95
Add test for LSPServerEventsHandler::GetConfiguration and OnRespond
Galarius Aug 3, 2023
2b9778b
Format lsp code
Galarius Aug 3, 2023
c58aac4
Add test for LSPServerEventsHandler::OnShutdown and OnExit
Galarius Aug 3, 2023
8b23afe
Replace --clinfo with subcommand
Galarius Aug 4, 2023
8c79452
Add 'diagnostics' subcommand
Galarius Aug 7, 2023
9679a06
[#19] Add uriparser dependency to convert URI to file path
Galarius Aug 11, 2023
9436274
Update test_package to support 'clinfo' subcommand
Galarius Aug 11, 2023
c5d3e26
Refactor Diagnostics class
Galarius Aug 12, 2023
68ad122
Create DiagnosticsParser class & tests
Galarius Aug 13, 2023
a102020
Add 'diagnostics' tests
Galarius Aug 14, 2023
2fd144d
Refactor utils, add tests
Galarius Aug 20, 2023
4f23ba3
Refactor logging
Galarius Aug 20, 2023
5e914cc
Refactor log messages
Galarius Aug 21, 2023
956e04c
Apply multiple fixes
Galarius Aug 22, 2023
5b9b4a7
Fix build
Galarius Aug 23, 2023
10cd952
Add a wrapper class for cl devices for better testability and perform…
Galarius Aug 25, 2023
c43e922
Update .gitignore
Galarius Aug 25, 2023
8e7d851
Return a valid JSON when no OpenCL platforms found
Galarius Aug 25, 2023
5878f4e
Bump version to 0.6.0
Galarius Aug 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ xcuserdata/
*.xccheckout
*.xcscmblueprint
.DS_Store
Testing

## Conan
.conan*
Expand All @@ -31,9 +32,9 @@ test_package/*
.build*

## VS Code
.vs
.vscode
CMakePresets.json


## Python
.pyenv
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ find_package(OpenCLHeadersCpp REQUIRED)
find_package(spdlog REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(CLI11 REQUIRED)
find_package(uriparser REQUIRED)

if(ENABLE_TESTING)
find_package(GTest REQUIRED)
Expand All @@ -57,15 +58,18 @@ endif()

set(headers
clinfo.hpp
device.hpp
diagnostics.hpp
jsonrpc.hpp
log.hpp
lsp.hpp
utils.hpp
)
set(sources
clinfo.cpp
diagnostics.cpp
jsonrpc.cpp
log.cpp
lsp.cpp
main.cpp
utils.cpp
Expand All @@ -75,7 +79,7 @@ list(TRANSFORM sources PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/src/")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/version.hpp.in version.hpp)
source_group("include" FILES ${headers})
source_group("src" FILES ${sources})
set(libs nlohmann_json::nlohmann_json spdlog::spdlog OpenCL::HeadersCpp CLI11::CLI11)
set(libs nlohmann_json::nlohmann_json spdlog::spdlog OpenCL::HeadersCpp CLI11::CLI11 uriparser::uriparser)
if(LINUX)
set(libs ${libs} stdc++fs OpenCL::OpenCL)
elseif(APPLE)
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ You can configure diagnostics with `json-rpc` request during the intitialization
"configuration": {
"buildOptions": [],
"deviceID": 0,
"maxNumberOfProblems": 100
"maxNumberOfProblems": 127
}
}
}
Expand All @@ -33,10 +33,10 @@ You can configure diagnostics with `json-rpc` request during the intitialization

|||
| --- | --- |
| `buildOptions` | Build options to be used for building the program. The list of [supported](https://registry.khronos.org/OpenCL/sdk/2.1/docs/man/xhtml/clBuildProgram.html) build options. |
| `buildOptions` | Options to be utilized when building the program. The list of [supported](https://registry.khronos.org/OpenCL/sdk/2.1/docs/man/xhtml/clBuildProgram.html) build options. |
| `deviceID` | Device ID or 0 (automatic selection) of the OpenCL device to be used for diagnostics. |
| | *Run `./opencl-language-server --clinfo` to get information about available OpenCL devices including identifiers.* |
| `maxNumberOfProblems` | Controls the maximum number of problems produced by the language server. |
| `maxNumberOfProblems` | Controls the maximum number of errors parsed by the language server. |

## Development

Expand Down
1 change: 1 addition & 0 deletions builder/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__
1 change: 1 addition & 0 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class OpenCLLanguageServerConanfile(ConanFile):
"nlohmann_json/[^3.11.2]",
"opencl-clhpp-headers/2022.09.30",
"spdlog/[^1.11.0]",
"uriparser/[^0.9.7]"
)
exports_sources = (
"include/**",
Expand Down
8 changes: 3 additions & 5 deletions include/clinfo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
// clinfo.hpp
// opencl-language-server
//
// Created by is on 5.2.2023.
// Created by Ilia Shoshin on 5.2.2023.
//

#pragma once

#include <CL/opencl.hpp>
#include "device.hpp"

#include <memory>
#include <nlohmann/json.hpp>
Expand All @@ -21,9 +21,7 @@ struct ICLInfo

virtual nlohmann::json json() = 0;

virtual uint32_t GetDeviceID(const cl::Device& device) = 0;

virtual std::string GetDeviceDescription(const cl::Device& device) = 0;
virtual std::vector<Device> GetDevices() = 0;
};

std::shared_ptr<ICLInfo> CreateCLInfo();
Expand Down
65 changes: 65 additions & 0 deletions include/device.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// device.hpp
// opencl-language-server
//
// Created by Ilia Shoshin on 24.8.2023.
//

#pragma once

#include <CL/opencl.hpp>

namespace ocls {

class Device
{
public:

Device(const Device&) = default;
Device& operator=(const Device&) = default;
Device(Device&&) noexcept = default;
Device& operator=(Device&&) noexcept = default;

#ifdef ENABLE_TESTING
Device(uint32_t identifier, std::string description, size_t powerIndex)
: m_identifier {identifier}
, m_description {std::move(description)}
, m_powerIndex {powerIndex}
{}
#endif

Device(cl::Device device, uint32_t identifier, std::string description, size_t powerIndex)
: m_device {std::move(device)}
, m_identifier {identifier}
, m_description {std::move(description)}
, m_powerIndex {powerIndex}
{}

const cl::Device& getUnderlyingDevice() const noexcept
{
return m_device;
}

uint32_t GetID() const noexcept
{
return m_identifier;
}

std::string GetDescription() const noexcept
{
return m_description;
}

size_t GetPowerIndex() const noexcept
{
return m_powerIndex;
}

private:
cl::Device m_device;
uint32_t m_identifier;
std::string m_description;
size_t m_powerIndex;
};

} // namespace ocls
32 changes: 29 additions & 3 deletions include/diagnostics.hpp
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// diagnostics.hpp
// opencl-language-server
//
// Created by Ilya Shoshin (Galarius) on 7/16/21.
// Created by Ilia Shoshin on 7/16/21.
//

#pragma once
Expand All @@ -12,23 +12,49 @@
#include <memory>
#include <nlohmann/json.hpp>
#include <string>
#include <regex>
#include <tuple>
#include <optional>

namespace ocls {

struct Source
{
std::string filePath;
std::string text;

bool operator==(const Source& other) const
{
return filePath == other.filePath && text == other.text;
}
};

struct IDiagnosticsParser
{
virtual ~IDiagnosticsParser() = default;

virtual std::tuple<std::string, long, long, long, std::string> ParseMatch(const std::smatch& matches) = 0;
virtual nlohmann::json ParseDiagnostics(
const std::string& buildLog, const std::string& name, uint64_t problemsLimit) = 0;
};

struct IDiagnostics
{
virtual ~IDiagnostics() = default;

virtual void SetBuildOptions(const nlohmann::json& options) = 0;
virtual void SetMaxProblemsCount(int maxNumberOfProblems) = 0;
virtual void SetBuildOptions(const std::string& options) = 0;
virtual void SetMaxProblemsCount(uint64_t maxNumberOfProblems) = 0;
virtual void SetOpenCLDevice(uint32_t identifier) = 0;
virtual nlohmann::json Get(const Source& source) = 0;

virtual std::optional<ocls::Device> GetDevice() const = 0;
virtual std::string GetBuildLog(const Source& source) = 0;
virtual nlohmann::json GetDiagnostics(const Source& source) = 0;
};

std::shared_ptr<IDiagnosticsParser> CreateDiagnosticsParser();
std::shared_ptr<IDiagnostics> CreateDiagnostics(std::shared_ptr<ICLInfo> clInfo);
std::shared_ptr<IDiagnostics> CreateDiagnostics(
std::shared_ptr<ICLInfo> clInfo, std::shared_ptr<IDiagnosticsParser> parser);

} // namespace ocls
96 changes: 34 additions & 62 deletions include/jsonrpc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,94 +2,66 @@
// jsonrpc.hpp
// opencl-language-server
//
// Created by Ilya Shoshin (Galarius) on 7/16/21.
// Created by Ilia Shoshin on 7/16/21.
//

#pragma once

#include <functional>
#include <iostream>
#include <memory>
#include <nlohmann/json.hpp>
#include <optional>
#include <regex>
#include <unordered_map>

namespace ocls {

class JsonRPC
{
using InputCallbackFunc = std::function<void(const nlohmann::json&)>;
using OutputCallbackFunc = std::function<void(const std::string&)>;
using InputCallbackFunc = std::function<void(const nlohmann::json&)>;
using OutputCallbackFunc = std::function<void(const std::string&)>;

public:
enum class ErrorCode : int
{
///@{
ParseError = -32700, ///< Parse error Invalid JSON was received by the server. An error occurred on the
///< server while parsing the JSON text.
InvalidRequest = -32600, ///< Invalid Request The JSON sent is not a valid Request object.
MethodNotFound = -32601, ///< Method not found The method does not exist / is not available.
InvalidParams = -32602, ///< Invalid params Invalid method parameter(s).
InternalError =
-32603, ///< Internal error Internal JSON-RPC error.
// -32000 to -32099 Server error Reserved for implementation-defined server-errors.
NotInitialized = -32002 ///< The first client's message is not equal to "initialize"
///@}
};
// clang-format off
enum class JRPCErrorCode : int
{
///@{
ParseError = -32700, ///< Parse error Invalid JSON was received by the server. An error occurred on the
///< server while parsing the JSON text.
InvalidRequest = -32600, ///< Invalid Request The JSON sent is not a valid Request object.
MethodNotFound = -32601, ///< Method not found The method does not exist / is not available.
InvalidParams = -32602, ///< Invalid params Invalid method parameter(s).
InternalError = -32603, ///< Internal error Internal JSON-RPC error.
// -32000 to -32099 Server error Reserved for implementation-defined server-errors.
NotInitialized = -32002 ///< The first client's message is not equal to "initialize"
///@}
};
// clang-format on

friend std::ostream& operator<<(std::ostream& out, ErrorCode const& code)
{
out << static_cast<int64_t>(code);
return out;
}
struct IJsonRPC
{
virtual ~IJsonRPC() = default;

/**
Register callback to be notified on the specific method notification.
All unregistered notifications will be responded with MethodNotFound automatically.
*/
void RegisterMethodCallback(const std::string& method, InputCallbackFunc&& func);
*/
virtual void RegisterMethodCallback(const std::string& method, InputCallbackFunc&& func) = 0;
/**
Register callback to be notified on client responds to server (our) requests.
*/
void RegisterInputCallback(InputCallbackFunc&& func);
virtual void RegisterInputCallback(InputCallbackFunc&& func) = 0;
/**
Register callback to be notified when server is going to send the final message to the client.
Basically it should be redirected to the stdout.
*/
void RegisterOutputCallback(OutputCallbackFunc&& func);
virtual void RegisterOutputCallback(OutputCallbackFunc&& func) = 0;

void Consume(char c);
bool IsReady() const;
void Write(const nlohmann::json& data) const;
void Reset();
virtual void Consume(char c) = 0;
virtual bool IsReady() const = 0;
virtual void Write(const nlohmann::json& data) const = 0;
virtual void Reset() = 0;
/**
Send trace message to client.
*/
void LogTrace(const std::string& message, const std::string& verbose = "");
void WriteError(JsonRPC::ErrorCode errorCode, const std::string& message) const;

private:
void OnInitialize();
void OnTracingChanged(const nlohmann::json& data);
bool ReadHeader();
void FireMethodCallback();
void FireRespondCallback();

private:
std::string m_method;
std::string m_buffer;
nlohmann::json m_body;
std::unordered_map<std::string, std::string> m_headers;
std::unordered_map<std::string, InputCallbackFunc> m_callbacks;
OutputCallbackFunc m_outputCallback;
InputCallbackFunc m_respondCallback;
bool m_isProcessing = true;
bool m_initialized = false;
bool m_validHeader = false;
bool m_tracing = false;
bool m_verbosity = false;
unsigned long m_contentLength = 0;
std::regex m_headerRegex {"([\\w-]+): (.+)\\r\\n(?:([^:]+)\\r\\n)?"};
virtual void WriteTrace(const std::string& message, const std::string& verbose) = 0;
virtual void WriteError(JRPCErrorCode errorCode, const std::string& message) const = 0;
};

std::shared_ptr<IJsonRPC> CreateJsonRPC();

} // namespace ocls
25 changes: 25 additions & 0 deletions include/log.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// logs.hpp
// opencl-language-server
//
// Created by Ilia Shoshin on 20/08/23.
//

#include <string>
#include <spdlog/spdlog.h>

namespace ocls {

struct LogName
{
static std::string main;
static std::string clinfo;
static std::string diagnostics;
static std::string jrpc;
static std::string lsp;
};

extern void ConfigureFileLogging(const std::string& filename, spdlog::level::level_enum level);
extern void ConfigureNullLogging();

} // namespace ocls
Loading
Loading