diff --git a/.buildkite/ci.mjs b/.buildkite/ci.mjs index 7143f562cc..118e452460 100755 --- a/.buildkite/ci.mjs +++ b/.buildkite/ci.mjs @@ -470,19 +470,30 @@ function getBuildCommand(target, options, label) { */ function getWindowsArm64CrossFlags(target) { if (target.os === "windows" && target.arch === "aarch64") { - return " --toolchain windows-aarch64 -DSKIP_CODEGEN=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl"; + return " --toolchain windows-aarch64"; } return ""; } + /** * @param {Platform} platform * @param {PipelineOptions} options * @returns {Step} */ function getBuildCppStep(platform, options) { + const { os, arch } = platform; const command = getBuildCommand(platform, options); const crossFlags = getWindowsArm64CrossFlags(platform); + + // Build commands for C++ dependencies and bun + const buildCommands = [`${command}${crossFlags} --target bun`, `${command}${crossFlags} --target dependencies`]; + + // Cross-compiling Windows ARM64 from x64 requires Rust ARM64 target + if (os === "windows" && arch === "aarch64") { + buildCommands.unshift("rustup target add aarch64-pc-windows-msvc"); + } + return { key: `${getTargetKey(platform)}-build-cpp`, label: `${getTargetLabel(platform)} - build-cpp`, @@ -496,7 +507,7 @@ function getBuildCppStep(platform, options) { // We used to build the C++ dependencies and bun in separate steps. // However, as long as the zig build takes longer than both sequentially, // it's cheaper to run them in the same step. Can be revisited in the future. - command: [`${command}${crossFlags} --target bun`, `${command}${crossFlags} --target dependencies`], + command: buildCommands, }; } diff --git a/cmake/targets/BuildBoringSSL.cmake b/cmake/targets/BuildBoringSSL.cmake index d9ce01c685..5dd0b998c9 100644 --- a/cmake/targets/BuildBoringSSL.cmake +++ b/cmake/targets/BuildBoringSSL.cmake @@ -7,6 +7,13 @@ register_repository( 4f4f5ef8ebc6e23cbf393428f0ab1b526773f7ac ) +set(BORINGSSL_CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF) + +# Disable ASM on Windows ARM64 to avoid mixing non-ARM object files into ARM64 libs +if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64") + list(APPEND BORINGSSL_CMAKE_ARGS -DOPENSSL_NO_ASM=1) +endif() + register_cmake_command( TARGET boringssl @@ -15,7 +22,7 @@ register_cmake_command( ssl decrepit ARGS - -DBUILD_SHARED_LIBS=OFF + ${BORINGSSL_CMAKE_ARGS} INCLUDES include ) diff --git a/cmake/targets/BuildBun.cmake b/cmake/targets/BuildBun.cmake index d692ea4387..bf661a670b 100644 --- a/cmake/targets/BuildBun.cmake +++ b/cmake/targets/BuildBun.cmake @@ -1457,6 +1457,8 @@ if(NOT BUN_CPP_ONLY) # ==856230==See https://github.com/google/sanitizers/issues/856 for possible workarounds. # the linked issue refers to very old kernels but this still happens to us on modern ones. # disabling ASLR to run the binary works around it + # Skip post-build test/features when cross-compiling (can't run the target binary on the host) + if(NOT CMAKE_CROSSCOMPILING) set(TEST_BUN_COMMAND_BASE ${BUILD_PATH}/${bunExe} --revision) set(TEST_BUN_COMMAND_ENV_WRAP ${CMAKE_COMMAND} -E env BUN_DEBUG_QUIET_LOGS=1) @@ -1505,6 +1507,7 @@ if(NOT BUN_CPP_ONLY) ${BUILD_PATH}/features.json ) endif() + endif() # NOT CMAKE_CROSSCOMPILING if(CMAKE_HOST_APPLE AND bunStrip) register_command( @@ -1551,7 +1554,10 @@ if(NOT BUN_CPP_ONLY) string(REPLACE bun ${bunTriplet} bunPath ${bun}) endif() - set(bunFiles ${bunExe} features.json) + set(bunFiles ${bunExe}) + if(NOT CMAKE_CROSSCOMPILING) + list(APPEND bunFiles features.json) + endif() if(WIN32) list(APPEND bunFiles ${bun}.pdb) elseif(APPLE) diff --git a/cmake/targets/BuildLibArchive.cmake b/cmake/targets/BuildLibArchive.cmake index 3a7058683a..fc2ba85a89 100644 --- a/cmake/targets/BuildLibArchive.cmake +++ b/cmake/targets/BuildLibArchive.cmake @@ -41,6 +41,7 @@ register_cmake_command( # spawn a processes to compress instead of using the library. -DENABLE_ZLIB=OFF -DHAVE_ZLIB_H=ON + -DENABLE_CNG=OFF -DCMAKE_C_FLAGS="-I${VENDOR_PATH}/zlib" LIB_PATH libarchive diff --git a/cmake/targets/BuildLolHtml.cmake b/cmake/targets/BuildLolHtml.cmake index 6a12791b2d..37eed27be0 100644 --- a/cmake/targets/BuildLolHtml.cmake +++ b/cmake/targets/BuildLolHtml.cmake @@ -26,6 +26,12 @@ if(RELEASE) list(APPEND LOLHTML_BUILD_ARGS --release) endif() +# Cross-compilation: tell cargo to target ARM64 +if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64") + list(APPEND LOLHTML_BUILD_ARGS --target aarch64-pc-windows-msvc) + set(LOLHTML_LIBRARY ${LOLHTML_BUILD_PATH}/aarch64-pc-windows-msvc/${LOLHTML_BUILD_TYPE}/${CMAKE_STATIC_LIBRARY_PREFIX}lolhtml${CMAKE_STATIC_LIBRARY_SUFFIX}) +endif() + # Windows requires unwind tables, apparently. if (NOT WIN32) # The encoded escape sequences are intentional. They're how you delimit multiple arguments in a single environment variable. @@ -51,7 +57,12 @@ if(WIN32) if(MSVC_VERSIONS) list(GET MSVC_VERSIONS -1 MSVC_LATEST) # Get the latest version if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64") - set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/HostARM64/arm64/link.exe") + # Use Hostx64/arm64 for cross-compilation from x64, fall back to native + if(EXISTS "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe") + set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe") + else() + set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/HostARM64/arm64/link.exe") + endif() set(CARGO_LINKER_VAR "CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER") else() set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/Hostx64/x64/link.exe") diff --git a/cmake/toolchains/windows-aarch64.cmake b/cmake/toolchains/windows-aarch64.cmake index f6fb70668f..a1461aaf39 100644 --- a/cmake/toolchains/windows-aarch64.cmake +++ b/cmake/toolchains/windows-aarch64.cmake @@ -4,17 +4,35 @@ set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER_WORKS ON) set(CMAKE_CXX_COMPILER_WORKS ON) -# Force ARM64 architecture ID - this is what CMake uses to determine /machine: flag -set(MSVC_C_ARCHITECTURE_ID ARM64 CACHE INTERNAL "") -set(MSVC_CXX_ARCHITECTURE_ID ARM64 CACHE INTERNAL "") +# Prevent CMake from trying to link ARM64 executables during try_compile tests. +# The x64 host can't link ARM64 executables (missing ARM64 CRT/system libs). +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) -# CMake 4.0+ policy CMP0197 controls how MSVC machine type flags are handled -set(CMAKE_POLICY_DEFAULT_CMP0197 NEW CACHE INTERNAL "") +# The rest only applies when building on Windows (C++ and link steps). +# The Zig step runs on Linux and only needs CMAKE_SYSTEM_NAME/PROCESSOR above. +if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") -# Clear any inherited static linker flags that might have wrong machine types -set(CMAKE_STATIC_LINKER_FLAGS "" CACHE STRING "" FORCE) + # Set compiler target for ARM64 cross-compilation + set(CMAKE_C_COMPILER_TARGET aarch64-pc-windows-msvc CACHE STRING "" FORCE) + set(CMAKE_CXX_COMPILER_TARGET aarch64-pc-windows-msvc CACHE STRING "" FORCE) -# Use wrapper script for llvm-lib that strips /machine:x64 flags -# This works around CMake 4.1.0 bug where both ARM64 and x64 machine flags are added -get_filename_component(_TOOLCHAIN_DIR "${CMAKE_CURRENT_LIST_DIR}" DIRECTORY) -set(CMAKE_AR "${_TOOLCHAIN_DIR}/scripts/llvm-lib-wrapper.bat" CACHE FILEPATH "" FORCE) \ No newline at end of file + # ARM64 has lock-free atomics (highway's FindAtomics check can't run ARM64 test binary on x64) + set(ATOMICS_LOCK_FREE_INSTRUCTIONS TRUE CACHE BOOL "" FORCE) + set(HAVE_CXX_ATOMICS_WITHOUT_LIB TRUE CACHE BOOL "" FORCE) + set(HAVE_CXX_ATOMICS64_WITHOUT_LIB TRUE CACHE BOOL "" FORCE) + + # Force ARM64 architecture ID - this is what CMake uses to determine /machine: flag + set(MSVC_C_ARCHITECTURE_ID ARM64 CACHE INTERNAL "") + set(MSVC_CXX_ARCHITECTURE_ID ARM64 CACHE INTERNAL "") + + # CMake 4.0+ policy CMP0197 controls how MSVC machine type flags are handled + set(CMAKE_POLICY_DEFAULT_CMP0197 NEW CACHE INTERNAL "") + + # Clear any inherited static linker flags that might have wrong machine types + set(CMAKE_STATIC_LINKER_FLAGS "" CACHE STRING "" FORCE) + + # Use wrapper script for llvm-lib that strips /machine:x64 flags + # This works around CMake 4.1.0 bug where both ARM64 and x64 machine flags are added + get_filename_component(_TOOLCHAIN_DIR "${CMAKE_CURRENT_LIST_DIR}" DIRECTORY) + set(CMAKE_AR "${_TOOLCHAIN_DIR}/scripts/llvm-lib-wrapper.bat" CACHE FILEPATH "" FORCE) +endif() \ No newline at end of file diff --git a/cmake/tools/SetupWebKit.cmake b/cmake/tools/SetupWebKit.cmake index 5263f02fa9..a04204c1a2 100644 --- a/cmake/tools/SetupWebKit.cmake +++ b/cmake/tools/SetupWebKit.cmake @@ -259,3 +259,19 @@ file(RENAME ${CACHE_PATH}/bun-webkit ${WEBKIT_PATH}) if(APPLE) file(REMOVE_RECURSE ${WEBKIT_INCLUDE_PATH}/unicode) endif() + +# Patch simde neon.h for clang-cl ARM64 compatibility. +# The MSVC workaround uses intrinsics that clang-cl doesn't provide. +# TODO: Remove once WebKit is rebuilt with the upstream fix. +if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64") + set(SIMDE_NEON_H "${WEBKIT_INCLUDE_PATH}/wtf/simde/arm/neon.h") + if(EXISTS "${SIMDE_NEON_H}") + file(READ "${SIMDE_NEON_H}" SIMDE_CONTENT) + string(REPLACE + "(defined _MSC_VER) && (defined SIMDE_ARM_NEON_A64V8_NATIVE)" + "(defined _MSC_VER) && !defined(__clang__) && (defined SIMDE_ARM_NEON_A64V8_NATIVE)" + SIMDE_CONTENT "${SIMDE_CONTENT}") + file(WRITE "${SIMDE_NEON_H}" "${SIMDE_CONTENT}") + message(STATUS "Patched simde/arm/neon.h for clang-cl ARM64 compatibility") + endif() +endif() diff --git a/scripts/build.mjs b/scripts/build.mjs index 3eccf7f6ba..050a7b2cd6 100755 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -92,21 +92,9 @@ async function build(args) { generateOptions["--toolchain"] = toolchainPath; } - // Windows ARM64: automatically set required options + // Windows ARM64: log detection (compiler is selected by CMake/toolchain) if (isWindowsARM64) { - // Use clang-cl instead of MSVC cl.exe for proper ARM64 flag support - if (!generateOptions["-DCMAKE_C_COMPILER"]) { - generateOptions["-DCMAKE_C_COMPILER"] = "clang-cl"; - } - if (!generateOptions["-DCMAKE_CXX_COMPILER"]) { - generateOptions["-DCMAKE_CXX_COMPILER"] = "clang-cl"; - } - // Skip codegen by default since x64 bun crashes under WoW64 emulation - // Can be overridden with -DSKIP_CODEGEN=OFF once ARM64 bun is available - if (!generateOptions["-DSKIP_CODEGEN"]) { - generateOptions["-DSKIP_CODEGEN"] = "ON"; - } - console.log("Windows ARM64 detected: using clang-cl and SKIP_CODEGEN=ON"); + console.log("Windows ARM64 detected"); } const generateArgs = Object.entries(generateOptions).flatMap(([flag, value]) => diff --git a/src/bun.js/bindings/highway_strings.cpp b/src/bun.js/bindings/highway_strings.cpp index f34d295d35..51688bb868 100644 --- a/src/bun.js/bindings/highway_strings.cpp +++ b/src/bun.js/bindings/highway_strings.cpp @@ -81,19 +81,6 @@ size_t IndexOfAnyCharImpl(const uint8_t* HWY_RESTRICT text, size_t text_len, con } else { ASSERT(chars_len <= 16); - // Use FixedTag to preload search characters into fixed-size vectors. - // ScalableTag vectors (SVE) are sizeless and cannot be stored in arrays. - // FixedTag gives us a known compile-time size that can be stored in arrays, - // then ResizeBitCast converts back to scalable vectors in the inner loop. - static constexpr size_t kMaxPreloadedChars = 16; - const hn::FixedTag d_fixed; - using VecFixed = hn::Vec; - VecFixed char_vecs[kMaxPreloadedChars]; - const size_t num_chars_to_preload = std::min(chars_len, kMaxPreloadedChars); - for (size_t c = 0; c < num_chars_to_preload; ++c) { - char_vecs[c] = hn::Set(d_fixed, chars[c]); - } - const size_t simd_text_len = text_len - (text_len % N); size_t i = 0; @@ -101,8 +88,12 @@ size_t IndexOfAnyCharImpl(const uint8_t* HWY_RESTRICT text, size_t text_len, con const auto text_vec = hn::LoadN(d, text + i, N); auto found_mask = hn::MaskFalse(d); - for (size_t c = 0; c < num_chars_to_preload; ++c) { - found_mask = hn::Or(found_mask, hn::Eq(text_vec, hn::ResizeBitCast(d, char_vecs[c]))); + // Broadcast and compare each search character individually. + // Cannot preload into a Vec array because SVE types are sizeless. + // hn::Set is a single broadcast instruction; the compiler will + // hoist these loop-invariant broadcasts out of the outer loop. + for (size_t c = 0; c < chars_len; ++c) { + found_mask = hn::Or(found_mask, hn::Eq(text_vec, hn::Set(d, chars[c]))); } const intptr_t pos = hn::FindFirstTrue(d, found_mask);