Memory aliasing rules and reinterpret

foobar_lv2
Wed, 07 Feb 2024 14:14:08 +0000
Internals & Design

I wanted to ask about the julia aliasing rules for memory accesses. Unfortunately, the docs are not super clear about that, e.g.

Julia currently uses LLVM’s Type Based Alias Analysis.
To find the comments that document the inclusion relationships, look for static MDNode* in src/codegen.cpp

The ultimate reason for my question is the following construction: It often makes sense to view the same chunk of memory as either v::Vector{SVector{N, T}} or as mat::Matrix{T} with size(mat) = (N, length(vec)), such that v[idx] = mat[:, idx].

Base Reinterpret is inappropriate for this – performance simply sucks / is unreliable.

The classic solution is something like

v = ccall(:jl_reshape_array, Vector{SVector{size(mat)[1], eltype(mat)}}, (Any, Any, Any), Vector{SVector{size(mat)[1], eltype(mat)}}, mat, (size(mat)[2],))

Now my question is: Are reads/writes to v and to mat permitted to alias in julia? In theory and in practice?

Is there a programmatic way of finding out whether something is permitted to alias per TBAA?

The next question would be: Is TBAA applied to unsafe_store! / unsafe_load?

If yes, could we maybe have an extra argument to Core.pointerref et al to say that a load/store is intended to have ccall(:memcpy, ...) semantics?

PS. It looks like they are allowed to alias in practice?

julia> mat = rand(Int64, 2, 2)
2×2 Matrix{Int64}:
 6764168577710771209  -7368470990085874290
 7379517068019859522     36090219286728105

julia> v=ccall(:jl_reshape_array, Vector{SVector{size(mat)[1], eltype(mat)}}, (Any, Any, Any), Vector{SVector{size(mat)[1], eltype(mat)}}, mat, (size(mat)[2],))
2-element Vector{SVector{2, Int64}}:
 [6764168577710771209, 7379517068019859522]
 [-7368470990085874290, 36090219286728105]

julia> function baz(v, mat)
       @inbounds v0 = v[1]
       @inbounds mat[1] += 1
       @inbounds v1 = v[1]
       v1==v0
       end
baz (generic function with 1 method)

julia> baz(v,mat)
false

Is this guaranteed behavior that will not change before 2.0 ? Or is this code UB that the compiler is just not smart enough to break?

PS. Likewise,

julia> mat2=rand(2,2)
2×2 Matrix{Float64}:
 0.932218  0.839201
 0.810208  0.952793
julia> x=ccall(:jl_reshape_array, Matrix{Int64}, (Any, Any, Any), Matrix{Int64}, mat2, size(mat2))
2×2 Matrix{Int64}:
 4611380756684956808  4605734070903863065
 4605472925120407489  4606757212508987398

julia> baz(x, mat2)
false

Is this code UB or is it valid and will return false until julia 2.0?

(cc @Keno because something along these lines triggered your ReinterpretArray rewrite years ago)

9 posts - 3 participants