GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 78.6% 125 / 0 / 159
Functions: 88.9% 8 / 0 / 9
Branches: 45.2% 199 / 0 / 440

src/global/CacheManager.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #include "CacheManager.h"
4
5 #include <algorithm>
6 #include <cstring>
7 #include <fstream>
8 #include <queue>
9 #include <sstream>
10 #include <unordered_set>
11
12 #include <SourceFile.h>
13 #include <driver/Driver.h>
14 #include <global/GlobalResourceManager.h>
15 #include <util/SystemUtil.h>
16
17 #include <nlohmann/json.hpp>
18
19 namespace spice::compiler {
20
21 590 CacheManager::CacheManager(const CliOptions &cliOptions) : cliOptions(cliOptions), cacheDir(cliOptions.cacheDir) {}
22
23 4882 std::string CacheManager::computeCacheKey(const std::string &sourceCode, const std::vector<std::string> &depCacheKeys) const {
24
1/2
✓ Branch 2 → 3 taken 4882 times.
✗ Branch 2 → 50 not taken.
4882 std::stringstream components;
25
2/4
✓ Branch 3 → 4 taken 4882 times.
✗ Branch 3 → 48 not taken.
✓ Branch 5 → 6 taken 4882 times.
✗ Branch 5 → 40 not taken.
4882 components << std::hex << std::hash<std::string>{}(sourceCode);
26
1/2
✓ Branch 6 → 7 taken 4882 times.
✗ Branch 6 → 48 not taken.
4882 components << static_cast<uint8_t>(cliOptions.buildMode);
27
1/2
✓ Branch 7 → 8 taken 4882 times.
✗ Branch 7 → 48 not taken.
4882 components << static_cast<uint8_t>(cliOptions.optLevel);
28
1/2
✓ Branch 8 → 9 taken 4882 times.
✗ Branch 8 → 48 not taken.
4882 components << static_cast<uint8_t>(cliOptions.instrumentation.sanitizer);
29
1/2
✓ Branch 9 → 10 taken 4882 times.
✗ Branch 9 → 48 not taken.
4882 components << cliOptions.instrumentation.generateDebugInfo;
30
1/2
✓ Branch 11 → 12 taken 4882 times.
✗ Branch 11 → 48 not taken.
4882 components << cliOptions.targetTriple.str();
31
1/2
✓ Branch 12 → 13 taken 4882 times.
✗ Branch 12 → 48 not taken.
4882 components << cliOptions.useLTO;
32 // The output container influences codegen (PIC/PIE levels, DSO-local attributes for symbols,
33 // etc.), so reusing an object emitted for a different container would produce wrong output.
34
1/2
✓ Branch 13 → 14 taken 4882 times.
✗ Branch 13 → 48 not taken.
4882 components << static_cast<uint8_t>(cliOptions.outputContainer);
35 // Fold transitive dependency cache keys (sorted for determinism) into the key so that any
36 // change in a dependency invalidates every dependent's cache entry too.
37
1/2
✓ Branch 14 → 15 taken 4882 times.
✗ Branch 14 → 48 not taken.
4882 std::vector<std::string> sortedDepKeys = depCacheKeys;
38
1/2
✓ Branch 15 → 16 taken 4882 times.
✗ Branch 15 → 46 not taken.
4882 std::ranges::sort(sortedDepKeys);
39
2/2
✓ Branch 30 → 18 taken 5713 times.
✓ Branch 30 → 31 taken 4882 times.
15477 for (const std::string &depKey : sortedDepKeys)
40
1/2
✓ Branch 20 → 21 taken 5713 times.
✗ Branch 20 → 41 not taken.
5713 components << depKey;
41
2/4
✓ Branch 31 → 32 taken 4882 times.
✗ Branch 31 → 44 not taken.
✓ Branch 33 → 34 taken 4882 times.
✗ Branch 33 → 42 not taken.
14646 return std::to_string(std::hash<std::string>{}(components.str()));
42 4882 }
43
44 10 bool CacheManager::lookupSourceFile(SourceFile *sourceFile) const {
45
1/2
✓ Branch 2 → 3 taken 10 times.
✗ Branch 2 → 153 not taken.
10 const char *objectFileExtension = SystemUtil::getOutputFileExtension(cliOptions, OutputContainer::OBJECT_FILE);
46
3/6
✓ Branch 3 → 4 taken 10 times.
✗ Branch 3 → 103 not taken.
✓ Branch 4 → 5 taken 10 times.
✗ Branch 4 → 101 not taken.
✓ Branch 5 → 6 taken 10 times.
✗ Branch 5 → 99 not taken.
10 const std::filesystem::path metadataFilePath = cacheDir / (sourceFile->cacheKey + ".json");
47
4/8
✓ Branch 8 → 9 taken 10 times.
✗ Branch 8 → 111 not taken.
✓ Branch 9 → 10 taken 10 times.
✗ Branch 9 → 109 not taken.
✓ Branch 10 → 11 taken 10 times.
✗ Branch 10 → 107 not taken.
✓ Branch 11 → 12 taken 10 times.
✗ Branch 11 → 105 not taken.
10 const std::filesystem::path objectFilePath = cacheDir / (sourceFile->cacheKey + "." + objectFileExtension);
48
49 // Check if cache entry is available
50
7/10
✓ Branch 15 → 16 taken 10 times.
✗ Branch 15 → 149 not taken.
✓ Branch 16 → 17 taken 4 times.
✓ Branch 16 → 19 taken 6 times.
✓ Branch 17 → 18 taken 4 times.
✗ Branch 17 → 149 not taken.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 4 times.
✓ Branch 21 → 22 taken 6 times.
✓ Branch 21 → 23 taken 4 times.
10 if (!exists(metadataFilePath) || !exists(objectFilePath))
51 6 return false;
52
53 // Read metadata
54
1/2
✓ Branch 23 → 24 taken 4 times.
✗ Branch 23 → 149 not taken.
4 std::ifstream metadataFile(metadataFilePath);
55
2/4
✓ Branch 24 → 25 taken 4 times.
✗ Branch 24 → 147 not taken.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 4 times.
4 if (!metadataFile)
56 return false;
57
58 4 nlohmann::json metadata;
59 try {
60
1/2
✓ Branch 29 → 30 taken 4 times.
✗ Branch 29 → 114 not taken.
4 metadata = nlohmann::json::parse(metadataFile);
61 } catch (nlohmann::detail::parse_error &) {
62 return false;
63 }
64
65 // Verify all transitive dependency object files exist and collect their paths. We keep
66 // these even though Spice imports register themselves via their own concludeCompilation,
67 // because runtime modules (string-rt, memory-rt, ...) are pulled in implicitly during
68 // symbol-table building - a stage that gets skipped for cache-restored files. Without
69 // this list those runtime objects would be missing from the link. The linker dedupes the
70 // overlap with deps that did register themselves.
71
2/4
✓ Branch 33 → 34 taken 4 times.
✗ Branch 33 → 145 not taken.
✓ Branch 34 → 35 taken 4 times.
✗ Branch 34 → 65 not taken.
4 if (metadata.contains("dependencies")) {
72
6/10
✓ Branch 35 → 36 taken 4 times.
✗ Branch 35 → 136 not taken.
✓ Branch 38 → 39 taken 3 times.
✗ Branch 38 → 135 not taken.
✓ Branch 60 → 62 taken 3 times.
✗ Branch 60 → 136 not taken.
✓ Branch 62 → 63 taken 7 times.
✗ Branch 62 → 136 not taken.
✓ Branch 63 → 38 taken 3 times.
✓ Branch 63 → 64 taken 4 times.
7 for (const auto &depKey : metadata["dependencies"]) {
73
1/2
✓ Branch 39 → 40 taken 3 times.
✗ Branch 39 → 135 not taken.
3 const std::string key = depKey.get<std::string>();
74
4/8
✓ Branch 40 → 41 taken 3 times.
✗ Branch 40 → 128 not taken.
✓ Branch 41 → 42 taken 3 times.
✗ Branch 41 → 126 not taken.
✓ Branch 42 → 43 taken 3 times.
✗ Branch 42 → 124 not taken.
✓ Branch 43 → 44 taken 3 times.
✗ Branch 43 → 122 not taken.
3 const std::filesystem::path depObjectFilePath = cacheDir / (key + "." + objectFileExtension);
75
2/4
✓ Branch 47 → 48 taken 3 times.
✗ Branch 47 → 131 not taken.
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 3 times.
3 if (!exists(depObjectFilePath))
76 return false;
77
1/2
✓ Branch 50 → 51 taken 3 times.
✗ Branch 50 → 131 not taken.
3 sourceFile->cachedObjectFilePaths.push_back(depObjectFilePath);
78
2/4
✓ Branch 53 → 54 taken 3 times.
✗ Branch 53 → 55 not taken.
✓ Branch 58 → 59 taken 3 times.
✗ Branch 58 → 61 not taken.
6 }
79 }
80
81 // Add this file's own object file last
82
1/2
✓ Branch 65 → 66 taken 4 times.
✗ Branch 65 → 145 not taken.
4 sourceFile->cachedObjectFilePaths.push_back(objectFilePath);
83
84 // Restore linker flags and additional source paths
85
2/4
✓ Branch 66 → 67 taken 4 times.
✗ Branch 66 → 145 not taken.
✓ Branch 67 → 68 taken 4 times.
✗ Branch 67 → 79 not taken.
4 if (metadata.contains("linkerFlags"))
86
3/8
✓ Branch 68 → 69 taken 4 times.
✗ Branch 68 → 140 not taken.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 140 not taken.
✓ Branch 76 → 77 taken 4 times.
✗ Branch 76 → 140 not taken.
✗ Branch 77 → 71 not taken.
✓ Branch 77 → 78 taken 4 times.
4 for (const auto &flag : metadata["linkerFlags"])
87 sourceFile->sourceLinkerFlags.push_back(flag.get<std::string>());
88
2/4
✓ Branch 79 → 80 taken 4 times.
✗ Branch 79 → 145 not taken.
✓ Branch 80 → 81 taken 4 times.
✗ Branch 80 → 92 not taken.
4 if (metadata.contains("additionalSourcePaths"))
89
3/8
✓ Branch 81 → 82 taken 4 times.
✗ Branch 81 → 144 not taken.
✗ Branch 84 → 85 not taken.
✗ Branch 84 → 144 not taken.
✓ Branch 89 → 90 taken 4 times.
✗ Branch 89 → 144 not taken.
✗ Branch 90 → 84 not taken.
✓ Branch 90 → 91 taken 4 times.
4 for (const auto &path : metadata["additionalSourcePaths"])
90 sourceFile->sourceAdditionalSourcePaths.emplace_back(path.get<std::string>());
91
92 4 return true;
93 10 }
94
95 6 void CacheManager::cacheSourceFile(const SourceFile *sourceFile) const {
96 // Don't cache if LTO is enabled and this isn't the main file (no object file produced)
97
1/4
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 6 times.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
6 if (cliOptions.useLTO && !sourceFile->isMainFile)
98 return;
99
100 // Determine the source object file path (mirrors runObjectEmitter's path logic)
101
2/4
✓ Branch 5 → 6 taken 6 times.
✗ Branch 5 → 174 not taken.
✓ Branch 6 → 7 taken 6 times.
✗ Branch 6 → 172 not taken.
6 std::filesystem::path sourceObjFilePath = cliOptions.outputDir / sourceFile->filePath.filename();
102
2/4
✓ Branch 8 → 9 taken 6 times.
✗ Branch 8 → 177 not taken.
✓ Branch 9 → 10 taken 6 times.
✗ Branch 9 → 175 not taken.
6 sourceObjFilePath.replace_extension("o");
103
104 // Determine cache paths
105
1/2
✓ Branch 11 → 12 taken 6 times.
✗ Branch 11 → 247 not taken.
6 const char *objectFileExtension = SystemUtil::getOutputFileExtension(cliOptions, OutputContainer::OBJECT_FILE);
106
4/8
✓ Branch 12 → 13 taken 6 times.
✗ Branch 12 → 184 not taken.
✓ Branch 13 → 14 taken 6 times.
✗ Branch 13 → 182 not taken.
✓ Branch 14 → 15 taken 6 times.
✗ Branch 14 → 180 not taken.
✓ Branch 15 → 16 taken 6 times.
✗ Branch 15 → 178 not taken.
6 const std::filesystem::path cachedObjectFilePath = cacheDir / (sourceFile->cacheKey + "." + objectFileExtension);
107
3/6
✓ Branch 19 → 20 taken 6 times.
✗ Branch 19 → 191 not taken.
✓ Branch 20 → 21 taken 6 times.
✗ Branch 20 → 189 not taken.
✓ Branch 21 → 22 taken 6 times.
✗ Branch 21 → 187 not taken.
6 const std::filesystem::path metadataFilePath = cacheDir / (sourceFile->cacheKey + ".json");
108
109 // Verify source object file exists
110 6 std::error_code error;
111
3/6
✓ Branch 26 → 27 taken 6 times.
✗ Branch 26 → 29 not taken.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 6 times.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 6 times.
6 if (!std::filesystem::exists(sourceObjFilePath, error) || error)
112 return;
113
114 // Ensure cache directory exists
115
1/2
✓ Branch 33 → 34 taken 6 times.
✗ Branch 33 → 243 not taken.
6 std::filesystem::create_directories(cacheDir, error);
116
1/2
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 6 times.
6 if (error)
117 return;
118
119 // Copy object file to cache
120
1/2
✓ Branch 37 → 38 taken 6 times.
✗ Branch 37 → 243 not taken.
6 std::filesystem::copy_file(sourceObjFilePath, cachedObjectFilePath, std::filesystem::copy_options::overwrite_existing, error);
121
1/2
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 6 times.
6 if (error)
122 return;
123
124 // Collect all transitive dependency cache keys, linker flags, and additional source paths.
125 // We need the transitive list so that cache-restored files can replay the full linker input
126 // even for implicit deps (e.g. runtime modules requested during symbol-table building, which
127 // a cache hit skips). The linker dedupes the overlap with deps that register themselves.
128 6 std::vector<std::string> depCacheKeys;
129
1/2
✓ Branch 41 → 42 taken 6 times.
✗ Branch 41 → 241 not taken.
6 std::vector<std::string> allLinkerFlags = sourceFile->sourceLinkerFlags;
130 6 std::vector<std::string> allAdditionalSourcePaths;
131
1/2
✗ Branch 58 → 44 not taken.
✓ Branch 58 → 59 taken 6 times.
12 for (const auto &p : sourceFile->sourceAdditionalSourcePaths)
132 allAdditionalSourcePaths.push_back(p.string());
133 6 std::unordered_set<std::string> visited;
134
1/2
✓ Branch 60 → 61 taken 6 times.
✗ Branch 60 → 235 not taken.
6 std::queue<const SourceFile *> worklist;
135
5/8
✓ Branch 61 → 62 taken 6 times.
✗ Branch 61 → 197 not taken.
✓ Branch 62 → 63 taken 6 times.
✗ Branch 62 → 197 not taken.
✓ Branch 63 → 64 taken 6 times.
✗ Branch 63 → 197 not taken.
✓ Branch 69 → 65 taken 4 times.
✓ Branch 69 → 70 taken 6 times.
10 for (const SourceFile *dep : sourceFile->dependencies | std::views::values)
136
1/2
✓ Branch 66 → 67 taken 4 times.
✗ Branch 66 → 197 not taken.
4 worklist.push(dep);
137
2/2
✓ Branch 113 → 71 taken 5 times.
✓ Branch 113 → 114 taken 6 times.
11 while (!worklist.empty()) {
138 5 const SourceFile *dep = worklist.front();
139 5 worklist.pop();
140
2/4
✓ Branch 73 → 74 taken 5 times.
✗ Branch 73 → 233 not taken.
✗ Branch 74 → 75 not taken.
✓ Branch 74 → 76 taken 5 times.
5 if (!visited.insert(dep->cacheKey).second)
141 continue;
142
1/2
✓ Branch 76 → 77 taken 5 times.
✗ Branch 76 → 233 not taken.
5 depCacheKeys.push_back(dep->cacheKey);
143
1/2
✓ Branch 83 → 84 taken 5 times.
✗ Branch 83 → 198 not taken.
10 allLinkerFlags.insert(allLinkerFlags.end(), dep->sourceLinkerFlags.begin(), dep->sourceLinkerFlags.end());
144
1/2
✗ Branch 100 → 86 not taken.
✓ Branch 100 → 101 taken 5 times.
10 for (const auto &p : dep->sourceAdditionalSourcePaths)
145 allAdditionalSourcePaths.push_back(p.string());
146
5/8
✓ Branch 101 → 102 taken 5 times.
✗ Branch 101 → 204 not taken.
✓ Branch 102 → 103 taken 5 times.
✗ Branch 102 → 204 not taken.
✓ Branch 103 → 104 taken 5 times.
✗ Branch 103 → 204 not taken.
✓ Branch 109 → 105 taken 1 time.
✓ Branch 109 → 110 taken 5 times.
6 for (const SourceFile *transitiveDep : dep->dependencies | std::views::values)
147
1/2
✓ Branch 106 → 107 taken 1 time.
✗ Branch 106 → 204 not taken.
1 worklist.push(transitiveDep);
148 }
149
150 // Write metadata file
151 6 nlohmann::json metadata;
152
3/6
✓ Branch 115 → 116 taken 6 times.
✗ Branch 115 → 209 not taken.
✓ Branch 116 → 117 taken 6 times.
✗ Branch 116 → 207 not taken.
✓ Branch 117 → 118 taken 6 times.
✗ Branch 117 → 205 not taken.
6 metadata["sourceFile"] = sourceFile->filePath.string();
153
2/4
✓ Branch 121 → 122 taken 6 times.
✗ Branch 121 → 213 not taken.
✓ Branch 122 → 123 taken 6 times.
✗ Branch 122 → 211 not taken.
6 metadata["fileName"] = sourceFile->fileName;
154
2/4
✓ Branch 125 → 126 taken 6 times.
✗ Branch 125 → 216 not taken.
✓ Branch 126 → 127 taken 6 times.
✗ Branch 126 → 214 not taken.
6 metadata["cacheKey"] = sourceFile->cacheKey;
155
2/4
✓ Branch 129 → 130 taken 6 times.
✗ Branch 129 → 219 not taken.
✓ Branch 130 → 131 taken 6 times.
✗ Branch 130 → 217 not taken.
6 metadata["dependencies"] = depCacheKeys;
156
2/4
✓ Branch 133 → 134 taken 6 times.
✗ Branch 133 → 222 not taken.
✓ Branch 134 → 135 taken 6 times.
✗ Branch 134 → 220 not taken.
6 metadata["linkerFlags"] = allLinkerFlags;
157
2/4
✓ Branch 137 → 138 taken 6 times.
✗ Branch 137 → 225 not taken.
✓ Branch 138 → 139 taken 6 times.
✗ Branch 138 → 223 not taken.
6 metadata["additionalSourcePaths"] = allAdditionalSourcePaths;
158
1/2
✓ Branch 141 → 142 taken 6 times.
✗ Branch 141 → 231 not taken.
6 std::ofstream metadataStream(metadataFilePath);
159
2/4
✓ Branch 142 → 143 taken 6 times.
✗ Branch 142 → 229 not taken.
✓ Branch 143 → 144 taken 6 times.
✗ Branch 143 → 148 not taken.
6 if (metadataStream)
160
2/4
✓ Branch 144 → 145 taken 6 times.
✗ Branch 144 → 228 not taken.
✓ Branch 145 → 146 taken 6 times.
✗ Branch 145 → 226 not taken.
6 metadataStream << metadata.dump();
161
3/6
✓ Branch 157 → 158 taken 6 times.
✗ Branch 157 → 159 not taken.
✓ Branch 162 → 163 taken 6 times.
✗ Branch 162 → 164 not taken.
✓ Branch 167 → 168 taken 6 times.
✗ Branch 167 → 170 not taken.
18 }
162
163 // Hash the content of a single linker input that's not produced by the Spice cache itself
164 // (e.g. C/C++ files referenced via @core.linker.additionalSource). Returns a sentinel that
165 // folds the path in if the file can't be opened, so a vanished file still produces a stable
166 // (but different) cache key.
167 3 std::string hashLinkedFile(const std::filesystem::path &path) {
168
1/2
✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 33 not taken.
3 std::ifstream stream(path, std::ios::binary);
169
2/4
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 31 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 10 taken 3 times.
3 if (!stream)
170 return "missing:" + path.string();
171
1/2
✓ Branch 10 → 11 taken 3 times.
✗ Branch 10 → 31 not taken.
3 std::stringstream content;
172
1/2
✓ Branch 12 → 13 taken 3 times.
✗ Branch 12 → 29 not taken.
3 content << stream.rdbuf();
173
2/4
✓ Branch 13 → 14 taken 3 times.
✗ Branch 13 → 27 not taken.
✓ Branch 15 → 16 taken 3 times.
✗ Branch 15 → 25 not taken.
6 return std::to_string(std::hash<std::string>{}(content.str()));
174 3 }
175
176 16 std::string computeExecutableCacheKey(const std::vector<std::string> &objectFileCacheKeys,
177 const std::vector<std::string> &linkerFlags,
178 const std::vector<std::filesystem::path> &additionalSourcePaths,
179 const CliOptions &cliOptions) {
180
1/2
✓ Branch 2 → 3 taken 16 times.
✗ Branch 2 → 84 not taken.
16 std::stringstream components;
181
2/2
✓ Branch 17 → 5 taken 21 times.
✓ Branch 17 → 18 taken 16 times.
53 for (const std::string &key : objectFileCacheKeys)
182
1/2
✓ Branch 7 → 8 taken 21 times.
✗ Branch 7 → 67 not taken.
21 components << key;
183
2/2
✓ Branch 32 → 20 taken 16 times.
✓ Branch 32 → 33 taken 16 times.
48 for (const std::string &flag : linkerFlags)
184
1/2
✓ Branch 22 → 23 taken 16 times.
✗ Branch 22 → 68 not taken.
16 components << flag;
185 // Sort additional source paths so traversal order doesn't perturb the key, then fold in
186 // path + content hash. Without this, edits to a referenced C/C++ source would leave every
187 // Spice object cache key unchanged, and we'd serve a stale executable.
188
1/2
✓ Branch 33 → 34 taken 16 times.
✗ Branch 33 → 82 not taken.
16 std::vector<std::filesystem::path> sortedAdditionalSources = additionalSourcePaths;
189
1/2
✓ Branch 34 → 35 taken 16 times.
✗ Branch 34 → 80 not taken.
16 std::ranges::sort(sortedAdditionalSources);
190
2/2
✓ Branch 55 → 37 taken 3 times.
✓ Branch 55 → 56 taken 16 times.
35 for (const std::filesystem::path &additionalSource : sortedAdditionalSources)
191
5/10
✓ Branch 39 → 40 taken 3 times.
✗ Branch 39 → 74 not taken.
✓ Branch 40 → 41 taken 3 times.
✗ Branch 40 → 72 not taken.
✓ Branch 41 → 42 taken 3 times.
✗ Branch 41 → 72 not taken.
✓ Branch 42 → 43 taken 3 times.
✗ Branch 42 → 71 not taken.
✓ Branch 43 → 44 taken 3 times.
✗ Branch 43 → 69 not taken.
3 components << additionalSource.string() << '\0' << hashLinkedFile(additionalSource);
192
1/2
✓ Branch 56 → 57 taken 16 times.
✗ Branch 56 → 80 not taken.
16 components << static_cast<uint8_t>(cliOptions.outputContainer);
193
1/2
✓ Branch 57 → 58 taken 16 times.
✗ Branch 57 → 80 not taken.
16 components << cliOptions.staticLinking;
194
2/4
✓ Branch 58 → 59 taken 16 times.
✗ Branch 58 → 78 not taken.
✓ Branch 60 → 61 taken 16 times.
✗ Branch 60 → 76 not taken.
48 return std::to_string(std::hash<std::string>{}(components.str()));
195 16 }
196
197 9 bool CacheManager::lookupExecutable(const std::vector<std::string> &objectFileCacheKeys,
198 const std::vector<std::string> &linkerFlags,
199 const std::vector<std::filesystem::path> &additionalSourcePaths,
200 std::filesystem::path &cachedExecutablePath) const {
201
1/2
✓ Branch 2 → 3 taken 9 times.
✗ Branch 2 → 61 not taken.
9 const std::string execCacheKey = computeExecutableCacheKey(objectFileCacheKeys, linkerFlags, additionalSourcePaths, cliOptions);
202
203 // Determine expected extension
204
1/2
✓ Branch 3 → 4 taken 9 times.
✗ Branch 3 → 59 not taken.
9 const char *extension = SystemUtil::getOutputFileExtension(cliOptions, cliOptions.outputContainer);
205
12/22
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 9 taken 8 times.
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 37 not taken.
✓ Branch 8 → 12 taken 1 time.
✗ Branch 8 → 37 not taken.
✓ Branch 11 → 12 taken 8 times.
✗ Branch 11 → 37 not taken.
✓ Branch 12 → 13 taken 9 times.
✗ Branch 12 → 35 not taken.
✓ Branch 14 → 15 taken 8 times.
✓ Branch 14 → 17 taken 1 time.
✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 19 taken 8 times.
✓ Branch 19 → 20 taken 1 time.
✓ Branch 19 → 22 taken 8 times.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 40 not taken.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 49 not taken.
26 const std::string fileName = execCacheKey + (strlen(extension) > 0 ? "." + std::string(extension) : "");
206
2/4
✓ Branch 22 → 23 taken 9 times.
✗ Branch 22 → 54 not taken.
✓ Branch 23 → 24 taken 9 times.
✗ Branch 23 → 52 not taken.
9 const std::filesystem::path execPath = cacheDir / fileName;
207
208
3/4
✓ Branch 25 → 26 taken 9 times.
✗ Branch 25 → 55 not taken.
✓ Branch 26 → 27 taken 7 times.
✓ Branch 26 → 28 taken 2 times.
9 if (!exists(execPath))
209 7 return false;
210
211
1/2
✓ Branch 28 → 29 taken 2 times.
✗ Branch 28 → 55 not taken.
2 cachedExecutablePath = execPath;
212 2 return true;
213 9 }
214
215 7 void CacheManager::cacheExecutable(const std::vector<std::string> &objFileCacheKeys, const std::vector<std::string> &linkerFlags,
216 const std::vector<std::filesystem::path> &additionalSourcePaths,
217 const std::filesystem::path &executablePath) const {
218
1/2
✓ Branch 2 → 3 taken 7 times.
✗ Branch 2 → 82 not taken.
7 const std::string execCacheKey = computeExecutableCacheKey(objFileCacheKeys, linkerFlags, additionalSourcePaths, cliOptions);
219
220 // Determine cached file name
221
1/2
✓ Branch 3 → 4 taken 7 times.
✗ Branch 3 → 80 not taken.
7 const char *extension = SystemUtil::getOutputFileExtension(cliOptions, cliOptions.outputContainer);
222
6/22
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 9 taken 7 times.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 58 not taken.
✗ Branch 8 → 12 not taken.
✗ Branch 8 → 58 not taken.
✓ Branch 11 → 12 taken 7 times.
✗ Branch 11 → 58 not taken.
✓ Branch 12 → 13 taken 7 times.
✗ Branch 12 → 56 not taken.
✓ Branch 14 → 15 taken 7 times.
✗ Branch 14 → 17 not taken.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 7 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 22 taken 7 times.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 61 not taken.
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 65 not taken.
✗ Branch 67 → 68 not taken.
✗ Branch 67 → 70 not taken.
21 const std::string fileName = execCacheKey + (strlen(extension) > 0 ? "." + std::string(extension) : "");
223
2/4
✓ Branch 22 → 23 taken 7 times.
✗ Branch 22 → 75 not taken.
✓ Branch 23 → 24 taken 7 times.
✗ Branch 23 → 73 not taken.
7 const std::filesystem::path cachedExecPath = cacheDir / fileName;
224
225 // Verify executable exists
226 7 std::error_code error;
227
5/6
✓ Branch 27 → 28 taken 6 times.
✓ Branch 27 → 30 taken 1 time.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 6 times.
✓ Branch 32 → 33 taken 1 time.
✓ Branch 32 → 34 taken 6 times.
7 if (!std::filesystem::exists(executablePath, error) || error)
228 1 return;
229
230 // Ensure cache directory exists
231
1/2
✓ Branch 34 → 35 taken 6 times.
✗ Branch 34 → 76 not taken.
6 std::filesystem::create_directories(cacheDir, error);
232
1/2
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 38 taken 6 times.
6 if (error)
233 return;
234
235 // Copy executable to cache
236
1/2
✓ Branch 38 → 39 taken 6 times.
✗ Branch 38 → 76 not taken.
6 std::filesystem::copy_file(executablePath, cachedExecPath, std::filesystem::copy_options::overwrite_existing, error);
237
6/6
✓ Branch 41 → 42 taken 6 times.
✓ Branch 41 → 43 taken 1 time.
✓ Branch 46 → 47 taken 6 times.
✓ Branch 46 → 48 taken 1 time.
✓ Branch 51 → 52 taken 6 times.
✓ Branch 51 → 54 taken 1 time.
21 }
238
239 void CacheManager::linkOrRestoreExecutable(GlobalResourceManager &resourceManager) const {
240 const ExternalLinkerInterface &linker = resourceManager.linker;
241
242 // Collect object file cache keys and any external linker inputs (e.g. C/C++ files added
243 // via @core.linker.additionalSource) that participate in the executable cache key.
244 std::vector<std::string> objectFileCacheKeys;
245 std::vector<std::filesystem::path> additionalSourcePaths;
246 for (const auto &sourceFile : resourceManager.sourceFiles | std::views::values) {
247 objectFileCacheKeys.push_back(sourceFile->cacheKey);
248 for (const std::filesystem::path &additionalSource : sourceFile->sourceAdditionalSourcePaths)
249 additionalSourcePaths.push_back(additionalSource);
250 }
251
252 // Check if we have a cached executable
253 std::filesystem::path cachedExecutablePath;
254 if (!cliOptions.ignoreCache &&
255 lookupExecutable(objectFileCacheKeys, linker.getLinkerFlags(), additionalSourcePaths, cachedExecutablePath)) {
256 // Restore cached executable
257 std::error_code ec;
258 std::filesystem::create_directories(linker.outputPath.parent_path(), ec);
259 std::filesystem::copy_file(cachedExecutablePath, linker.outputPath, std::filesystem::copy_options::overwrite_existing, ec);
260 } else {
261 // Link and cache the result
262 linker.run();
263 if (!cliOptions.ignoreCache)
264 cacheExecutable(objectFileCacheKeys, linker.getLinkerFlags(), additionalSourcePaths, linker.outputPath);
265 }
266 }
267
268 } // namespace spice::compiler
269