From 1690bbe712f516e25953e606fabadeea9dc952cb Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 13:59:18 +0200 Subject: [PATCH 1/5] Avoid undefined-reference errors for BigFloat/BigInt --- src/mapreduce.jl | 7 +++++++ test/othertests.jl | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/mapreduce.jl b/src/mapreduce.jl index 45e3980..be02584 100644 --- a/src/mapreduce.jl +++ b/src/mapreduce.jl @@ -6,6 +6,13 @@ LinearAlgebra.adjoint!(dst::StridedView, src::StridedView) = copy!(dst, adjoint( LinearAlgebra.transpose!(C::StridedView, A::StridedView) = copy!(C, transpose(A)) Base.permutedims!(dst::StridedView, src::StridedView, p) = copy!(dst, permutedims(src, p)) Base.fill!(A::StridedView, val) = map!(Returns(val), A, A) +function Base.fill!( + A::StridedView{<:Union{BigFloat, Complex{BigFloat}, BigInt, Complex{BigInt}}}, val + ) + isempty(A) && return A + _mapreduce_order!(Returns(val), nothing, nothing, size(A), (A,)) + return A +end # This is a wrapper function intended to allow us to # intercept "conj" and rewrite it in cases where the diff --git a/test/othertests.jl b/test/othertests.jl index 556b860..12aa718 100644 --- a/test/othertests.jl +++ b/test/othertests.jl @@ -155,6 +155,37 @@ end end end +@testset "fill! and reductions with undef-initialized BigFloat/BigInt storage" begin + @testset for T in (BigFloat, Complex{BigFloat}, BigInt, Complex{BigInt}) + # test the specific path in src/mapreduce here that avoids undefined + # reference errors + @test collect(fill!(StridedView(Vector{T}(undef, 5)), one(T))) == ones(T, 5) + + A = StridedView(Vector{T}(undef, 12), (2, 3), (1, 4), 0) + @test collect(fill!(A, T(3))) == fill(T(3), 2, 3) + + B = StridedView(Vector{T}(undef, 6), (2, 3), (-1, 2), 1) + @test collect(fill!(B, T(5))) == fill(T(5), 2, 3) + + @test fill!(StridedView(Vector{T}(undef, 0), (0,), (1,), 0), one(T)) |> isempty + + S = StridedView(Vector{T}(undef, 1), (), (), 0) + @test fill!(S, T(7))[] == T(7) + + R = rand(T, 6) + V, M = StridedView(copy(R), (2, 3), (1, 2), 0), reshape(copy(R), 2, 3) + @test norm(V) == norm(M) + @test sum(V) == sum(M) + @test prod(V) == prod(M) + @test maximum(abs, V) == maximum(abs, M) + @test minimum(abs, V) == minimum(abs, M) + + # large enough to reach the threaded reduction path + L = StridedView(Vector{T}(undef, 1 << 17)) + @test sum(fill!(L, T(2))) == T(2) * (1 << 17) + end +end + @testset "0-dimensional (scalar) StridedView" begin @testset for T in (Float32, Float64, ComplexF32, ComplexF64) R = fill(rand(T)) # 0-dimensional Array From caf67645f1d9e3d7d2df35fa70808601cc31f0be Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 13:59:40 +0200 Subject: [PATCH 2/5] Bump patch version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index c483aaa..bba7c4f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Strided" uuid = "5e0ebb24-38b0-5f93-81fe-25c709ecae67" -version = "2.6.4" +version = "2.6.5" authors = ["Lukas Devos ", "Maarten Van Damme ", "Jutho Haegeman "] [deps] From c640da31be6a4cf9660bdbf1a483def29404c28c Mon Sep 17 00:00:00 2001 From: Katharine Hyatt Date: Thu, 6 Aug 2026 14:36:58 +0200 Subject: [PATCH 3/5] Don't use rand (BigInt hates it) --- test/othertests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/othertests.jl b/test/othertests.jl index 12aa718..ba873b1 100644 --- a/test/othertests.jl +++ b/test/othertests.jl @@ -172,7 +172,7 @@ end S = StridedView(Vector{T}(undef, 1), (), (), 0) @test fill!(S, T(7))[] == T(7) - R = rand(T, 6) + R = T[1, 4, 2, 3, 5, 6] V, M = StridedView(copy(R), (2, 3), (1, 2), 0), reshape(copy(R), 2, 3) @test norm(V) == norm(M) @test sum(V) == sum(M) From 9de6ee3ae47b5072804fd27118a635348a6fe21d Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 13 Aug 2026 05:09:17 -0400 Subject: [PATCH 4/5] rework using `map!` --- src/mapreduce.jl | 16 +++++----------- test/gpu.jl | 3 +++ test/othertests.jl | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/mapreduce.jl b/src/mapreduce.jl index be02584..fb87ad2 100644 --- a/src/mapreduce.jl +++ b/src/mapreduce.jl @@ -5,14 +5,7 @@ Base.conj!(a::StridedView) = map!(conj, a, a) LinearAlgebra.adjoint!(dst::StridedView, src::StridedView) = copy!(dst, adjoint(src)) LinearAlgebra.transpose!(C::StridedView, A::StridedView) = copy!(C, transpose(A)) Base.permutedims!(dst::StridedView, src::StridedView, p) = copy!(dst, permutedims(src, p)) -Base.fill!(A::StridedView, val) = map!(Returns(val), A, A) -function Base.fill!( - A::StridedView{<:Union{BigFloat, Complex{BigFloat}, BigInt, Complex{BigInt}}}, val - ) - isempty(A) && return A - _mapreduce_order!(Returns(val), nothing, nothing, size(A), (A,)) - return A -end +Base.fill!(A::StridedView, val) = map!(Returns(val), A) # This is a wrapper function intended to allow us to # intercept "conj" and rewrite it in cases where the @@ -47,21 +40,22 @@ function Base.map( return map!(f, similar(a1, T), a1, A...) end +# The source arrays may be empty: `map!(f, b)` writes `f()` into every entry of `b` without +# ever reading it, unlike `Base.map!(f, inout)` (Julia >= 1.12), which means `map!(f, inout, inout)`. function Base.map!( - @nospecialize(f), b::StridedView{<:Any, N}, a1::StridedView{<:Any, N}, + @nospecialize(f), b::StridedView{<:Any, N}, A::Vararg{StridedView{<:Any, N}} ) where {N} dims = size(b) # Check dimesions - size(a1) == dims || throw(DimensionMismatch()) for a in A size(a) == dims || throw(DimensionMismatch()) end any(isequal(0), dims) && return b # don't do anything - _mapreduce_order!(f, nothing, nothing, dims, (b, a1, A...)) + _mapreduce_order!(f, nothing, nothing, dims, (b, A...)) return b end diff --git a/test/gpu.jl b/test/gpu.jl index cf08ee5..3509cf0 100644 --- a/test/gpu.jl +++ b/test/gpu.jl @@ -96,6 +96,8 @@ end @test compare((x, y, z) -> map((a, b, c) -> sin(a) + b / exp(-abs(c)), x, y, z), AT, A1, A2, A3) @test compare((x, y) -> mul!(x, 1, y), AT, A1, A2) @test compare((x, y) -> mul!(x, y, 1), AT, A1, A2) + # `fill!` writes without reading, i.e. hits the single-array kernel + @test compare(x -> fill!(x, one(T)), AT, A1) end dims = ntuple(Returns(20), 2) @@ -107,6 +109,7 @@ end @test compare((x, y) -> axpy!(1 // 3, x, y), AT, A1, A2) @test compare((x, y) -> axpby!(1 // 3, x, 1 // 2, y), AT, A1, A2) @test compare((x, y, z) -> map((a, b, c) -> sin(a) + b / exp(-abs(c)), x, y, z), AT, A1, A2, A3) + @test compare(x -> fill!(x, one(T)), AT, A1) @test compare((x, y) -> mul!(x, 1, y), AT, A1, A2) @test compare((x, y) -> mul!(x, y, 1), AT, A1, A2) end diff --git a/test/othertests.jl b/test/othertests.jl index ba873b1..a4301de 100644 --- a/test/othertests.jl +++ b/test/othertests.jl @@ -186,6 +186,49 @@ end end end +@testset "map! without source arrays" begin + @testset for T in (Float64, ComplexF64) + x = T <: Complex ? T(3 + 2im) : T(3) + + A = StridedView(zeros(T, 5)) + @test map!(Returns(x), A) === A + @test collect(A) == fill(x, 5) + + # strided view: entries of the parent outside the view are left alone + P = T[1:12;] + B = StridedView(P, (2, 3), (1, 4), 0) + @test collect(map!(Returns(x), B)) == fill(x, 2, 3) + @test P[[3, 4, 7, 8, 11, 12]] == T[3, 4, 7, 8, 11, 12] + + # negative strides and a nonzero offset + C = StridedView(zeros(T, 6), (2, 3), (-1, 2), 1) + @test collect(map!(Returns(x), C)) == fill(x, 2, 3) + + # the view's `op` is applied on write + D = conj(StridedView(zeros(T, 4))) + @test collect(map!(Returns(x), D)) == fill(x, 4) + @test parent(D) == fill(conj(x), 4) + + # 0-dimensional and empty views + S = StridedView(zeros(T, 1), (), (), 0) + @test map!(Returns(x), S)[] == x + E = StridedView(T[], (0,), (1,), 0) + @test map!(Returns(x), E) |> isempty + + # large enough to reach the threaded path + L = StridedView(zeros(T, 1 << 17)) + @test sum(map!(Returns(x), L)) == x * (1 << 17) + + # the multi-source forms are unchanged + a1, a2 = StridedView(T[1:6;]), StridedView(fill(T(2), 6)) + b = StridedView(zeros(T, 6)) + @test collect(map!(-, b, a1)) == -collect(a1) + @test collect(map!(+, b, a1, a2)) == collect(a1) .+ collect(a2) + @test_throws DimensionMismatch map!(identity, b, StridedView(zeros(T, 5))) + @test_throws DimensionMismatch map!(+, b, a1, StridedView(zeros(T, 5))) + end +end + @testset "0-dimensional (scalar) StridedView" begin @testset for T in (Float32, Float64, ComplexF32, ComplexF64) R = fill(rand(T)) # 0-dimensional Array From 9f25aca06692738507babd232ac18f2e29f5a063 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 13 Aug 2026 05:15:50 -0400 Subject: [PATCH 5/5] Revert "rework using `map!`" This reverts commit 9722ed930f7495b86df7b84d2d48067785bb7e62. --- src/mapreduce.jl | 16 +++++++++++----- test/gpu.jl | 3 --- test/othertests.jl | 43 ------------------------------------------- 3 files changed, 11 insertions(+), 51 deletions(-) diff --git a/src/mapreduce.jl b/src/mapreduce.jl index fb87ad2..be02584 100644 --- a/src/mapreduce.jl +++ b/src/mapreduce.jl @@ -5,7 +5,14 @@ Base.conj!(a::StridedView) = map!(conj, a, a) LinearAlgebra.adjoint!(dst::StridedView, src::StridedView) = copy!(dst, adjoint(src)) LinearAlgebra.transpose!(C::StridedView, A::StridedView) = copy!(C, transpose(A)) Base.permutedims!(dst::StridedView, src::StridedView, p) = copy!(dst, permutedims(src, p)) -Base.fill!(A::StridedView, val) = map!(Returns(val), A) +Base.fill!(A::StridedView, val) = map!(Returns(val), A, A) +function Base.fill!( + A::StridedView{<:Union{BigFloat, Complex{BigFloat}, BigInt, Complex{BigInt}}}, val + ) + isempty(A) && return A + _mapreduce_order!(Returns(val), nothing, nothing, size(A), (A,)) + return A +end # This is a wrapper function intended to allow us to # intercept "conj" and rewrite it in cases where the @@ -40,22 +47,21 @@ function Base.map( return map!(f, similar(a1, T), a1, A...) end -# The source arrays may be empty: `map!(f, b)` writes `f()` into every entry of `b` without -# ever reading it, unlike `Base.map!(f, inout)` (Julia >= 1.12), which means `map!(f, inout, inout)`. function Base.map!( - @nospecialize(f), b::StridedView{<:Any, N}, + @nospecialize(f), b::StridedView{<:Any, N}, a1::StridedView{<:Any, N}, A::Vararg{StridedView{<:Any, N}} ) where {N} dims = size(b) # Check dimesions + size(a1) == dims || throw(DimensionMismatch()) for a in A size(a) == dims || throw(DimensionMismatch()) end any(isequal(0), dims) && return b # don't do anything - _mapreduce_order!(f, nothing, nothing, dims, (b, A...)) + _mapreduce_order!(f, nothing, nothing, dims, (b, a1, A...)) return b end diff --git a/test/gpu.jl b/test/gpu.jl index 3509cf0..cf08ee5 100644 --- a/test/gpu.jl +++ b/test/gpu.jl @@ -96,8 +96,6 @@ end @test compare((x, y, z) -> map((a, b, c) -> sin(a) + b / exp(-abs(c)), x, y, z), AT, A1, A2, A3) @test compare((x, y) -> mul!(x, 1, y), AT, A1, A2) @test compare((x, y) -> mul!(x, y, 1), AT, A1, A2) - # `fill!` writes without reading, i.e. hits the single-array kernel - @test compare(x -> fill!(x, one(T)), AT, A1) end dims = ntuple(Returns(20), 2) @@ -109,7 +107,6 @@ end @test compare((x, y) -> axpy!(1 // 3, x, y), AT, A1, A2) @test compare((x, y) -> axpby!(1 // 3, x, 1 // 2, y), AT, A1, A2) @test compare((x, y, z) -> map((a, b, c) -> sin(a) + b / exp(-abs(c)), x, y, z), AT, A1, A2, A3) - @test compare(x -> fill!(x, one(T)), AT, A1) @test compare((x, y) -> mul!(x, 1, y), AT, A1, A2) @test compare((x, y) -> mul!(x, y, 1), AT, A1, A2) end diff --git a/test/othertests.jl b/test/othertests.jl index a4301de..ba873b1 100644 --- a/test/othertests.jl +++ b/test/othertests.jl @@ -186,49 +186,6 @@ end end end -@testset "map! without source arrays" begin - @testset for T in (Float64, ComplexF64) - x = T <: Complex ? T(3 + 2im) : T(3) - - A = StridedView(zeros(T, 5)) - @test map!(Returns(x), A) === A - @test collect(A) == fill(x, 5) - - # strided view: entries of the parent outside the view are left alone - P = T[1:12;] - B = StridedView(P, (2, 3), (1, 4), 0) - @test collect(map!(Returns(x), B)) == fill(x, 2, 3) - @test P[[3, 4, 7, 8, 11, 12]] == T[3, 4, 7, 8, 11, 12] - - # negative strides and a nonzero offset - C = StridedView(zeros(T, 6), (2, 3), (-1, 2), 1) - @test collect(map!(Returns(x), C)) == fill(x, 2, 3) - - # the view's `op` is applied on write - D = conj(StridedView(zeros(T, 4))) - @test collect(map!(Returns(x), D)) == fill(x, 4) - @test parent(D) == fill(conj(x), 4) - - # 0-dimensional and empty views - S = StridedView(zeros(T, 1), (), (), 0) - @test map!(Returns(x), S)[] == x - E = StridedView(T[], (0,), (1,), 0) - @test map!(Returns(x), E) |> isempty - - # large enough to reach the threaded path - L = StridedView(zeros(T, 1 << 17)) - @test sum(map!(Returns(x), L)) == x * (1 << 17) - - # the multi-source forms are unchanged - a1, a2 = StridedView(T[1:6;]), StridedView(fill(T(2), 6)) - b = StridedView(zeros(T, 6)) - @test collect(map!(-, b, a1)) == -collect(a1) - @test collect(map!(+, b, a1, a2)) == collect(a1) .+ collect(a2) - @test_throws DimensionMismatch map!(identity, b, StridedView(zeros(T, 5))) - @test_throws DimensionMismatch map!(+, b, a1, StridedView(zeros(T, 5))) - end -end - @testset "0-dimensional (scalar) StridedView" begin @testset for T in (Float32, Float64, ComplexF32, ComplexF64) R = fill(rand(T)) # 0-dimensional Array