diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8dfcac6..60184f7 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -35,7 +35,7 @@ jobs: - uses: actions/setup-node@v5 with: node-version: 22 - cache: 'yarn' + cache: "yarn" - name: Install dependencies run: yarn install - name: Download fixtures @@ -58,7 +58,7 @@ jobs: - uses: actions/setup-node@v5 with: node-version: 22 - cache: 'yarn' + cache: "yarn" - name: Install dependencies run: yarn install - name: Download fixtures @@ -69,6 +69,110 @@ jobs: run: cargo miri test env: MIRIFLAGS: "-Zmiri-disable-isolation" + + asan: + name: ASAN - Linux-x86_64 - ${{ matrix.asan.flag }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + asan: + - flag: sanitizer=address + options: detect_leaks=1 detect_stack_use_after_return=1 + - flag: sanitizer=memory + options: "" + - flag: sanitizer=safestack + options: "" + + steps: + - uses: actions/checkout@v5 + + - name: Setup node + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: "yarn" + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: nightly + components: rust-src + + - name: Install dependencies + run: yarn install --immutable --mode=skip-build + + - name: Download fixtures + run: node download-fixtures.js + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test with ASAN + run: cargo test --tests --target x86_64-unknown-linux-gnu + env: + RUST_TARGET: x86_64-unknown-linux-gnu + RUST_BACKTRACE: 1 + RUSTFLAGS: "-Z${{ matrix.asan.flag }}" + ASAN_OPTIONS: ${{ matrix.asan.options }} + CARGO_UNSTABLE_BUILD_STD: std,panic_abort + + + asan-win32: + name: ASAN - Windows-x86_64 + runs-on: windows-latest + + steps: + - uses: actions/checkout@v5 + + - name: Setup node + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: "yarn" + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: nightly + components: rust-src + + - name: Install dependencies + run: yarn install --immutable --mode=skip-build + + - name: Download fixtures + run: node download-fixtures.js + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Test with ASAN (Windows) + shell: pwsh + run: | + # Set ASAN environment variables for Windows + $env:ASAN_OPTIONS = "windows_hook_rtl_allocators=true:detect_leaks=0:print_stats=1:check_initialization_order=true:strict_string_checks=true" + + # Find and set the path to the ASAN runtime DLL + $vsPath = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath + $asanDllPath = Get-ChildItem -Path "$vsPath\VC\Tools\MSVC" -Recurse -Filter "clang_rt.asan_dynamic-x86_64.dll" | Select-Object -First 1 + if ($asanDllPath) { + $env:PATH = "$($asanDllPath.DirectoryName);$env:PATH" + Write-Host "Found ASAN DLL at: $($asanDllPath.FullName)" + } + cargo test --tests --target x86_64-pc-windows-msvc + env: + RUSTFLAGS: -Zsanitizer=address + RUST_BACKTRACE: 1 + CARGO_PROFILE_DEV_OPT_LEVEL: 1 + CARGO_UNSTABLE_BUILD_STD: std,panic_abort + + - name: Upload ASAN logs (Windows) + if: failure() + uses: actions/upload-artifact@v4 + with: + name: windows-asan-logs + path: | + asan.log* + *.asan.log + bench: strategy: matrix: @@ -94,7 +198,7 @@ jobs: - uses: actions/setup-node@v5 with: node-version: 22 - cache: 'yarn' + cache: "yarn" - name: Install dependencies run: yarn install - name: Download fixtures @@ -104,4 +208,11 @@ jobs: - name: Run benchmarks run: cargo bench env: - RUSTFLAGS: '-C target-cpu=native' \ No newline at end of file + RUSTFLAGS: "-C target-cpu=native" + + done: + runs-on: ubuntu-latest + needs: [test, miri, asan, asan-win32, bench] + steps: + - run: exit 1 + if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }} diff --git a/src/simd/avx2.rs b/src/simd/avx2.rs index 94a4546..c1cac44 100644 --- a/src/simd/avx2.rs +++ b/src/simd/avx2.rs @@ -220,25 +220,27 @@ pub unsafe fn format_string(value: &str, dst: &mut [u8]) -> usize { // Handle remaining bytes let mut placeholder: [u8; LANES] = [0; LANES]; while nb > 0 { - #[cfg(not(any(target_os = "linux", target_os = "macos")))] let v = { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd256u::loadu(placeholder.as_ptr()) - }; - #[cfg(any(target_os = "linux", target_os = "macos"))] - let v = { - if check_cross_page(sptr, LANES) { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd256u::loadu(placeholder.as_ptr()) - } else { - #[cfg(any(debug_assertions, miri))] - { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd256u::loadu(placeholder.as_ptr()) - } - #[cfg(not(any(debug_assertions, miri)))] - { - Simd256u::loadu(sptr) + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd256u::loadu(placeholder[..].as_ptr()) + } + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + if check_cross_page(sptr, LANES) { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd256u::loadu(placeholder[..].as_ptr()) + } else { + #[cfg(any(debug_assertions, miri))] + { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd256u::loadu(placeholder[..].as_ptr()) + } + #[cfg(not(any(debug_assertions, miri)))] + { + Simd256u::loadu(sptr) + } } } }; diff --git a/src/simd/avx512.rs b/src/simd/avx512.rs index c6ad65c..316edd2 100644 --- a/src/simd/avx512.rs +++ b/src/simd/avx512.rs @@ -212,25 +212,27 @@ pub unsafe fn format_string(value: &str, dst: &mut [u8]) -> usize { // Handle remaining bytes let mut placeholder: [u8; LANES] = [0; LANES]; while nb > 0 { - #[cfg(not(any(target_os = "linux", target_os = "macos")))] let v = { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd512u::loadu(placeholder.as_ptr()) - }; - #[cfg(any(target_os = "linux", target_os = "macos"))] - let v = { - if check_cross_page(sptr, LANES) { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd512u::loadu(placeholder.as_ptr()) - } else { - #[cfg(any(debug_assertions, miri))] - { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd512u::loadu(placeholder.as_ptr()) - } - #[cfg(not(any(debug_assertions, miri)))] - { - Simd512u::loadu(sptr) + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd512u::loadu(placeholder[..].as_ptr()) + } + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + if check_cross_page(sptr, LANES) { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd512u::loadu(placeholder[..].as_ptr()) + } else { + #[cfg(any(debug_assertions, miri))] + { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd512u::loadu(placeholder[..].as_ptr()) + } + #[cfg(not(any(debug_assertions, miri)))] + { + Simd512u::loadu(sptr) + } } } }; diff --git a/src/simd/neon.rs b/src/simd/neon.rs index c3e1f85..0300fc1 100644 --- a/src/simd/neon.rs +++ b/src/simd/neon.rs @@ -219,27 +219,29 @@ pub unsafe fn format_string(value: &str, dst: &mut [u8]) -> usize { } // Handle remaining bytes - let mut placeholder: [u8; 16] = [0; 16]; + let mut placeholder: [u8; LANES] = [0; LANES]; while nb > 0 { - #[cfg(not(any(target_os = "linux", target_os = "macos")))] let v = { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd128u::loadu(placeholder.as_ptr()) - }; - #[cfg(any(target_os = "linux", target_os = "macos"))] - let v = { - if check_cross_page(sptr, LANES) { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd128u::loadu(placeholder.as_ptr()) - } else { - #[cfg(any(debug_assertions, miri))] - { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd128u::loadu(placeholder.as_ptr()) - } - #[cfg(not(any(debug_assertions, miri)))] - { - Simd128u::loadu(sptr) + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd128u::loadu(placeholder[..].as_ptr()) + } + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + if check_cross_page(sptr, LANES) { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd128u::loadu(placeholder[..].as_ptr()) + } else { + #[cfg(any(debug_assertions, miri))] + { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd128u::loadu(placeholder[..].as_ptr()) + } + #[cfg(not(any(debug_assertions, miri)))] + { + Simd128u::loadu(sptr) + } } } }; diff --git a/src/simd/sse2.rs b/src/simd/sse2.rs index c433f21..cdcc112 100644 --- a/src/simd/sse2.rs +++ b/src/simd/sse2.rs @@ -215,27 +215,29 @@ pub unsafe fn format_string(value: &str, dst: &mut [u8]) -> usize { } // Handle remaining bytes - let mut placeholder: [u8; 16] = [0; 16]; + let mut placeholder: [u8; LANES] = [0; LANES]; while nb > 0 { - #[cfg(not(any(target_os = "linux", target_os = "macos")))] let v = { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd128u::loadu(placeholder.as_ptr()) - }; - #[cfg(any(target_os = "linux", target_os = "macos"))] - let v = { - if check_cross_page(sptr, LANES) { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd128u::loadu(placeholder.as_ptr()) - } else { - #[cfg(any(debug_assertions, miri))] - { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd128u::loadu(placeholder.as_ptr()) - } - #[cfg(not(any(debug_assertions, miri)))] - { - Simd128u::loadu(sptr) + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd128u::loadu(placeholder[..].as_ptr()) + } + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + if check_cross_page(sptr, LANES) { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd128u::loadu(placeholder[..].as_ptr()) + } else { + #[cfg(any(debug_assertions, miri))] + { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd128u::loadu(placeholder[..].as_ptr()) + } + #[cfg(not(any(debug_assertions, miri)))] + { + Simd128u::loadu(sptr) + } } } }; @@ -247,7 +249,7 @@ pub unsafe fn format_string(value: &str, dst: &mut [u8]) -> usize { dptr = dptr.add(nb); break; } else { - let cn = mask.trailing_zeros() as usize; + let cn = mask.first_offset(); nb -= cn; dptr = dptr.add(cn); sptr = sptr.add(cn); diff --git a/src/simd/v128.rs b/src/simd/v128.rs index f75157f..0e20392 100644 --- a/src/simd/v128.rs +++ b/src/simd/v128.rs @@ -1,10 +1,14 @@ use std::ops::{BitAnd, BitOr, BitOrAssign}; +use crate::simd::traits::BitMask; + use super::{Mask, Simd, util::escape_unchecked}; #[cfg(any(target_os = "linux", target_os = "macos"))] use super::util::check_cross_page; +const LANES: usize = 16; + #[derive(Debug)] pub struct Simd128u([u8; 16]); @@ -124,17 +128,17 @@ pub fn format_string(value: &str, dst: &mut [u8]) -> usize { dptr = dptr.add(1); // Main loop: process LANES bytes at a time - while nb >= Simd128u::LANES { + while nb >= LANES { let v = Simd128u::loadu(sptr); v.storeu(dptr); let mask = escaped_mask(v); if mask == 0 { - nb -= Simd128u::LANES; - dptr = dptr.add(Simd128u::LANES); - sptr = sptr.add(Simd128u::LANES); + nb -= LANES; + dptr = dptr.add(LANES); + sptr = sptr.add(LANES); } else { - let cn = mask.trailing_zeros() as usize; + let cn = mask.first_offset(); nb -= cn; dptr = dptr.add(cn); sptr = sptr.add(cn); @@ -143,27 +147,29 @@ pub fn format_string(value: &str, dst: &mut [u8]) -> usize { } // Handle remaining bytes - let mut placeholder: [u8; 16] = [0; 16]; + let mut placeholder: [u8; LANES] = [0; LANES]; while nb > 0 { - #[cfg(not(any(target_os = "linux", target_os = "macos")))] - let v = { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd128u::loadu(placeholder.as_ptr()) - }; - #[cfg(any(target_os = "linux", target_os = "macos"))] let v = { - if check_cross_page(sptr, Simd128u::LANES) { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd128u::loadu(placeholder.as_ptr()) - } else { - #[cfg(any(debug_assertions, miri))] - { - std::ptr::copy_nonoverlapping(sptr, placeholder.as_mut_ptr(), nb); - Simd128u::loadu(placeholder.as_ptr()) - } - #[cfg(not(any(debug_assertions, miri)))] - { - Simd128u::loadu(sptr) + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd128u::loadu(placeholder[..].as_ptr()) + } + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + if check_cross_page(sptr, Simd128u::LANES) { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd128u::loadu(placeholder[..].as_ptr()) + } else { + #[cfg(any(debug_assertions, miri))] + { + std::ptr::copy_nonoverlapping(sptr, placeholder[..].as_mut_ptr(), nb); + Simd128u::loadu(placeholder[..].as_ptr()) + } + #[cfg(not(any(debug_assertions, miri)))] + { + Simd128u::loadu(sptr) + } } } }; @@ -177,7 +183,7 @@ pub fn format_string(value: &str, dst: &mut [u8]) -> usize { dptr = dptr.add(nb); break; } else { - let cn = mask.trailing_zeros() as usize; + let cn = mask.first_offset(); nb -= cn; dptr = dptr.add(cn); sptr = sptr.add(cn);