lto
Some checks failed
CI / build-test-release (push) Failing after 10m47s

This commit is contained in:
Jorijn van der Graaf 2026-06-08 19:28:20 +02:00
commit dbee23c564

View file

@ -635,6 +635,20 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
std::string cmakeBuildType; std::string cmakeBuildType;
// Behaviour-neutral release performance for the binaries we emit: ThinLTO
// (cross-TU inlining) plus dead-section GC and safe identical-code folding.
// Default-on in Release, never Debug (keeps builds fast and debuggable).
// Excluded for wasm32 — its -mllvm/sjlj codegen flags don't compose with
// LTO here — and for nvcc .cu objects below (they can't emit LLVM bitcode,
// so they link in as ordinary objects alongside the bitcode ones). ThinLTO
// rather than monolithic -flto so link time and memory scale with arbitrary
// user project sizes. Compile-side flags (-flto, -ffunction/-fdata-sections)
// ride on `command`, which is reused as the link base; the link-only -Wl
// flags go on linkExtras so they don't warn on each -c compile.
const bool useLto = !config.debug && !isWasm;
const std::string ltoCompileFlags = useLto ? " -flto=thin -ffunction-sections -fdata-sections" : "";
const std::string ltoLinkFlags = useLto ? " -flto=thin -Wl,--gc-sections -Wl,--icf=safe" : "";
if(config.debug) { if(config.debug) {
cmakeBuildType = "Debug"; cmakeBuildType = "Debug";
command += " -g -D CRAFTER_BUILD_CONFIGURATION_DEBUG"; command += " -g -D CRAFTER_BUILD_CONFIGURATION_DEBUG";
@ -642,6 +656,7 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
cmakeBuildType = "Release"; cmakeBuildType = "Release";
command += " -O3"; command += " -O3";
} }
command += ltoCompileFlags;
// Same target-aware setup as the C++ compile path (line 459-): wasm32 // Same target-aware setup as the C++ compile path (line 459-): wasm32
// rejects -march, silently ignores -mtune, and needs --sysroot to find // rejects -march, silently ignores -mtune, and needs --sysroot to find
@ -664,11 +679,11 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
const std::string objPath = (buildDir / cFile.filename()).string() + "_source.o"; const std::string objPath = (buildDir / cFile.filename()).string() + "_source.o";
const std::string srcPath = cFile.string() + ".c"; const std::string srcPath = cFile.string() + ".c";
if (!fs::exists(objPath) || (fs::exists(srcPath) && fs::last_write_time(srcPath) > fs::last_write_time(objPath))) { if (!fs::exists(objPath) || (fs::exists(srcPath) && fs::last_write_time(srcPath) > fs::last_write_time(objPath))) {
threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled, &config, &includeFlags, &defineFlags, &userFlags, &cArchFlags]() { threads.emplace_back([&cFile, &buildDir, &buildError, &buildCancelled, &config, &includeFlags, &defineFlags, &userFlags, &cArchFlags, &ltoCompileFlags]() {
Progress::Task task(std::format("Compiling {}.c", cFile.filename().string())); Progress::Task task(std::format("Compiling {}.c", cFile.filename().string()));
if (buildCancelled.load(std::memory_order_relaxed)) return; if (buildCancelled.load(std::memory_order_relaxed)) return;
std::string result = RunCommand(std::format("clang {}.c --target={}{} -O3 -c{}{}{} -o {}_source.o", cFile.string(), config.target, cArchFlags, includeFlags, defineFlags, userFlags, (buildDir / cFile.filename()).string())); std::string result = RunCommand(std::format("clang {}.c --target={}{} -O3{} -c{}{}{} -o {}_source.o", cFile.string(), config.target, cArchFlags, ltoCompileFlags, includeFlags, defineFlags, userFlags, (buildDir / cFile.filename()).string()));
if (result.empty()) return; if (result.empty()) return;
bool expected = false; bool expected = false;
@ -876,6 +891,10 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
for(const std::string& flag : buildResult.libs) { for(const std::string& flag : buildResult.libs) {
linkExtras += " " + flag; linkExtras += " " + flag;
} }
// Link-only LTO/section flags (empty in Debug and for wasm). Every exe/lib
// link below ends with linkExtras, so this reaches them all; the static-lib
// archive path uses ar instead and is handled separately.
linkExtras += ltoLinkFlags;
// mingw uses libstdc++; C++26 std::print/format extras live in libstdc++exp. // mingw uses libstdc++; C++26 std::print/format extras live in libstdc++exp.
// libstdc++ on mingw uses winpthreads for std::atomic_wait / // libstdc++ on mingw uses winpthreads for std::atomic_wait /
// counting_semaphore / stop_token, so -lpthread is required as soon as // counting_semaphore / stop_token, so -lpthread is required as soon as
@ -1058,7 +1077,10 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map<fs::path, s
#endif #endif
} else if(config.type == ConfigurationType::LibraryStatic) { } else if(config.type == ConfigurationType::LibraryStatic) {
#ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu #ifdef CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_linux_gnu
buildResult.result = RunCommand(std::format("ar rcs {}.a {}", (outputDir/fs::path(std::string("lib")+config.outputName)).string(), files)); // ThinLTO emits LLVM bitcode objects; plain `ar` writes an archive
// index that omits their symbols, so a consumer's lld link can't
// pull the needed members. llvm-ar writes a bitcode-aware index.
buildResult.result = RunCommand(std::format("{} rcs {}.a {}", useLto ? "llvm-ar" : "ar", (outputDir/fs::path(std::string("lib")+config.outputName)).string(), files));
#endif #endif
#if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32) #if defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_pc_windows_msvc) || defined(CRAFTER_BUILD_CONFIGURATION_TARGET_x86_64_w64_mingw32)