feat: persistent PCH/PCM cache across sessions (#391)
## Summary
PCH and PCM artifacts are now cached to disk at
`.clice/cache/{pch/,pcm/}` with content-addressed filenames, so they
survive server restarts. Dependency metadata is persisted in
`cache.json` (using eventide serde) with a shared path table for
deduplication.
### Key changes
- **protocol.h**: `output_path` field on `BuildPCHParams` /
`BuildPCMParams` so master specifies where workers write
- **stateless_worker.cpp**: Atomic write via `.tmp` + `fs::rename`;
`CompilationUnit` destroyed before rename to flush the file to disk;
fallback to temp file when `output_path` is empty (unit tests)
- **master_server.h**: `PCMState` struct, `pcm_states` map,
`load_cache()` / `save_cache()` / `cleanup_cache()` methods
- **master_server.cpp**: Cache lifecycle — load from `cache.json` on
startup, save after each PCH/PCM build and on exit; deterministic path
computation (`xxh3` preamble hash for PCH, module name + source path
hash for PCM); stale files (>7 days) cleaned on startup; `cache.json`
uses shared path table to avoid redundant storage of header paths across
entries
- **filesystem.h**: `fs::rename()` helper; `ThreadSafeFS` broadened to
match `.pch` extension instead of `preamble-` prefix
- **tests**: 11 new integration tests covering PCH/PCM persistence,
cross-session reuse, staleness detection, shared preamble dedup, and
restart survival; unit tests updated with `output_path`
### Naming scheme
- **PCH**: `.clice/cache/pch/<016x(xxh3(preamble))>.pch`
- **PCM**:
`.clice/cache/pcm/<module_name>-<016x(xxh3(source_path))>.pcm`
## Test plan
- [x] Unit tests — 448 passed
- [x] Integration tests — 92 passed (including 11 new persistent cache
tests)
- [x] Smoke tests — 1 passed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,7 @@ TEST_CASE(BuildPCMThenCompileWithImport) {
|
||||
"--precompile",
|
||||
iface};
|
||||
params.module_name = "Hello";
|
||||
params.output_path = tmp.path("Hello.pcm");
|
||||
|
||||
auto result = co_await sl.peer->send_request(params);
|
||||
CO_ASSERT_TRUE(result.has_value());
|
||||
@@ -134,6 +135,7 @@ TEST_CASE(BuildPCMChainThenCompile) {
|
||||
"--precompile",
|
||||
mod_a};
|
||||
params.module_name = "A";
|
||||
params.output_path = tmp.path("A.pcm");
|
||||
|
||||
auto result = co_await sl.peer->send_request(params);
|
||||
CO_ASSERT_TRUE(result.has_value() && result.value().success);
|
||||
@@ -152,6 +154,7 @@ TEST_CASE(BuildPCMChainThenCompile) {
|
||||
"--precompile",
|
||||
mod_b};
|
||||
params.module_name = "B";
|
||||
params.output_path = tmp.path("B.pcm");
|
||||
params.pcms = {
|
||||
{"A", pcm_a}
|
||||
};
|
||||
@@ -232,6 +235,7 @@ TEST_CASE(ModuleImplementationUnitWithWorker) {
|
||||
"--precompile",
|
||||
iface};
|
||||
params.module_name = "Calc";
|
||||
params.output_path = tmp.path("Calc.pcm");
|
||||
|
||||
auto result = co_await sl.peer->send_request(params);
|
||||
CO_ASSERT_TRUE(result.has_value() && result.value().success);
|
||||
|
||||
Reference in New Issue
Block a user