Description
This is a regression from #98112. I suppose it's not possible to disable this specific check only while preserving debug assertions...
The core problem is that the x86 ABI on Windows doesn't guarantee the stack alignment above 4. See for example https://developercommunity.visualstudio.com/t/vs2017-64-bit-int-alignment-problem/294259
And while some types have an alignment reported of 8 (e.g. UINT64), in practice, the C compiler will happily not align them on the stack.
So for example, this C code, compiled by MSVC for 32-bits:
#include <cinttypes>
extern void hoge(uint64_t*);
void foo() {
uint64_t a;
hoge(&a);
}
will produce this assembly:
_a$ = -8 ; size = 8
void foo(void) PROC ; foo
push ebp
mov ebp, esp
sub esp, 8
lea eax, DWORD PTR _a$[ebp]
push eax
call void hoge(unsigned __int64 *) ; hoge
add esp, 4
mov esp, ebp
pop ebp
ret 0
void foo(void) ENDP
If the stack pointer is not 8-bytes aligned when entering the function, the pointer passed to hoge
is not going to be 8-bytes aligned.
As mentioned in the linked community post above, adding alignas(8)
to the type definition makes the compiler align the stack:
#include <cinttypes>
extern void hoge(uint64_t*);
void foo() {
alignas(8) uint64_t a;
hoge(&a);
}
becomes
_a$ = -8 ; size = 8
void foo(void) PROC ; foo
push ebx
mov ebx, esp
sub esp, 8
and esp, -8 ; fffffff8H
add esp, 4
push ebp
mov ebp, DWORD PTR [ebx+4]
mov DWORD PTR [esp+4], ebp
mov ebp, esp
sub esp, 8
lea eax, DWORD PTR _a$[ebp]
push eax
call void hoge(unsigned __int64 *) ; hoge
add esp, 4
mov esp, ebp
pop ebp
mov esp, ebx
pop ebx
ret 0
void foo(void) ENDP
Now, what this means is that if that hoge
function is a rust FFI function, and it uses that pointer, the "misaligned pointer dereference" check is hit and panic ensues.
Real life case, for the curious:
https://github.com/servo/dwrote-rs/blob/master/src/font_file_loader_impl.rs#L116-L123
That function is called from dwrite.dll (which comes with Windows).
t-opsem FCP comment
Summary of the MSVC alignment rules
Activity
glandium commentedon Jun 10, 2023
Let me substantiate this:
compiles to:
(on godbolt)
Actually, if the stack pointer is 8-bytes aligned when entering the function, the pointer passed to hoge is going to be unaligned (because there's a push first).
saethlin commentedon Jun 10, 2023
If you can pass unstable flags, currently, you can say
-Zmir-enable-passes=-CheckAlignment
.saethlin commentedon Jun 10, 2023
I think your report here is a duplicate of https://developercommunity.visualstudio.com/t/visual-c-alignof-is-not-conformant-on-x86/1258506
That is, this appears to be a known MSVC bug.
glandium commentedon Jun 10, 2023
The MSVC bug would be the value returned by alignof. That doesn't change anything for the alignment check in rust.
saethlin commentedon Jun 10, 2023
std::mem::align_of::<u64>()
returns8
on i686-pc-windows-msvc. Are you saying that is a bug?ChrisDenton commentedon Jun 10, 2023
It's complicated. See https://learn.microsoft.com/en-us/cpp/c-language/alignment-c?view=msvc-170
So a type with a preferred alignment of 8 bytes can be 4-byte aligned on 32-bit targets.
saethlin commentedon Jun 10, 2023
Ah, but
std::mem::align_of
does not return the preferred alignment.ChrisDenton commentedon Jun 10, 2023
But see also https://learn.microsoft.com/en-us/cpp/build/reference/zp-struct-member-alignment?view=msvc-170
Where
N
is 8 for x86.glandium commentedon Jun 10, 2023
It's true that from the perspective that in
struct { int a; uint64_t b; }
, b is 8-bytes aligned, alignof returning 8 is actually correct. But it's more indicative of "you'll be safe if you align to 8 bytes" than "you must align to 8 bytes" or "it will always be aligned to 8 bytes". Because the struct itself, on the stack, will still be 4-bytes aligned. The x86 windows ABI is weird that way... but that essentially breaks the assumption from the alignment check.ChrisDenton commentedon Jun 10, 2023
Right. But it matters in terms of how this is addressed. It would be wrong to change
align_of
as that's documented to be for struct fields.For the short term, skipping this check on i686-windows might be the only decent options (which is unfortunate). But longer term it would be better to have a proper fix. I'm not really what that would look like tbh. As you say, it's breaking a core assumption.
saethlin commentedon Jun 10, 2023
There is no assumption made by the alignment check. We unambiguously tell LLVM that a dereference of a
*const u64
oni686-pc-windows-msvc
is 8-byte-aligned: https://godbolt.org/z/MrdTPqWbYIf you are saying that this alignment assumption is wrong, then we have a soundness issue. The bug is not in the alignment check, we either have invalid codegen from MSVC because it does not sufficiently align its
uint64_t
s, or we have invalid codegen from rustc because it assumesu64
is 8-byte-aligned on this target when it is not.110 remaining items