23

x86 CPUs have always supported unaligned load/store.

Early RISC CPUs didn't. So imagine writing portable code on a 386. It seems to work fine, but how do you know you haven't accidentally misaligned some data, so that when a customer tries running it on a RISC workstation, it will crash? 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.

Did any x86 CPU ever provide, by any means, the ability to trap unaligned memory accesses?

Adding clarification: when I talk about writing portable code on a 386, I mean writing hopefully-portable C, compiling it on your 386 workstation to test, then supplying the C to whoever is going to compile it on a RISC machine.

5
  • 16
    The compiler for whatever portable language being used would usually do the work to make sure any accesses were aligned. That would include doing extra aligned loads, moves, shifts etc to get unaligned data into a register if needed.
    – Brian
    Commented Feb 1 at 1:50
  • 4
    Modern RISC actually allows implicit misalignment. For example MIPSv6 removed all unaligned access commands and require an implementation to handle it either in SW or HW. IIRC the same applies to RISC-V
    – phuclv
    Commented Feb 1 at 13:23
  • 1
    "how do you know you haven't accidentally misaligned some data" You get a customer phone up, complaining that your software is very slow, and that the console is full of "unaligned access" messages. At which point you realise that such things matter...
    – TripeHound
    Commented Feb 1 at 14:37
  • 3
    Conversely, on SPARC, in both SunOS and Solaris, IIRC, there was a way to ask the kernel to emulate unaligned access. Albeit slow when unaligned accesses abounded, it was very useful to debug programs being ported from x86, if they didn't care aligning their data properly. asm("ta 6") it was.
    – Leo B.
    Commented Feb 2 at 3:39
  • 1
    When I was in university - I would occasionally write my code on Linux/x86 and then recompile for Sun/Sparc. The latter would blow up real fast on unaligned memory access - like trying to deference an int* on an odd numbered memory address. I think that's how I learned what aligned memory access is.
    – selbie
    Commented Feb 4 at 6:47

3 Answers 3

24

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 locked 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).

1
  • Fantastic answer, TY
    – davidbak
    Commented Feb 2 at 16:48
40

Not on a 386, but all x86 processors from the 486 forward support an alignment checking mode, which does basically what you describe: it causes an exception on any unaligned access from ring 3.

See the i486 Microprocessor Programmer's Reference Manual, page 4-2 (This is describing the EFLAGS register):

AC (Alignment Check Mode, bit 18)

Setting the AC flag and the AM bit in the CR0 register enables alignment checking on memory references. An alignment-check exception is generated when reference is made to an unaligned operand, such as a word at an odd byte address or a doubleword at an address which is not an integral multiple of four. Alignment-check exceptions are generated only in user mode (privilege level 3). Memory references which default to privilege level 0, such as segment descriptor loads, do not generate this exception even when caused by a memory reference in user-mode.

The alignment check interrupt can be used to check alignment of data. This is useful when exchanging data with other processors like i860™ 64-bit microprocessor which require all data to be aligned. The alignment check interrupt can also be used by interpreters to flag some pointers as special by misaligning the pointer. This eliminates overhead of checking each pointer and only handle the special pointer when used.

Note that this requires both the AC bit of EFLAGS to be set (which can freely be set or cleared by user processes) and the AM bit of CR0 to be set. The latter can only be done by the operating system, which must also handle the interrupt generated by the exception. I don't know that there is any operating system that actually supports this, so it wouldn't be much use in practice, but the capability is there in the CPU.

13
  • 10
    At this point the only answer which actually answers the user's question directly instead of telling him why he's making a mistake thinking this way. (For example, yes a compiler for a machine which requires alignment will generate code to correctly access misaligned items correctly. But that usually takes a performance hit. User may very well want to know on his x86 machine that that performance hit will happen on some other architecture.)
    – davidbak
    Commented Feb 1 at 4:59
  • 12
    The Linux kernel supports alignment checks on x86, see this SO Q&A (but the C library doesn’t expect it, so this can be difficult to use in practice). Commented Feb 1 at 5:58
  • 9
    @StephenKitt: I've tried it, and it is hard. You get alignment traps in ld.so. Commented Feb 1 at 9:21
  • 6
    @JohnDallman exactly — ld.so is part of the C library ;-). You can work around some of the errors in ld.so by enforcing early binding. But the main point is that the Linux kernel sets things up so that AC can be used. Commented Feb 1 at 9:26
  • 5
    Unfortunately, this flag is unusable in any code that uses floating point, because the floating point ABI for 32-bit x86 only has double and long double aligned at 4-byte boundaries, but when the AC bit of EFLAGS is set, load/store of a double-precision floating point value from an address that's 4 mod 8 will trap. I actually discovered this personally, trying to use it, many many years ago, and being disappointed that it was not usable. Commented Feb 1 at 15:24
-4

Not sure if I understand the question at all, but let's try:

So imagine writing portable code on a 386.

Define portable code.

Usually this means writing in a HLL (C,Pascal, Ada, whatsoever), which get translated by a compiler when ported to another machine using a different CPU. After all, it's rather rare that one CPU can execute another CPU's code right away.

It seems to work fine, but how do you know you haven't accidentally misaligned some data, so that when a customer tries running it on a RISC workstation, it will crash?

It won't as the RISC CPU will not execute any 386 code, it executes its own code. Usually the one compiled for that CPU, being stand alone or part of a Fat Binary. When compiling the RISC compiler will already align right and trow errors where it can't arrange proper.

Or the RISC machine runs some 386 emulation (in RISC code). Either way there will be no issues.

Not to mention that for one many RISC had exceptions for unaligned data and OSes provision to handle unaligned access without any harm - that is beside getting slower.

Would be nice if you could somehow set your 386 to turn off unalignment tolerance,

'unalignment tolerance' sounds like an optional feature to go around an issue, but it isn't. x86 follows a basic everything-is-a-byte philosophy, withthe whole point to shield programs from alignment issues. Everything in fetch is made to support this. So why adding additional hardware to detect something not needed (*1)?

so you know if it doesn't crash on your machine, it's good.

Why? Since it's not the same code, it wouldn't give any helpful information. Beside of course that aligned code is executing better on x86 as well, so aligning is a good idea anyway.

Did any x86 CPU ever provide, by any means, the ability to trap unaligned memory accesses?

No. at least no mainstream one (*2).

when I talk about writing portable code on a 386, I mean writing hopefully-portable C, compiling it on your 386 workstation to test, then supplying the C to whoever is going to compile it on a RISC machine.

Which will always result in a different code with different memory layout and different constrains. Thus any result of an 'alignment test' on the 386 code will not be of help judging the resulting RISC code.

In fact, not even beingable to compile without error on the 386 will give any guarantee that the source does compile on the other target. It will have to go thru the very same crunch cycle of

  • compiling
  • removing compiler errors/warnings
  • compiling again
  • full test suite
  • fixing whatever comes up
  • and so on.

Porting is still a full time job at the same level than originaldevelopment -if not harder as one has to mingle in another persons code.


*1 - Also, even RISC CPUs do need hardware o shift bytes and words around, at least in part and as soon as allowed data sizes are smaller than bus size. E.G. selecting high/low 16 bit word on a 32 bit bus or either of 4 16 bit words on a 64 bit bus.

*2 - With x86 one can never be sure that there isn't some weird one as well :)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .