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