From 5ac1ffb58b0b1f14ce0a64a501f2881b8abfd353 Mon Sep 17 00:00:00 2001 From: StefanStojanovic Date: Thu, 29 Oct 2026 12:51:24 +0100 Subject: [PATCH 1/2] build,win: add pgo generate workflow Signed-off-by: StefanStojanovic --- .github/workflows/pgo-windows.yml | 243 ++++++++++++++++++++++++++++++ tools/pgo/README.md | 51 +++++++ 2 files changed, 294 insertions(+) create mode 100644 .github/workflows/pgo-windows.yml diff --git a/.github/workflows/pgo-windows.yml b/.github/workflows/pgo-windows.yml new file mode 100644 index 000000000000..091c9761eb00 --- /dev/null +++ b/.github/workflows/pgo-windows.yml @@ -0,0 +1,243 @@ +name: Windows PGO profdata generation + +on: + workflow_dispatch: + inputs: + source_ref: + description: Commit SHA, branch, or tag in this fork to build and profile + required: true + type: string + architecture: + description: Target architecture to build and profile + required: true + type: choice + default: arm64 + options: + - arm64 + - x64 + committer_name: + description: Your full name for the commit and DCO sign-off + required: true + type: string + committer_email: + description: Your email for the commit and DCO sign-off + required: true + type: string + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ inputs.architecture }} + cancel-in-progress: false + +env: + PGO_ARCH: ${{ inputs.architecture }} + PYTHON_VERSION: '3.15' + RUSTC_VERSION: '1.88' + +jobs: + build: + if: github.repository == 'JaneaSystems/node' + runs-on: windows-2025 + timeout-minutes: 300 + outputs: + source_sha: ${{ steps.source.outputs.sha }} + source_date: ${{ steps.source.outputs.date }} + publish_branch: ${{ steps.source.outputs.branch }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.source_ref }} + persist-credentials: false + - name: Resolve the source commit and date + id: source + shell: pwsh + run: | + $sourceSha = git rev-parse --verify 'HEAD^{commit}' + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $timestamp = git show --no-patch --format=%ct $sourceSha + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $sourceDate = [DateTimeOffset]::FromUnixTimeSeconds([long]$timestamp).UtcDateTime.ToString('yyyy-MM-dd') + "sha=$sourceSha" >> $env:GITHUB_OUTPUT + "date=$sourceDate" >> $env:GITHUB_OUTPUT + "branch=pgo-windows-$env:PGO_ARCH-$sourceSha-$sourceDate" >> $env:GITHUB_OUTPUT + "Source commit: $sourceSha" >> $env:GITHUB_STEP_SUMMARY + "Source commit date (UTC): $sourceDate" >> $env:GITHUB_STEP_SUMMARY + if (!(Test-Path vcbuild.bat) -or !(Test-Path tools/pgo/pgo.py)) { + throw 'The selected commit must contain vcbuild.bat and tools/pgo/pgo.py for Windows PGO support.' + } + - name: Check whether the profile was already published + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + PGO_BRANCH: ${{ steps.source.outputs.branch }} + run: | + gh auth setup-git + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $existingBranch = git ls-remote --heads origin "refs/heads/$env:PGO_BRANCH" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + if ($existingBranch) { + $message = "This action was already done previously: branch $env:PGO_BRANCH already exists." + Write-Output "::error::$message" + $message >> $env:GITHUB_STEP_SUMMARY + exit 1 + } + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Set up Rust for the target architecture + shell: pwsh + run: | + if ($env:PGO_ARCH -cnotin @('arm64', 'x64')) { + throw 'The target architecture must be arm64 or x64.' + } + rustup override set $env:RUSTC_VERSION + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + if ($env:PGO_ARCH -eq 'arm64') { + rustup target add aarch64-pc-windows-msvc + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } + - name: Install NASM for x64 + if: inputs.architecture == 'x64' + shell: pwsh + run: choco install nasm --yes --no-progress + - name: Build the instrumented ${{ inputs.architecture }} release + shell: cmd + run: | + if not "%PROCESSOR_ARCHITECTURE%"=="AMD64" exit /b 1 + call vcbuild.bat build-release %PGO_ARCH% pgo-generate nosign + if errorlevel 1 exit /b 1 + if not exist Release\node.exe exit /b 1 + echo LLVM_BIN=%VCINSTALLDIR%\Tools\Llvm\x64\bin>>"%GITHUB_ENV%" + - name: Stage the binary and matching profile tool + if: inputs.architecture == 'arm64' + shell: pwsh + run: | + New-Item -ItemType Directory -Path pgo-artifact/Release -Force | Out-Null + Copy-Item Release/node.exe pgo-artifact/Release/ + Copy-Item "$env:LLVM_BIN/llvm-profdata.exe" pgo-artifact/ + Get-ChildItem $env:LLVM_BIN -Filter '*.dll' | Copy-Item -Destination pgo-artifact/ + - name: Upload the instrumented build + if: inputs.architecture == 'arm64' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: node-pgo-windows-arm64 + path: pgo-artifact/ + if-no-files-found: error + retention-days: 7 + - name: Generate the x64 profile on the build runner + if: inputs.architecture == 'x64' + shell: pwsh + timeout-minutes: 30 + env: + LLVM_PROFILE_FILE: ${{ runner.temp }}\node-check-%p.profraw + run: | + $env:LLVM_PROFDATA = Join-Path $env:LLVM_BIN 'llvm-profdata.exe' + .\Release\node.exe -e "require('node:assert/strict').equal(process.arch, 'x64')" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + python tools\pgo\pgo.py + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + if ((Get-Item node.profdata).Length -eq 0) { + throw 'The generated profile is empty.' + } + - name: Upload the x64 profile + if: inputs.architecture == 'x64' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: profdata-windows-${{ inputs.architecture }} + path: node.profdata + if-no-files-found: error + retention-days: 7 + + train: + needs: build + if: inputs.architecture == 'arm64' + runs-on: windows-11-arm + timeout-minutes: 30 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ needs.build.outputs.source_sha }} + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + architecture: arm64 + - name: Download the instrumented build + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: node-pgo-windows-arm64 + - name: Generate the ARM64 profile + shell: pwsh + env: + LLVM_PROFDATA: ${{ github.workspace }}\llvm-profdata.exe + LLVM_PROFILE_FILE: ${{ runner.temp }}\node-check-%p.profraw + run: | + if ($env:RUNNER_ARCH -ne 'ARM64') { + throw 'PGO training requires a Windows ARM64 runner.' + } + .\Release\node.exe -e "require('node:assert/strict').equal(process.arch, 'arm64')" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + python tools\pgo\pgo.py + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + if ((Get-Item node.profdata).Length -eq 0) { + throw 'The generated profile is empty.' + } + - name: Upload the profile + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: profdata-windows-${{ inputs.architecture }} + path: node.profdata + if-no-files-found: error + retention-days: 7 + + publish: + needs: [build, train] + if: >- + ${{ + !cancelled() && + github.repository == 'JaneaSystems/node' && + needs.build.result == 'success' && + ( + (inputs.architecture == 'arm64' && needs.train.result == 'success') || + (inputs.architecture == 'x64' && needs.train.result == 'skipped') + ) + }} + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + env: + PGO_BRANCH: ${{ needs.build.outputs.publish_branch }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: JaneaSystems/node + ref: ${{ needs.build.outputs.source_sha }} + persist-credentials: false + - name: Download the profile into the repository + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: profdata-windows-${{ inputs.architecture }} + path: tools/pgo/profdata/windows/${{ inputs.architecture }} + - name: Commit and push the profile + shell: bash + env: + GH_TOKEN: ${{ github.token }} + GIT_AUTHOR_NAME: ${{ inputs.committer_name }} + GIT_AUTHOR_EMAIL: ${{ inputs.committer_email }} + GIT_COMMITTER_NAME: ${{ inputs.committer_name }} + GIT_COMMITTER_EMAIL: ${{ inputs.committer_email }} + run: | + profile="tools/pgo/profdata/windows/$PGO_ARCH/node.profdata" + test -s "$profile" + gh auth setup-git + git switch --create "$PGO_BRANCH" + git add --force -- "$profile" + git commit --allow-empty --signoff -m "win,build: add $PGO_ARCH pgo profdata" \ + --trailer "Assisted-by: GitHub Copilot" + git push origin "HEAD:refs/heads/$PGO_BRANCH" + echo "Published branch: $PGO_BRANCH" >> "$GITHUB_STEP_SUMMARY" diff --git a/tools/pgo/README.md b/tools/pgo/README.md index 956535217928..df4e4ff12b18 100644 --- a/tools/pgo/README.md +++ b/tools/pgo/README.md @@ -54,6 +54,57 @@ Clang installation. python tools\pgo\pgo.py --duration=30 ``` +### GitHub Actions: Windows PGO profdata generation + +The [Windows PGO workflow](../../.github/workflows/pgo-windows.yml) +is manually dispatched and only runs in `JaneaSystems/node`. Once it is on the +fork's default branch, open +**Actions > Windows PGO profdata generation > Run workflow** and select the +branch containing the workflow definition. Set `source_ref` to a full commit +SHA, branch, or tag to fetch from `JaneaSystems/node`; this input does not select +another fork. The repository condition checks where the workflow runs, not the +origin of the selected commit. Choose the target `architecture`: `arm64` +(the default) or `x64`. The selected source commit does not need to contain the +workflow file. Supply `committer_name` and `committer_email` with your full name +and email for the commit identity and `Signed-off-by` trailer, attesting to the +[Developer Certificate of Origin](https://developercertificate.org/). + +The workflow builds with +`vcbuild.bat build-release pgo-generate nosign` on `windows-2025`. +The `nosign` option avoids the Azure Trusted Signing credentials otherwise +required by `build-release`. The source ref is resolved once at checkout, and +every job uses that exact commit even if the source branch moves during the run. +The selected revision must support the Windows PGO build command and contain +`tools/pgo/pgo.py`; revisions predating that support cannot use this workflow. +The workflow sets up Python 3.15 and Rust 1.88, adds the +`aarch64-pc-windows-msvc` Rust target for ARM64, and installs NASM for x64. + +For ARM64, the workflow transfers `Release/node.exe` and the matching +`llvm-profdata` tool to `windows-11-arm` for training. Only the x64 profile merge +tool runs under Windows emulation; Node.js and the workloads run natively on +ARM64. For x64, `pgo.py` runs directly after compilation in the same job on the +same machine. The separate ARM64 training job and binary transfer are skipped. + +After `pgo.py` succeeds, the workflow places `node.profdata` in +`tools/pgo/profdata/windows//` and pushes a new branch named +`pgo-windows---YYYY-MM-DD` to `JaneaSystems/node`, using +the full source commit SHA and its committer date converted to UTC. Immediately +after resolving the source commit, the build job checks whether this branch +exists, before setting up tools or compiling. If it does, the build job fails +and reports that the action was already done previously; training and publication +are skipped. The generated commit is based on that source commit. +Its subject is `win,build: add pgo profdata`, with +your sign-off and an `Assisted-by: GitHub Copilot` trailer. The downloaded profile +replaces any existing file, and both new and modified profiles are staged. The +workflow commits and pushes even if the profile is unchanged, allowing an empty +commit in that case. + +Only the publication job receives `contents: write`, using the fork's +`GITHUB_TOKEN`; no personal access token is needed. Review the generated commit +before using it in a contribution. Profiles are retained as workflow artifacts +for seven days. ARM64 runs also retain the instrumented build transferred to the +training runner. + ## Quick Start: Linux with GCC ```bash From 7ec6cd4a6102b63731c7463a9d369114941fe54a Mon Sep 17 00:00:00 2001 From: StefanStojanovic Date: Mon, 2 Nov 2026 16:34:24 +0100 Subject: [PATCH 2/2] build,win: use vs2022 Signed-off-by: StefanStojanovic --- .github/workflows/pgo-windows.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pgo-windows.yml b/.github/workflows/pgo-windows.yml index 091c9761eb00..8ce591f42241 100644 --- a/.github/workflows/pgo-windows.yml +++ b/.github/workflows/pgo-windows.yml @@ -39,7 +39,10 @@ env: jobs: build: if: github.repository == 'JaneaSystems/node' - runs-on: windows-2025 + # windows-2022 comes with VS2022, windows-2025 comes with VS2026 + # node.profdata from VS2026 is not compatible with VS2022, but vice versa is + # after moving to VS2026, the profdata can still be used with VS2022, but should be updated + runs-on: windows-2022 timeout-minutes: 300 outputs: source_sha: ${{ steps.source.outputs.sha }}