diff --git a/.gitignore b/.gitignore index 64b04e2f..6a8fd376 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,7 @@ note.md temp* /build* +notes +temp/ diff --git a/include/Compiler/Compiler.h b/include/Compiler/Compiler.h index 480a0463..37569a88 100644 --- a/include/Compiler/Compiler.h +++ b/include/Compiler/Compiler.h @@ -1,11 +1,14 @@ #pragma once -#include -#include -#include +#include "Clang.h" +#include "Module.h" +#include "Preamble.h" +#include "Resolver.h" +#include "Directive.h" -#include +#include "Support/Error.h" #include "Basic/Location.h" + #include "llvm/ADT/StringSet.h" namespace clice { @@ -114,60 +117,6 @@ llvm::Expected compile(CompilationParams& params); /// Run code completion at the given location. llvm::Expected compile(CompilationParams& params, clang::CodeCompleteConsumer* consumer); -struct PCHInfo { - /// PCM file path. - std::string path; - - /// Source file path. - std::string srcPath; - - /// The content of source file used to build this PCM. - std::string preamble; - - /// Files involved in building this PCM. - std::vector deps; - - clang::PreambleBounds bounds() const { - /// We use '@' to mark the end of the preamble. - bool endAtStart = preamble.ends_with('@'); - unsigned int size = preamble.size() - endAtStart; - return {size, endAtStart}; - } -}; - -/// Build PCH from given file path and content. -llvm::Expected compile(CompilationParams& params, PCHInfo& out); - -struct ModuleInfo { - /// Whether this module is an interface unit. - /// i.e. has export module declaration. - bool isInterfaceUnit = false; - - /// Module name. - std::string name; - - /// Dependent modules of this module. - std::vector mods; -}; - -/// Run the preprocessor to scan the given module unit to -/// collect its module name and dependencies. -llvm::Expected scanModule(CompilationParams& params); - -inherited_struct(PCMInfo, ModuleInfo) { - /// PCM file path. - std::string path; - - /// Source file path. - std::string srcPath; - - /// Files involved in building this PCM(not include module). - std::vector deps; -}; - -/// Build PCM from given file path and content. -llvm::Expected compile(CompilationParams& params, PCMInfo& out); - struct CompilationParams { /// Source file content. llvm::StringRef content; @@ -213,7 +162,7 @@ struct CompilationParams { void addPCH(const PCHInfo& info) { pch = info.path; - pchBounds = info.bounds(); + /// pchBounds = info.bounds(); } void addPCM(const PCMInfo& info) { diff --git a/include/Compiler/Module.h b/include/Compiler/Module.h new file mode 100644 index 00000000..10c740ab --- /dev/null +++ b/include/Compiler/Module.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include "Support/Struct.h" +#include "Support/Error.h" + +namespace clice { + +class ASTInfo; + +struct CompilationParams; + +struct ModuleInfo { + /// Whether this module is an interface unit. + /// i.e. has export module declaration. + bool isInterfaceUnit = false; + + /// Module name. + std::string name; + + /// Dependent modules of this module. + std::vector mods; +}; + +inherited_struct(PCMInfo, ModuleInfo) { + /// PCM file path. + std::string path; + + /// Source file path. + std::string srcPath; + + /// Files involved in building this PCM(not include module). + std::vector deps; +}; + +/// If input file is module interface unit, return its module name. +/// Otherwise, return an empty string. +std::string scanModuleName(CompilationParams& params); + +/// Run the preprocessor to scan the given module unit to +/// collect its module name and dependencies. +llvm::Expected scanModule(CompilationParams& params); + +/// Build PCM from given file path and content. +llvm::Expected compile(CompilationParams& params, PCMInfo& out); + +} // namespace clice diff --git a/include/Compiler/Preamble.h b/include/Compiler/Preamble.h new file mode 100644 index 00000000..9abcf885 --- /dev/null +++ b/include/Compiler/Preamble.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "Support/Error.h" + +namespace clice { + +class ASTInfo; + +struct CompilationParams; + +struct PCHInfo { + /// The content used to build this PCH. + std::string preamble; + + /// The command used to build this PCH. + std::string command; + + /// The path of the output PCH file. + std::string path; + + /// All files involved in building this PCH. + std::vector deps; +}; + +/// Compute the preamble to build PCH with the given content. +std::string computePreamble(CompilationParams& params); + +/// Build PCH from given file path and content. +llvm::Expected compile(CompilationParams& params, PCHInfo& out); + +} // namespace clice diff --git a/include/Server/Async.h b/include/Server/Async.h index 93f7d995..35e85cee 100644 --- a/include/Server/Async.h +++ b/include/Server/Async.h @@ -1,32 +1,23 @@ #pragma once -#include "uv.h" - -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/FunctionExtras.h" -#include "llvm/Support/raw_ostream.h" - -#include "Server/Logger.h" +#include +#include +#include +#include +#include +#include +#include +#include #include "Support/JSON.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "uv.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/FunctionExtras.h" namespace clice::async { -template -struct promise; - -extern uv_loop_t* loop; - #define uv_check_call(func, ...) \ if(int error = func(__VA_ARGS__); error < 0) { \ log::fatal("An error occurred in " #func ": {0}", uv_strerror(error)); \ @@ -38,304 +29,152 @@ T& uv_cast(U* u) { return *static_cast*>(u->data); } -using Callback = llvm::unique_function(json::Value)>; +using core_handle = std::coroutine_handle<>; -void start_server(Callback callback); +/// Schedule the coroutine to resume in the event loop. +void schedule(core_handle core); -void start_server(Callback callback, const char* ip, unsigned int port); +template +class Task; -/// Send a request to the client. -void request(llvm::StringRef method, json::Value params); +namespace impl { -/// Send a notification to the client. -void notify(llvm::StringRef method, json::Value params); +template +struct promise_base { + std::optional value; -/// Send a response to the client. -void response(json::Value id, json::Value result); - -/// Send an register capability to the client. -void registerCapacity(llvm::StringRef id, llvm::StringRef method, json::Value registerOptions); - -template -struct result { - std::optional value; - - result() {} - - ~result() {} - - bool await_ready() noexcept { - return false; - } - - decltype(auto) await_resume() noexcept { - assert(value.has_value() && "await_resume: no value"); - return std::move(*value); - } - - template - void return_value(T&& val) noexcept { + template + void return_value(U&& val) noexcept { assert(!value.has_value() && "return_value: value already set"); - value.emplace(std::forward(val)); + value.emplace(std::forward(val)); } }; template <> -struct result { - bool await_ready() noexcept { - return false; - } - - void await_resume() noexcept {} - +struct promise_base { void return_void() noexcept {} }; -/// Schedule a coroutine to run in the event loop. -inline void schedule(std::coroutine_handle<> handle) { - assert(handle && "schedule: invalid coroutine handle"); - uv_async_t* async = new uv_async_t(); - async->data = handle.address(); - - auto callback = [](uv_async_t* async) { - auto handle = std::coroutine_handle<>::from_address(async->data); - handle.resume(); - uv_close(reinterpret_cast(async), nullptr); - delete async; - }; - - uv_check_call(uv_async_init, loop, async, callback); - uv_check_call(uv_async_send, async); -} - template -void schedule(const promise& promise) { - schedule(promise.handle()); -} - -template > -struct task_awaiter : result { - std::remove_cvref_t task; - uv_work_t work; - std::coroutine_handle<> caller; - - void await_suspend(std::coroutine_handle<> caller) noexcept { - static_assert(!std::is_reference_v, "return type must not be a reference"); - this->caller = caller; - work.data = this; - - auto work_cb = [](uv_work_t* work) { - auto& awaiter = uv_cast(work); - if constexpr(!std::is_void_v) { - awaiter.value.emplace(awaiter.task()); - } else { - awaiter.task(); - } - }; - - auto after_work_cb = [](uv_work_t* work, int status) { - auto& awaiter = uv_cast(work); - awaiter.caller.resume(); - }; - - uv_check_call(uv_queue_work, loop, &work, work_cb, after_work_cb); - } -}; - -/// Schedule a task to run in the thread pool. -template -task_awaiter schedule_task(Task&& task) { - return {{}, std::forward(task)}; -} - -struct sleep_awaiter { - std::chrono::milliseconds ms; - std::coroutine_handle<> caller; - - bool await_ready() noexcept { - return ms.count() == 0; - } - - void await_suspend(std::coroutine_handle<> caller) noexcept { - this->caller = caller; - uv_timer_t* timer = new uv_timer_t{.data = this}; - uv_check_call(uv_timer_init, loop, timer); - - auto callback = [](uv_timer_t* timer) { - auto& awaiter = uv_cast(timer); - awaiter.caller.resume(); - uv_close((uv_handle_t*)timer, [](uv_handle_t* handle) { delete handle; }); - }; - - uv_check_call(uv_timer_start, timer, callback, ms.count(), 0); - } - - void await_resume() noexcept {} -}; - -inline sleep_awaiter sleep(std::chrono::milliseconds ms) { - return {ms}; -} - -struct fs_awaiter { - std::string path; - std::coroutine_handle<> caller; - std::chrono::system_clock::time_point modified; - - bool await_ready() noexcept { - return false; - } - - void await_suspend(std::coroutine_handle<> caller) noexcept { - this->caller = caller; - uv_fs_t* req = new uv_fs_t{.data = this}; - - auto callback = [](uv_fs_t* req) { - auto& awaiter = uv_cast(req); - if(req->result < 0) { - log::fatal("An error occurred while opening file: {0}", uv_strerror(req->result)); - awaiter.caller.resume(); - delete req; - return; - } - - uv_timespec_t& mtime = req->statbuf.st_mtim; - using namespace std::chrono; - awaiter.modified = - system_clock::time_point(seconds(mtime.tv_sec) + nanoseconds(mtime.tv_nsec)); - awaiter.caller.resume(); - delete req; - }; - - uv_check_call(uv_fs_open, loop, req, path.c_str(), UV_FS_O_RDONLY, 0, callback); - } - - decltype(auto) await_resume() noexcept { - return modified; - } -}; - -inline fs_awaiter modified_time(llvm::StringRef path) { - return {path.str()}; -} - -template -int run(Ps&&... ps) { - (schedule(std::forward(ps)), ...); - return uv_run(loop, UV_RUN_DEFAULT); -} - -template -struct awaiter { - using coroutine_handle = typename promise::coroutine_handle; - - coroutine_handle h; - - bool await_ready() noexcept { - return false; - } - - void await_suspend(std::coroutine_handle<> handle) noexcept { - h.promise().caller = handle; - async::schedule(h); - } - - decltype(auto) await_resume() noexcept { - if constexpr(!std::is_void_v) { - auto value = std::move(*h.promise().value); - h.destroy(); - return value; - } else { - h.destroy(); - } - } -}; - -template -struct final_awaiter { - std::coroutine_handle<> caller; - - bool await_ready() noexcept { - return false; - } - - template - void await_suspend(std::coroutine_handle self) noexcept { - /// If this coroutine is a top-level coroutine, its caller is empty. - if(!caller) { - return; - } - - /// Schedule the caller to run in the event loop. - async::schedule(caller); - } - - void await_resume() noexcept {} -}; - -template -struct promise_type : result { - std::coroutine_handle<> caller; +struct promise_type : promise_base { + /// The coroutine handle that is waiting for the task to complete. + /// If this is a top-level coroutine, it is empty. + core_handle waiting; auto get_return_object() { - return promise{std::coroutine_handle::from_promise(*this)}; + return Task(std::coroutine_handle::from_promise(*this)); } void unhandled_exception() { std::abort(); } - std::suspend_always initial_suspend() { - return {}; + auto initial_suspend() { + return std::suspend_always(); } auto final_suspend() noexcept { - return final_awaiter{caller}; + struct awaiter { + core_handle waiting; + + bool await_ready() noexcept { + return false; + } + + void await_suspend(core_handle core) noexcept { + if(waiting) { + /// In the final suspend point, this coroutine is already done. + /// So try to resume the waiting coroutine if it exists. + async::schedule(waiting); + } else { + /// If waiting is empty, this is a top-level coroutine. + /// We decide to destroy it here. For non-top-level coroutines, + /// they are destroyed in the destructor of Task. + core.destroy(); + } + } + + void await_resume() noexcept {} + }; + + return awaiter{waiting}; } }; +} // namespace impl + template -class promise { +class Task { public: - using promise_type = async::promise_type; + using promise_type = impl::promise_type; using coroutine_handle = std::coroutine_handle; - promise(coroutine_handle handle) : h(handle) {} + using value_type = T; - promise(const promise&) = delete; +public: + Task(coroutine_handle handle) : core(handle) {} - promise(promise&& other) : h(other.h) { - other.h = nullptr; + Task(const Task&) = delete; + + Task(Task&& other) : core(other.core) { + other.core = nullptr; } - awaiter operator co_await() const noexcept { - return {h}; + Task& operator= (const Task&) = delete; + + Task& operator= (Task&& other) = delete; + + ~Task() { + if(core) { + core.destroy(); + } } - coroutine_handle handle() const noexcept { - return h; +public: + core_handle handle() const noexcept { + return core; + } + + core_handle release() noexcept { + auto handle = core; + core = nullptr; + return handle; } bool done() const noexcept { - return h.done(); + return core.done(); } - void destroy() noexcept { - assert(h && h.done() && "destroy: invalid coroutine handle"); - h.destroy(); + bool await_ready() const noexcept { + return false; + } + + /// Task is also awaitable. + void await_suspend(core_handle waiting) noexcept { + /// When another coroutine awaits this task, set the waiting coroutine. + assert(!core.promise().waiting && "await_suspend: already waiting"); + core.promise().waiting = waiting; + + /// Schedule the task to run. Note that the waiting coroutine is scheduled + /// in final_suspend. See `impl::promise_type::final_suspend`. + async::schedule(core); + } + + T await_resume() noexcept { + if constexpr(!std::is_void_v) { + assert(core.promise().value.has_value() && "await_resume: value not set"); + return std::move(*core.promise().value); + } } private: - coroutine_handle h; + coroutine_handle core; }; +void run(); - -/// Suspend current coroutine and invoke the callback with its handle. -/// Note the callback invoked before the coroutine is suspended. So it is -/// -template > Callback> +template auto suspend(Callback&& callback) { struct suspend_awaiter { Callback callback; @@ -344,7 +183,7 @@ auto suspend(Callback&& callback) { return false; } - void await_suspend(std::coroutine_handle<> handle) noexcept { + void await_suspend(core_handle handle) noexcept { callback(handle); } @@ -354,5 +193,261 @@ auto suspend(Callback&& callback) { return suspend_awaiter{std::forward(callback)}; } -} // namespace clice::async +template +auto gather(Tasks&&... tasks) + -> Task::value_type...>> { + bool all_done = (tasks.done() && ...); + if(!all_done) { + llvm::SmallVector handles = {tasks.handle()...}; + bool started = false; + + if(!started) { + for(auto handle: handles) { + async::schedule(handle); + } + started = true; + } + + while(!all_done) { + co_await async::suspend([](core_handle handle) { async::schedule(handle); }); + all_done = (tasks.done() && ...); + } + } + + /// If all tasks are done, return the results. + co_return std::tuple{tasks.await_resume()...}; +} + +/// Run the tasks in parallel and return the results. +template +auto run(Tasks&&... tasks) { + auto core = gather(std::forward(tasks)...); + schedule(core.handle()); + async::run(); + assert(core.done() && "run: not done"); + return core.await_resume(); +} + +template + requires (!std::same_as) +class Future { +public: + bool await_ready() const noexcept { + return value.has_value(); + } + + void await_suspend(core_handle waiting) noexcept { + if(!value.has_value()) { + waiters.push_back(waiting); + } else { + async::schedule(waiting); + } + } + + T await_resume() noexcept { + assert(value.has_value() && "await_resume: value not set"); + return std::move(*value); + } + + template + void emplace(Args&&... args) { + value.emplace(std::forward(args)...); + for(auto waiter: waiters) { + async::schedule(waiter); + } + } + +private: + std::optional value; + std::vector waiters; +}; + +class Lock { +public: + Lock(bool ready) : ready(ready) {} + + void lock() { + ready = false; + } + + void unlock() { + ready = true; + } + + Task operator co_await() { + while(!ready) { + co_await suspend([](core_handle handle) { async::schedule(handle); }); + } + } + +private: + bool ready = false; +}; + +namespace awaiter { + +template +struct thread_pool_base { + std::optional value; + + Ret await_resume() noexcept { + assert(value.has_value() && "await_resume: value not set"); + return std::move(*value); + } +}; + +template <> +struct thread_pool_base { + void await_resume() noexcept {} +}; + +template +struct thread_pool : thread_pool_base { + uv_work_t work; + Callback callback; + core_handle waiting; + + bool await_ready() noexcept { + return false; + } + + void await_suspend(core_handle waiting) noexcept { + work.data = this; + this->waiting = waiting; + + /// This callback is called in the thread pool. + auto work_cb = [](uv_work_t* work) { + auto& awaiter = *static_cast(work->data); + if constexpr(!std::is_void_v) { + awaiter.value.emplace(awaiter.callback()); + } else { + awaiter.callback(); + } + }; + + /// This callback is called in the event loop thread. + auto after_work_cb = [](uv_work_t* work, int status) { + auto& awaiter = *static_cast(work->data); + async::schedule(awaiter.waiting); + }; + + uv_queue_work(uv_default_loop(), &work, work_cb, after_work_cb); + } +}; + +} // namespace awaiter + +/// Submit a task to the thread pool. +template Callback, typename R = std::invoke_result_t> +auto submit(Callback&& callback) { + using C = std::remove_cvref_t; + return awaiter::thread_pool{{}, {}, std::forward(callback)}; +} + +namespace awaiter { + +struct sleep { + uv_timer_t timer; + std::chrono::milliseconds duration; + core_handle waiting; + + bool await_ready() const noexcept { + return duration.count() <= 0; + } + + void await_suspend(core_handle waiting) noexcept { + timer.data = this; + this->waiting = waiting; + + auto callback = [](uv_timer_t* timer) { + auto& awaiter = *static_cast(timer->data); + async::schedule(awaiter.waiting); + uv_close(reinterpret_cast(timer), nullptr); + }; + + uv_timer_init(uv_default_loop(), &timer); + uv_timer_start(&timer, callback, duration.count(), 0); + } + + void await_resume() noexcept {} +}; + +} // namespace awaiter + +/// Suspend the current coroutine for a duration. +inline auto wait_for(std::chrono::milliseconds duration) { + return awaiter::sleep{{}, duration, {}}; +} + +struct Stats { + using time_point = std::chrono::time_point; + time_point mtime; +}; + +namespace awaiter { + +struct stat { + uv_fs_t fs; + std::string path; + Stats stats; + core_handle waiting; + + bool await_ready() const noexcept { + return false; + } + + void await_suspend(core_handle waiting) noexcept { + fs.data = this; + this->waiting = waiting; + + auto callback = [](uv_fs_t* fs) { + auto transform = [](uv_timespec_t& mtime) { + using namespace std::chrono; + return system_clock::time_point(seconds(mtime.tv_sec) + nanoseconds(mtime.tv_nsec)); + }; + + auto& awaiter = *static_cast(fs->data); + + /// FIXME: handle error. + awaiter.stats.mtime = transform(fs->statbuf.st_mtim); + + async::schedule(awaiter.waiting); + }; + + uv_fs_stat(uv_default_loop(), &fs, path.c_str(), callback); + } + + Stats await_resume() noexcept { + return stats; + } +}; + +} // namespace awaiter + +/// Get the file status asynchronously. +inline auto stat(llvm::StringRef path) { + return awaiter::stat{{}, path.str(), {}, {}}; +} + +using Callback = llvm::unique_function(json::Value)>; + +/// Listen on stdin/stdout, callback is called when there is a LSP message available. +void listen(Callback callback); + +/// Listen on the given ip and port, callback is called when there is a LSP message available. +void listen(Callback callback, const char* ip, unsigned int port); + +/// Send a request to the client. +Task<> request(llvm::StringRef method, json::Value params); + +/// Send a notification to the client. +Task<> notify(llvm::StringRef method, json::Value params); + +/// Send a response to the client. +Task<> response(json::Value id, json::Value result); + +/// Send an register capability to the client. +Task<> registerCapacity(llvm::StringRef id, llvm::StringRef method, json::Value registerOptions); + +} // namespace clice::async diff --git a/include/Server/Cache.h b/include/Server/Cache.h new file mode 100644 index 00000000..606d24da --- /dev/null +++ b/include/Server/Cache.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include "Async.h" +#include "Compiler/Module.h" +#include "Compiler/Preamble.h" +#include "Server/Database.h" + +#include "llvm/ADT/StringMap.h" + +namespace clice { + +struct CacheOption { + /// The directory to store the cache files. + std::string dir; +}; + +/// This class is responsible for PCH and PCM building. +class CacheController { +public: + CacheController(CacheOption& option, CompilationDatabase& database); + + /// Generate `cache.json` to store the cache information. + void loadFromDisk(); + + /// Load the cache information from `cache.json`. + void saveToDisk(); + + /// Complete the PCH or PCM information required for the compilation arguments. + /// If no suitable PCH or PCM is available, a build will be triggered. + async::Task<> prepare(CompilationParams& params); + + async::Task<> updatePCH(); + +private: + const CacheOption& option; + + CompilationDatabase& database; + + struct CachedPCHInfo : PCHInfo { + /// The hash of the preamble, for fast comparison. + std::uint64_t hash; + + /// The reference count of this PCH. When server exit, all PCH with zero + /// reference count will be removed. + std::uint32_t reference; + }; + + /// All PCHs. + std::deque pchs; + + /// A map between source file and its PCH. + llvm::StringMap pchMap; + + /// [module name] -> [PCMInfo] + llvm::StringMap pcms; +}; + +} // namespace clice diff --git a/include/Server/Config.h b/include/Server/Config.h deleted file mode 100644 index 1cff2a1f..00000000 --- a/include/Server/Config.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include "Support/Support.h" - -namespace clice::config { - -/// Read the config file, call when the program starts. -void load(llvm::StringRef execute, llvm::StringRef filename); - -/// Initialize the config, replace all predefined variables in the config file. -/// called in `Server::initialize`. -void init(std::string_view workplace); - -struct ServerOptions { - std::vector compile_commands_dirs; -}; - -struct CacheOptions { - std::string dir; - uint32_t limit = 0; -}; - -struct IndexOptions { - std::string dir; - bool implicitInstantiation = true; -}; - -struct Rule { - std::string pattern; - std::vector append; - std::vector remove; - std::string readonly; - std::string header; - std::vector context; -}; - -extern llvm::StringRef version; -extern llvm::StringRef binary; -extern llvm::StringRef llvm_version; -extern llvm::StringRef workspace; - -extern const ServerOptions& server; -extern const CacheOptions& cache; -extern const IndexOptions& index; -extern llvm::ArrayRef rules; - -}; // namespace clice::config - diff --git a/include/Server/Database.h b/include/Server/Database.h new file mode 100644 index 00000000..4048289d --- /dev/null +++ b/include/Server/Database.h @@ -0,0 +1,38 @@ +#pragma once + +#include "llvm/ADT/StringMap.h" + +namespace clice { + +/// `CompilationDatabase` is responsible for managing the compile commands. +/// +/// FIXME: currently we assume that a file only occurs once in the CDB. +/// This is not always correct, but it is enough for now. +class CompilationDatabase { +public: + /// Update the compile commands with the given file. + void update(llvm::StringRef file); + + /// Update the module map with the given file and module name. + void update(llvm::StringRef file, llvm::StringRef name); + + /// Lookup the compile commands of the given file. + llvm::StringRef getCommand(llvm::StringRef file); + + /// Lookup the module interface unit file path of the given module name. + llvm::StringRef getModuleFile(llvm::StringRef name); + +private: + /// A map between file path and compile commands. + llvm::StringMap commands; + + /// For C++20 module, we only can got dependent module name + /// in source context. But we need dependent module file path + /// to build PCM. So we will scan(preprocess) all project files + /// to build a module map between module name and module file path. + /// **Note that** this only includes module interface unit, for module + /// implementation unit, the scan could be delayed until compiling it. + llvm::StringMap moduleMap; +}; + +} // namespace clice diff --git a/include/Server/Indexer.h b/include/Server/Indexer.h deleted file mode 100644 index d5c2dea8..00000000 --- a/include/Server/Indexer.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "Support/Support.h" - -namespace clice { - -/// Responsible for index all files, distinguish active and inactive files. -class Indexer {}; - -} // namespace clice diff --git a/include/Server/Scheduler.h b/include/Server/Scheduler.h index da579a9b..3816f684 100644 --- a/include/Server/Scheduler.h +++ b/include/Server/Scheduler.h @@ -1,109 +1,41 @@ #pragma once -#include - -#include "Server/Async.h" -#include "Server/Protocol.h" -#include "Server/Trace.h" -#include "Compiler/Compiler.h" +#include "Cache.h" namespace clice { -/// A C++ source file may have different AST in different context. -/// `SourceContext` is used to distinguish different context. -struct SourceContext { - /// Compile options for this context. - std::string command; +struct Rule { + /// The file name pattern. + std::string pattern; - /// For a header file, it may be not self contained and need a main file. - /// `includes` record the include chain of the header file. Each different - /// include chain will have a different context. - std::vector includes; + /// ... + std::vector append; + + /// ... + std::vector remove; + + std::string readonly; + + std::string header; + + std::vector context; }; -} // namespace clice - -template <> -struct llvm::DenseMapInfo { - static clice::SourceContext getEmptyKey() { - return clice::SourceContext{.command = "Empty", .includes = {}}; - } - - static clice::SourceContext getTombstoneKey() { - return clice::SourceContext{.command = "Tombstone", .includes = {}}; - } - - static unsigned getHashValue(const clice::SourceContext& context) { - return clice::refl::hash(context); - } - - static bool isEqual(const clice::SourceContext& lhs, const clice::SourceContext& rhs) { - return clice::refl::equal(lhs, rhs); - } -}; - -namespace clice { - -struct File { - bool isIdle = true; - - /// The current ASTInfo. - std::unique_ptr info = std::make_unique(); - - /// Coroutine handles that are waiting for the file to be updated. - std::deque> waiters; -}; - -/// Responsible for manage the files and schedule the tasks. -class Scheduler { -private: - /// Update the PCH for the given source file. - async::promise<> updatePCH(llvm::StringRef srcPath, llvm::StringRef content); - - /// Clang requires all direct and indirect dependent modules to be added during module building. - /// This function adds the dependencies of the given module to the compilation parameters. - /// Note: It is assumed that all dependent modules have already been built. - llvm::Error addModuleDeps(CompilationParams& params, const ModuleInfo& moduleInfo) const; - - /// Update the PCM for the given module. - async::promise<> updatePCM(llvm::StringRef moduleName, class Synchronizer& sync); - - /// Update the AST for the given source file. - async::promise<> updateAST(llvm::StringRef filename, - llvm::StringRef content, - class Synchronizer& sync); - - /// Wait for until the file is idle. - async::promise<> waitForFile(llvm::StringRef filename); - - void scheduleNext(llvm::StringRef filename); - +/// This class is responsible for managing all opened files. +class FileController { public: - /// Update the given file. - async::promise<> update(llvm::StringRef filename, - llvm::StringRef content, - class Synchronizer& sync); + FileController(CompilationDatabase& database, llvm::ArrayRef rules) : + database(database), rules(rules) {} - /// Execute the given action on the given file. - async::promise<> execute(llvm::StringRef filename, - llvm::unique_function action); + async::Task<> open(llvm::StringRef path); - /// Load all Information about PCHs and PCMs from disk. - void loadCache(); + async::Task<> update(llvm::StringRef path); - /// Save all Information about PCHs and PCMs to disk. - /// So that we can reuse them next time. - void saveCache() const; + async::Task<> close(llvm::StringRef path); private: - /// [file name] -> [PCHInfo] - llvm::StringMap pchs; - - /// [module name] -> [PCMInfo] - llvm::StringMap pcms; - - /// [file name] -> [File] - llvm::StringMap files; + CompilationDatabase& database; + llvm::ArrayRef rules; }; } // namespace clice diff --git a/include/Server/Server.h b/include/Server/Server.h index 1170eaf3..4a3308e5 100644 --- a/include/Server/Server.h +++ b/include/Server/Server.h @@ -1,13 +1,7 @@ #pragma once -#include "Compiler/Compiler.h" -#include "Server/Async.h" -#include "Server/Config.h" -#include "Server/Logger.h" -#include "Server/Scheduler.h" -#include "Server/Synchronizer.h" -#include "Server/Protocol.h" -#include "Support/Support.h" +#include "Async.h" +#include "Protocol.h" namespace clice { @@ -18,137 +12,126 @@ public: int run(int argc, const char** argv); private: - using onRequest = llvm::unique_function(json::Value, json::Value)>; - using onNotification = llvm::unique_function(json::Value)>; + using onRequest = llvm::unique_function(json::Value, json::Value)>; + using onNotification = llvm::unique_function(json::Value)>; template void addMethod(llvm::StringRef name, - async::promise (Server::*method)(json::Value, const Param&)) { - requests.try_emplace( - name, - [this, method](json::Value id, json::Value value) -> async::promise { - co_await (this->*method)(std::move(id), json::deserialize(value)); - }); + async::Task<> (Server::*method)(json::Value, const Param&)) { + requests.try_emplace(name, + [this, method](json::Value id, json::Value value) -> async::Task<> { + co_await (this->*method)(std::move(id), + json::deserialize(value)); + }); } template - void addMethod(llvm::StringRef name, async::promise (Server::*method)(const Param&)) { - notifications.try_emplace(name, [this, method](json::Value value) -> async::promise { + void addMethod(llvm::StringRef name, async::Task<> (Server::*method)(const Param&)) { + notifications.try_emplace(name, [this, method](json::Value value) -> async::Task<> { co_await (this->*method)(json::deserialize(value)); }); } + llvm::StringMap requests; + llvm::StringMap notifications; + private: /// ============================================================================ /// Lifestyle Message /// ============================================================================ - async::promise onInitialize(json::Value id, const proto::InitializeParams& params); + async::Task<> onInitialize(json::Value id, const proto::InitializeParams& params); - async::promise onInitialized(const proto::InitializedParams& params); + async::Task<> onInitialized(const proto::InitializedParams& params); - async::promise onShutdown(json::Value id, const proto::None&); + async::Task<> onShutdown(json::Value id, const proto::None&); - async::promise onExit(const proto::None&); + async::Task<> onExit(const proto::None&); /// ============================================================================ /// Document Synchronization /// ============================================================================ - async::promise onDidOpen(const proto::DidOpenTextDocumentParams& document); + async::Task<> onDidOpen(const proto::DidOpenTextDocumentParams& document); - async::promise onDidChange(const proto::DidChangeTextDocumentParams& document); + async::Task<> onDidChange(const proto::DidChangeTextDocumentParams& document); - async::promise onDidSave(const proto::DidSaveTextDocumentParams& document); + async::Task<> onDidSave(const proto::DidSaveTextDocumentParams& document); - async::promise onDidClose(const proto::DidCloseTextDocumentParams& document); + async::Task<> onDidClose(const proto::DidCloseTextDocumentParams& document); /// ============================================================================ /// Language Features /// ============================================================================ - async::promise onGotoDeclaration(json::Value id, const proto::DeclarationParams& params); + async::Task<> onGotoDeclaration(json::Value id, const proto::DeclarationParams& params); - async::promise onGotoDefinition(json::Value id, const proto::DefinitionParams& params); + async::Task<> onGotoDefinition(json::Value id, const proto::DefinitionParams& params); - async::promise onGotoTypeDefinition(json::Value id, - const proto::TypeDefinitionParams& params); + async::Task<> onGotoTypeDefinition(json::Value id, const proto::TypeDefinitionParams& params); - async::promise onGotoImplementation(json::Value id, - const proto::ImplementationParams& params); + async::Task<> onGotoImplementation(json::Value id, const proto::ImplementationParams& params); - async::promise onFindReferences(json::Value id, const proto::ReferenceParams& params); + async::Task<> onFindReferences(json::Value id, const proto::ReferenceParams& params); - async::promise onPrepareCallHierarchy(json::Value id, - const proto::CallHierarchyPrepareParams& params); + async::Task<> onPrepareCallHierarchy(json::Value id, + const proto::CallHierarchyPrepareParams& params); - async::promise onIncomingCall(json::Value id, - const proto::CallHierarchyIncomingCallsParams& params); + async::Task<> onIncomingCall(json::Value id, + const proto::CallHierarchyIncomingCallsParams& params); - async::promise onOutgoingCall(json::Value id, - const proto::CallHierarchyOutgoingCallsParams& params); + async::Task<> onOutgoingCall(json::Value id, + const proto::CallHierarchyOutgoingCallsParams& params); - async::promise onPrepareTypeHierarchy(json::Value id, - const proto::TypeHierarchyPrepareParams& params); + async::Task<> onPrepareTypeHierarchy(json::Value id, + const proto::TypeHierarchyPrepareParams& params); - async::promise onSupertypes(json::Value id, - const proto::TypeHierarchySupertypesParams& params); + async::Task<> onSupertypes(json::Value id, const proto::TypeHierarchySupertypesParams& params); - async::promise onSubtypes(json::Value id, - const proto::TypeHierarchySubtypesParams& params); + async::Task<> onSubtypes(json::Value id, const proto::TypeHierarchySubtypesParams& params); - async::promise onDocumentHighlight(json::Value id, - const proto::DocumentHighlightParams& params); + async::Task<> onDocumentHighlight(json::Value id, const proto::DocumentHighlightParams& params); - async::promise onDocumentLink(json::Value id, const proto::DocumentLinkParams& params); + async::Task<> onDocumentLink(json::Value id, const proto::DocumentLinkParams& params); - async::promise onHover(json::Value id, const proto::HoverParams& params); + async::Task<> onHover(json::Value id, const proto::HoverParams& params); - async::promise onCodeLens(json::Value id, const proto::CodeLensParams& params); + async::Task<> onCodeLens(json::Value id, const proto::CodeLensParams& params); - async::promise onFoldingRange(json::Value id, const proto::FoldingRangeParams& params); + async::Task<> onFoldingRange(json::Value id, const proto::FoldingRangeParams& params); - async::promise onDocumentSymbol(json::Value id, - const proto::DocumentSymbolParams& params); + async::Task<> onDocumentSymbol(json::Value id, const proto::DocumentSymbolParams& params); - async::promise onSemanticTokens(json::Value id, - const proto::SemanticTokensParams& params); + async::Task<> onSemanticTokens(json::Value id, const proto::SemanticTokensParams& params); - async::promise onInlayHint(json::Value id, const proto::InlayHintParams& params); + async::Task<> onInlayHint(json::Value id, const proto::InlayHintParams& params); - async::promise onCodeCompletion(json::Value id, const proto::CompletionParams& params); + async::Task<> onCodeCompletion(json::Value id, const proto::CompletionParams& params); - async::promise onSignatureHelp(json::Value id, const proto::SignatureHelpParams& params); + async::Task<> onSignatureHelp(json::Value id, const proto::SignatureHelpParams& params); - async::promise onCodeAction(json::Value id, const proto::CodeActionParams& params); + async::Task<> onCodeAction(json::Value id, const proto::CodeActionParams& params); - async::promise onFormatting(json::Value id, - const proto::DocumentFormattingParams& params); + async::Task<> onFormatting(json::Value id, const proto::DocumentFormattingParams& params); - async::promise onRangeFormatting(json::Value id, - const proto::DocumentRangeFormattingParams& params); + async::Task<> onRangeFormatting(json::Value id, + const proto::DocumentRangeFormattingParams& params); /// ============================================================================ /// Workspace Features /// ============================================================================ - async::promise onDidChangeWatchedFiles(const proto::DidChangeWatchedFilesParams& params); + async::Task<> onDidChangeWatchedFiles(const proto::DidChangeWatchedFilesParams& params); /// ============================================================================ /// Extension /// ============================================================================ - async::promise onContextCurrent(const proto::TextDocumentIdentifier& params); + async::Task<> onContextCurrent(const proto::TextDocumentIdentifier& params); - async::promise onContextAll(const proto::TextDocumentIdentifier& params); + async::Task<> onContextAll(const proto::TextDocumentIdentifier& params); - async::promise onContextSwitch(const proto::TextDocumentIdentifier& params); - -private: - Scheduler scheduler; - Synchronizer synchronizer; - llvm::StringMap requests; - llvm::StringMap notifications; + async::Task<> onContextSwitch(const proto::TextDocumentIdentifier& params); }; } // namespace clice diff --git a/include/Server/Synchronizer.h b/include/Server/Synchronizer.h deleted file mode 100644 index 615afd24..00000000 --- a/include/Server/Synchronizer.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "Support/Support.h" - -namespace clice { - -/// Responsible for synchronizing changes to the CDB file, -/// including updating compile commands and the module map. -class Synchronizer { -public: - /// FIXME: The CDB file can be very large, reaching the size of - /// several gigabytes (GB). Therefore, it's better for this function - /// to be an asynchronous function. - - /// Update the compile commands. - void sync(llvm::StringRef file); - - /// Update the module map for active file. - void sync(llvm::StringRef name, llvm::StringRef path); - - /// Lookup the compile commands of the given file. - llvm::StringRef lookup(llvm::StringRef file) const; - - /// Lookup the module interface unit file path of the given module name. - llvm::StringRef map(llvm::StringRef name) const; - -private: - /// FIXME: currently we assume that a file only occurs once in the CDB. - /// This is not always correct, but it is enough for now. - - /// A map between file path and compile commands. - llvm::StringMap commands; - - /// For C++20 module, we only can got dependent module name - /// in source context. But we need dependent module file path - /// to build PCM. So we will scan(preprocess) all project files - /// to build a module map between module name and module file path. - /// **Note that** this only includes module interface unit, for module - /// implementation unit, the scan could be delayed until compiling it. - llvm::StringMap moduleMap; -}; - -} // namespace clice diff --git a/include/Server/Trace.h b/include/Server/Trace.h deleted file mode 100644 index e595fb36..00000000 --- a/include/Server/Trace.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -namespace clice { - -struct Tracer { - std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); - - auto duration() { - return std::chrono::duration_cast( - std::chrono::system_clock::now() - start); - } -}; - -} // namespace clice diff --git a/src/Compiler/Compiler.cpp b/src/Compiler/Compiler.cpp index 4a3a6235..096f2f95 100644 --- a/src/Compiler/Compiler.cpp +++ b/src/Compiler/Compiler.cpp @@ -365,7 +365,6 @@ llvm::Expected compile(CompilationParams& params, PCHInfo& out) { } out.path = params.outPath.str(); - out.srcPath = params.srcPath.str(); auto& bounds = *params.bounds; out.preamble = params.content.substr(0, bounds.Size).str(); diff --git a/src/Compiler/Module.cpp b/src/Compiler/Module.cpp new file mode 100644 index 00000000..921070eb --- /dev/null +++ b/src/Compiler/Module.cpp @@ -0,0 +1,91 @@ +#include "Compiler/Module.h" +#include "Compiler/Compiler.h" + +namespace clice { + +std::string scanModuleName(CompilationParams& params) { + /// Because [P3034](https://github.com/cplusplus/papers/issues/1696) has been + /// accepted, the module name in module declaration cannot be a macro now. + /// It means that if the module declaration doesn't occur in condition preprocess + /// directive, we can determine the module name just by lexing the source file. + clang::LangOptions langOpts; + langOpts.Modules = true; + langOpts.CPlusPlus20 = true; + + /// We use raw mode of lexer to avoid the preprocessor. + clang::Lexer lexer(clang::SourceLocation(), + langOpts, + params.content.begin(), + params.content.begin(), + params.content.end()); + + /// Whether we are in a condition directive. + bool isInDirective = false; + + /// Whether we need to preprocess the source file. + bool needPreprocess = false; + + std::string name; + clang::Token token; + while(!lexer.LexFromRawLexer(token)) { + auto kind = token.getKind(); + if(kind == clang::tok::hash) { + lexer.LexFromRawLexer(token); + auto diretive = token.getRawIdentifier(); + if(diretive == "if" || diretive == "ifdef" || diretive == "ifndef") { + isInDirective = true; + } else if(diretive == "endif") { + isInDirective = false; + } + } else if(kind == clang::tok::raw_identifier) { + if(token.getRawIdentifier() != "export") [[likely]] { + continue; + } + + lexer.LexFromRawLexer(token); + if(token.getRawIdentifier() != "module") { + continue; + } + + /// We are after `export module`. + if(isInDirective) { + /// If the module name occurs in a condition directive, we have + /// to preprocess the source file to determine the module name. + needPreprocess = true; + break; + } + + /// Otherwise, we can determine the module name directly. + while(!lexer.LexFromRawLexer(token)) { + kind = token.getKind(); + if(kind == clang::tok::raw_identifier) { + name += token.getRawIdentifier(); + } else if(kind == clang::tok::colon) { + name += ":"; + } else if(kind == clang::tok::period) { + name += "."; + } else { + break; + } + } + return name; + } else { + continue; + } + } + + /// If not need to preprocess and the function doesn't return, it means + /// that this file is not a module interface unit. + if(!needPreprocess) { + return ""; + } + + auto info = scanModule(params); + if(!info) { + return ""; + } + + return info->name; +} + +} // namespace clice diff --git a/src/Compiler/Preamble.cpp b/src/Compiler/Preamble.cpp new file mode 100644 index 00000000..9830746d --- /dev/null +++ b/src/Compiler/Preamble.cpp @@ -0,0 +1,3 @@ +#include "Compiler/Preamble.h" + +namespace clice {} diff --git a/src/Server/Async.cpp b/src/Server/Async.cpp index e6022510..b6f1195c 100644 --- a/src/Server/Async.cpp +++ b/src/Server/Async.cpp @@ -1,17 +1,54 @@ -#include "Server/Async.h" +#include -#include "llvm/ADT/SmallString.h" +#include "Server/Async.h" +#include "Server/Logger.h" namespace clice::async { -using Callback = llvm::unique_function(json::Value)>; -uv_loop_t* loop = uv_default_loop(); - namespace { +/// The default event loop. +uv_loop_t* loop = uv_default_loop(); + +/// The task queue waiting for resuming. +std::deque> tasks; + Callback callback = {}; + uv_stream_t* writer = {}; +} // namespace + +/// This function is called by the event loop to resume the tasks. +static void event_loop(uv_idle_t* handle) { + if(tasks.empty()) { + return; + } + + auto task = tasks.front(); + tasks.pop_front(); + task.resume(); + + if(tasks.empty()) { + uv_stop(loop); + } +} + +void schedule(std::coroutine_handle<> core) { + assert(core && !core.done() && "schedule: invalid coroutine handle"); + tasks.emplace_back(core); +} + +void run() { + uv_idle_t idle; + uv_idle_init(loop, &idle); + uv_idle_start(&idle, event_loop); + + uv_run(loop, UV_RUN_DEFAULT); +} + +namespace { + void on_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { /// This function is called synchronously before `on_read`. See the implementation of /// `uv__read` in libuv/src/unix/stream.c. So it is safe to use a static buffer here. @@ -61,7 +98,11 @@ void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { buffer.append({buf->base, static_cast(nread)}); if(auto message = buffer.peek(); !message.empty()) { if(auto json = json::parse(message)) { - async::schedule(callback(std::move(*json))); + /// This is a top-level coroutine. + auto core = callback(std::move(*json)); + /// It will be destroyed in final suspend point. + /// So we release it here. + async::schedule(core.release()); buffer.consume(); } else { log::fatal("An error occurred while parsing JSON: {0}", json.takeError()); @@ -75,42 +116,9 @@ void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { } } -/// Write a JSON value to the client. -void write(json::Value value) { - struct Buffer { - uv_write_t req; - llvm::SmallString<128> header; - llvm::SmallString<4096> message; - }; - - Buffer* buffer = new Buffer(); - buffer->req.data = buffer; - - llvm::raw_svector_ostream os(buffer->message); - os << value; - - llvm::raw_svector_ostream sos(buffer->header); - sos << "Content-Length: " << buffer->message.size() << "\r\n\r\n"; - - uv_buf_t bufs[2] = { - uv_buf_init(buffer->header.data(), buffer->header.size()), - uv_buf_init(buffer->message.data(), buffer->message.size()), - }; - - auto on_write = [](uv_write_t* req, int status) { - if(status < 0) { - log::fatal("An error occurred while writing: {0}", uv_strerror(status)); - } - - delete static_cast(req->data); - }; - - uv_check_call(uv_write, &buffer->req, writer, bufs, 2, on_write); -} - } // namespace -void start_server(Callback callback) { +void listen(Callback callback) { static uv_pipe_t in; static uv_pipe_t out; @@ -129,7 +137,7 @@ void start_server(Callback callback) { uv_check_call(uv_run, async::loop, UV_RUN_DEFAULT); } -void start_server(Callback callback, const char* ip, unsigned int port) { +void listen(Callback callback, const char* ip, unsigned int port) { static uv_tcp_t server; static uv_tcp_t client; @@ -159,10 +167,49 @@ void start_server(Callback callback, const char* ip, unsigned int port) { uv_check_call(uv_run, async::loop, UV_RUN_DEFAULT); } -/// Send a request to the client. -void request(llvm::StringRef method, json::Value params) { +/// Write a JSON value to the client. +static auto write(json::Value value) { + struct awaiter { + uv_write_t write; + uv_buf_t buf[2]; + llvm::SmallString<128> header; + llvm::SmallString<4096> message; + core_handle waiting; + + bool await_ready() const noexcept { + return false; + } + + void await_suspend(core_handle waiting) noexcept { + write.data = this; + + this->waiting = waiting; + buf[0] = uv_buf_init(header.data(), header.size()); + buf[1] = uv_buf_init(message.data(), message.size()); + + uv_check_call(uv_write, &write, writer, buf, 2, [](uv_write_t* req, int status) { + if(status < 0) { + log::fatal("An error occurred while writing: {0}", uv_strerror(status)); + } + + auto& awaiter = uv_cast(req); + async::schedule(awaiter.waiting); + }); + } + + void await_resume() noexcept {} + } awaiter; + + llvm::raw_svector_ostream(awaiter.message) << value; + llvm::raw_svector_ostream(awaiter.header) + << "Content-Length: " << awaiter.message.size() << "\r\n\r\n"; + + return awaiter; +} + +Task<> request(llvm::StringRef method, json::Value params) { static std::uint32_t id = 0; - write(json::Object{ + co_await write(json::Object{ {"jsonrpc", "2.0" }, {"id", id += 1 }, {"method", method }, @@ -170,33 +217,31 @@ void request(llvm::StringRef method, json::Value params) { }); } -/// Send a notification to the client. -void notify(llvm::StringRef method, json::Value params) { - write(json::Object{ +Task<> notify(llvm::StringRef method, json::Value params) { + co_await write(json::Object{ {"jsonrpc", "2.0" }, {"method", method }, {"params", std::move(params)}, }); } -void response(json::Value id, json::Value result) { - write(json::Object{ +Task<> response(json::Value id, json::Value result) { + co_await write(json::Object{ {"jsonrpc", "2.0" }, {"id", id }, {"result", std::move(result)}, }); } -/// Send an register capability to the client. -void registerCapacity(llvm::StringRef id, llvm::StringRef method, json::Value registerOptions) { - request("client/registerCapability", - json::Object{ - {"registrations", - json::Array{json::Object{ - {"id", id}, - {"method", method}, - {"registerOptions", std::move(registerOptions)}, - }}}, +Task<> registerCapacity(llvm::StringRef id, llvm::StringRef method, json::Value registerOptions) { + co_await request("client/registerCapability", + json::Object{ + {"registrations", + json::Array{json::Object{ + {"id", id}, + {"method", method}, + {"registerOptions", std::move(registerOptions)}, + }}}, }); } diff --git a/src/Server/Cache.cpp b/src/Server/Cache.cpp new file mode 100644 index 00000000..3248824a --- /dev/null +++ b/src/Server/Cache.cpp @@ -0,0 +1,26 @@ +#include "Server/Cache.h" + +namespace clice { + +void CacheController::loadFromDisk() { + +} + +void CacheController::saveToDisk() { + +} + +async::Task<> CacheController::prepare(CompilationParams& params) { + /// 首先要区分这个文件是不是 module file. + bool isModule = false; + + if(isModule){ + /// Update dependent mods .... + } + + /// 准备构建 PCH ... + + co_return; +} + +} // namespace clice diff --git a/src/Server/Config.cpp b/src/Server/Config.cpp deleted file mode 100644 index a7008574..00000000 --- a/src/Server/Config.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#define TOML_EXCEPTIONS 0 -#include - -#include -#include -#include - -namespace clice::config { - -static llvm::StringMap predefined = { - /// the directory of the workplace. - {"workplace", "" }, - /// the directory of the executable. - {"binary", "" }, - /// the version of the clice. - {"version", "0.0.1"}, - /// the version of dependent llvm. - {"llvm_version", "20" }, -}; - -/// predefined variables. -llvm::StringRef version = predefined["version"]; -llvm::StringRef binary = predefined["binary"]; -llvm::StringRef llvm_version = predefined["llvm_version"]; -llvm::StringRef workspace = predefined["workplace"]; - -struct Config { - ServerOptions server; - CacheOptions cache; - IndexOptions index; - std::vector rules; -}; - -/// global config instance. -static Config config = {}; - -const ServerOptions& server = config.server; -const CacheOptions& cache = config.cache; -const IndexOptions& index = config.index; -llvm::ArrayRef rules = config.rules; - -template -static void parse(Object& object, auto&& value) { - if constexpr(std::is_same_v) { - if(auto v = value.as_boolean()) { - object = v->get(); - } - } else if constexpr(clice::integral) { - if(auto v = value.as_integer()) { - object = v->get(); - } - } else if constexpr(std::is_same_v) { - if(auto v = value.as_string()) { - object = v->get(); - } - } else if constexpr(clice::is_specialization_of) { - if(auto v = value.as_array()) { - for(auto& item: *v) { - object.emplace_back(); - parse(object.back(), item); - } - } - } else if constexpr(refl::reflectable) { - if(auto table = value.as_table()) { - refl::foreach(object, [&](std::string_view key, auto& member) { - if(auto v = (*table)[key]) { - parse(member, v); - } - }); - } - } else { - static_assert(dependent_false, "Unsupported type"); - } -} - -void load(llvm::StringRef execute, llvm::StringRef filename) { - predefined["version"] = "0.0.1"; - predefined["binary"] = execute; - predefined["llvm_version"] = "20"; - - auto toml = toml::parse_file(filename); - if(toml.failed()) { - log::fatal("Failed to parse config file: {0}. Because: {1}", - filename, - toml.error().description()); - } - - parse(config, toml.table()); -} - -/// replace all predefined variables in the text. -static void resolve(std::string& input) { - std::string_view text = input; - llvm::SmallString<128> path; - std::size_t pos = 0; - while((pos = text.find("${", pos)) != std::string::npos) { - path.append(text.substr(0, pos)); - - auto end = text.find('}', pos + 2); - if(end == std::string::npos) { - path.append(text.substr(pos)); - break; - } - - auto variable = text.substr(pos + 2, end - (pos + 2)); - - if(auto iter = predefined.find(variable); iter != predefined.end()) { - path.append(iter->second); - } else { - path.append(text.substr(pos, end - pos + 1)); - } - - text.remove_prefix(end + 1); - pos = 0; - } - - path.append(text); - path::remove_dots(path, true); - input = path.str(); -} - -template -static void replace(Object& object) { - if constexpr(std::is_same_v) { - resolve(object); - } else if constexpr(clice::is_specialization_of) { - for(auto& item: object) { - replace(item); - } - } else if constexpr(refl::reflectable) { - refl::foreach(object, [&](auto, auto& member) { replace(member); }); - } -} - -void init(std::string_view workplace) { - predefined["workspace"] = workplace; - - replace(config); - - log::info("Config initialized successfully: {0}", json::serialize(config)); - return; -} - -} // namespace clice::config diff --git a/src/Server/Synchronizer.cpp b/src/Server/Database.cpp similarity index 84% rename from src/Server/Synchronizer.cpp rename to src/Server/Database.cpp index 635abfb7..4f541540 100644 --- a/src/Server/Synchronizer.cpp +++ b/src/Server/Database.cpp @@ -1,10 +1,12 @@ -#include "Compiler/Compiler.h" #include "Server/Logger.h" -#include "Server/Synchronizer.h" +#include "Server/Database.h" +#include "Support/Support.h" +#include "Compiler/Compiler.h" namespace clice { -void Synchronizer::sync(llvm::StringRef filename) { +/// Update the compile commands with the given file. +void CompilationDatabase::update(llvm::StringRef filename) { /// Read the compile commands from the file. json::Value json = nullptr; @@ -95,11 +97,13 @@ void Synchronizer::sync(llvm::StringRef filename) { log::info("Successfully built module map, total {0} modules", moduleMap.size()); } -void Synchronizer::sync(llvm::StringRef name, llvm::StringRef path) { - moduleMap[name] = path; +/// Update the module map with the given file and module name. +void CompilationDatabase::update(llvm::StringRef file, llvm::StringRef name) { + moduleMap[name] = file; } -llvm::StringRef Synchronizer::lookup(llvm::StringRef file) const { +/// Lookup the compile commands of the given file. +llvm::StringRef CompilationDatabase::getCommand(llvm::StringRef file) { auto iter = commands.find(file); if(iter == commands.end()) { return ""; @@ -107,7 +111,8 @@ llvm::StringRef Synchronizer::lookup(llvm::StringRef file) const { return iter->second; } -llvm::StringRef Synchronizer::map(llvm::StringRef name) const { +/// Lookup the module interface unit file path of the given module name. +llvm::StringRef CompilationDatabase::getModuleFile(llvm::StringRef name) { auto iter = moduleMap.find(name); if(iter == moduleMap.end()) { return ""; diff --git a/src/Server/Document.cpp b/src/Server/Document.cpp index f3031bc3..66c49505 100644 --- a/src/Server/Document.cpp +++ b/src/Server/Document.cpp @@ -3,27 +3,19 @@ namespace clice { -async::promise Server::onDidOpen(const proto::DidOpenTextDocumentParams& params) { - auto path = SourceConverter::toPath(params.textDocument.uri); - llvm::StringRef content = params.textDocument.text; - co_await scheduler.update(path, content, synchronizer); -} - -async::promise Server::onDidChange(const proto::DidChangeTextDocumentParams& document) { - auto path = SourceConverter::toPath(document.textDocument.uri); - llvm::StringRef content = document.contentChanges[0].text; - co_await scheduler.update(path, content, synchronizer); -} - -async::promise Server::onDidSave(const proto::DidSaveTextDocumentParams& document) { - auto path = SourceConverter::toPath(document.textDocument.uri); - /// co_await scheduler.save(path); +async::Task<> Server::onDidOpen(const proto::DidOpenTextDocumentParams& params) { co_return; } -async::promise Server::onDidClose(const proto::DidCloseTextDocumentParams& document) { - auto path = SourceConverter::toPath(document.textDocument.uri); - /// co_await scheduler.close(path); +async::Task<> Server::onDidChange(const proto::DidChangeTextDocumentParams& document) { + co_return; +} + +async::Task<> Server::onDidSave(const proto::DidSaveTextDocumentParams& document) { + co_return; +} + +async::Task<> Server::onDidClose(const proto::DidCloseTextDocumentParams& document) { co_return; } diff --git a/src/Server/Extension.cpp b/src/Server/Extension.cpp index 37e9b5cd..5008fcaa 100644 --- a/src/Server/Extension.cpp +++ b/src/Server/Extension.cpp @@ -2,15 +2,15 @@ namespace clice { -async::promise Server::onContextCurrent(const proto::TextDocumentIdentifier& params) { +async::Task<> Server::onContextCurrent(const proto::TextDocumentIdentifier& params) { co_return; } -async::promise Server::onContextAll(const proto::TextDocumentIdentifier& params) { +async::Task<> Server::onContextAll(const proto::TextDocumentIdentifier& params) { co_return; } -async::promise Server::onContextSwitch(const proto::TextDocumentIdentifier& params) { +async::Task<> Server::onContextSwitch(const proto::TextDocumentIdentifier& params) { co_return; } diff --git a/src/Server/Feature.cpp b/src/Server/Feature.cpp index 2ada6162..058cb686 100644 --- a/src/Server/Feature.cpp +++ b/src/Server/Feature.cpp @@ -3,127 +3,110 @@ namespace clice { -async::promise Server::onGotoDeclaration(json::Value id, - const proto::DeclarationParams& params) { +async::Task<> Server::onGotoDeclaration(json::Value id, const proto::DeclarationParams& params) { co_return; } -async::promise Server::onGotoDefinition(json::Value id, - const proto::DefinitionParams& params) { +async::Task<> Server::onGotoDefinition(json::Value id, const proto::DefinitionParams& params) { co_return; } -async::promise Server::onGotoTypeDefinition(json::Value id, - const proto::TypeDefinitionParams& params) { +async::Task<> Server::onGotoTypeDefinition(json::Value id, + const proto::TypeDefinitionParams& params) { co_return; } -async::promise Server::onGotoImplementation(json::Value id, - const proto::ImplementationParams& params) { +async::Task<> Server::onGotoImplementation(json::Value id, + const proto::ImplementationParams& params) { co_return; } -async::promise Server::onFindReferences(json::Value id, - const proto::ReferenceParams& params) { +async::Task<> Server::onFindReferences(json::Value id, const proto::ReferenceParams& params) { co_return; } -async::promise Server::onPrepareCallHierarchy( - json::Value id, const proto::CallHierarchyPrepareParams& params) { +async::Task<> Server::onPrepareCallHierarchy(json::Value id, + const proto::CallHierarchyPrepareParams& params) { co_return; } -async::promise Server::onIncomingCall(json::Value id, - const proto::CallHierarchyIncomingCallsParams& params) { +async::Task<> Server::onIncomingCall(json::Value id, + const proto::CallHierarchyIncomingCallsParams& params) { co_return; } -async::promise Server::onOutgoingCall(json::Value id, - const proto::CallHierarchyOutgoingCallsParams& params) { +async::Task<> Server::onOutgoingCall(json::Value id, + const proto::CallHierarchyOutgoingCallsParams& params) { co_return; } -async::promise Server::onPrepareTypeHierarchy( - json::Value id, const proto::TypeHierarchyPrepareParams& params) { +async::Task<> Server::onPrepareTypeHierarchy(json::Value id, + const proto::TypeHierarchyPrepareParams& params) { co_return; } -async::promise Server::onSupertypes(json::Value id, - const proto::TypeHierarchySupertypesParams& params) { +async::Task<> Server::onSupertypes(json::Value id, + const proto::TypeHierarchySupertypesParams& params) { co_return; } -async::promise Server::onSubtypes(json::Value id, - const proto::TypeHierarchySubtypesParams& params) { +async::Task<> Server::onSubtypes(json::Value id, const proto::TypeHierarchySubtypesParams& params) { co_return; } -async::promise Server::onDocumentHighlight(json::Value id, - const proto::DocumentHighlightParams& params) { +async::Task<> Server::onDocumentHighlight(json::Value id, + const proto::DocumentHighlightParams& params) { co_return; } -async::promise Server::onDocumentLink(json::Value id, - const proto::DocumentLinkParams& params) { +async::Task<> Server::onDocumentLink(json::Value id, const proto::DocumentLinkParams& params) { co_return; } -async::promise Server::onHover(json::Value id, const proto::HoverParams& params) { +async::Task<> Server::onHover(json::Value id, const proto::HoverParams& params) { co_return; } -async::promise Server::onCodeLens(json::Value id, const proto::CodeLensParams& params) { +async::Task<> Server::onCodeLens(json::Value id, const proto::CodeLensParams& params) { co_return; } -async::promise Server::onFoldingRange(json::Value id, - const proto::FoldingRangeParams& params) { +async::Task<> Server::onFoldingRange(json::Value id, const proto::FoldingRangeParams& params) { co_return; } -async::promise Server::onDocumentSymbol(json::Value id, - const proto::DocumentSymbolParams& params) { +async::Task<> Server::onDocumentSymbol(json::Value id, const proto::DocumentSymbolParams& params) { co_return; } -async::promise Server::onSemanticTokens(json::Value id, - const proto::SemanticTokensParams& params) { - auto path = SourceConverter::toPath(params.textDocument.uri); - proto::SemanticTokens result; - co_await scheduler.execute(path, [&id, &path, &result](ASTInfo& info) { - result = feature::semanticTokens(info, path); - }); - async::response(std::move(id), json::serialize(result)); +async::Task<> Server::onSemanticTokens(json::Value id, const proto::SemanticTokensParams& params) { co_return; } -async::promise Server::onInlayHint(json::Value id, const proto::InlayHintParams& params) { +async::Task<> Server::onInlayHint(json::Value id, const proto::InlayHintParams& params) { co_return; } -async::promise Server::onCodeCompletion(json::Value id, - const proto::CompletionParams& params) { +async::Task<> Server::onCodeCompletion(json::Value id, const proto::CompletionParams& params) { // auto path = URI::resolve(params.textDocument.uri); // async::response(std::move(id), json::serialize(result)); co_return; } -async::promise Server::onSignatureHelp(json::Value id, - const proto::SignatureHelpParams& params) { +async::Task<> Server::onSignatureHelp(json::Value id, const proto::SignatureHelpParams& params) { co_return; } -async::promise Server::onCodeAction(json::Value id, const proto::CodeActionParams& params) { +async::Task<> Server::onCodeAction(json::Value id, const proto::CodeActionParams& params) { co_return; } -async::promise Server::onFormatting(json::Value id, - const proto::DocumentFormattingParams& params) { +async::Task<> Server::onFormatting(json::Value id, const proto::DocumentFormattingParams& params) { co_return; } -async::promise Server::onRangeFormatting(json::Value id, - const proto::DocumentRangeFormattingParams& params) { +async::Task<> Server::onRangeFormatting(json::Value id, + const proto::DocumentRangeFormattingParams& params) { co_return; } diff --git a/src/Server/File.cpp b/src/Server/File.cpp new file mode 100644 index 00000000..25ab55dc --- /dev/null +++ b/src/Server/File.cpp @@ -0,0 +1,41 @@ +#include "Server/Scheduler.h" + +namespace clice { + +async::Task<> FileController::open(llvm::StringRef file) { + /// 首先根据 rule 来对 file 进行判别 + + /// 查看一下这 file 属于什么模式 + + /// 1. 正常的源文件或者模块文件 + + /// 预处理一遍源文件,获取一些相关的信息 + /// 是不是 module file? + /// 获取模块依赖关系 + /// 或者计算 Preamble 的位置 + + auto command = database.getCommand(file); + + /// 如果不是 readonly 模式 + /// 调用 CacheController 里面对应的函数更新这个文件的 cache + /// PCH or PCM + + /// 准备 CompilationParams 进行 AST 编译 + + /// 调度索引任务 ... + + /// 2. 头文件 + + /// 先检查一下该头文件是否有选中的上下文... [一组 include 位置] + /// + /// 对上下文源文件进行上述的处理 + + /// + /// ... + + /// 重新索引依赖这个头文件的源文件 + + co_return; +} + +} // namespace clice diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp new file mode 100644 index 00000000..a8b5eea8 --- /dev/null +++ b/src/Server/Lifecycle.cpp @@ -0,0 +1,22 @@ +#include "Basic/SourceConverter.h" +#include "Server/Server.h" + +namespace clice { + +async::Task<> Server::onInitialize(json::Value id, const proto::InitializeParams& params) { + co_return; +} + +async::Task<> Server::onInitialized(const proto::InitializedParams& params) { + co_return; +} + +async::Task<> Server::onExit(const proto::None&) { + co_return; +} + +async::Task<> Server::onShutdown(json::Value id, const proto::None&) { + co_return; +} + +} // namespace clice diff --git a/src/Server/Lifestyle.cpp b/src/Server/Lifestyle.cpp deleted file mode 100644 index d755c405..00000000 --- a/src/Server/Lifestyle.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "Basic/SourceConverter.h" -#include "Server/Server.h" - -namespace clice { - -async::promise Server::onInitialize(json::Value id, const proto::InitializeParams& params) { - auto workplace = SourceConverter::toPath(params.workspaceFolders[0].uri); - config::init(workplace); - - if(!params.capabilities.workspace.didChangeWatchedFiles.dynamicRegistration) { - log::fatal( - "clice requires the client to support file event watching to monitor updates to CDB files"); - } - - proto::InitializeResult result = {}; - async::response(std::move(id), json::serialize(result)); - - /// Load the compile commands from the workspace. - for(auto dir: config::server.compile_commands_dirs) { - synchronizer.sync(dir + "/compile_commands.json"); - } - - co_return; -} - -async::promise Server::onInitialized(const proto::InitializedParams& params) { - proto::DidChangeWatchedFilesRegistrationOptions options; - for(auto& dir: config::server.compile_commands_dirs) { - options.watchers.emplace_back(proto::FileSystemWatcher{ - dir + "/compile_commands.json", - }); - } - async::registerCapacity("watchedFiles", - "workspace/didChangeWatchedFiles", - json::serialize(options)); - - /// Load all information about PCHs and PCMs from disk. - scheduler.loadCache(); - co_return; -} - -async::promise Server::onExit(const proto::None&) { - co_return; -} - -async::promise Server::onShutdown(json::Value id, const proto::None&) { - /// Save all information about PCHs and PCMs to disk. - scheduler.saveCache(); - co_return; -} - -} // namespace clice diff --git a/src/Server/Scheduler.cpp b/src/Server/Scheduler.cpp deleted file mode 100644 index d1b8be14..00000000 --- a/src/Server/Scheduler.cpp +++ /dev/null @@ -1,416 +0,0 @@ -#include "Server/Config.h" -#include "Server/Scheduler.h" -#include "Server/Server.h" - -namespace clice { - -static std::string getPCHOutPath(llvm::StringRef srcPath) { - llvm::SmallString<128> outPath = srcPath; - path::replace_path_prefix(outPath, config::workspace, config::cache.dir); - path::replace_extension(outPath, ".pch"); - - if(auto dir = path::parent_path(outPath); !fs::exists(dir)) { - if(auto error = fs::create_directories(dir)) { - log::fatal("Failed to create directory {0}, because {1}", dir, error.message()); - } - } - - return outPath.str().str(); -} - -static std::string getPCMOutPath(llvm::StringRef srcPath) { - llvm::SmallString<128> outPath = srcPath; - path::replace_path_prefix(outPath, config::workspace, config::cache.dir); - path::replace_extension(outPath, ".pcm"); - - if(auto dir = path::parent_path(outPath); !fs::exists(dir)) { - if(auto error = fs::create_directories(dir)) { - log::fatal("Failed to create directory {0}, because {1}", dir, error.message()); - } - } - - return outPath.str().str(); -} - -/// Check whether the file has been modified after the given status. -static bool hasModifiedAfter(fs::file_status& src, llvm::StringRef file) { - fs::file_status status; - if(auto error = fs::status(file, status)) { - log::warn("Failed to get status of {0}, because {1}", file, error.message()); - return true; - } - - return status.getLastModificationTime() > src.getLastModificationTime(); -} - -async::promise<> Scheduler::updatePCH(llvm::StringRef srcPath, llvm::StringRef content) { - bool needUpdate = false; - - PCHInfo info; - - /// Check whether the PCH needs to be updated. - if(auto iter = pchs.find(srcPath); iter == pchs.end()) { - needUpdate = true; - } else { - info = iter->second; - co_await async::schedule_task([&content, &needUpdate, &info] { - /// Check whether PCH file exists. - if(!fs::exists(info.path)) { - needUpdate = true; - } - - /// Check whether the content of the PCH is consistent with the source file. - auto size = info.bounds().Size; - if(content.substr(0, size) != info.preamble.substr(0, size)) { - needUpdate = true; - } - - /// Check whether the dependent files have been modified. - fs::file_status status; - if(auto error = fs::status(info.path, status)) { - log::warn("Failed to get status of {0}, because {1}", info.path, error.message()); - needUpdate = true; - } - - for(auto& dep: info.deps) { - if(hasModifiedAfter(status, dep)) { - needUpdate = true; - } - } - }); - } - - if(!needUpdate) { - log::info("PCH for {0} is already up-to-date, reuse it", srcPath); - co_return; - } - - CompilationParams params; - params.content = content; - params.srcPath = srcPath; - params.outPath = getPCHOutPath(srcPath); - - /// Build PCH. - Tracer tracer; - log::info("Building PCH for {0}", srcPath); - - /// FIXME: consider header context. - params.computeBounds(); - - llvm::Error error = co_await async::schedule_task([&] -> llvm::Error { - auto result = compile(params, info); - if(!result) { - return result.takeError(); - } - - /// FIXME: consider indexing PCH here. - - return llvm::Error::success(); - }); - - pchs[srcPath] = std::move(info); - - if(error) { - log::warn("Failed to build PCH for {0}, because {1}", srcPath, error); - co_return; - } else { - log::info("PCH for {0} is up-to-date, elapsed {1}", srcPath, tracer.duration()); - } -} - -llvm::Error Scheduler::addModuleDeps(CompilationParams& params, - const ModuleInfo& moduleInfo) const { - for(auto& mod: moduleInfo.mods) { - auto iter = pcms.find(mod); - - if(iter == pcms.end()) { - return error("Cannot find PCM for module {0}", mod); - } - - /// Add prerequired PCM. - if(auto error = addModuleDeps(params, iter->second)) { - return error; - } - - params.addPCM(iter->second); - } - - return llvm::Error::success(); -} - -async::promise<> Scheduler::updatePCM(llvm::StringRef moduleName, class Synchronizer& sync) { - llvm::StringRef srcPath = sync.map(moduleName); - if(srcPath.empty()) { - log::warn("Cannot find source file for module {0}", moduleName); - co_return; - } - - /// At first, scan the module to get module name and dependent modules. - CompilationParams params; - params.srcPath = srcPath; - params.command = sync.lookup(srcPath); - params.outPath = getPCMOutPath(srcPath); - - auto moduleInfo = scanModule(params); - if(!moduleInfo) { - log::warn("Build AST for {0} failed, because {1}", srcPath, moduleInfo.takeError()); - co_return; - } - - /// If the module is an interface unit, we need to update the module map. - if(moduleInfo->isInterfaceUnit) { - sync.sync(moduleName, srcPath); - } - - /// FIXME: If two pcms have same deps, we will check the same deps twice. - /// Try to skip this by using a set to store deps. - - /// Try to update dependent PCMs. - for(auto& mod: moduleInfo->mods) { - co_await updatePCM(mod, sync); - } - - /// All dependent PCMs are up-to-date, check whether the PCM needs to be updated. - bool needUpdate = false; - - PCMInfo info; - - if(auto iter = pcms.find(moduleName); iter == pcms.end()) { - needUpdate = true; - } else { - info = iter->second; - needUpdate = co_await async::schedule_task([&info] { - /// Check whether PCM file exists. - if(!fs::exists(info.path)) { - return true; - } - - fs::file_status status; - if(auto error = fs::status(info.path, status)) { - log::warn("Failed to get status of {0}, because {1}", info.path, error.message()); - return true; - } - - /// Check whether the source file has been modified. - if(hasModifiedAfter(status, info.srcPath)) { - return true; - } - - /// Check whether the dependent files have been modified. - for(auto& dep: info.deps) { - if(hasModifiedAfter(status, dep)) { - return true; - } - } - - return false; - }); - } - - if(!needUpdate) { - log::info("PCM for {0} is already up-to-date, reuse it", srcPath); - co_return; - } - - /// Build PCM. - Tracer tracer; - log::info("Building PCM for {0}", srcPath); - - /// Add deps. - if(auto error = addModuleDeps(params, *moduleInfo)) { - log::warn("Failed to build PCM for {0}, because {1}", srcPath, error); - co_return; - } - - llvm::Error error = co_await async::schedule_task([&] -> llvm::Error { - auto result = compile(params, info); - if(!result) { - return result.takeError(); - } - - /// FIXME: consider indexing PCH here. - - return llvm::Error::success(); - }); - - pcms[moduleName] = std::move(info); - - if(error) { - log::warn("Failed to build PCM for {0}, because {1}", srcPath, error); - co_return; - } else { - log::info("PCM for {0} is up-to-date, elapsed {1}", srcPath, tracer.duration()); - } -} - -async::promise<> Scheduler::updateAST(llvm::StringRef filename, - llvm::StringRef content, - class Synchronizer& sync) { - CompilationParams params; - params.content = content; - params.srcPath = filename; - params.command = sync.lookup(filename); - - auto moduleInfo = scanModule(params); - if(!moduleInfo) { - log::warn("Build AST for {0} failed, because {1}", filename, moduleInfo.takeError()); - co_return; - } - - if(moduleInfo->name.empty() && moduleInfo->mods.empty()) { - co_await updatePCH(filename, content); - params.addPCH(pchs[params.srcPath]); - } else { - /// FIXME: it is possible that building PCM parallelly, if they have same deps. - for(auto& mod: moduleInfo->mods) { - co_await updatePCM(mod, sync); - } - - if(auto error = addModuleDeps(params, *moduleInfo)) { - log::warn("Failed to build PCM for {0}, because {1}", filename, error); - co_return; - } - } - - /// Build AST. - ASTInfo& info = *files[filename].info; - - Tracer tracer; - - log::info("Building AST for {0}", filename); - - co_await async::schedule_task([&] { - auto result = compile(params); - if(!result) { - log::warn("Failed to build AST for {0}, because {1}", filename, result.takeError()); - return; - } - - info = std::move(*result); - }); - - /// Build AST successfully. - log::info("AST for {0} is up-to-date, elapsed {1}", filename, tracer.duration()); -} - -void Scheduler::loadCache() { - llvm::SmallString<128> fileName; - path::append(fileName, config::cache.dir, "cache.json"); - - auto buffer = llvm::MemoryBuffer::getFile(fileName); - if(!buffer) { - log::warn("Failed to load cache from disk, because {0}", buffer.getError().message()); - return; - } - - auto json = json::parse(buffer.get()->getBuffer()); - if(!json) { - log::warn("Failed to parse cache from disk, because {0}", json.takeError()); - return; - } - - auto object = json->getAsObject(); - if(!object) { - log::warn("Failed to parse cache from disk, because {0}", json.takeError()); - return; - } - - if(auto pchArray = object->getArray("PCH")) { - for(auto& value: *pchArray) { - auto pch = json::deserialize(value); - pchs[pch.srcPath] = std::move(pch); - } - } - - if(auto pcmArray = object->getArray("PCM")) { - for(auto& value: *pcmArray) { - auto pcm = json::deserialize(value); - pcms[pcm.name] = std::move(pcm); - } - } - - log::info("Cache loaded from {0}", fileName); -} - -void Scheduler::saveCache() const { - json::Object result; - - json::Array pchArray; - for(auto& [name, pch]: pchs) { - pchArray.emplace_back(json::serialize(pch)); - } - result.try_emplace("PCH", std::move(pchArray)); - - json::Array pcmArray; - for(auto& [name, pcm]: pcms) { - pcmArray.emplace_back(json::serialize(pcm)); - } - result.try_emplace("PCM", std::move(pcmArray)); - - llvm::SmallString<128> fileName; - path::append(fileName, config::cache.dir, "cache.json"); - - std::error_code EC; - llvm::raw_fd_ostream stream(fileName, EC, llvm::sys::fs::OF_Text); - - if(EC) { - log::warn("Failed save cache to disk, because {0}", EC.message()); - return; - } - - stream << json::Value(std::move(result)); - log::info("Cache saved to {0}", fileName); -} - -async::promise<> Scheduler::waitForFile(llvm::StringRef filename) { - File* file = &files[filename]; - - if(!file->isIdle) { - co_await async::suspend([file](auto handle) { file->waiters.push_back(handle); }); - } -} - -void Scheduler::scheduleNext(llvm::StringRef filename) { - auto file = &files[filename]; - if(file->waiters.empty()) { - file->isIdle = true; - } else { - /// If waiters exist, wake up the first waiter. - auto handle = file->waiters.front(); - file->waiters.pop_front(); - async::schedule(handle); - } -} - -async::promise<> Scheduler::update(llvm::StringRef filename, - llvm::StringRef content, - class Synchronizer& sync) { - co_await waitForFile(filename); - - /// files may be modified during the action. - auto file = &files[filename]; - file->isIdle = false; - - /// If the file is idle, execute the action directly. - assert(file->info && "ASTInfo is required"); - co_await updateAST(filename, content, sync); - - scheduleNext(filename); -} - -async::promise<> Scheduler::execute(llvm::StringRef filename, - llvm::unique_function action) { - co_await waitForFile(filename); - - /// files may be modified during the action. - auto file = &files[filename]; - assert(file->info && "ASTInfo is required"); - - /// If the file is idle, execute the action directly. - file->isIdle = false; - co_await async::schedule_task([&action, file] { action(*file->info); }); - - scheduleNext(filename); -} - -} // namespace clice diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index 222059fe..a1f76639 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -1,3 +1,4 @@ +#include "Server/Logger.h" #include "Server/Server.h" #include "llvm/Support/CommandLine.h" @@ -18,7 +19,7 @@ int Server::run(int argc, const char** argv) { if(cl::config.empty()) { log::warn("No config file specified; using default configuration."); } else { - config::load(argv[0], cl::config.getValue()); + /// config::load(argv[0], cl::config.getValue()); log::info("Successfully loaded configuration file from {0}.", cl::config.getValue()); } @@ -28,7 +29,7 @@ int Server::run(int argc, const char** argv) { return 1; } - auto dispatch = [this](json::Value value) -> async::promise { + auto dispatch = [this](json::Value value) -> async::Task<> { assert(value.kind() == json::Value::Object); auto object = value.getAsObject(); assert(object && "value is not an object"); @@ -37,11 +38,11 @@ int Server::run(int argc, const char** argv) { auto params = object->get("params"); if(auto id = object->get("id")) { if(auto iter = requests.find(name); iter != requests.end()) { - auto tracer = Tracer(); + /// auto tracer = Tracer(); log::info("Receive request: {0}", name); co_await iter->second(std::move(*id), params ? std::move(*params) : json::Value(nullptr)); - log::info("Request {0} is done, elapsed {1}", name, tracer.duration()); + log::info("Request {0} is done, elapsed {1}", name, 0); } else { log::warn("Unknown request: {0}", name); @@ -59,7 +60,7 @@ int Server::run(int argc, const char** argv) { co_return; }; - async::start_server(dispatch, "127.0.0.1", 50051); + async::listen(dispatch, "127.0.0.1", 50051); return 0; } diff --git a/src/Server/Workplace.cpp b/src/Server/Workplace.cpp deleted file mode 100644 index e10a697b..00000000 --- a/src/Server/Workplace.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "Basic/SourceConverter.h" -#include "Server/Server.h" - -namespace clice { - -async::promise<> Server::onDidChangeWatchedFiles(const proto::DidChangeWatchedFilesParams& params) { - for(auto& event: params.changes) { - switch(event.type) { - case proto::FileChangeType::Created: { - break; - } - - case proto::FileChangeType::Changed: { - auto path = SourceConverter::toPath(event.uri); - synchronizer.sync(path); - break; - } - - case proto::FileChangeType::Deleted: { - break; - } - } - } - - co_return; -} - -} // namespace clice diff --git a/src/Server/Workspace.cpp b/src/Server/Workspace.cpp new file mode 100644 index 00000000..d5fca396 --- /dev/null +++ b/src/Server/Workspace.cpp @@ -0,0 +1,10 @@ +#include "Basic/SourceConverter.h" +#include "Server/Server.h" + +namespace clice { + +async::Task<> Server::onDidChangeWatchedFiles(const proto::DidChangeWatchedFilesParams& params) { + co_return; +} + +} // namespace clice diff --git a/unittests/Compiler/Module.cpp b/unittests/Compiler/Module.cpp index a6aee260..a156d4aa 100644 --- a/unittests/Compiler/Module.cpp +++ b/unittests/Compiler/Module.cpp @@ -108,6 +108,51 @@ export module A; // ASSERT_EQ(pcm.mods.size(), 0); } +TEST(Module, ScanModuleName) { + CompilationParams params; + + /// Test module name not in condition directive. + params.content = "export module A;"; + ASSERT_EQ(scanModuleName(params), "A"); + + params.content = "export module A.B.C.D;"; + ASSERT_EQ(scanModuleName(params), "A.B.C.D"); + + params.content = "export module A:B;"; + ASSERT_EQ(scanModuleName(params), "A:B"); + + params.content = R"( +module; +#ifdef TEST +#include +#endif +export module A; +)"; + ASSERT_EQ(scanModuleName(params), "A"); + + /// Test non-module interface unit. + params.content = "module A;"; + ASSERT_EQ(scanModuleName(params), ""); + + params.content = ""; + ASSERT_EQ(scanModuleName(params), ""); + + /// Test module name in condition directive. + params.content = R"( +#ifdef TEST +export module A; +#else +export module B; +#endif +)"; + params.srcPath = "main.cppm"; + params.command = "clang++ -std=c++20 -x c++ main.cppm -DTEST"; + ASSERT_EQ(scanModuleName(params), "A"); + + params.command = "clang++ -std=c++20 -x c++ main.cppm"; + ASSERT_EQ(scanModuleName(params), "B"); +} + } // namespace } // namespace clice diff --git a/unittests/Compiler/Preamble.cpp b/unittests/Compiler/Preamble.cpp new file mode 100644 index 00000000..4e64a8ba --- /dev/null +++ b/unittests/Compiler/Preamble.cpp @@ -0,0 +1,8 @@ +#include "gtest/gtest.h" +#include "Compiler/Preamble.h" + +namespace { + + + +} diff --git a/unittests/Server/Async.cpp b/unittests/Server/Async.cpp index aa94e130..d89ceaf2 100644 --- a/unittests/Server/Async.cpp +++ b/unittests/Server/Async.cpp @@ -3,26 +3,7 @@ namespace clice { -namespace { - -async::promise add(int a, int b) { - co_return a + b; -} - -async::promise add2(int a, int b) { - auto result = co_await add(a, b); - co_return result; -} - -TEST(clice, coroutine) { - auto p = add2(1, 2); - async::run(p); - ASSERT_TRUE(p.done()); - ASSERT_EQ(p.handle().promise().value, 3); - p.destroy(); -} - -} // namespace +namespace {} // namespace } // namespace clice