fix: key the std module's directory on everything it is compiled with

StdPcmDir named the directory by target, march and toolchain, then
grew a case per target for the rest: the wasm flags, and with the first
fix for #43 the mingw prefix. Every other flag BuildStdPcm passes went
unkeyed, so configurations that differed only there took turns
rebuilding one std PCM in place, under the PCMs built against it: a
project with -mtune=generic against project.cpp's -mtune=native on every
run, or two sysroots for one target. And the mingw prefix was keyed only
when there was one, so a build without a prefix landed in the directory
an older release had filled with a CRAFTER_MINGW_DIR=/usr build, and
relinked its objects.

StdPcmFlags is now the one source of the flags BuildStdPcm compiles
with, and StdPcmKey names the directory by target, march and toolchain
followed by a hash of those flags and, for the mingw target, of the
prefix behind its --sysroot. VariantId hashes StdPcmKey in place of the
mingw prefix: units do not notice a std module that moved directories,
so a build has to move with it. Every std PCM directory and build
directory moves once, as after a compiler update; the old ones stay
until `clean` and the cache is cleared by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 22:58:44 +01:00
commit 5d25495621
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
4 changed files with 114 additions and 65 deletions

View file

@ -247,36 +247,51 @@ void Configuration::GetInterfacesAndImplementations(std::span<fs::path> interfac
}
}
std::string Crafter::StdPcmFlags(const Configuration& config) {
if (config.target == "x86_64-w64-mingw32") {
// -femulated-tls keeps PCM TLS access matching libstdc++'s emutls
// definitions; a mismatch surfaces as undefined std::__once_callable.
return std::format(" --target={} -march={} -mtune={} -femulated-tls -O3 -std=c++26 -Wno-reserved-identifier -Wno-reserved-module-identifier", config.target, config.march, config.mtune);
}
const bool isWasm = config.target.starts_with("wasm32");
const std::string sysrootFlag = config.sysroot.empty() ? std::string() : std::format(" --sysroot={}", config.sysroot);
// wasm32 rejects -march, and the std module has to be compiled the way
// the units importing it are, a variant pass's flags (e.g.
// -mrelaxed-simd) included, or the BMI is not feature-compatible.
std::string archFlags = isWasm ? WasmCompileFlags(config) : std::format(" -march={} -mtune={}", config.march, config.mtune);
// non-wasm cross builds: the driver ranks the host toolchain's own
// libc++ headers (<install>/../include/c++/v1) above --sysroot, so the
// PCM would mix host libc++ with the foreign libc. Force the sysroot's
// copy.
if (!isWasm && !config.sysroot.empty()) {
archFlags += std::format(" -nostdinc++ -isystem {}/usr/include/c++/v1", config.sysroot);
}
return std::format(" --target={} -std=c++26{}{} -O3 -Wno-reserved-identifier -Wno-reserved-module-identifier", config.target, sysrootFlag, archFlags);
}
std::string Crafter::StdPcmKey(const Configuration& config, const Toolchain& toolchain) {
// A PCM is only valid for the clang that wrote it and the flags it was
// written with, so a compiler bump starts a cold directory instead of
// handing the new clang a PCM it will crash on, and so does any flag.
// With only some of them in the name, configurations that differed in
// the rest took turns rebuilding one std PCM in place: -mtune=generic
// against project.cpp's -mtune=native on every run, two sysroots, or two
// releases sharing a cache, as the Flatpak's branches do, each time under
// the PCMs built against the previous one, which clang crashes on rather
// than rejecting.
std::string compiled = StdPcmFlags(config);
// The base command's part: the toolchain's key covers the compiler and
// libc++, and for the mingw target what clang finds mingw-w64 through is
// the prefix, given whole or through a view (MingwSysroot), named here by
// the prefix rather than by the view's own path.
if (config.target == "x86_64-w64-mingw32" && !toolchain.mingwPrefix.empty()) {
compiled += std::format("|mingw:{}|{}", toolchain.mingwPrefix.string(), toolchain.mingwPrefixIsSystem ? "view" : "whole");
}
return std::format("{}-{}-{}-{:08x}", config.target, config.march, toolchain.Key(), static_cast<std::uint32_t>(std::hash<std::string>{}(compiled)));
}
fs::path Crafter::StdPcmDir(const Configuration& config) {
// The std PCM is cached per target+march+toolchain: a PCM is only valid
// for the exact clang that wrote it, so a compiler bump starts a cold
// directory instead of handing the new clang a PCM it will crash on. A
// wasm variant pass compiles it with extra codegen flags (e.g.
// -mrelaxed-simd) — its BMI must not be shared with the baseline's, or
// the consuming TUs see a target-feature mismatch. Suffix the cache dir
// with the variant flags when present.
std::string stdPcmKey = std::format("{}-{}-{}", config.target, config.march, GetToolchain().Key());
// The flags every wasm unit gets are compiled into the std PCM as well,
// and the importing units have to match them. Two releases that share a
// cache and a compiler, as the Flatpak's branches do, would otherwise
// take turns rebuilding this one std PCM, each time underneath the PCMs
// built against the previous one, and clang crashes on those rather than
// rejecting them.
if (config.target.starts_with("wasm32")) {
stdPcmKey += std::format("-{:08x}", static_cast<std::uint32_t>(std::hash<std::string_view>{}(WasmBaselineFlags)));
}
// The same goes for the mingw-w64 prefix the mingw std module is
// compiled against: releases up to the fix for issue #43 gave clang the
// prefix itself as the sysroot and the ones after give it a view of it,
// and two prefixes are two sets of headers.
if (config.target == "x86_64-w64-mingw32" && !GetToolchain().mingwPrefix.empty()) {
stdPcmKey += std::format("-{:08x}", static_cast<std::uint32_t>(std::hash<std::string>{}(GetToolchain().mingwPrefix.string())));
}
for (const std::string& f : config.wasmVariantFlags) {
stdPcmKey += "+";
for (char c : f) stdPcmKey += (c == '/' || c == '\\') ? '_' : c;
}
return GetCacheDir()/stdPcmKey;
return GetCacheDir() / StdPcmKey(config, GetToolchain());
}
std::string Crafter::WasmCompileFlags(const Configuration& config) {