diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 2a1d434641a23..ef73446cbb09d 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -1128,8 +1128,33 @@ Perform a conservative test to check if arrays `A` and `B` might share the same By default, this simply checks if either of the arrays reference the same memory regions, as identified by their [`Base.dataids`](@ref). """ -mightalias(A::AbstractArray, B::AbstractArray) = !isbits(A) && !isbits(B) && !_isdisjoint(dataids(A), dataids(B)) -mightalias(x, y) = false +mightalias(A, B) = _anymightalias(aliasingroots(A), aliasingroots(B)) + +_anymightalias(::Tuple{}, ::Tuple) = false +_anymightalias(as::Tuple, bs::Tuple) = + any(b -> _mightalias(as[1], b), bs) || _anymightalias(tail(as), bs) + +_mightalias(A::AbstractArray, B::AbstractArray) = + !isbits(A) && !isbits(B) && !_isdisjoint(dataids(A), dataids(B)) + +""" + Base.aliasingroots(A) -> Tuple + +Return a tuple of "parent" arrays in which the mutable data for `A` is stored. + +Custom arrays that would like to opt-in to aliasing detection of their component +parts can specialize this method to return the concatenation of the +`aliasingroots` of their component parts. A typical definition for an array +that wraps a parent is +`Base.aliasingroots(C::CustomArray) = Base.aliasingroots(C.parent)`. + +See also [`Base.dataids`](@ref). +""" +aliasingroots(A::AbstractArray) = (A,) + +# For non-array object, we suppose that it has no mutable data. It then ensures +# that `mightalias` returns `false` if one of its argument is not an array. +aliasingroots(::Any) = () _isdisjoint(as::Tuple{}, bs::Tuple{}) = true _isdisjoint(as::Tuple{}, bs::Tuple{Any}) = true @@ -1146,10 +1171,8 @@ _isdisjoint(as::Tuple, bs::Tuple) = !(as[1] in bs) && _isdisjoint(tail(as), bs) Return a tuple of `UInt`s that represent the mutable data segments of an array. -Custom arrays that would like to opt-in to aliasing detection of their component -parts can specialize this method to return the concatenation of the `dataids` of -their component parts. A typical definition for an array that wraps a parent is -`Base.dataids(C::CustomArray) = dataids(C.parent)`. +For custom array types wrapping other array types (e.g., `ReshapedArray`), it is +recommended to define [`Base.aliasingroots`](@ref) instead of `Base.dataids`. """ dataids(A::AbstractArray) = (UInt(objectid(A)),) dataids(A::Array) = (UInt(pointer(A)),) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index cdfabcfaf4140..57dbfea034a3b 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -707,7 +707,7 @@ end # In the common case where we have two views into the same parent, aliasing checks # are _much_ easier and more important to get right -function mightalias(A::SubArray{T,<:Any,P}, B::SubArray{T,<:Any,P}) where {T,P} +function _mightalias(A::SubArray{T,<:Any,P}, B::SubArray{T,<:Any,P}) where {T,P} if !_parentsmatch(A.parent, B.parent) # We cannot do any better than the usual dataids check return !_isdisjoint(dataids(A), dataids(B)) diff --git a/base/reinterpretarray.jl b/base/reinterpretarray.jl index 2ea21ec981b56..344cdd08f6677 100644 --- a/base/reinterpretarray.jl +++ b/base/reinterpretarray.jl @@ -68,6 +68,7 @@ IndexStyle(a::ReinterpretArray) = IndexStyle(a.parent) parent(a::ReinterpretArray) = a.parent dataids(a::ReinterpretArray) = dataids(a.parent) +aliasingroots(a::ReinterpretArray) = aliasingroots(a.parent) function size(a::ReinterpretArray{T,N,S} where {N}) where {T,S} psize = size(a.parent) diff --git a/base/reshapedarray.jl b/base/reshapedarray.jl index 07e661bc72203..23ba81db85e44 100644 --- a/base/reshapedarray.jl +++ b/base/reshapedarray.jl @@ -196,6 +196,7 @@ elsize(::Type{<:ReshapedArray{<:Any,<:Any,P}}) where {P} = elsize(P) unaliascopy(A::ReshapedArray) = typeof(A)(unaliascopy(A.parent), A.dims, A.mi) dataids(A::ReshapedArray) = dataids(A.parent) +aliasingroots(a::ReshapedArray) = aliasingroots(a.parent) @inline ind2sub_rs(ax, ::Tuple{}, i::Int) = (i,) @inline ind2sub_rs(ax, strds, i) = _ind2sub_rs(ax, strds, i - 1) diff --git a/base/subarray.jl b/base/subarray.jl index 95c7446ff8583..07001221166fd 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -92,6 +92,8 @@ julia> parentindices(V) parentindices(a::AbstractArray) = map(OneTo, size(a)) ## Aliasing detection +# Note: Not defining `aliasingroots(::SubArray)` since `SubArray` needs to act +# as a root for the custom `mightalias` check to work. dataids(A::SubArray) = (dataids(A.parent)..., _splatmap(dataids, A.indices)...) _splatmap(f, ::Tuple{}) = () _splatmap(f, t::Tuple) = (f(t[1])..., _splatmap(f, tail(t))...) diff --git a/test/arrayops.jl b/test/arrayops.jl index 962d8a37149ee..b5c3e53ea57b7 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -1150,6 +1150,7 @@ end @test @views mightalias(A[:], A[3,1:1]) @test @views mightalias(A[:,:], A[3,1:1]) + # view of reshape B = reshape(A,10,2) @test mightalias(A, A) @test mightalias(A, B) @@ -1158,6 +1159,25 @@ end @test @views mightalias(B[1:2], A[1:2]) @test @views !mightalias(B[1:end÷2], A[end÷2+1:end]) + # reshape of view + A1 = @view A[:, 1] + A2 = @view A[:, 2] + @test !mightalias(reshape(A1, (:, 1)), A2) + @test mightalias(reshape(A1, (:, 1)), A1) + @test !mightalias(reshape(A1, (:, 1)), reshape(A2, (1, :))) + @test mightalias(reshape(A1, (:, 1)), reshape(A1, (1, :))) + + # reinterpret of view + buffer = zeros(UInt8, 4 * sizeof(Int)) + x1 = reinterpret(Int, @view buffer[1:end÷2]) + x2 = reinterpret(Int, @view buffer[end÷2+1:end]) + @test Base.mightalias(x1, x1) + @test !Base.mightalias(x1, x2) + @test @views Base.mightalias(x1[1:1], x1[1:end]) + @test @views !Base.mightalias(x1[1:1], x1[2:end]) + @test Base.mightalias(reshape(x1, (1, :)), reshape(x1, (:, 1))) + @test !Base.mightalias(reshape(x1, (1, :)), reshape(x2, (:, 1))) + AA = [[1],[2]] @test @views mightalias(AA, AA[:]) @test @views mightalias(AA[:], AA[:]) diff --git a/test/reinterpretarray.jl b/test/reinterpretarray.jl index bdb588433dba9..9a19c752966f5 100644 --- a/test/reinterpretarray.jl +++ b/test/reinterpretarray.jl @@ -160,3 +160,14 @@ let a = [0.1 0.2; 0.3 0.4], at = reshape([(i,i+1) for i = 1:2:8], 2, 2) r = reinterpret(Int, vt) @test r == OffsetArray(reshape(1:8, 2, 2, 2), (0, offsetvt...)) end + +@testset "broadcast" begin + # https://github.com/JuliaLang/julia/issues/29545 + buffer = zeros(UInt8, 4 * sizeof(Int)) + mid = length(buffer) ÷ 2 + x1 = reinterpret(Int, @view buffer[1:mid]) + x2 = reinterpret(Int, @view buffer[mid+1:end]) + @test !Base.mightalias(x1, x2) + x1 .= x2 + @test x1 == x2 +end