Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
158 changes: 157 additions & 1 deletion src/server/master_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "support/filesystem.h"
#include "support/logging.h"
#include "syntax/dependency_graph.h"
#include "syntax/scan.h"

namespace clice {

Expand Down Expand Up @@ -103,13 +104,71 @@ et::task<> MasterServer::run_build_drain(std::uint32_t path_id, std::string uri)
doc_it->second.build_requested = false;
auto gen = doc_it->second.generation;

// Ensure module dependencies are compiled first.
if(compile_graph) {
// Scan this file for module imports and compile them.
auto file_path = path_pool.resolve(path_id);
auto cdb_results =
cdb.lookup(file_path, {.query_toolchain = true, .suppress_logging = true});
bool deps_ok = true;
if(!cdb_results.empty()) {
auto scan_result = scan_precise(cdb_results[0].arguments, cdb_results[0].directory);
for(auto& mod_name: scan_result.modules) {
auto mod_ids = dependency_graph.lookup_module(mod_name);
if(!mod_ids.empty()) {
auto r = co_await compile_graph->compile(mod_ids[0]).catch_cancel();
if(!r.has_value() || !*r) {
deps_ok = false;
}
}
}
// Module implementation units (module M; without export) need
// their interface PCM but don't have an explicit import.
if(!scan_result.module_name.empty() && !scan_result.is_interface_unit) {
auto mod_ids = dependency_graph.lookup_module(scan_result.module_name);
if(!mod_ids.empty()) {
auto r = co_await compile_graph->compile(mod_ids[0]).catch_cancel();
if(!r.has_value() || !*r) {
deps_ok = false;
}
}
}
}
if(!deps_ok) {
LOG_WARN("Module dependency build failed for {}, skipping compile", uri);
doc_it = documents.find(path_id);
if(doc_it != documents.end()) {
doc_it->second.build_running = false;
doc_it->second.drain_scheduled = false;
}
co_return;
}
}

// Re-lookup document after co_awaits in compile_graph section.
doc_it = documents.find(path_id);
if(doc_it == documents.end())
co_return;

// Send compile request to stateful worker
worker::CompileParams params;
params.path = std::string(path_pool.resolve(path_id));
params.version = doc_it->second.version;
params.text = doc_it->second.text;
fill_compile_args(path_pool.resolve(path_id), params.directory, params.arguments);

// Fill all available PCM paths (clang needs transitive deps).
// Skip the file's own PCM — a module interface must not receive its
// own precompiled module, or clang reports "multiple module declarations".
for(auto& [pid, pcm_path]: pcm_paths) {
if(pid == path_id)
continue;
auto mod_it = path_to_module.find(pid);
if(mod_it != path_to_module.end()) {
params.pcms[mod_it->second] = pcm_path;
}
}

LOG_DEBUG("Sending compile: path={}, args={}, gen={}",
params.path,
params.arguments.size(),
Expand Down Expand Up @@ -217,12 +276,86 @@ et::task<> MasterServer::load_workspace() {
if(unresolved > 0) {
LOG_WARN("{} unresolved includes", unresolved);
}

// Build reverse mapping: path_id -> module name.
for(auto& [module_name, path_ids]: dependency_graph.modules()) {
for(auto path_id: path_ids) {
path_to_module[path_id] = module_name.str();
}
}

if(path_to_module.empty()) {
LOG_INFO("No C++20 modules detected, skipping CompileGraph");
co_return;
}

// Lazy dependency resolver: scans a module file on demand to discover imports.
auto resolve = [this](std::uint32_t path_id) -> llvm::SmallVector<std::uint32_t> {
auto file_path = path_pool.resolve(path_id);
auto results = cdb.lookup(file_path, {.query_toolchain = true, .suppress_logging = true});
if(results.empty()) {
return {};
}

auto& ctx = results[0];
auto scan_result = scan_precise(ctx.arguments, ctx.directory);

llvm::SmallVector<std::uint32_t> deps;
for(auto& mod_name: scan_result.modules) {
auto mod_ids = dependency_graph.lookup_module(mod_name);
if(!mod_ids.empty()) {
deps.push_back(mod_ids[0]);
}
}
return deps;
};

// Dispatch: sends BuildPCM request to a stateless worker.
auto dispatch = [this](std::uint32_t path_id) -> et::task<bool> {
auto mod_it = path_to_module.find(path_id);
if(mod_it == path_to_module.end()) {
co_return false;
}

auto file_path = std::string(path_pool.resolve(path_id));
worker::BuildPCMParams pcm_params;
pcm_params.file = file_path;
fill_compile_args(file_path, pcm_params.directory, pcm_params.arguments);
pcm_params.module_name = mod_it->second;

// Clang needs ALL transitive PCM deps, not just direct imports.
for(auto& [pid, pcm_path]: pcm_paths) {
auto dep_mod_it = path_to_module.find(pid);
if(dep_mod_it != path_to_module.end()) {
pcm_params.pcms[dep_mod_it->second] = pcm_path;
}
}

auto result = co_await pool.send_stateless(pcm_params);
if(!result.has_value() || !result.value().success) {
LOG_WARN("BuildPCM failed for module {}: {}",
mod_it->second,
result.has_value() ? result.value().error : result.error().message);
co_return false;
}

pcm_paths[path_id] = result.value().pcm_path;
LOG_INFO("Built PCM for module {}: {}", mod_it->second, result.value().pcm_path);
co_return true;
};
Comment thread
16bit-ykiko marked this conversation as resolved.
Resolved

compile_graph = std::make_unique<CompileGraph>(std::move(dispatch), std::move(resolve));
LOG_INFO("CompileGraph initialized with {} module(s)", path_to_module.size());
}

void MasterServer::fill_compile_args(llvm::StringRef path,
std::string& directory,
std::vector<std::string>& arguments) {
auto results = cdb.lookup(path, {.query_toolchain = true});
if(results.empty()) {
LOG_WARN("No CDB entry for {}", path);
return;
}
auto& ctx = results.front();
directory = ctx.directory.str();
arguments.clear();
Expand Down Expand Up @@ -527,7 +660,30 @@ void MasterServer::register_handlers() {
if(lifecycle != ServerLifecycle::Ready)
return;

// TODO: Trigger dependent file rebuilds
auto path = uri_to_path(params.text_document.uri);
auto path_id = path_pool.intern(path);

// Invalidate this file and cascade to dependents in the compile graph.
if(compile_graph) {
auto dirtied = compile_graph->update(path_id);
// Remove stale PCMs for all invalidated units.
for(auto dirty_id: dirtied) {
pcm_paths.erase(dirty_id);
}
// Schedule rebuilds for dirtied units that are currently open.
for(auto dirty_id: dirtied) {
if(dirty_id == path_id)
continue; // The saved file itself is rebuilt by its own didChange.
if(documents.count(dirty_id)) {
auto dirty_path = path_pool.resolve(dirty_id);
auto uri = lsp::URI::from_file_path(dirty_path);
if(uri.has_value()) {
schedule_build(dirty_id, uri->str());
}
}
}
}

LOG_DEBUG("didSave: {}", params.text_document.uri);
});

Expand Down
10 changes: 10 additions & 0 deletions src/server/master_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "eventide/ipc/lsp/protocol.h"
#include "eventide/ipc/peer.h"
#include "eventide/serde/serde/raw_value.h"
#include "server/compile_graph.h"
#include "server/config.h"
#include "server/worker_pool.h"
#include "support/path_pool.h"
Expand Down Expand Up @@ -61,6 +62,15 @@ class MasterServer {
CompilationDatabase cdb;
DependencyGraph dependency_graph;

// Module compilation graph (lazy dependency resolution).
std::unique_ptr<CompileGraph> compile_graph;

// path_id -> built PCM output path (set after successful module build).
llvm::DenseMap<std::uint32_t, std::string> pcm_paths;

// path_id -> module name (for files that provide a module interface).
llvm::DenseMap<std::uint32_t, std::string> path_to_module;

// Document state: path_id -> DocumentState
llvm::DenseMap<std::uint32_t, DocumentState> documents;

Expand Down
1 change: 1 addition & 0 deletions src/server/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ struct BuildPCMParams {
struct BuildPCMResult {
bool success;
std::string error;
std::string pcm_path;
};

struct IndexParams {
Expand Down
4 changes: 2 additions & 2 deletions src/server/stateless_worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ int run_stateless_worker_mode() {

if(unit.completed()) {
LOG_INFO("BuildPCM done: module={}, {}ms", params.module_name, timer.ms());
return {true, ""};
return {true, "", std::string(cp.output_file)};
} else {
LOG_WARN("BuildPCM failed: module={}, {}ms", params.module_name, timer.ms());
return {false, "PCM compilation failed"};
return {false, "PCM compilation failed", ""};
}
});
co_return result.value();
Expand Down
11 changes: 8 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,14 @@ def test_data_dir():
if main_cpp.exists() and not cdb_path.exists():
cdb = [
{
"directory": str(hw_dir),
"file": str(main_cpp),
"arguments": ["clang++", "-std=c++17", "-fsyntax-only", str(main_cpp)],
"directory": hw_dir.as_posix(),
"file": main_cpp.as_posix(),
"arguments": [
"clang++",
"-std=c++17",
"-fsyntax-only",
main_cpp.as_posix(),
],
}
]
cdb_path.write_text(json.dumps(cdb, indent=2))
Expand Down
Loading
Loading