x86 features
The AC
flag in EFLAGS enables alignment checking. (486 and later). The OS has to enable it by setting the AM bit in CR0. The Linux kernel for example does do that. (But GNU/Linux user-space does potentially-unaligned accesses in libc and in compiled code which wouldn't do so if compiled for a system where that wasn't both safe and expected to be efficient. Your proposed use-case is an X-Y problem, see below.)
It applies only in user-space if SMAP (Supervisor Mode Access Prevention) is activated (by setting a bit in CR4): then AC in kernel mode (ring 0) stops kernel code from accidentally accessing pages that user-space can read/write, to harden the kernel against user-space tricking it into jumping to or accidentally reading pages user-space controls.
Note that there are stac
/ clac
instructions to set and clear it, but those only work in ring 0 and were new with SMAP in Broadwell. (Unlike the earlier SMEP execution protection, the kernel has to toggle AC for system calls like read
, write
, and stat
where user-space passes a pointer, e.g. for Linux's copy_to_user
/ copy_from_user
functions.) So for user-space alignment checking, you still need pushf
/ or dword [esp], 1<<18
/ popf
Cache-line split lock
ed instructions (atomic RMWs) can be made to fault, without making other misaligned accesses fault, by setting a bit in an MSR. https://lwn.net/Articles/810317/ / https://www.phoronix.com/news/Linux-Split-Locks-Detection They're disastrously slow system-wide, especially on systems with many cores. (Actual bus-lock style blocking memory access from all cores, instead of the usual MESI cache-lock trick of just not responding to requests for this line so we keep it in Exclusive / Modified state for the duration of the atomic RMW, no extra off-core communication beyond what would be needed for a plain store.)
There was already a performance-counter event just for this (Intel sq_misc.split_lock
), but VMs don't always pass through performance events, and usually the goal is for the count to be exactly zero. Trapping lets the kernel or hypervisor print a log message and then single-step it or something, to discover any cases where a misaligned pointer gets used with an atomic RMW. (Atomic pure-load / pure-store with misaligned pointers isn't possible, those are done with plain loads and stores that depend on the guarantee that all aligned accesses of certain widths are atomic.)
SSE movaps
/ movdqa
instructions require 16-byte alignment, as do 16-byte memory source operands for SSE instructions like paddb xmm0, [rdi]
. movups
/ movdqu
are the only way to do 16-byte unaligned loads/stores with SSE. (Narrower memory operands like 8-byte movq xmm, [mem]
or MMX punpckldq mm0, [mem]
don't require alignment.)
AVX / AVX-512 are the same for vmovaps
/ vmovdqa
vs. vmovups
/ vmovdqu
, but memory source operands for other instructions like vaddps ymm1, ymm0, [rdi]
don't require alignment. The only way to get alignment checking is to use separate vmovaps
/ vmovdqa
instructions, opposite of legacy SSE encodings where alignment requirement was the default.
(Some compilers, specifically MSVC, use vmovdqu
even if you use alignment-checked intrinsics like _mm_store_si128
not storeu
. GCC / Clang do respect the source intrinsics, but will fold a load
intrinsic into a memory source operand for an instruction that removes the alignment checking.)
NT loads / stores of 16 bytes or wider only come in alignment-required flavours (movntps
/ movntdq
stores which are weakly ordered even on normal WB memory, SSE4.1 movntdqa
loads which only do anything special on WC memory which is already weakly ordered.) Narrow NT stores like movnti
or MMX maskmovq
don't require alignment, though.
Related:
Performance of byte and unaligned-word stores on non-x86 CPUs that support them
See Are there any modern CPUs where a cached byte store is actually slower than a word store? - apparently yes, most non-x86. My answer there found details about ARM Cortex-A15 MPCore, Alpha 21264, and PowerPC RS64-III which are all fairly old by today's standards. But I wouldn't be surprised if there's still a small throughput penalty for byte and unaligned-word stores in modern AArch64.
Coalescing in the store buffer can potentially turn multiple byte stores into a word store that writes a whole ECC granule without having to read the old data first. But I don't know how if it's common for a stream of contiguous misaligned word stores to be able to commit to L1d cache as efficiently as aligned, except when crossing cache-line boundaries. i.e. whether coalescing can grab part but not all of an unaligned word store to complete the word containing the store at the head of the store buffer.
(Modern Intel CPUs have literally zero penalty for misaligned stores within a cache line, at least since Haswell eliminated cache-bank conflicts. Same for modern AMD, although some older AMD had effects when crossing a 32-byte or even 16-byte boundary. See https://travisdowns.github.io/blog/2019/06/11/speed-limits.html / https://stackoverflow.com/questions/45128763/how-can-i-accurately-benchmark-unaligned-access-speed-on-x86-64)
You have an X-Y problem.
Detecting misalignment UB in C programs without false positives
The problem you actually want to solve is to find alignment undefined behaviour in your C programs, because that's what will compile to broken asm on machines where misaligned loads fault. Alignment checking in x86 asm (with the AC flag) could have false positives, both in your own code when memcpy
inlines or many other ways, and in libc.
Modern GCC and Clang have -fsanitize=alignment
(or in general -fsanitize=undefined
) to add run-time checks ahead of every pointer deref that has any alignment requirement in C.
UBSAN docs:
Alignment UB isn't safe even on x86
C that dereferences misaligned pointers has UB, and is already broken even when compiling for a target where unaligned loads are safe in asm, such as x86 with the AC (alignment-check) flag not set.
It will often happen to work, but there are some cases where the UB causes problems in practice with GCC and Clang for x86-64:
In GNU C, you can use typedef uint32_t unaligned_u32 __attribute__((aligned(1),may_alias));
and point unaligned_u32*
at anything regardless of alignment (or strict-aliasing; you could leave out that part if you want.) Or portably you can use memcpy
to do unaligned loads or stores in C programs.
False positives: safe C can compile to misaligned load/store when that's known to be safe on the target ISA
memcpy
(or deref of an unaligned_u32 *
) will inline to a single load or store instruction when compiling for an ISA with safe and efficient unaligned loads, for a constant size = sizeof(primitive type)
. Or not when compiling for a RISC like MIPS without unaligned loads; it might use lwl
/ lwr
(load-word left/right for the halves of an unaligned load), or worse might emit a call to memcpy
if you used that instead of a GNU C typedef.
But conversely "safe" source without alignment UB can compile to asm that does do unaligned loads/stores, for targets where that's known to be safe and usually efficient, like x86-64, AArch64, ARMv6 and ARMv7, and some others. For example (Godbolt) a function like void foo(char *p){ p[1] = p[3]; p[2] = p[4]; }
- GCC and Clang coalesce the two assignments into a potentially-misaligned 2-byte load and 2-byte store. Except for RISC-V: misaligned loads are guaranteed supported, but are allowed to be extremely slow (like trapping to software emulation). IDK how many real RISC-V cores implement unaligned loads in hardware.
I'm defining "misaligned" as when T*
isn't aligned by alignof(T)
. This might be less than sizeof(T)
in some ABIs, such as alignof(long long) = alignof(double) = 4
in the i386 System V ABI. Compilers try to align those by their size when possible, but struct layout is fixed by the ABI so you can have a misaligned object in a valid program. That's not UB, the compiler has to respect the possibility that a long long*
in 32-bit code might only be aligned by 4, but can still assume the low 2 bits of the address are 0.
Would be nice if you could somehow set your 386 to turn off unalignment tolerance, so you know if it doesn't crash on your machine, it's good.
That could potentially work for a debug build (which won't do load/store coalescing to generate unaligned loads from C which doesn't). But __attribute__((packed))
on structs can still compile to unaligned loads on x86, vs. to whatever is necessary on a RISC without unaligned loads/stores (e.g. not AArch64).
So safe C (that would have to compile to safe asm on any target) that uses memcpy
or a GNU C aligned(1)
typedef for its unaligned accesses would still have misaligned accesses in its x86 asm.
And more importantly for practicality, libc functions like memcpy
make use of unaligned loads/stores, especially for small copies that aren't a power-of-2 size or aren't aligned. So you'd need a custom version of libc and ld.so
to be able to turn on user-space alignment checking on x86 (by setting the AC flag in EFLAGS).
asm("ta 6")
it was.int*
on an odd numbered memory address. I think that's how I learned what aligned memory access is.