Getting around the strict aliasing rule in Julia

I am trying to better understand Julia’s equivalent of the strict aliasing rule from C.

As background I would like to implement various decoding algorithms in Julia. Looking at the LZ4 C code, they are careful to use memcpy and casting to unsigned char * when loading or storing to pointers.

From a discussion on slack, Julia’s version of the strict aliasing rule is much stricter than C’s so these methods are not effective in Julia.

The worry is the following simplified example of loading data into a vector may break in some future version of Julia.

function unsafe_foo!(dst::Ptr{UInt8}, n::Int64)
    for i in 0:n-1
        unsafe_store!(dst + i, 0x01)
    end
end

dst = zeros(UInt64, 4)
dst .= 2
dst_cconv = Base.cconvert(Ptr{UInt8}, reinterpret(UInt8, dst))
GC.@preserve dst_cconv begin
    dst_p = Base.unsafe_convert(Ptr{UInt8}, dst_cconv)
    unsafe_foo!(dst_p, Int64(length(dst)*8))
end

display(dst[1])
# 0x0101010101010101

Since unsafe_foo! is storing using Ptr{UInt8}, but dst is a Vector{UInt64}, my understand of the strict aliasing rule is that the compiler may reorder the dst .= 2 to after the call to unsafe_foo!: transforming the program to.

dst = zeros(UInt64, 4)
dst_cconv = Base.cconvert(Ptr{UInt8}, reinterpret(UInt8, dst))
GC.@preserve dst_cconv begin
    dst_p = Base.unsafe_convert(Ptr{UInt8}, dst_cconv)
    unsafe_foo!(dst_p, Int64(length(dst)*8))
end

dst .= 2
display(dst[1])
# 0x0000000000000002

Which is obviously not the intended result.

Comments in Get rid of reinterpret in its current form · Issue #22849 · JuliaLang/julia · GitHub seem to imply that @noinline can be used as a “TBAA” barrier, but I’m not sure why that would work. Even if the function is not inline can’t the compiler still analyse how it is writing to memory?

My current idea is to use ccall on a global function pointer:

unsafe_foo!_fp = @cfunction(unsafe_foo!, Cvoid, (Ptr{UInt8}, Int64))
@noinline function indirect_unsafe_foo!(dst::Ptr{UInt8}, n::Int64)
    @ccall $unsafe_foo!_fp(dst::Ptr{UInt8}, n::Int64)::Cvoid
end

Am I misunderstanding how the strict aliasing rule applies to Julia code, and there is a less overkill way to solve this?

Should I just document that pointers from reinterpreted arrays should never be passed to unsafe_foo!?