What the hell is going on here

77 points StyMaar

Today, Mara Bos posted a intriguing quizz on twitter

#[repr(transparent)]
struct W(i32);

let w = W(123);
let a: &dyn Sync = &w;
let b: &dyn Sync = &w.0;
println!("{:?}", std::ptr::eq(a, b));

Which gets even funnier if you actually display the value for those pointers:

println!("{:p} {:p} {:?}", a, b, std::ptr::eq(a, b));

And the result is:

!in debug mode: 0x7fff6b5c986c 0x7fff6b5c986c false!<

but even more interestingly,

!in release mode: 0x7fff4f6a99b4 0x7fff4f6a99b4 true!<

I know almost nothing in assembly, but I decided to try and see what the compiler explorer would say about it to understand what's going on.

You can see it by yourself here

In short, in release, the code does:

    lea     rax, [rip + .L__unnamed_1]
    lea     rcx, [rip + .L__unnamed_2]
    cmp     rcx, rax

.L__unnamed_2:
    .quad   core::ptr::drop_in_place<i32>
    .asciz  "\\004\\000\\000\\000\\000\\000\\000\\000\\004\\000\\000\\000\\000\\000\\000"

.L__unnamed_1:
    .quad   core::ptr::drop_in_place<i32>
    .asciz  "\\004\\000\\000\\000\\000\\000\\000\\000\\004\\000\\000\\000\\000\\000\\000"

where in debug it does:

lea     rdi, [rsp + 20]
lea     rdx, [rsp + 20]
lea     rsi, [rip + .L__unnamed_3]
lea     rcx, [rip + .L__unnamed_4]

cmp     rdi, rdx
sete    al
cmp     rsi, rcx
sete    cl
and     al, cl


.L__unnamed_3:
    .quad   core::ptr::drop_in_place<example::main::W>
    .asciz  "\\004\\000\\000\\000\\000\\000\\000\\000\\004\\000\\000\\000\\000\\000\\000"

.L__unnamed_4:
    .quad   core::ptr::drop_in_place<i32>
    .asciz  "\\004\\000\\000\\000\\000\\000\\000\\000\\004\\000\\000\\000\\000\\000\\000"

So in very short, in release mode, it compares L__unnamed_1 and L__unnamed_2 (which are the same, why is LLVM even emitting two different ones?), whereas in debug it compares L__unnamed_3 and L__unnamed_4 which are different because of the generic parameter in drop_in_place.

But now that I've seen that, I still don't really understand what's going on.

Why is a ptr comparison comparing things beyond the address themselves (I feel this has to do with this I guess), and then why is it OK if the behavior aren't consistent before and after optimization?

Edit: and I also ran it in Miri to see if it wasn't some soundness bug in the compiler triggering UB, but Miri think this code is OK (and the answer is false indeed)