JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.42k stars 5.45k forks source link

Apple M1 shows inconsistent field offset after a primitive type #42326

Open tkf opened 2 years ago

tkf commented 2 years ago

@anandijain noticed (https://github.com/tkf/ConcurrentCollections.jl/issues/35) the following difference between ARM (Apple M1) and x86_64

julia> primitive type PadAfter64 448 end

julia> mutable struct CheckPadAfter64
           a::UInt64
           pad::PadAfter64
           b::UInt64
       end

julia> fieldoffset(CheckPadAfter64, 3)
0x0000000000000040  # x86_64
0x0000000000000050  # Apple M1?

It seems to be a specific behavior that occurs only when a primitive type is used as a field. That is to say, the following tests show no difference between Apple M1 (tested with 1.7.0-rc1) and x86_64:

julia> ofs = [fieldoffset(Tuple{UInt64,NTuple{n,UInt8},UInt64}, 3) for n in 1:64]
       idx = findall(diff(ofs) .> 0)
       idx .=> ofs[idx]
7-element Vector{Pair{Int64, UInt64}}:
  8 => 0x0000000000000010
 16 => 0x0000000000000018
 24 => 0x0000000000000020
 32 => 0x0000000000000028
 40 => 0x0000000000000030
 48 => 0x0000000000000038
 56 => 0x0000000000000040

julia> sizeof(PadAfter64)
56

julia> sizeof(Some{NTuple{56,UInt8}})
56

julia> mutable struct PadWithNTuple
           a::UInt64
           pad::NTuple{56,UInt8}
           b::UInt64
       end

julia> fieldoffset(PadWithNTuple, 3)
0x0000000000000040

I don't have access to Apple M1 so I can't check this myself. But this looks like a bug to me.

mryodo commented 2 years ago

I don't have access to Apple M1 so I can't check this myself. But this looks like a bug to me.

I've checked and can confirm that Apple M1 indeed yields such output

quinnj commented 1 year ago

I believe this is the same core issue as can be seen by:

alignment(::Type{T}) where {T} = unsafe_load(convert(Ptr{Int16}, T.layout + 12))

# macos intel
alignment(UInt128) == 8

# macos m1
alignment(UInt128) == 16
gbaraldi commented 1 year ago

This is not macos specific. But aarch64 specific. And in the UInt128 case it seems we are showing it incorrectly for intel, as UInt128 is 16 byte aligned everywhere I checked https://reviews.llvm.org/D28990

37974 Shows why primitive types should be aligned.

vtjnash commented 1 year ago

That appears to have been immediately reverted, and not yet re-landed (https://reviews.llvm.org/D86310)

gbaraldi commented 1 year ago

All the ABIs do show that it's 16byte aligned, so this does look kind of wrong. But I imagine we just follow whatever LLVM does in this case, right?

https://godbolt.org/z/YEo5nv5nP We are differing from both clang and GCC here, at least on x86 we are.

julia> struct Foo
              a::UInt128
              c::UInt8
              b::UInt128
              end

julia> sizeof(Foo)
40
gbaraldi commented 1 month ago

I believe this is correct now in LLVM 18?

giordano commented 1 month ago

If so, should we have a regression test about this?

gbaraldi commented 1 month ago

Probably. I think they should go in https://github.com/JuliaLang/julia/pull/49362

giordano commented 3 weeks ago

For the record, I confirm on 48d4fd48430 I see

julia> primitive type PadAfter64 448 end

julia> mutable struct CheckPadAfter64
           a::UInt64
           pad::PadAfter64
           b::UInt64
       end

julia> fieldoffset(CheckPadAfter64, 3)
0x0000000000000050

on both aarch64 and x86_64, on both linux and macOS. I can open a separate PR to add this test, since #49362 seems to be stuck in review (and now there are also merge conflicts)