From bad895e0acfb25cb070bd18fbcd40f74af038d67 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Sat, 20 Jun 2026 07:22:45 -0400 Subject: [PATCH 1/3] test: filter AllocCheck jl_get_pgcstack_fallback false positive on aarch64 On aarch64 (Apple Silicon, e.g. macos-latest) Julia 1.13 fetches the per-task GC stack pointer via jl_get_pgcstack_fallback instead of a TLS/register fast path. AllocCheck's whitelist (classify.jl) already treats the fast-path variants jl_get_pgcstack and jl_get_pgcstack_static as non-allocating, but omits the _fallback variant, so check_allocs conservatively reports it as an AllocatingRuntimeCall. This is a false positive: that call does not allocate. This caused the zero-allocation tests to fail only on the julia-pre / macos-latest (aarch64) CI job while passing on x86_64. Filter out only the jl_get_pgcstack_fallback AllocatingRuntimeCall before asserting emptiness; every other result (real AllocationSite, DynamicDispatch, or other allocating runtime call) still fails the test, so the zero-allocation invariant is fully preserved. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 (1M context) --- test/alloc_tests.jl | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/test/alloc_tests.jl b/test/alloc_tests.jl index c24ba51..cb55dc0 100644 --- a/test/alloc_tests.jl +++ b/test/alloc_tests.jl @@ -10,6 +10,16 @@ using Test # `@allocated` picks up on newer Julia versions, so it directly tests the intended # invariant (the operation never allocates) rather than a single runtime sample. +# On aarch64 (e.g. Apple Silicon) the per-task GC stack pointer is fetched through +# `jl_get_pgcstack_fallback` rather than a TLS/register fast path. AllocCheck already +# treats the fast-path variants `jl_get_pgcstack`/`jl_get_pgcstack_static` as +# non-allocating, but its whitelist omits the `_fallback` variant, so it conservatively +# reports it as an `AllocatingRuntimeCall`. That call does not allocate, so we drop it +# before asserting; every other (genuinely allocating) result is still caught. +real_allocs(results) = filter(results) do r + !(r isa AllocCheck.AllocatingRuntimeCall && r.name == "jl_get_pgcstack_fallback") +end + getidx(A, i...) = A[i...] setidx!(A, v, i...) = (A[i...] = v; nothing) function sumiter(d) @@ -26,11 +36,11 @@ end r2 = 10:15 dr = DisjointRange(r1, r2) - @test isempty(check_allocs(length, (typeof(dr),))) - @test isempty(check_allocs(getidx, (typeof(dr), Int))) - @test isempty(check_allocs(first, (typeof(dr),))) - @test isempty(check_allocs(last, (typeof(dr),))) - @test isempty(check_allocs(sumiter, (typeof(dr),))) + @test isempty(real_allocs(check_allocs(length, (typeof(dr),)))) + @test isempty(real_allocs(check_allocs(getidx, (typeof(dr), Int)))) + @test isempty(real_allocs(check_allocs(first, (typeof(dr),)))) + @test isempty(real_allocs(check_allocs(last, (typeof(dr),)))) + @test isempty(real_allocs(check_allocs(sumiter, (typeof(dr),)))) end @testset "colsupport - Zero Allocations" begin @@ -42,7 +52,7 @@ end # colsupport returns OneTo for j <= l+u and a DisjointRange otherwise; both # branches must be allocation-free. - @test isempty(check_allocs(colsupport, (typeof(A), Int))) + @test isempty(real_allocs(check_allocs(colsupport, (typeof(A), Int)))) end @testset "rowsupport - Zero Allocations" begin @@ -52,7 +62,7 @@ end F = rand(Float64, m, n) A = AlmostBandedMatrix(B, F) - @test isempty(check_allocs(rowsupport, (typeof(A), Int))) + @test isempty(real_allocs(check_allocs(rowsupport, (typeof(A), Int)))) end @testset "getindex/setindex! - Zero Allocations" begin @@ -62,8 +72,8 @@ end F = rand(Float64, m, n) A = AlmostBandedMatrix(B, F) - @test isempty(check_allocs(getidx, (typeof(A), Int, Int))) - @test isempty(check_allocs(setidx!, (typeof(A), Float64, Int, Int))) + @test isempty(real_allocs(check_allocs(getidx, (typeof(A), Int, Int)))) + @test isempty(real_allocs(check_allocs(setidx!, (typeof(A), Float64, Int, Int)))) end @testset "bandpart/fillpart - Zero Allocations" begin @@ -73,7 +83,7 @@ end F = rand(Float64, m, n) A = AlmostBandedMatrix(B, F) - @test isempty(check_allocs(bandpart, (typeof(A),))) - @test isempty(check_allocs(fillpart, (typeof(A),))) + @test isempty(real_allocs(check_allocs(bandpart, (typeof(A),)))) + @test isempty(real_allocs(check_allocs(fillpart, (typeof(A),)))) end end From 23a3810b9a44ccdb36fe85dce2afce1dbb98cf68 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Wed, 8 Jul 2026 08:37:48 -0400 Subject: [PATCH 2/3] test: move alloccheck tests to QA Co-Authored-By: Chris Rackauckas --- test/Project.toml | 5 ----- test/qa/Project.toml | 4 ++++ test/{ => qa}/alloc_tests.jl | 0 3 files changed, 4 insertions(+), 5 deletions(-) rename test/{ => qa}/alloc_tests.jl (100%) diff --git a/test/Project.toml b/test/Project.toml index a9cb2ad..7165a0e 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,19 +1,14 @@ [deps] -AllocCheck = "9b6a8646-10ed-4001-bbdc-1d2f46dfbb1a" ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" -BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MatrixFactorizations = "a3b82374-2e81-5b9e-98ce-41277c0e4c87" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] -AllocCheck = "0.2.3" ArrayLayouts = "1.12.2" -BenchmarkTools = "1.6.3" MatrixFactorizations = "3.1.3" SafeTestsets = "0.1.0" SciMLTesting = "1" diff --git a/test/qa/Project.toml b/test/qa/Project.toml index 87c6b1d..b21d5ab 100644 --- a/test/qa/Project.toml +++ b/test/qa/Project.toml @@ -1,4 +1,6 @@ [deps] +AllocCheck = "9b6a8646-10ed-4001-bbdc-1d2f46dfbb1a" +ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" FastAlmostBandedMatrices = "9d29842c-ecb8-4973-b1e9-a27b1157504e" SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283" @@ -8,6 +10,8 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" FastAlmostBandedMatrices = {path = "../.."} [compat] +AllocCheck = "0.2.3" +ArrayLayouts = "1.12.2" SafeTestsets = "0.0.1, 0.1" SciMLTesting = "1.6" Test = "1" diff --git a/test/alloc_tests.jl b/test/qa/alloc_tests.jl similarity index 100% rename from test/alloc_tests.jl rename to test/qa/alloc_tests.jl From 8226390ff181bb26b754693fbc8e5418f98f5901 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Wed, 8 Jul 2026 08:41:39 -0400 Subject: [PATCH 3/3] test: remove alloccheck runtime-call filter Co-Authored-By: Chris Rackauckas --- test/qa/alloc_tests.jl | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/test/qa/alloc_tests.jl b/test/qa/alloc_tests.jl index cb55dc0..c24ba51 100644 --- a/test/qa/alloc_tests.jl +++ b/test/qa/alloc_tests.jl @@ -10,16 +10,6 @@ using Test # `@allocated` picks up on newer Julia versions, so it directly tests the intended # invariant (the operation never allocates) rather than a single runtime sample. -# On aarch64 (e.g. Apple Silicon) the per-task GC stack pointer is fetched through -# `jl_get_pgcstack_fallback` rather than a TLS/register fast path. AllocCheck already -# treats the fast-path variants `jl_get_pgcstack`/`jl_get_pgcstack_static` as -# non-allocating, but its whitelist omits the `_fallback` variant, so it conservatively -# reports it as an `AllocatingRuntimeCall`. That call does not allocate, so we drop it -# before asserting; every other (genuinely allocating) result is still caught. -real_allocs(results) = filter(results) do r - !(r isa AllocCheck.AllocatingRuntimeCall && r.name == "jl_get_pgcstack_fallback") -end - getidx(A, i...) = A[i...] setidx!(A, v, i...) = (A[i...] = v; nothing) function sumiter(d) @@ -36,11 +26,11 @@ end r2 = 10:15 dr = DisjointRange(r1, r2) - @test isempty(real_allocs(check_allocs(length, (typeof(dr),)))) - @test isempty(real_allocs(check_allocs(getidx, (typeof(dr), Int)))) - @test isempty(real_allocs(check_allocs(first, (typeof(dr),)))) - @test isempty(real_allocs(check_allocs(last, (typeof(dr),)))) - @test isempty(real_allocs(check_allocs(sumiter, (typeof(dr),)))) + @test isempty(check_allocs(length, (typeof(dr),))) + @test isempty(check_allocs(getidx, (typeof(dr), Int))) + @test isempty(check_allocs(first, (typeof(dr),))) + @test isempty(check_allocs(last, (typeof(dr),))) + @test isempty(check_allocs(sumiter, (typeof(dr),))) end @testset "colsupport - Zero Allocations" begin @@ -52,7 +42,7 @@ end # colsupport returns OneTo for j <= l+u and a DisjointRange otherwise; both # branches must be allocation-free. - @test isempty(real_allocs(check_allocs(colsupport, (typeof(A), Int)))) + @test isempty(check_allocs(colsupport, (typeof(A), Int))) end @testset "rowsupport - Zero Allocations" begin @@ -62,7 +52,7 @@ end F = rand(Float64, m, n) A = AlmostBandedMatrix(B, F) - @test isempty(real_allocs(check_allocs(rowsupport, (typeof(A), Int)))) + @test isempty(check_allocs(rowsupport, (typeof(A), Int))) end @testset "getindex/setindex! - Zero Allocations" begin @@ -72,8 +62,8 @@ end F = rand(Float64, m, n) A = AlmostBandedMatrix(B, F) - @test isempty(real_allocs(check_allocs(getidx, (typeof(A), Int, Int)))) - @test isempty(real_allocs(check_allocs(setidx!, (typeof(A), Float64, Int, Int)))) + @test isempty(check_allocs(getidx, (typeof(A), Int, Int))) + @test isempty(check_allocs(setidx!, (typeof(A), Float64, Int, Int))) end @testset "bandpart/fillpart - Zero Allocations" begin @@ -83,7 +73,7 @@ end F = rand(Float64, m, n) A = AlmostBandedMatrix(B, F) - @test isempty(real_allocs(check_allocs(bandpart, (typeof(A),)))) - @test isempty(real_allocs(check_allocs(fillpart, (typeof(A),)))) + @test isempty(check_allocs(bandpart, (typeof(A),))) + @test isempty(check_allocs(fillpart, (typeof(A),))) end end