Skip to content

Add ARM64 native build support#2027

Open
tyrielv wants to merge 3 commits into
masterfrom
tyrielv/arm64-prototype
Open

Add ARM64 native build support#2027
tyrielv wants to merge 3 commits into
masterfrom
tyrielv/arm64-prototype

Conversation

@tyrielv

@tyrielv tyrielv commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Add ARM64 native build support

Adds native ARM64 build and release support alongside the existing x64
build. Eliminates Prism (x64 emulation) overhead on ARM64 Windows devices.

Why

VFS for Git 2.0 ships a single x64 NativeAOT binary that runs under Prism
on ARM64 Windows. A native ARM64 build eliminates that overhead: 1.47-1.53x
faster cold startup, 1.37x faster functional test suite.

Commits

1. Add architecture field to telemetry events

Adds ProcessHelper.GetCurrentProcessArchitecture() (RID-style "x64"/"arm64")
and emits it alongside Version in JsonTracer.WriteStartEvent,
HeartbeatThread.EmitHeartbeat, GVFSService.Windows.Run, and
TelemetryDaemonEventListener pipe messages.

2. Add ARM64 native build support

Architecture parameterization of the build:

  • Directory.Build.props: introduces $(VfsArch)/$(VfsNativePlatform); all hardcoded win-x64 / x64-windows-* references go through these.
  • Build.bat: gains 4th ARCH argument (default x64). Threads through vcpkg triplet, MSBuild Platform, dotnet RID.
  • .vcxproj files: ARM64 configurations, parameterized toolset/paths.
  • GVFS.sln: ARM64 solution configurations.
  • Installer: ARM64 gets -arm64 suffix; x64 keeps historical name.
  • New vcpkg triplets for ARM64 (static-aot and dynamic).
  • RunUnitTests.bat: parameterized for architecture.

CI (GitHub Actions):

  • build.yaml: matrix gains architecture: [x64, arm64] dimension, routes to windows-11-arm / windows-2025 runners.
  • functional-tests.yaml: downloads arch-keyed artifacts.
  • upgrade-tests.yaml: updated to arch-suffixed artifact names.

3. Add ARM64 to ADO release pipeline

Restructures .azure-pipelines/release.yml to build, ESRP-sign, and publish both x64 and ARM64 installers in parallel using a build_matrix parameter with ${{ each }}`.

Notable workaround: UseDotNet@2 is broken on ARM64 Windows (azure-pipelines-tasks#20300) -- its get-os-platform.ps1 runs under x86 WindowsPowerShell 5.1 and always detects win-x86. Bypassed with dotnet-install.ps1 under pwsh.

Verification

  • GHA CI passes on both architectures (build + unit tests + functional tests)
  • ADO release pipeline: build + unit tests pass on both x64 and arm64
  • Perf: 1.47-1.53x faster startup, 1.37x faster functional tests on ARM64

@tyrielv tyrielv force-pushed the tyrielv/arm64-prototype branch 10 times, most recently from 8768f70 to b9332bc Compare June 19, 2026 19:10
tyrielv added 2 commits June 19, 2026 13:39
The telemetry events we emit today record the GVFS process version but not
the architecture. With ARM64 support landing in a parallel commit, telemetry
queries can't distinguish ARM64-native installs from x64-under-Prism installs
on ARM64 hardware without this field.

This commit:
  * Adds ProcessHelper.GetCurrentProcessArchitecture() — returns the .NET
    RID-style lowercase arch ("x64", "arm64") of the running process. Caches
    the result like GetCurrentProcessVersion() does.
  * Adds an "Architecture" metadata field alongside the existing "Version"
    field in the three in-process telemetry emit sites:
      - JsonTracer.WriteStartEvent  (start-of-session log marker)
      - HeartbeatThread.EmitHeartbeat (hourly Heartbeat event)
      - GVFSService.Windows.Run (service startup event)
  * Adds an "architecture" top-level property to the PipeMessage schema in
    TelemetryDaemonEventListener — peer to "version". The constructor caches
    the value once and CreatePipeMessage attaches it to every outgoing
    message.
  * Updates TelemetryDaemonEventListenerTests for the new field (top-level
    property count 6 -> 7).

The collector side (devprod.git.telemetry) needs a matching change to pick
up the new field and pass it through to ETW / AppInsights; that lands as a
separate PR in that repo. Until then the collector silently drops the field;
old GVFS clients that don't send it cause no break either.

Assisted-by: Claude Opus 4.7
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
.NET 10 AOT means we now ship arch-specific binaries for everything; the
previous .NET Framework managed-code path that used the system-installed
framework is gone. On ARM64 Windows that means today's x64 build runs
under Prism emulation. Adding a native ARM64 build path eliminates that
overhead for users on ARM64 hosts.

Perf wins on a Snapdragon Windows host (Release / NativeAOT):
  * Per-process startup (gvfs version, gvfs --help, post-index-changed-hook):
    1.47-1.53x faster, saving 15-25 ms per invocation
  * Unit test suite: 1.21x faster
  * Full functional test suite: 1.37x faster (552/1/16 pass/fail/skip on both
    arches; same single known-flaky failure)
  * os.2020 real workload (clone + checkout + hydrate + blame): tied with
    x64-under-Prism once blob cache warmth is controlled

Architecture parameterization (foundation):
  * Directory.Build.props introduces $(VfsArch) — lowercase RID/vcpkg form
    used for RuntimeIdentifier and vcpkg triplet selection. Also introduces
    $(VfsNativePlatform) — mixed-case vcxproj-style form (x64 / ARM64).
    All hardcoded "win-x64", "x64-windows-*", "<Platform>x64</Platform>"
    references replaced with these properties.
  * Build.bat gains a 4th ARCH argument defaulting to x64 (so existing
    invocations keep their exact behaviour). The argument threads through
    vcpkg triplet, MSBuild Platform, dotnet RID. Two cross-cutting fixes
    accompany this:
      - Prepend the VS Installer dir to PATH so ilc can find vswhere when
        invoked from a non-developer cmd shell. Pre-existing trip hazard
        that was masked by how local-build invoked Build.bat.
      - Call vcvarsall.bat <arch> before the native C++ build loop so MSBuild
        can locate the matching cl.exe / link.exe and the right INCLUDE/LIB
        search paths for that arch. Without this, MSBuild finds the v145
        ARM64 toolset's targets file but cannot locate the actual ARM64
        build tool binaries.
      - Clear the Platform env var (vcvarsall.bat sets it) before the
        managed publish loop so csproj defaults to AnyCPU and doesn't add
        a spurious "\<arch>\" segment to managed output paths.
  * All 5 .vcxproj files gain Debug|ARM64 and Release|ARM64
    ProjectConfiguration entries; existing Configuration|Platform
    conditions simplified to Configuration-only (their contents were never
    platform-specific). Hardcoded UCRT lib arch path parameterized to
    \$(Platform.ToLower()). <PlatformToolset> parameterized via new
    $(VfsPlatformToolset) — v143 for x64 (preserves baseline byte-for-byte),
    v145 for arm64 (VS 2026's own toolset, the only one with ARM64
    cross-compile binaries here).
  * GVFS.sln gains Debug|ARM64 and Release|ARM64 SolutionConfigurationPlatforms
    and ProjectConfigurationPlatforms entries for every project.
  * GVFS.NativeTests.vcxproj: the x64-only GVFS.ProjFS NuGet's
    ProjectedFSLib.lib path is replaced with Windows SDK 10.0.26100's
    Lib\<sdk>\um\$(Platform.ToLower())\, which ships ProjectedFSLib.lib for
    x86, x64, and arm64 natively. The unused packages.config and its
    GVFS.ProjFS 2019.411.1 reference are removed.
  * layout.bat takes an arch arg and selects per-arch paths
    (win-<arch>, bin\<NATIVE_PLATFORM>\). For arm64 it falls back to
    %VCToolsRedistDir%\arm64\Microsoft.VC*.CRT\ for the VC runtime DLLs
    because GVFS.VCRuntime NuGet ships x64 only.
  * GVFS.Payload.csproj passes $(VfsArch) to layout.bat.
  * GVFS.Installers.csproj LayoutPath uses win-$(VfsArch); the
    InstallerArchSuffix property (empty for x64, "-arm64" for arm64) is
    passed to Inno Setup via /DArchSuffix. Setup.iss uses
    {#ArchSuffix} on OutputBaseFilename so the x64 installer keeps its
    historical name (SetupGVFS.<v>.exe) and the arm64 installer gets a
    "-arm64" suffix (SetupGVFS.<v>-arm64.exe). The two files can then
    coexist as assets on the same GitHub release.
  * GVFS.FunctionalTests.csproj NativeTests copy paths parameterized.
  * FastFetch.csproj drops its own <PlatformTarget>x64</PlatformTarget>
    override (now inherited from Directory.Build.props).
  * New vcpkg triplets: triplets/arm64-windows-static-aot.cmake and
    arm64-windows-dynamic.cmake.

Functional-test infrastructure:
  * GVFS.FunctionalTests/Settings.cs: dev-mode payload discovery uses
    RuntimeInformation.ProcessArchitecture instead of a hardcoded
    "win-x64". This way an ARM64 functional-test driver targets the ARM64
    payload it was built alongside, instead of silently falling through to
    PathToGVFS = "C:\Program Files\VFS for Git\GVFS.exe" (the system
    install) when the expected publish dir doesn't exist.
  * scripts/RunFunctionalTests-Dev.ps1 gains an -Arch parameter (x64
    default, arm64). The $payloadDir computation is fixed to match the
    Payload csproj's actual output layout
    (bin\<cfg>\win-<arch>\ — the Payload sets
    AppendTargetFrameworkToOutputPath=false), and a missing-file check
    fails loudly so the bug above can't silently recur.

CI matrix (per-arch native builds, no arch-cross):
  * .github/workflows/build.yaml gains an architecture dimension on the
    matrix (x64 + arm64). runs-on routes to windows-11-arm for arm64
    builds, windows-2025 for x64 (same pattern as the existing
    functional-tests workflow). Artifact names get an _<arch> suffix.
  * .github/workflows/functional-tests.yaml downloads arch-keyed
    GVFS_<cfg>_<arch> and FunctionalTests_<cfg>_<arch> artifacts so each
    hardware arch tests its own native build. The matrix value 'x86_64'
    is renamed to 'x64' for consistency with the artifact-name suffix and
    with everywhere else this PR uses the arch value.
  * .github/workflows/upgrade-tests.yaml is x64-only; its
    GVFS_<cfg> download is updated to GVFS_<cfg>_x64 to match the new
    build artifact name.

Assisted-by: Claude Opus 4.7
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/arm64-prototype branch from b9332bc to 2794233 Compare June 19, 2026 20:43
Restructures the release pipeline to build, ESRP-sign, and publish both
x64 and ARM64 installers in parallel. Uses the same ${{ each }} pattern
as microsoft/git's release pipeline (1ES templates don't support
strategy.matrix).

Changes:
  * .azure-pipelines/release.yml: single Build job replaced with a
    build_matrix parameter that generates Build_x64 and Build_arm64
    jobs. Each job runs on its native pool (GitClientPME-1ESHostedPool-
    intel-pc for x64, GitClientPME-1ESHostedPool-arm64-pc for arm64),
    builds with the arch arg to Build.bat, signs its own payload and
    installer via ESRP, and stages arch-suffixed pipeline artifacts.
    The release stage downloads both Installer_x64 and Installer_arm64
    and publishes both SetupGVFS.<v>.exe and SetupGVFS.<v>-arm64.exe
    as assets on the same draft GitHub Release.
  * scripts/CreateBuildArtifacts.bat: gains an optional 3rd ARCH arg
    (default x64) to select the correct win-<arch> output paths.
  * .github/workflows/build.yaml: passes matrix.architecture to
    CreateBuildArtifacts.bat.

Assisted-by: Claude Opus 4.7
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/arm64-prototype branch from 2794233 to b252981 Compare June 19, 2026 21:27
@tyrielv tyrielv marked this pull request as ready for review June 19, 2026 22:04

@mjcheetham mjcheetham left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks as correct as I can tell, at least without running the ADO pipeline itself (as is such with YAML pipelines 😅 )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants