-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathfingerprint.cppm
More file actions
141 lines (121 loc) · 5.23 KB
/
Copy pathfingerprint.cppm
File metadata and controls
141 lines (121 loc) · 5.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// mcpp.toolchain.fingerprint — 11-field fingerprint that gates BMI cache safety.
//
// Per docs/91-toolchain-internals.md, the fingerprint MUST cover:
// 1. compiler id 2. compiler version
// 3. compiler driver identity 4. target triple
// 5. stdlib id+version 6. C++ standard
// 7. compile flags hash 8. mcpp version
// 9. dependency lock hash 10. std module BMI hash
// 11. runtime binding (which libc this targets)
//
// Field 11 is the prerequisite for building one project under two subos. The
// target triple says `x86_64-linux-gnu` for both glibc 2.39 and 2.44, so
// without it switching subos leaves `target/<fp>/` untouched: ninja replays a
// graph whose objects were compiled against the other libc, the link
// succeeds, and the result references symbols the interpreter cannot provide.
//
// MVP uses FNV-1a 64-bit (deterministic, collision-rare-enough for cache
// invalidation); spec calls for SHA-256 but for "did anything change?"
// FNV-1a is operationally sufficient. Swappable later.
export module mcpp.toolchain.fingerprint;
import std;
// `Toolchain`, and nothing else. It used to be reached through
// `mcpp.toolchain.detect`, which re-exports the type it defines in
// `mcpp.toolchain.model` — so the import named a CONSUMER of the type rather
// than its provider, and dragged in toolchain detection, xlings and the
// package manager behind it.
//
// That distinction is not cosmetic here. Every module on the build-program
// side needs this one for `hash_file`, so while this edge pointed at `detect`
// the whole subsystem was transitively above the package manager and could not
// be separated from it. Naming the provider is what makes the layering true.
import mcpp.toolchain.model;
import mcpp.version;
export namespace mcpp::toolchain {
// The version itself lives in the leaf module mcpp.version — see that file for
// why. Kept spelled `mcpp::toolchain::MCPP_VERSION` here because that is what
// every existing reader says, and because the fingerprint genuinely is one of
// its consumers (field 8 of the 10).
inline constexpr std::string_view MCPP_VERSION = mcpp::MCPP_VERSION;
struct FingerprintInputs {
Toolchain toolchain;
std::string cppStandard = "c++23";
std::string compileFlags; // canonicalized
std::string dependencyLockHash;// hex string of mcpp.lock
std::string stdBmiHash; // hex string of pre-built std.gcm
};
struct Fingerprint {
std::string hex; // 16 hex chars
std::array<std::string, 11> parts; // each field's stringified form
};
std::string hash_file(const std::filesystem::path& p); // returns 16 hex
std::string hash_string(std::string_view s); // returns 16 hex
Fingerprint compute_fingerprint(const FingerprintInputs& in);
} // namespace mcpp::toolchain
namespace mcpp::toolchain {
namespace {
inline std::string to_hex16(std::uint64_t h) {
// Hand-rolled to avoid `std::format("{:016x}", ...)` which throws on
// GCC 15's `import std` (libstdc++ partial format support).
static constexpr char hex[] = "0123456789abcdef";
char buf[16];
for (int i = 15; i >= 0; --i) { buf[i] = hex[h & 0xf]; h >>= 4; }
return std::string(buf, 16);
}
inline std::uint64_t fnv1a_64(std::string_view data, std::uint64_t seed = 0xcbf29ce484222325ull) {
std::uint64_t h = seed;
for (unsigned char c : data) {
h ^= c;
h *= 0x100000001b3ull;
}
return h;
}
inline std::uint64_t fnv1a_64_file(const std::filesystem::path& p) {
std::ifstream is(p, std::ios::binary);
if (!is) return 0;
std::array<char, 8192> buf;
std::uint64_t h = 0xcbf29ce484222325ull;
while (is) {
is.read(buf.data(), buf.size());
std::streamsize got = is.gcount();
for (std::streamsize i = 0; i < got; ++i) {
h ^= static_cast<unsigned char>(buf[i]);
h *= 0x100000001b3ull;
}
}
return h;
}
} // namespace
std::string hash_file(const std::filesystem::path& p) {
return to_hex16(fnv1a_64_file(p));
}
std::string hash_string(std::string_view s) {
return to_hex16(fnv1a_64(s));
}
Fingerprint compute_fingerprint(const FingerprintInputs& in) {
Fingerprint fp;
auto& tc = in.toolchain;
fp.parts[0] = std::string(tc.compiler_name());
fp.parts[1] = tc.version;
fp.parts[2] = !tc.driverIdent.empty()
? hash_string(tc.driverIdent)
: (tc.binaryPath.empty() ? "" : hash_file(tc.binaryPath));
fp.parts[3] = tc.targetTriple;
fp.parts[4] = std::format("{} {}", tc.stdlibId, tc.stdlibVersion);
fp.parts[5] = in.cppStandard;
fp.parts[6] = hash_string(in.compileFlags);
fp.parts[7] = std::string(MCPP_VERSION);
fp.parts[8] = in.dependencyLockHash;
fp.parts[9] = in.stdBmiHash;
fp.parts[10] = tc.runtimeContractHash.empty()
? tc.runtimeBinding : tc.runtimeContractHash;
// Combine all parts deterministically.
std::uint64_t h = 0xcbf29ce484222325ull;
for (auto& p : fp.parts) {
h = fnv1a_64(p, h);
h = fnv1a_64("\x1f", h); // unit separator
}
fp.hex = to_hex16(h);
return fp;
}
} // namespace mcpp::toolchain