Skip to content
Draft
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
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-15, windows-2025]
os: [ubuntu-24.04, macos-15, windows-2025, windows-11-arm]
runs-on: ${{ matrix.os }}
defaults:
run:
Expand Down Expand Up @@ -99,6 +99,7 @@ jobs:
- macos-15-intel
- windows-2022
- windows-2025
- windows-11-arm
runs-on: ${{ matrix.os }}
defaults:
run:
Expand Down
50 changes: 37 additions & 13 deletions nss/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,36 +196,41 @@ runs:
- name: Install build dependencies (Windows)
shell: bash
if: ${{ runner.os == 'Windows' && steps.check_build.outputs.build_nss }}
run: |
env:
RUNNER_ARCH: ${{ runner.arch }}
run: | # zizmor: ignore[github-env]
# shellcheck disable=SC2028
{
echo C:/msys64/usr/bin
echo C:/msys64/mingw64/bin
# mingw64/bin provides x86_64 MinGW toolchain binaries; not used on ARM64
# where the build uses MSVC exclusively.
[ "$RUNNER_ARCH" != "ARM64" ] && echo C:/msys64/mingw64/bin || true
} >> "$GITHUB_PATH"
/c/msys64/usr/bin/pacman -S --noconfirm python3-pip nsinstall
mkdir -p "$HOME/bin"
# renovate: depName=firefox datasource=custom.firefox versioning=loose
curl --proto '=https' --tlsv1.2 -o "$HOME/bin/nsinstall.py" -sSf https://hg-edge.mozilla.org/releases/mozilla-release/raw-file/FIREFOX_149_0_2_RELEASE/config/nsinstall.py
echo "f88df5591ee50d6e86625efd6e5f9c7b0dd4cba61e27ab533b1990115e100caf $HOME/bin/nsinstall.py" | sha256sum -c -
echo "#! /usr/bin/env python3" | cat - "$HOME/bin/nsinstall.py" > "$HOME/bin/nsinstall"
chmod +x "$HOME/bin/nsinstall"
echo "$HOME/bin" >> "$GITHUB_PATH"
Comment thread
larseggert marked this conversation as resolved.
# gyp-next>=0.22.0 added Windows ARM64 target support (nodejs/gyp-next#331).
# renovate: depName=gyp-next datasource=pypi
echo "gyp-next==0.22.2" > req.txt
echo "mozfile" >> req.txt # required by nsinstall.py
Comment thread
larseggert marked this conversation as resolved.
Comment thread
larseggert marked this conversation as resolved.
Comment thread
larseggert marked this conversation as resolved.
Comment thread
larseggert marked this conversation as resolved.
python3 -m pip install -r req.txt
Comment thread
larseggert marked this conversation as resolved.

- name: Set up MSVC (Windows)
if: ${{ runner.os == 'Windows' && steps.check_build.outputs.build_nss }}
uses: ilammy/msvc-dev-cmd@v1 # zizmor: ignore[unpinned-uses]
# TODO: Would like to pin this, but the Mozilla org allowlist requires "ilammy/msvc-dev-cmd@v1*"
# uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
with:
arch: ${{ runner.arch == 'ARM64' && 'arm64' || 'amd64' }}
Comment thread
larseggert marked this conversation as resolved.

- name: Set up build environment (Windows)
shell: bash
if: ${{ runner.os == 'Windows' && steps.check_build.outputs.build_nss }}
run: |
# Work around NSS's coreconf/msvc.sh bug with MSVC 18 (VS 2026):
# Set VSPATH so msvc.sh skips its setup branch, and configure the gyp overrides ourselves.
if [ "${VSCMD_VER%%.*}" = "18" ]; then
{
echo "VSPATH=$VSINSTALLDIR"
echo "GYP_MSVS_OVERRIDE_PATH=$VSINSTALLDIR"
echo "GYP_MSVS_VERSION=2026"
} >> "$GITHUB_ENV"
fi
# See https://github.com/ilammy/msvc-dev-cmd#name-conflicts-with-shell-bash
rm /usr/bin/link.exe || true
Comment thread
larseggert marked this conversation as resolved.

Expand Down Expand Up @@ -255,6 +260,7 @@ runs:
env:
TARGET_PLATFORM: ${{ inputs.target }}
RUNNER_OS: ${{ runner.os }}
RUNNER_ARCH: ${{ runner.arch }}
run: |
# We want to do an optimized build for accurate CPU profiling, but
# we also want debug symbols and frame pointers for that, which the normal optimized NSS
Expand Down Expand Up @@ -312,7 +318,25 @@ runs:
cp -vaL "$LIB_DIR"/* "dist/Release/lib"
else
[ "$SCCACHE_CC" ] && [ "$SCCACHE_CXX" ] && export CC="$SCCACHE_CC" CXX="$SCCACHE_CXX"
$NSS_DIR/build.sh -g -Ddisable_tests=1 -Ddisable_dbm=1 -Ddisable_libpkix=1 -Ddisable_ckbi=1 -Ddisable_fips=1 --opt --static
NSS_EXTRA=""
if [ "$RUNNER_OS" == "Windows" ]; then
# nss-msvc-arm64.patch also fixes VS 2026 compatibility on all Windows
# targets, so it is applied unconditionally.
patch -p1 < "$GITHUB_ACTION_PATH/patches/nss-msvc-arm64.patch"
Comment thread
larseggert marked this conversation as resolved.
Comment thread
larseggert marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

Consider adding --fuzz=0 to both patch invocations (here and in the loop below) to enforce exact context matching. Default fuzz of 2 means patch can silently apply a hunk at the wrong offset if an upstream NSS/NSPR release shifts code by a few lines — a strict match is safer for automated CI where you'd rather fail loudly than patch the wrong location.

Suggested change
patch -p1 < "$GITHUB_ACTION_PATH/patches/nss-msvc-arm64.patch"
patch --fuzz=0 -p1 < "$GITHUB_ACTION_PATH/patches/nss-msvc-arm64.patch"

fi
if [ "$RUNNER_OS" == "Windows" ] && [ "$RUNNER_ARCH" == "ARM64" ]; then
# Apply ARM64-specific patches to NSPR and NSS.
# Each patch file can be removed individually as upstream adds support,
# except nss-arm64-gyp-and-aes.patch which bundles two coupled changes.
for p in "$GITHUB_ACTION_PATH/patches/"*.patch; do
Comment thread
larseggert marked this conversation as resolved.
[ "$p" = "$GITHUB_ACTION_PATH/patches/nss-msvc-arm64.patch" ] && continue
patch -p1 < "$p"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

Same --fuzz=0 suggestion here for consistency:

Suggested change
patch -p1 < "$p"
patch --fuzz=0 -p1 < "$p"

done

NSS_EXTRA="--target=arm64"
fi
# shellcheck disable=SC2086
$NSS_DIR/build.sh -g -Ddisable_tests=1 -Ddisable_dbm=1 -Ddisable_libpkix=1 -Ddisable_ckbi=1 -Ddisable_fips=1 --opt --static $NSS_EXTRA
fi

- name: Save NSS cache
Expand Down
24 changes: 24 additions & 0 deletions nss/patches/nspr-detect-arm64-cpu.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Fix CPU architecture detection in NSPR configure for Windows ARM64.
#
# When NSS builds NSPR with --target=arm64, nspr.sh passes
# --host=aarch64-pc-mingw32 which activates configure's cross-compile path.
# That path hardcoded CPU_ARCH=x86 for all mingw* targets regardless of
# target_cpu. Use target_cpu instead so aarch64 cross-compile targets are
# handled correctly.
#
# Removable once NSPR configure handles ARM64 in the mingw* cross-compile case.
--- a/nspr/configure
+++ b/nspr/configure
@@ -6647,7 +6647,12 @@
linux*) OS_ARCH=Linux ;;
solaris*) OS_ARCH=SunOS OS_RELEASE=5 ;;
- mingw*) OS_ARCH=WINNT CPU_ARCH=x86 ;;
+ mingw*)
+ OS_ARCH=WINNT
+ case "$target_cpu" in
+ aarch64) CPU_ARCH=aarch64; USE_64=1 ;;
+ *) CPU_ARCH=x86 ;;
+ esac ;;
cygwin*) OS_ARCH=WINNT ;;
darwin*) OS_ARCH=Darwin ;;
riscos*) OS_ARCH=RISCOS ;;
49 changes: 49 additions & 0 deletions nss/patches/nspr-ntmisc-arm64.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Add MSVC ARM64 implementations for PR_StackPush and PR_StackPop in ntmisc.c.
#
# The existing MSVC code path uses x86 inline assembly (__asm { ... }) which
# the ARM64 MSVC compiler does not support. Add a _M_ARM64 branch using
# _InterlockedExchangePointer (ARM64 compiler intrinsic, available via
# <intrin.h> already included by _winnt.h) to implement the same spin-lock
# semantics as the x86 xchg instruction.
#
# Removable once NSPR provides ARM64 implementations upstream.
--- a/nspr/pr/src/md/windows/ntmisc.c
+++ b/nspr/pr/src/md/windows/ntmisc.c
@@ -1075,6 +1075,15 @@
*(void**)stack_elem = tmp;
__asm__("" : : : "memory");
*tos = stack_elem;
+# elif defined(_M_ARM64)
+ void* volatile* tos = (void* volatile*)stack;
+ void* tmp;
+retry:
+ if (*tos == (void*)-1) goto retry;
Comment thread
larseggert marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

The ARM64 PR_StackPush and PR_StackPop spin-loops use a non-atomic *tos read as a fast-path check before the _InterlockedExchangePointer. On ARM64's weakly-ordered memory model, this read could observe a stale value (e.g. see -1 after the lock was already released), causing extra spins. This doesn't affect correctness — the subsequent interlocked exchange is the real synchronization point — but on high-contention workloads it could spin longer than necessary. A ReadAcquire((volatile LONG64*)tos) or __iso_volatile_load64 would make the fast-path read more timely, though it's a marginal improvement for code that is inherently a spin-lock.

+ tmp = _InterlockedExchangePointer(tos, (void*)-1);
+ if (tmp == (void*)-1) goto retry;
+ *(void**)stack_elem = tmp;
+ _InterlockedExchangePointer(tos, stack_elem);
# else
__asm
{
@@ -1119,6 +1128,21 @@
*tos = tmp;
}

+ return tmp;
+# elif defined(_M_ARM64)
+ void* volatile* tos = (void* volatile*)stack;
+ void* tmp;
+retry:
+ if (*tos == (void*)-1) goto retry;
+ tmp = _InterlockedExchangePointer(tos, (void*)-1);
+ if (tmp == (void*)-1) goto retry;
+ if (tmp != (void*)0) {
+ void* next = *(void**)tmp;
+ _InterlockedExchangePointer(tos, next);
+ *(void**)tmp = 0;
+ } else {
+ _InterlockedExchangePointer(tos, tmp);
+ }
return tmp;
# else
__asm
25 changes: 25 additions & 0 deletions nss/patches/nspr-skip-rc-compilation.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Skip NSPR resource file compilation on Windows ARM64.
#
# NSPR's Makefile builds nspr.rc into a .res file via rc.exe. On ARM64,
# rc.exe's preprocessor cannot parse ARM64-specific constructs in NSPR's
# headers and fails. The resource file is only needed for shared-library
# version metadata and is not required for the static build we produce.
# Suppressing it on ARM64 only (via CPU_ARCH=aarch64) leaves x64/x86
# builds unaffected.
#
# Removable once NSPR's Windows Makefile handles ARM64 resource compilation.
--- a/nspr/pr/src/Makefile.in
+++ b/nspr/pr/src/Makefile.in
@@ -245,7 +245,11 @@
endif

ifeq ($(OS_ARCH), WINNT)
-RES=$(OBJDIR)/nspr.res
+ifeq ($(CPU_ARCH), aarch64)
+RES=
+else
+RES=$(OBJDIR)/nspr.res
+endif
RESNAME=nspr.rc
endif # WINNT

17 changes: 17 additions & 0 deletions nss/patches/nspr-winnt-arch-arm64.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Add ARM64 to _PR_SI_ARCHITECTURE detection in _winnt.h.
#
# _winnt.h defines _PR_SI_ARCHITECTURE for x86, x86-64, and ia64, but
# has a #error fallthrough for any other architecture. Without this patch
# every ARM64 compilation fails with "unknown processor architecture".
#
# Removable once NSPR adds ARM64 support to _winnt.h upstream.
--- a/nspr/pr/include/md/_winnt.h
+++ b/nspr/pr/include/md/_winnt.h
@@ -45,6 +45,8 @@
#elif defined(_M_IA64) || defined(_IA64_)
#define _PR_SI_ARCHITECTURE "ia64"
+#elif defined(_M_ARM64) || defined(_ARM64_)
+#define _PR_SI_ARCHITECTURE "aarch64"
#else
#error unknown processor architecture
#endif
18 changes: 18 additions & 0 deletions nss/patches/nspr-winnt-cfg-arm64.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Extend the 64-bit platform branch in _winnt.cfg to cover ARM64.
#
# NSPR's _winnt.cfg uses _M_X64 / _M_AMD64 / _AMD64_ to select the 64-bit
# configuration (IS_64, pointer sizes, etc.). ARM64 uses the same LLP64 ABI
# as x64 (64-bit pointers, 32-bit long) and belongs on the same branch.
#
# Removable once NSPR adds _M_ARM64 / _ARM64_ support upstream.
--- a/nspr/pr/include/md/_winnt.cfg
+++ b/nspr/pr/include/md/_winnt.cfg
@@ -68,7 +68,7 @@
#define PR_BYTES_PER_WORD_LOG2 2
#define PR_BYTES_PER_DWORD_LOG2 2

-#elif defined(_M_X64) || defined(_M_AMD64) || defined(_AMD64_)
+#elif defined(_M_X64) || defined(_M_AMD64) || defined(_AMD64_) || defined(_M_ARM64) || defined(_ARM64_)

#define IS_LITTLE_ENDIAN 1
#undef IS_BIG_ENDIAN
28 changes: 28 additions & 0 deletions nss/patches/nspr-winnt-interlocked-intrinsics.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Redirect Win32 Interlocked* exports to _Interlocked* compiler intrinsics.
#
# On ARM64 Windows, kernel32.lib does not export the Win32 Interlocked*
# functions as callable symbols; they are only available as compiler
# intrinsics (underscore-prefixed). Placing redirects immediately after
# <windows.h> rewrites every call site in NSPR via the preprocessor:
#
# - _MD_ATOMIC_* macros in _winnt.h (all builds)
# - _MD_LOCK lock-contention counters in _winnt.h (#ifdef PROFILE_LOCKS)
# - InterlockedCompareExchange called directly in ntio.c and prmwait.c
#
# Removable once NSPR uses compiler intrinsics unconditionally on Windows.
--- a/nspr/pr/include/md/_winnt.h
+++ b/nspr/pr/include/md/_winnt.h
@@ -17,6 +17,13 @@
#endif /* _WIN32_WINNT */

#include <windows.h>
+/* ARM64 kernel32.lib does not export Interlocked* as callable symbols;
+ * redirect all call sites to the equivalent compiler intrinsics. */
+#define InterlockedCompareExchange _InterlockedCompareExchange
+#define InterlockedIncrement _InterlockedIncrement
+#define InterlockedDecrement _InterlockedDecrement
+#define InterlockedExchange _InterlockedExchange
+#define InterlockedExchangeAdd _InterlockedExchangeAdd
#include <winsock.h>
Comment thread
larseggert marked this conversation as resolved.
#ifdef __MINGW32__
Comment thread
larseggert marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

The redirect list covers the five Interlocked functions NSPR uses (_MD_ATOMIC_* macros and direct calls in ntio.c/prmwait.c), but InterlockedExchangePointer is absent. This is correct — on 64-bit Windows, InterlockedExchangePointer is already defined as _InterlockedExchangePointer by the SDK headers, so no redirect is needed. Worth a brief comment in the patch header to explain the deliberate omission, since a future reader might think it was accidentally left out.

#include <mswsock.h>
65 changes: 65 additions & 0 deletions nss/patches/nss-arm64-gyp-and-aes.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Add ARM64 MSVC support to NSS's gyp configuration and AES implementation.
#
# These two changes are coupled and must be applied or removed together:
# aes-armv8.c has a `#ifndef __ARM_FEATURE_CRYPTO / #error` guard; that
# macro is only defined via the PreprocessorDefinitions added to config.gypi.
#
# config.gypi: NSS's config.gypi has per-arch MSVS settings for ia32 and x64
# but not ARM64. Without this block, gyp leaves msvs_configuration_platform
# unset for ARM64 (defaults to x86), causing ninja to use the wrong environment
# and tool paths. gyp-next >= 0.22.0 handles ARM64 toolchain discovery
# (environment.arm64, armasm64.exe), but NSS still needs its own preprocessor
# definitions here. /EHsc matches what the ia32 and x64 blocks already set.
#
# aes-armv8.c: guards its entire body with a preprocessor condition that only
# matches GCC/Clang. MSVC ARM64 supports the same NEON AES intrinsics
# (vaeseq_u8, vaesdq_u8, etc.) but does not define __clang__ or __GNUC__.
# IS_LITTLE_ENDIAN is included for consistency with the ghash patch — Windows
# ARM64 is always little-endian, but the explicit guard matches intent.
# Also adds a no-op __builtin_assume_aligned compat macro.
#
# Removable once NSS adds ARM64 MSVC support to config.gypi and aes-armv8.c.
--- a/nss/coreconf/config.gypi
+++ b/nss/coreconf/config.gypi
@@ -589,6 +589,19 @@
},
},
}],
+ [ 'target_arch=="arm64"', {
+ 'msvs_configuration_platform': 'ARM64',
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'PreprocessorDefinitions': [
+ 'WIN64',
+ '_ARM64_',
+ '__ARM_FEATURE_CRYPTO',
+ ],
+ 'AdditionalOptions': [ '/EHsc' ],
+ },
+ },
+ }],
],
}],
Comment thread
larseggert marked this conversation as resolved.
[ 'disable_dbm==1', {
--- a/nss/lib/freebl/aes-armv8.c
+++ b/nss/lib/freebl/aes-armv8.c
@@ -5,10 +5,15 @@
#include "secerr.h"
#include "rijndael.h"

-#if ((defined(__clang__) || \
- (defined(__GNUC__) && defined(__GNUC_MINOR__) && \
- (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 8)))) && \
- defined(IS_LITTLE_ENDIAN))
+#if (((defined(__clang__) || \
+ (defined(__GNUC__) && defined(__GNUC_MINOR__) && \
+ (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 8)))) && \
+ defined(IS_LITTLE_ENDIAN)) || \
+ (defined(_MSC_VER) && defined(_M_ARM64) && defined(IS_LITTLE_ENDIAN)))
+
+#ifdef _MSC_VER
+#define __builtin_assume_aligned(ptr, align) (ptr)
+#endif

#ifndef __ARM_FEATURE_CRYPTO
#error "Compiler option is invalid"
40 changes: 40 additions & 0 deletions nss/patches/nss-blinit-arm64.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Extend __aarch64__ guards in blinit.c to cover MSVC ARM64 (_M_ARM64).
#
# blinit.c uses __aarch64__ in three places where MSVC ARM64 needs to be
# included:
# 1. The _WIN64 + __aarch64__ guard for including <windows.h> (needed for
# IsProcessorFeaturePresent used inside CheckARMSupport on Windows).
# 2. The standalone #if defined(__aarch64__) that gates the ARM64-specific
# CheckARMSupport function definition.
# 3. The FreeblInit() dispatch that calls CheckARMSupport().
#
# Removable once NSS adds _M_ARM64 checks to blinit.c upstream.
--- a/nss/lib/freebl/blinit.c
+++ b/nss/lib/freebl/blinit.c
@@ -17,7 +17,7 @@
#include <intrin.h> /* for _xgetbv() */
#endif

-#if defined(_WIN64) && defined(__aarch64__)
+#if defined(_WIN64) && (defined(__aarch64__) || defined(_M_ARM64))
#include <windows.h>
#endif

@@ -186,7 +186,7 @@
#endif /* defined(__aarch64__) || defined(__arm__) */
/* clang-format on */

-#if defined(__aarch64__)
+#if defined(__aarch64__) || defined(_M_ARM64)

#if defined(__linux__)
// Defines from hwcap.h in Linux kernel - ARM64
@@ -557,7 +557,7 @@
{
#ifdef NSS_X86_OR_X64
CheckX86CPUSupport();
-#elif (defined(__aarch64__) || defined(__arm__))
+#elif (defined(__aarch64__) || defined(__arm__) || defined(_M_ARM64))
CheckARMSupport();
#elif (defined(__powerpc__))
CheckPPCSupport();
Loading
Loading