[llvm] [Windows] 12% faster compile by improved PGO training and re-enable ThinLTO (PR #219263)

Marcus Perryman via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 28 05:01:54 PDT 2026


https://github.com/marcpems updated https://github.com/llvm/llvm-project/pull/219263

>From 026c0f8ccf24b44930091d0123286058b5688e78 Mon Sep 17 00:00:00 2001
From: marcpems <marcpems at users.noreply.github.com>
Date: Fri, 28 Aug 2026 11:05:26 +0100
Subject: [PATCH] [Windows] Add optional PGO/ThinLTO/PDB build-variant support
 to release-binaries.yml

Adds a minimal, additive build-variant/enable-pdb input to the
existing release-binaries.yml workflow (rather than a separate
duplicate workflow file), plus the corresponding
--enable-pgo/--enable-thinlto/--enable-pdb flags in
build_llvm_release.bat. Default behavior (build-variant=neither,
enable-pdb=false) is unchanged.

Co-authored-by: Copilot <223556219+Copilot at users.noreply.github.com>
---
 .github/workflows/release-binaries.yml    | 105 +++++++++++++++++++++-
 llvm/utils/release/build_llvm_release.bat |  89 ++++++++++++++----
 2 files changed, 171 insertions(+), 23 deletions(-)

diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml
index d837461b24643..8d2d8d163dffd 100644
--- a/.github/workflows/release-binaries.yml
+++ b/.github/workflows/release-binaries.yml
@@ -12,6 +12,26 @@ on:
         required: true
         default: false
         type: boolean
+      build-variant:
+        description: "Windows only: which of PGO training / ThinLTO to enable"
+        required: false
+        default: neither
+        type: choice
+        options:
+          - neither
+          - pgo-only
+          - thinlto-only
+          - pgo-and-thinlto
+      enable-pdb:
+        description: "Windows only: also generate and upload PDB debug info"
+        required: false
+        default: false
+        type: boolean
+      enhanced-pgo:
+        description: "Windows only: when PGO is enabled, train the instrumented clang by building LLVMSupport instead of the legacy single-file Sema.cpp training step"
+        required: false
+        default: true
+        type: boolean
       runs-on:
         description: "Runner to use for the build"
         required: true
@@ -36,6 +56,21 @@ on:
         required: true
         default: false
         type: boolean
+      build-variant:
+        description: "Windows only: which of PGO training / ThinLTO to enable"
+        required: false
+        default: neither
+        type: string
+      enable-pdb:
+        description: "Windows only: also generate and upload PDB debug info"
+        required: false
+        default: false
+        type: boolean
+      enhanced-pgo:
+        description: "Windows only: when PGO is enabled, train the instrumented clang by building LLVMSupport instead of the legacy single-file Sema.cpp training step"
+        required: false
+        default: true
+        type: boolean
       runs-on:
         description: "Runner to use for the build"
         required: true
@@ -69,6 +104,10 @@ jobs:
       build-runs-on: ${{ steps.vars.outputs.build-runs-on }}
       test-runs-on: ${{ steps.vars.outputs.build-runs-on }}
       attestation-name: ${{ steps.vars.outputs.attestation-name }}
+      windows-enable-pgo: ${{ steps.vars.outputs.windows-enable-pgo }}
+      windows-enable-thinlto: ${{ steps.vars.outputs.windows-enable-thinlto }}
+      windows-enable-pdb: ${{ steps.vars.outputs.windows-enable-pdb }}
+      windows-enhanced-pgo: ${{ steps.vars.outputs.windows-enhanced-pgo }}
 
     steps:
     - name: Validate Release Version
@@ -90,6 +129,9 @@ jobs:
         LLVM_VERSION_MAJOR: ${{ steps.version-from-source.outputs.major }}
         INPUTS_RUNS_ON: ${{ inputs.runs-on }}
         INPUTS_RELEASE_VERSION: ${{ inputs.release-version }}
+        INPUTS_BUILD_VARIANT: ${{ inputs.build-variant }}
+        INPUTS_ENABLE_PDB: ${{ inputs.enable-pdb }}
+        INPUTS_ENHANCED_PGO: ${{ inputs.enhanced-pgo }}
       shell: bash
       # In order for the test-release.sh script to run correctly, the LLVM
       # source needs to be at the following location relative to the build dir:
@@ -160,8 +202,15 @@ jobs:
         fi
 
         if [ "$RUNNER_OS" = "Windows" ]; then
-          # The build times out on Windows, so we need to disable LTO.
-          target_cmake_flags="$target_cmake_flags -DLLVM_RELEASE_ENABLE_LTO=OFF"
+          # The build times out on Windows, so we need to disable LTO by
+          # default; --build-variant can re-enable ThinLTO explicitly.
+          case "$INPUTS_BUILD_VARIANT" in
+            thinlto-only|pgo-and-thinlto)
+              ;;
+            *)
+              target_cmake_flags="$target_cmake_flags -DLLVM_RELEASE_ENABLE_LTO=OFF"
+              ;;
+          esac
         fi
 
         case "$INPUTS_RUNS_ON" in
@@ -206,16 +255,41 @@ jobs:
 
         target_cmake_flags="$target_cmake_flags -D${bootstrap_prefix}_CPACK_PACKAGE_FILE_NAME=$release_binary_basename"
 
+        # Translate --build-variant/--enable-pdb into individual flags
+        # consumed by the Windows build_llvm_release.bat invocation below
+        # (only meaningful for Windows builds).
+        windows_enable_pgo="false"
+        windows_enable_thinlto="false"
+        case "$INPUTS_BUILD_VARIANT" in
+          pgo-only) windows_enable_pgo="true" ;;
+          thinlto-only) windows_enable_thinlto="true" ;;
+          pgo-and-thinlto) windows_enable_pgo="true"; windows_enable_thinlto="true" ;;
+        esac
+        windows_enable_pdb="false"
+        if [ "$INPUTS_ENABLE_PDB" = "true" ]; then
+          windows_enable_pdb="true"
+        fi
+        windows_enhanced_pgo="false"
+        if [ "$INPUTS_ENHANCED_PGO" = "true" ]; then
+          windows_enhanced_pgo="true"
+        fi
+
         echo "target-cmake-flags=$target_cmake_flags" >> $GITHUB_OUTPUT
         echo "build-runs-on=$build_runs_on" >> $GITHUB_OUTPUT
         echo "test-runs-on=$test_runs_on" >> $GITHUB_OUTPUT
         echo "attestation-name=$RUNNER_OS-$RUNNER_ARCH-release-binary-attestation" >> $GITHUB_OUTPUT
+        echo "windows-enable-pgo=$windows_enable_pgo" >> $GITHUB_OUTPUT
+        echo "windows-enable-thinlto=$windows_enable_thinlto" >> $GITHUB_OUTPUT
+        echo "windows-enable-pdb=$windows_enable_pdb" >> $GITHUB_OUTPUT
+        echo "windows-enhanced-pgo=$windows_enhanced_pgo" >> $GITHUB_OUTPUT
 
   build-release-package:
     name: "Build Release Package"
     needs: prepare
     if: github.repository_owner == 'llvm'
     runs-on: ${{ needs.prepare.outputs.build-runs-on }}
+    # Combined PGO+ThinLTO(+PDB) Windows builds can exceed the default 6h limit.
+    timeout-minutes: 600
     outputs:
       digest: ${{ steps.digest.outputs.digest }}
       artifact-id: ${{ steps.artifact-upload.outputs.artifact-id }}
@@ -295,20 +369,36 @@ jobs:
       env:
         INPUTS_RUNS_ON: ${{ inputs.runs-on }}
         LLVM_VERSION: ${{ needs.prepare.outputs.release-version }}
+        WINDOWS_ENABLE_PGO: ${{ needs.prepare.outputs.windows-enable-pgo }}
+        WINDOWS_ENABLE_THINLTO: ${{ needs.prepare.outputs.windows-enable-thinlto }}
+        WINDOWS_ENABLE_PDB: ${{ needs.prepare.outputs.windows-enable-pdb }}
+        WINDOWS_ENHANCED_PGO: ${{ needs.prepare.outputs.windows-enhanced-pgo }}
       run: |
         subst S: ${{ github.workspace }}
         cd S:\llvm\utils\release\
-        .\build_llvm_release.bat "--$($env:RUNNER_ARCH.ToLower())" --version $env:LLVM_VERSION --local-python --skip-checkout
+        $variantArgs = @()
+        if ($env:WINDOWS_ENABLE_PGO -eq "true") { $variantArgs += "--enable-pgo" }
+        if ($env:WINDOWS_ENABLE_THINLTO -eq "true") { $variantArgs += "--enable-thinlto" }
+        if ($env:WINDOWS_ENABLE_PDB -eq "true") { $variantArgs += "--enable-pdb" }
+        if ($env:WINDOWS_ENHANCED_PGO -eq "true") { $variantArgs += "--enhanced-pgo" }
+        .\build_llvm_release.bat "--$($env:RUNNER_ARCH.ToLower())" --version $env:LLVM_VERSION --local-python --skip-checkout @variantArgs
         if ($env:INPUTS_RUNS_ON -eq "windows-11-arm") {
           $zstd = (Get-ChildItem -Recurse -Filter "zstd.exe" | Select-Object -First 1).fullName
           mv $zstd $env:GITHUB_WORKSPACE
         }
         $installer = (Get-ChildItem -Recurse -Filter "LLVM-*.msi" | Select-Object -First 1).fullName
-        $tarball = (Get-ChildItem -Recurse -Filter "*.tar.xz" | Select-Object -First 1).fullName
+        $tarball = (Get-ChildItem -Recurse -Filter "*.tar.xz" | Where-Object { $_.Name -notlike "*-pdb.tar.xz" } | Select-Object -First 1).fullName
         # Move installer to top-level directory so it is easier to upload.
         mv $installer $env:GITHUB_WORKSPACE
         mv $tarball $env:GITHUB_WORKSPACE
         echo "windows-installer-filename=$(Split-Path -Path $installer -Leaf)" >> $env:GITHUB_OUTPUT
+        if ($env:WINDOWS_ENABLE_PDB -eq "true") {
+          $pdbArchive = (Get-ChildItem -Recurse -Filter "*-pdb.tar.xz" | Select-Object -First 1).fullName
+          if ($pdbArchive) {
+            mv $pdbArchive $env:GITHUB_WORKSPACE
+            echo "windows-pdb-filename=$(Split-Path -Path $pdbArchive -Leaf)" >> $env:GITHUB_OUTPUT
+          }
+        }
     
     - name: Dump Wix logs
       if: runner.os == 'Windows' && failure()
@@ -361,6 +451,13 @@ jobs:
           ${{ needs.prepare.outputs.release-binary-filename-zstd }}
           ${{ steps.build-windows.outputs.windows-installer-filename }}
 
+    - name: Upload Windows PDB debug info
+      if: runner.os == 'Windows' && steps.build-windows.outputs.windows-pdb-filename != ''
+      uses: actions/upload-artifact at 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+      with:
+        name: ${{ runner.os }}-${{ runner.arch }}-release-pdb
+        path: ${{ steps.build-windows.outputs.windows-pdb-filename }}
+
     - name: Run Tests
       # These almost always fail so don't let them fail the build and prevent the uploads.
       if : runner.os != 'Windows'
diff --git a/llvm/utils/release/build_llvm_release.bat b/llvm/utils/release/build_llvm_release.bat
index db74c8bb0456d..818b956acae3f 100644
--- a/llvm/utils/release/build_llvm_release.bat
+++ b/llvm/utils/release/build_llvm_release.bat
@@ -10,7 +10,7 @@ goto begin
 echo Script for building the LLVM installer on Windows,
 echo used for the releases at https://github.com/llvm/llvm-project/releases
 echo.
-echo Usage: build_llvm_release.bat --version ^<version^> [--x86,--x64, --arm64] [--skip-checkout] [--local-python] [--force-msvc]
+echo Usage: build_llvm_release.bat --version ^<version^> [--x86,--x64, --arm64] [--skip-checkout] [--local-python] [--force-msvc] [--enable-pgo] [--enhanced-pgo] [--enable-thinlto] [--enable-pdb]
 echo.
 echo Options:
 echo --version: [required] version to build
@@ -21,6 +21,11 @@ echo --arm64: build and test arm64 variant
 echo --skip-checkout: use local git checkout instead of downloading src.zip
 echo --local-python: use installed Python and does not try to use a specific version (3.11)
 echo --force-msvc: use MSVC compiler for stage0, even if clang-cl is present
+echo --enable-pgo: build an instrumented stage1 clang, train it, and use the resulting profile for stage2 (64-bit builds only)
+echo --enhanced-pgo: when used with --enable-pgo, train the instrumented clang by building LLVMSupport
+echo   instead of the legacy single-file Sema.cpp training step (64-bit builds only)
+echo --enable-thinlto: build stage2 with ThinLTO (64-bit builds only)
+echo --enable-pdb: generate PDB debug info files for stage2 and include them as an additional artifact (64-bit builds only)
 echo.
 echo Note: At least one variant to build is required.
 echo.
@@ -40,6 +45,10 @@ set arm64=
 set skip-checkout=
 set local-python=
 set force-msvc=
+set enable-pgo=
+set enhanced-pgo=
+set enable-thinlto=
+set enable-pdb=
 call :parse_args %*
 
 if "%help%" NEQ "" goto usage
@@ -185,8 +194,8 @@ set common_cmake_flags=^
   -DCMAKE_C_FLAGS="%common_compiler_flags%" ^
   -DCMAKE_CXX_FLAGS="%common_compiler_flags%" ^
   -DLLVM_ENABLE_RPMALLOC=ON ^
-  -DLLVM_ENABLE_PROJECTS="clang;lld" ^
-  -DLLVM_ENABLE_RUNTIMES="compiler-rt" ^
+  -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld" ^
+  -DLLVM_ENABLE_RUNTIMES="compiler-rt;openmp" ^
   -DCPACK_GENERATOR="WIX" ^
   -DCOMPILER_RT_BUILD_ORC=OFF
 
@@ -263,7 +272,6 @@ REM with forward slash.
 set all_cmake_flags=^
   %cmake_flags% ^
   -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld;lldb;" ^
-  -DLLVM_ENABLE_RUNTIMES="compiler-rt;openmp" ^
   %common_lldb_flags% ^
   -DPYTHON_HOME=%PYTHONHOME% ^
   -DCMAKE_C_COMPILER=%stage0_bin_dir%/clang-cl.exe ^
@@ -332,6 +340,8 @@ ninja check-lld || exit /b 1
 if "%arch%"=="amd64" (
   ninja check-runtimes || exit /b 1
 )
+ninja check-clang-tools || ninja check-clang-tools || ninja check-clang-tools || exit /b 1
+ninja check-clangd || ninja check-clangd || ninja check-clangd || exit /b 1
 cd..
 
 REM CMake expects the paths that specifies the compiler and linker to be
@@ -351,10 +361,12 @@ set cmake_flags=%all_cmake_flags:\=/%
 
 mkdir build_%arch%
 cd build_%arch%
-call :do_generate_profile || exit /b 1
+if "%enable-pgo%" == "true" call :do_generate_profile || exit /b 1
+set lto_cmake_flag=
+if "%enable-thinlto%" == "true" set lto_cmake_flag=-DLLVM_ENABLE_LTO=Thin
 cmake -GNinja %cmake_flags% ^
   -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld;lldb;flang;mlir" ^
-  -DLLVM_ENABLE_RUNTIMES="compiler-rt;openmp" ^
+  %lto_cmake_flag% ^
   %common_lldb_flags% ^
   -DPYTHON_HOME=%PYTHONHOME% ^
   %cmake_profile_flags% %llvm_src%\llvm || exit /b 1
@@ -378,14 +390,32 @@ if "%arch%"=="amd64" (
 ) else (
   set filename=clang+llvm-%version%-aarch64-pc-windows-msvc
 )
+REM NOTE: LLVM_ENABLE_PDB is intentionally only set for this toolchain-only
+REM (tarball) reconfigure, not for the MSI/WiX "ninja package" build above:
+REM bundling PDBs into the WiX-generated MSI causes CPack/WiX packaging
+REM failures (likely due to duplicate file basenames / component limits),
+REM so PDBs are packaged separately as their own tarball instead.
+set pdb_cmake_flag=
+if "%enable-pdb%" == "true" set pdb_cmake_flag=-DLLVM_ENABLE_PDB=ON
 cmake -GNinja %cmake_flags% %cmake_profile_flags% -DLLVM_INSTALL_TOOLCHAIN_ONLY=OFF ^
-  -DCMAKE_INSTALL_PREFIX=%build_dir%/%filename% %llvm_src%\llvm || exit /b 1
+  -DCMAKE_INSTALL_PREFIX=%build_dir%/%filename% ^
+  %pdb_cmake_flag% ^
+  %llvm_src%\llvm || exit /b 1
 ninja install || exit /b 1
 :: check llvm_config is present & returns something
 %build_dir%/%filename%/bin/llvm-config.exe --bindir || exit /b 1
 cd ..
 7z a -ttar -so %filename%.tar %filename% | 7z a -txz -si %filename%.tar.xz
 
+if "%enable-pdb%" == "true" (
+  :: Package the PDB debug info files produced alongside the install tree
+  :: into their own archive so they can be uploaded as a separate artifact.
+  set pdb_filename=%filename%-pdb
+  pushd %filename%
+  7z a -ttar -so ..\!pdb_filename!.tar bin\*.pdb lib\*.pdb | 7z a -txz -si ..\!pdb_filename!.tar.xz
+  popd
+)
+
 exit /b 0
 
 ::==============================================================================
@@ -399,8 +429,10 @@ set python_dir=%1
 
 REM Set Python environment
 if "%local-python%" == "true" (
-  FOR /F "delims=" %%i IN ('where python.exe ^| head -1') DO set python_exe=%%i
-  set PYTHONHOME=!python_exe:~0,-11!
+  set python_exe=
+  FOR /F "delims=" %%i IN ('where python.exe') DO if not defined python_exe set python_exe=%%i
+  for %%p in ("!python_exe!") do set PYTHONHOME=%%~dpp
+  if "!PYTHONHOME:~-1!" == "\" set PYTHONHOME=!PYTHONHOME:~0,-1!
 ) else (
   %python_dir%/python.exe --version || exit /b 1
   set PYTHONHOME=%python_dir%
@@ -497,18 +529,37 @@ cmake -GNinja %cmake_flags% -DLLVM_TARGETS_TO_BUILD=Native ^
 ninja clang || exit /b 1
 set instrumented_clang=%cd:\=/%/bin/clang-cl.exe
 cd ..
-REM Use that to build part of llvm to generate a profile.
 mkdir train
 cd train
-cmake -GNinja %cmake_flags% ^
-  -DCMAKE_C_COMPILER=%instrumented_clang% ^
-  -DCMAKE_CXX_COMPILER=%instrumented_clang% ^
-  -DLLVM_ENABLE_PROJECTS=clang ^
-  -DLLVM_TARGETS_TO_BUILD=Native ^
-  %llvm_src%\llvm || exit /b 1
-REM Drop profiles generated from running cmake; those are not representative.
-del ..\instrument\profiles\*.profraw
-ninja tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/Sema.cpp.obj
+if "%enhanced-pgo%" == "true" (
+  REM Build LLVMSupport with the instrumented clang to generate a broad profile.
+  REM This mirrors the Linux perf-training approach (llvm-support/build.test)
+  REM and exercises the compiler across many translation units and code paths,
+  REM rather than compiling a single file (Sema.cpp).
+  cmake -GNinja ^
+    -DCMAKE_BUILD_TYPE=Release ^
+    -DCMAKE_C_COMPILER=%instrumented_clang% ^
+    -DCMAKE_CXX_COMPILER=%instrumented_clang% ^
+    -DLLVM_TARGETS_TO_BUILD=Native ^
+    -DLLVM_ENABLE_PROJECTS="" ^
+    -DLLVM_ENABLE_RUNTIMES="" ^
+    %llvm_src%\llvm || exit /b 1
+  REM Drop profiles generated from running cmake; those are not representative.
+  del ..\instrument\profiles\*.profraw
+  ninja LLVMSupport || exit /b 1
+) else (
+  REM Legacy training step: use that build of clang to compile a single file,
+  REM as a cheap approximation of a real training workload.
+  cmake -GNinja %cmake_flags% ^
+    -DCMAKE_C_COMPILER=%instrumented_clang% ^
+    -DCMAKE_CXX_COMPILER=%instrumented_clang% ^
+    -DLLVM_ENABLE_PROJECTS=clang ^
+    -DLLVM_TARGETS_TO_BUILD=Native ^
+    %llvm_src%\llvm || exit /b 1
+  REM Drop profiles generated from running cmake; those are not representative.
+  del ..\instrument\profiles\*.profraw
+  ninja tools/clang/lib/Sema/CMakeFiles/obj.clangSema.dir/Sema.cpp.obj || exit /b 1
+)
 cd ..
 set profile=%cd:\=/%/profile.profdata
 %stage0_bin_dir%\llvm-profdata merge -output=%profile% instrument\profiles\*.profraw || exit /b 1



More information about the llvm-commits mailing list