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 |