Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 248 additions & 0 deletions .github/workflows/pgo-windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
name: Windows PGO profdata generation

on:
workflow_dispatch:
inputs:
source_ref:
description: Commit SHA, branch, or tag in nodejs/node 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.14'
RUSTC_VERSION: '1.88'

jobs:
build:
if: github.repository == 'nodejs/node' || github.repository == 'JaneaSystems/node'
# 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 }}
publish_branch: ${{ steps.source.outputs.branch }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: nodejs/node
ref: ${{ inputs.source_ref }}
persist-credentials: false
- name: Resolve the source commit
id: source
shell: pwsh
run: |
$sourceSha = git rev-parse --verify 'HEAD^{commit}'
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
"sha=$sourceSha" >> $env:GITHUB_OUTPUT
"branch=pgo-windows-$env:PGO_ARCH-$sourceSha" >> $env:GITHUB_OUTPUT
"Source commit: $sourceSha" >> $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 "https://github.com/$env:GITHUB_REPOSITORY.git" "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:
repository: nodejs/node
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 == 'nodejs/node' || 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: nodejs/node
ref: ${{ needs.build.outputs.source_sha }}
fetch-depth: 0
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"
git remote add destination "https://github.com/$GITHUB_REPOSITORY.git"
gh auth setup-git
existing_branch="$(git ls-remote --heads destination "refs/heads/$PGO_BRANCH")"
if [[ -n "$existing_branch" ]]; then
echo "::error::Branch $PGO_BRANCH already exists; refusing to overwrite it."
exit 1
fi
git switch --create "$PGO_BRANCH"
git add --force -- "$profile"
git commit --allow-empty --signoff -m "win,build: add $PGO_ARCH pgo profdata"
git push destination "HEAD:refs/heads/$PGO_BRANCH"
echo "Published branch: $GITHUB_REPOSITORY:$PGO_BRANCH" >> "$GITHUB_STEP_SUMMARY"
59 changes: 59 additions & 0 deletions tools/pgo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,65 @@ Clang installation.
python tools\pgo\pgo.py --duration=30
```

### GitHub Actions: Windows PGO profdata generation

The [Windows PGO workflow](../../.github/workflows/pgo-windows.yml)
can be manually dispatched from `nodejs/node` or `JaneaSystems/node`. Once it is
on that repository'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 in `nodejs/node`. Builds always use that upstream repository,
and profile branches are pushed only to the repository running the workflow.
For example, a run in `JaneaSystems/node` builds from `nodejs/node` and publishes
back to `JaneaSystems/node`.

Choose the target `architecture`: `arm64` (the default) or `x64`. The selected
source commit does not need to contain this 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 <architecture> pgo-generate nosign` on `windows-2022`.
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.14 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/<architecture>/` and pushes a new branch named
`pgo-windows-<architecture>-<full-source-sha>` in the repository running the
workflow. 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. Publication also checks for an existing
branch and fails without overwriting it. The generated commit is based on that
source commit. When publishing to a fork, the branch also includes the selected
upstream commit's history, even if those commits were not previously in the fork.
Its subject is `win,build: add <architecture> pgo profdata`, with
your sign-off. 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`. It uses the repository's
built-in `GITHUB_TOKEN` to push back to the same repository; no additional token
or Actions secret is needed. Repository rules and Actions permission policies
still apply.

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
Expand Down
Loading