From 000ba0ffe36995ae182ed8385b23fb29c26d6f04 Mon Sep 17 00:00:00 2001 From: Harish RS Date: Wed, 25 Mar 2026 17:08:44 +0530 Subject: [PATCH] Add Windows ARM64 support to CI and build system --- .github/workflows/build.yml | 17 ++++++++++++++--- CMakeLists.txt | 30 +++++++++++++++++++++++++----- src/crc32c_arm64.cc | 32 ++++++++++++++++++++++++++++---- src/crc32c_arm64.h | 10 ++++++++++ src/crc32c_arm64_check.h | 9 +++++++++ 5 files changed, 86 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b27b2f..18acc3a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: fail-fast: false matrix: compiler: [clang, gcc, msvc] - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest, windows-11-arm] optimized: [true, false] shared_lib: [true, false] use_glog: [true, false] @@ -38,16 +38,26 @@ jobs: # Not testing with GCC on macOS. - os: macos-latest compiler: gcc - # Only testing with MSVC on Windows. + # Only testing with MSVC on Windows x64. - os: windows-latest compiler: clang - os: windows-latest compiler: gcc - # Not testing fringe configurations (glog, shared libraries) on Windows. + # Only testing with MSVC on Windows arm64. + - os: windows-11-arm + compiler: clang + - os: windows-11-arm + compiler: gcc + # Not testing fringe configurations (glog, shared libraries) on Windows x64. - os: windows-latest use_glog: true - os: windows-latest shared_lib: true + # Not testing fringe configurations (glog, shared libraries) on Windows arm64. + - os: windows-11-arm + use_glog: true + - os: windows-11-arm + shared_lib: true include: - compiler: clang CC: clang @@ -79,6 +89,7 @@ jobs: - name: Generate build config run: >- cmake -S "${{ github.workspace }}" -B "${{ env.CMAKE_BUILD_DIR }}" + ${{ matrix.os == 'windows-11-arm' && '-A ARM64' || '' }} -DCMAKE_BUILD_TYPE=${{ env.CMAKE_BUILD_TYPE }} -DCMAKE_INSTALL_PREFIX=${{ runner.temp }}/install_test/ -DBUILD_SHARED_LIBS=${{ matrix.shared_lib && '1' || '0' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index e75211f..9a8d122 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,19 +139,35 @@ set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQURED_FLAGS}) # Check for ARMv8 w/ CRC and CRYPTO extensions support in the compiler. set(OLD_CMAKE_REQURED_FLAGS ${CMAKE_REQUIRED_FLAGS}) if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - # TODO(pwnall): Insert correct flag when VS gets ARM CRC32C support. - set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} /arch:NOTYET") + # MSVC ARM64 builds have intrinsics available without special flags when + # targeting ARM64. For cross-compilation or emulation, use /arch:armv8.0 + if(NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|arm64|aarch64" OR + CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64")) + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} /arch:armv8.0") + endif() else(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -march=armv8-a+crc+crypto") endif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") check_cxx_source_compiles(" +#if defined(_MSC_VER) +#include +#include +#else // !defined(_MSC_VER) #include #include +#endif // defined(_MSC_VER) int main() { __crc32cb(0, 0); __crc32ch(0, 0); __crc32cw(0, 0); __crc32cd(0, 0); +#if defined(_MSC_VER) + __n64 na, nb; + na.n64_u64[0] = 0; + nb.n64_u64[0] = 0; + neon_pmull_64(na, nb); +#else // !defined(_MSC_VER) vmull_p64(0, 0); - return 0; +#endif // defined(_MSC_VER) + return 0; } " HAVE_ARM64_CRC32C) set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQURED_FLAGS}) @@ -228,8 +244,12 @@ target_sources(crc32c_arm64 ) if(HAVE_ARM64_CRC32C) if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - # TODO(pwnall): Insert correct flag when VS gets ARM64 CRC32C support. - target_compile_options(crc32c_arm64 PRIVATE "/arch:NOTYET") + # MSVC ARM64 builds have intrinsics available without special flags when + # targeting ARM64. For cross-compilation or emulation, use /arch:armv8.0 + if(NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|arm64|aarch64" OR + CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64")) + target_compile_options(crc32c_arm64 PRIVATE "/arch:armv8.0") + endif() else(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") target_compile_options(crc32c_arm64 PRIVATE "-march=armv8-a+crc+crypto") endif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") diff --git a/src/crc32c_arm64.cc b/src/crc32c_arm64.cc index 701326d..164a9b6 100644 --- a/src/crc32c_arm64.cc +++ b/src/crc32c_arm64.cc @@ -20,9 +20,33 @@ #if HAVE_ARM64_CRC32C +#if defined(_MSC_VER) + +#include +#include + +// MSVC ARM64 vmull_p64 wrapper - handles polynomial multiplication +inline uint64_t Arm64_Vmull_P64(uint64_t a, uint64_t b) { + __n64 na, nb; + na.n64_u64[0] = a; + nb.n64_u64[0] = b; + __n128 result = neon_pmull_64(na, nb); + return result.n128_u64[0]; +} + +#else + #include #include +// GCC/Clang version - handles polynomial multiplication +inline uint64_t Arm64_Vmull_P64(uint64_t a, uint64_t b) { + poly128_t result = vmull_p64(a, b); + return vgetq_lane_u64(vreinterpretq_u64_p128(result), 0); +} + +#endif + #define KBYTES 1032 #define SEGMENTBYTES 256 @@ -67,7 +91,7 @@ uint32_t ExtendArm64(uint32_t crc, const uint8_t *data, size_t size) { // k0=CRC(x^(3*SEGMENTBYTES*8)), k1=CRC(x^(2*SEGMENTBYTES*8)), // k2=CRC(x^(SEGMENTBYTES*8)) - const poly64_t k0 = 0x8d96551c, k1 = 0xbd6f81f8, k2 = 0xdcb17aa4; + const uint64_t k0 = 0x8d96551c, k1 = 0xbd6f81f8, k2 = 0xdcb17aa4; crc = crc ^ kCRC32Xor; @@ -81,9 +105,9 @@ uint32_t ExtendArm64(uint32_t crc, const uint8_t *data, size_t size) { CRC32C1024BYTES(data); // Merge the 4 partial CRC32C values. - t2 = (uint64_t)vmull_p64(crc2, k2); - t1 = (uint64_t)vmull_p64(crc1, k1); - t0 = (uint64_t)vmull_p64(crc0, k0); + t2 = Arm64_Vmull_P64(crc2, k2); + t1 = Arm64_Vmull_P64(crc1, k1); + t0 = Arm64_Vmull_P64(crc0, k0); crc = __crc32cd(crc3, ReadUint64LE(data)); data += sizeof(uint64_t); crc ^= __crc32cd(0, t2); diff --git a/src/crc32c_arm64.h b/src/crc32c_arm64.h index dedd27e..688e2ec 100644 --- a/src/crc32c_arm64.h +++ b/src/crc32c_arm64.h @@ -14,6 +14,16 @@ #if HAVE_ARM64_CRC32C +#if defined(_MSC_VER) +// MSVC ARM64: arm64_neon.h includes both NEON and ACLE intrinsics +#include +#include +#else +// GCC/Clang: separate headers +#include +#include +#endif + namespace crc32c { uint32_t ExtendArm64(uint32_t crc, const uint8_t* data, size_t count); diff --git a/src/crc32c_arm64_check.h b/src/crc32c_arm64_check.h index c5a5e46..536c855 100644 --- a/src/crc32c_arm64_check.h +++ b/src/crc32c_arm64_check.h @@ -31,6 +31,10 @@ extern "C" unsigned long getauxval(unsigned long type) __attribute__((weak)); #include #endif // defined (__APPLE__) +#if defined(_WIN32) +#include +#endif + namespace crc32c { inline bool CanUseArm64Crc32() { @@ -54,6 +58,11 @@ inline bool CanUseArm64Crc32() { size_t len = sizeof(val); return sysctlbyname("hw.optional.armv8_crc32", &val, &len, nullptr, 0) == 0 && val != 0; +#elif defined(_WIN32) && (defined(_M_ARM64) || defined(_M_ARM64EC)) + // On Windows ARM64, CRC32 and Crypto extensions are always available + // PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE = 31 + // PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE = 30 + return IsProcessorFeaturePresent(31) && IsProcessorFeaturePresent(30); #else return false; #endif // HAVE_STRONG_GETAUXVAL || HAVE_WEAK_GETAUXVAL