Skip to content

Commit 4b063cf

Browse files
mbaumanKristofferC
authored andcommitted
more precise aliasing checks for SubArray (#54624)
This avoids returning false positives where only the indices are shared. As the indices are not mutated by array assignments (and are explicitly warned against mutation in general), we can ignore the case where _only_ the indices are aliasing. Fix #54621 (cherry picked from commit 97bf148)
1 parent 43cdc58 commit 4b063cf

File tree

2 files changed

+56
-16
lines changed

2 files changed

+56
-16
lines changed

base/multidimensional.jl

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,25 +1025,34 @@ end
10251025

10261026
### from abstractarray.jl
10271027

1028-
# In the common case where we have two views into the same parent, aliasing checks
1029-
# are _much_ easier and more important to get right
1030-
function mightalias(A::SubArray{T,<:Any,P}, B::SubArray{T,<:Any,P}) where {T,P}
1031-
if !_parentsmatch(A.parent, B.parent)
1032-
# We cannot do any better than the usual dataids check
1033-
return !_isdisjoint(dataids(A), dataids(B))
1034-
end
1035-
# Now we know that A.parent === B.parent. This means that the indices of A
1036-
# and B are the same length and indexing into the same dimensions. We can
1037-
# just walk through them and check for overlaps: O(ndims(A)). We must finally
1038-
# ensure that the indices don't alias with either parent
1039-
return _indicesmightoverlap(A.indices, B.indices) ||
1040-
!_isdisjoint(dataids(A.parent), _splatmap(dataids, B.indices)) ||
1041-
!_isdisjoint(dataids(B.parent), _splatmap(dataids, A.indices))
1028+
function mightalias(A::SubArray, B::SubArray)
1029+
# There are three ways that SubArrays might _problematically_ alias one another:
1030+
# 1. The parents are the same we can conservatively check if the indices might overlap OR
1031+
# 2. The parents alias eachother in a more complicated manner (and we can't trace indices) OR
1032+
# 3. One's parent is used in the other's indices
1033+
# Note that it's ok for just the indices to alias each other as those should not be mutated,
1034+
# so we can always do better than the default !_isdisjoint(dataids(A), dataids(B))
1035+
if isbits(A.parent) || isbits(B.parent)
1036+
return false # Quick out for immutables
1037+
elseif _parentsmatch(A.parent, B.parent)
1038+
# Each SubArray unaliases its own parent from its own indices upon construction, so if
1039+
# the two parents are the same, then by construction one cannot alias the other's indices
1040+
# and therefore this is the only test we need to perform:
1041+
return _indicesmightoverlap(A.indices, B.indices)
1042+
else
1043+
A_parent_ids = dataids(A.parent)
1044+
B_parent_ids = dataids(B.parent)
1045+
return !_isdisjoint(A_parent_ids, B_parent_ids) ||
1046+
!_isdisjoint(A_parent_ids, _splatmap(dataids, B.indices)) ||
1047+
!_isdisjoint(B_parent_ids, _splatmap(dataids, A.indices))
1048+
end
10421049
end
1050+
# Test if two arrays are backed by exactly the same memory in exactly the same order
10431051
_parentsmatch(A::AbstractArray, B::AbstractArray) = A === B
1044-
# Two reshape(::Array)s of the same size aren't `===` because they have different headers
1045-
_parentsmatch(A::Array, B::Array) = pointer(A) == pointer(B) && size(A) == size(B)
1052+
_parentsmatch(A::DenseArray, B::DenseArray) = elsize(A) == elsize(B) && pointer(A) == pointer(B) && size(A) == size(B)
1053+
_parentsmatch(A::StridedArray, B::StridedArray) = elsize(A) == elsize(B) && pointer(A) == pointer(B) && strides(A) == strides(B)
10461054

1055+
# Given two SubArrays with the same parent, check if the indices might overlap (returning true if unsure)
10471056
_indicesmightoverlap(A::Tuple{}, B::Tuple{}) = true
10481057
_indicesmightoverlap(A::Tuple{}, B::Tuple) = error("malformed subarray")
10491058
_indicesmightoverlap(A::Tuple, B::Tuple{}) = error("malformed subarray")

test/subarray.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,3 +779,34 @@ end
779779
@test parent(@inferred(view(A, :, 3, 1, CartesianIndices(()), 1))) === A
780780
@test_throws BoundsError view(A, :, 3, 2, CartesianIndices(()), 1)
781781
end
782+
783+
@testset "aliasing checks with shared indices" begin
784+
indices = [1,3]
785+
a = rand(3)
786+
av = @view a[indices]
787+
b = rand(3)
788+
bv = @view b[indices]
789+
@test !Base.mightalias(av, bv)
790+
@test Base.mightalias(a, av)
791+
@test Base.mightalias(b, bv)
792+
@test Base.mightalias(indices, av)
793+
@test Base.mightalias(indices, bv)
794+
@test Base.mightalias(view(indices, :), av)
795+
@test Base.mightalias(view(indices, :), bv)
796+
end
797+
798+
@testset "aliasing checks with disjoint arrays" begin
799+
A = rand(3,4,5)
800+
@test Base.mightalias(view(A, :, :, 1), view(A, :, :, 1))
801+
@test !Base.mightalias(view(A, :, :, 1), view(A, :, :, 2))
802+
803+
B = reinterpret(UInt64, A)
804+
@test Base.mightalias(view(B, :, :, 1), view(A, :, :, 1))
805+
@test !Base.mightalias(view(B, :, :, 1), view(A, :, :, 2))
806+
807+
C = reinterpret(UInt32, A)
808+
@test Base.mightalias(view(C, :, :, 1), view(A, :, :, 1))
809+
@test Base.mightalias(view(C, :, :, 1), view(A, :, :, 2)) # This is overly conservative
810+
@test Base.mightalias(@view(C[begin:2:end, :, 1]), view(A, :, :, 1))
811+
@test Base.mightalias(@view(C[begin:2:end, :, 1]), view(A, :, :, 2)) # This is overly conservative
812+
end

0 commit comments

Comments
 (0)