From 4a379b0cff9228de0387e1ffbced7fb994fa1390 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Sun, 12 Jul 2026 06:45:47 -0400 Subject: [PATCH 1/2] Add generic findfirstsortedequal fallback (fixes 32-bit) and drop the broken 32-bit CI job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #95. Two things: 1. Fix a correctness gap #95 shipped. Unlike `findfirstequal`, `findfirstsortedequal` had ONLY the `(Int64, DenseVector{Int64})` method and no generic fallback. On 32-bit platforms native `Int` is `Int32`, so `findfirstsortedequal(::Int32, ::Vector{Int32})` throws MethodError — 8385 such errors in the suite, and a bare `Pkg.test()` / any `Int`-vector caller on 32-bit is broken. Add a generic `searchsortedfirst` + equality post-check fallback (mirroring `findfirstequal`), so it works for any sorted vector on any platform. Add a test covering the fallback on Int32/Int16/Float32. 2. Remove the `tests-32bit` job #95 added to Tests.yml. Running 32-bit Julia on the 64-bit `ubuntu-latest` runner fails at `setup-julia` (the i686 binary can't exec without the 32-bit loader, which is not installed before Julia is invoked: `spawn .../x86/bin/julia ENOENT`), so that job is red regardless of this fix. A proper 32-bit CI lane needs runner/reusable-workflow changes and is deferred; Tests.yml goes back to a thin grouped-tests caller. Minor bump 3.0.3 -> 3.1.0 (new public behavior: `findfirstsortedequal` now accepts any sorted vector). Verified locally: full Core suite passes on 32-bit i686 Julia 1.11 (0 errors) and on x64 (171915 tests); GROUP=All passes. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01EcQkauMQ4KSytnTpJpXUZu --- .github/workflows/Tests.yml | 9 --------- Project.toml | 2 +- src/equality.jl | 9 ++++++++- test/core_tests.jl | 21 +++++++++++++++++++++ 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index a09aa2f..d6f622d 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -22,12 +22,3 @@ jobs: tests: uses: "SciML/.github/.github/workflows/grouped-tests.yml@v1" secrets: "inherit" - tests-32bit: - name: "Core (julia 1, ubuntu-latest, x86)" - uses: "SciML/.github/.github/workflows/tests.yml@v1" - with: - julia-version: "1" - julia-arch: "x86" - group: "Core" - coverage: false - secrets: "inherit" diff --git a/Project.toml b/Project.toml index 503e897..9f93a85 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "FindFirstFunctions" uuid = "64ca27bc-2ba2-4a57-88aa-44e436879224" -version = "3.0.3" +version = "3.1.0" authors = ["Chris Elrod and contributors"] [deps] diff --git a/src/equality.jl b/src/equality.jl index 89099dc..a178a6a 100644 --- a/src/equality.jl +++ b/src/equality.jl @@ -36,7 +36,10 @@ Find the index of the first occurrence of `var` in the sorted vector `DenseVector{Int64}` via a branchless binary bisection down to a small basecase, followed by the same SIMD equality scan that backs [`findfirstequal`](@ref) — faster than plain `findfirst(==(var), vars)` -or `searchsortedfirst` + post-check for typical Int64 vectors. +or `searchsortedfirst` + post-check for typical Int64 vectors. Every +other element-type and array-storage combination (including native `Int` +on 32-bit platforms, where `Int` is not `Int64`) falls back to a generic +`searchsortedfirst` + equality post-check. The strategy-framework equivalent is [`findequal(BisectThenSIMD(), vars, var)`](@ref findequal); that wrapper @@ -45,6 +48,10 @@ which is type-stable and composes with the rest of the strategy dispatch. Prefer `findequal` for new code; `findfirstsortedequal` remains as the dedicated `Union{Int64, Nothing}`-returning name. """ +function findfirstsortedequal(var, vars) + i = searchsortedfirst(vars, var) + return (i <= lastindex(vars) && isequal(@inbounds(vars[i]), var)) ? i : nothing +end function findfirstsortedequal( var::Int64, vars::DenseVector{Int64}, diff --git a/test/core_tests.jl b/test/core_tests.jl index 702a2b1..3366964 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -1377,3 +1377,24 @@ end end end end + +@safetestset "findfirstsortedequal generic fallback" begin + using FindFirstFunctions, StableRNGs + F = FindFirstFunctions + + # The `(Int64, DenseVector{Int64})` method is the SIMD fast path; every + # other type (e.g. `Int32`, which is the native `Int` on 32-bit platforms) + # takes the generic searchsortedfirst fallback. Exercise it explicitly here + # so both paths are covered regardless of the host word size. + rng = StableRNG(2029) + for T in (Int32, Int16, Float32), _ in 1:500 + n = rand(rng, 0:64) + v = sort!(rand(rng, T(-40):T(40), n)) + for i in eachindex(v) + @test F.findfirstsortedequal(v[i], v) == searchsortedfirst(v, v[i]) + end + x = rand(rng, T(-50):T(50)) + ref = insorted(x, v) ? searchsortedfirst(v, x) : nothing + @test F.findfirstsortedequal(x, v) == ref + end +end From 23940bebc149b61a0477efbfa410897521641a29 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Sun, 12 Jul 2026 07:30:19 -0400 Subject: [PATCH 2/2] Add the 32-bit CI lane via test_groups.toml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declare a `Core 32-bit` matrix-only alias that dispatches the Core body (group = "Core") on a single x86 Julia cell, so the WORD_SIZE != 64 scalar fallbacks in src/simd_ir.jl are exercised in CI — replacing #95's hand-added `tests-32bit` job (removed in the parent commit) with a declarative lane. This uses the arch axis / group alias in SciML/.github v1 (v1.24.0) and the i386 runtime-lib install before setup-julia (v1.24.1), so the x86 leg can actually run on the 64-bit ubuntu runner. Require `SciMLTesting = "2.2"`: the alias section is skipped by folder discovery only in SciMLTesting >= 2.2 (SciMLTesting.jl#22), which GROUP=All (the Downgrade job) and a bare `Pkg.test()` need. FindFirstFunctions runs unchanged on SciMLTesting 2.x (verified: Core suite passes on 2.1.0). Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01EcQkauMQ4KSytnTpJpXUZu --- Project.toml | 2 +- test/test_groups.toml | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 9f93a85..acc1fb4 100644 --- a/Project.toml +++ b/Project.toml @@ -9,7 +9,7 @@ PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" [compat] PrecompileTools = "1.0.1" SafeTestsets = "0.1" -SciMLTesting = "1" +SciMLTesting = "2.2" StableRNGs = "1" Test = "1.10" julia = "1.10" diff --git a/test/test_groups.toml b/test/test_groups.toml index 238e907..09a4f24 100644 --- a/test/test_groups.toml +++ b/test/test_groups.toml @@ -6,5 +6,15 @@ versions = ["1", "lts", "pre"] os = ["ubuntu-latest", "macos-latest", "windows-latest"] +# 32-bit lane: a matrix-only alias that dispatches the Core body (GROUP=Core) on +# a single x86 Julia, so the WORD_SIZE != 64 scalar fallbacks in src/simd_ir.jl +# are exercised in CI. The `arch = "x86"` cell needs SciML/.github v1's i386-libs +# step (setup-julia) and the alias skip in SciMLTesting >= 2.2 (folder discovery). +["Core 32-bit"] +group = "Core" +versions = ["1"] +os = ["ubuntu-latest"] +arch = "x86" + [QA] versions = ["lts", "1"]