===== Unaligned Access ===== Unaligned memory access is one of the most common reasons software fails when first built for Alpha. Every Alpha load and store except ''LDQ_U'' and ''STQ_U'' traps when the address is not a multiple of the operand size, and no implementation corrects the access in hardware. The construct responsible is almost always a pointer cast such as ''%%*(uint32_t *)(buf + 1)%%''. It is undefined behavior in C on every target, including x86, where the hardware ordinarily tolerates it; the sections below include an x86-64 program that fails because of it. Alpha differs from such targets only in reporting the error immediately. The fix is to load and store through ''memcpy'', which compilers reduce to the right instruction sequence for the target: uint32_t val; memcpy(&val, buf + 1, sizeof val); /* not: val = *(uint32_t *)(buf + 1); */ Kernel code uses ''get_unaligned()'' and ''put_unaligned()'' instead; see [[#in_kernel_code|In kernel code]]. See also [[documentation:porting:byte_word_access|Byte and Word Access]] and [[documentation:porting:memory_model|Memory Model]]. ==== What counts as aligned ==== An access is //aligned// when the address is a multiple of the operand size. ^ Instruction ^ Operand ^ Required alignment ^ | ''LDBU'' / ''STB'' | byte | any address | | ''LDWU'' / ''STW'' | word, 16-bit | address % 2 == 0 | | ''LDL'' / ''STL'' | longword, 32-bit | address % 4 == 0 | | ''LDQ'' / ''STQ'' | quadword, 64-bit | address % 8 == 0 | | ''LDS'' / ''STS'' | S_floating, 32-bit | address % 4 == 0 | | ''LDT'' / ''STT'' | T_floating, 64-bit | address % 8 == 0 | | ''LDL_L'' / ''STL_C'' | longword, 32-bit | address % 4 == 0 | | ''LDQ_L'' / ''STQ_C'' | quadword, 64-bit | address % 8 == 0 | | ''LDQ_U'' / ''STQ_U'' | quadword | none; address bits <2:0> are ignored | ''LDBU'', ''LDWU'', ''STB'' and ''STW'' are the byte/word extension (BWX, ''amask'' bit 0), introduced with the EV56 (21164A). No Alpha has an ''LDB'' or ''LDW'' instruction. Earlier parts have no byte or word memory instructions at all; see [[documentation:porting:byte_word_access|Byte and Word Access]]. Every load and store except ''LDQ_U''/''STQ_U'' faults on an unaligned address. From the Architecture Reference Manual, on ''LDS'':
If the data is not naturally aligned, an alignment exception is generated.
The same sentence appears for ''LDT'', ''STS'' and ''STT''. No Alpha implementation fixes unaligned accesses up in hardware. The unaligned data reference is an architecturally defined trap on every part from the 21064 to the 21364. BWX does not change this. It adds byte and word instructions; it does not make misaligned longword or quadword access work. ==== What Alpha does at runtime ==== An unaligned load or store traps to PALcode, which reflects the trap to the kernel. User-space accesses are handled by ''do_entUnaUser()'' in ''arch/alpha/kernel/traps.c''; kernel-mode accesses go to ''do_entUna()'' in the same file, which fixes up only accesses covered by an exception-table entry and otherwise panics. By default the kernel emulates the access, logs a message, and lets the process continue. It emulates the integer loads and stores ''LDWU'', ''LDL'', ''LDQ'', ''STW'', ''STL'', and ''STQ'', and the floating-point ''LDS'', ''LDT'', ''STS'', and ''STT''. Any other instruction that traps on a misaligned address, in particular the load-locked and store-conditional instructions ''LDL_L'', ''LDQ_L'', ''STL_C'', and ''STQ_C'', is never emulated and always results in ''SIGBUS''. A misaligned atomic operation, for example on a member of a packed structure, therefore crashes the program regardless of the settings below. [(>[[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/alpha/kernel/traps.c?h=v7.3-rc1|arch/alpha/kernel/traps.c]], Linux 7.3)] The behavior is controlled per process by three bits: #define UAC_BITMASK 7 #define UAC_NOPRINT 1 #define UAC_NOFIX 2 #define UAC_SIGBUS 4 === The log message === printk("%s(%d): unaligned trap at %016lx: %p %lx %ld\n", current->comm, task_pid_nr(current), regs->pc - 4, va, opcode, reg); ^ Field ^ Meaning ^ | ''%s(%d)'' | process name and PID | | ''%016lx'' | address of the faulting instruction (the trap PC points past it, hence the ''- 4'') | | ''%p'' | the unaligned virtual address | | ''%lx'' | the memory-format opcode, in **hex**: ''0c''=LDWU, ''0d''=STW, ''22''=LDS, ''23''=LDT, ''26''=STS, ''27''=STT, ''28''=LDL, ''29''=LDQ, ''2c''=STL, ''2d''=STQ | | ''%ld'' | the instruction's Ra register number, in **decimal**. This is the destination of a load or the source of a store. It is not a cause code. | Two properties of the message limit its use: * The ''%p'' address is hashed on any modern kernel unless it is booted with ''no_hash_pointers''. The instruction address is real; the data address is not. * The message is rate limited to 5 per 5 seconds. A hot loop loses almost all of them, so ''dmesg'' output cannot be used to count occurrences. === Counting them properly === Alpha exports cumulative, un-rate-limited, system-wide counters: $ grep unaligned /proc/cpuinfo kernel unaligned acc : 0 (pc=0,va=0) user unaligned acc : 1174 (pc=120001234,va=11ffff8e5) Sampling these counters before and after a run shows whether any unaligned access occurred during the run. The kernel keeps one counter for all user processes, so on a busy system other processes also increment it. === Making it fatal === The portable interface works on Alpha: #include /* SIGBUS on any unaligned access in this process */ prctl(PR_SET_UNALIGN, PR_UNALIGN_SIGBUS, 0, 0, 0); /* fix up silently: no log message, no signal */ prctl(PR_SET_UNALIGN, PR_UNALIGN_NOPRINT, 0, 0, 0); ''PR_SET_UNALIGN'' replaces the mask rather than OR-ing into it. Alpha also accepts a third bit, bare value ''4'', mapping to ''TS_UAC_NOFIX'' ("do not fix up at all"), which has no ''PR_UNALIGN_*'' name. The Alpha-specific interface inherited from Digital UNIX is ''osf_setsysinfo()''. glibc does not wrap it, so it must be invoked directly: #include #include #include unsigned int buf[2] = { SSIN_UACPROC, UAC_SIGBUS }; syscall(__NR_osf_setsysinfo, SSI_NVPAIRS, buf, 1, 0, 0); The third argument is the number of name/value pairs in the buffer; the kernel processes that many pairs, so it must be given. [(>[[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/alpha/kernel/osf_sys.c?h=v7.3-rc1|arch/alpha/kernel/osf_sys.c]], Linux 7.3)] The two interfaces assign different meanings to the same bit values: the value 2 is ''UAC_NOFIX'' to ''osf_setsysinfo()'' and ''PR_UNALIGN_SIGBUS'' to ''prctl()''. The interface is modeled on Digital UNIX's ''setsysinfo(2)'', [(>[[https://github.com/alphalinux/mailing-list-archives/blob/main/axp-list-mbox/1997-October.mbox#L4654|"Re: Unaligned Traps - have they been turned off !!!!"]], Richard Henderson, axp-list, 6 Oct 1997)] but only the per-process ''SSIN_UACPROC'' setting is implemented, not Digital UNIX's system-wide ''SSIN_UACSYS'' or per-parent ''SSIN_UACPARNT''. [(>[[https://github.com/alphalinux/mailing-list-archives/blob/main/axp-list-mbox/1999-August.mbox#L27175|"Re: Unaligned traps et al"]], Richard Henderson, axp-list, 18 Aug 1999)] Setting one of these at the top of ''main()'' in a test harness turns every unaligned access into a core dump with a usable backtrace instead of a line in ''dmesg''. === Absence of a sysctl === The command ''echo 2 > /proc/sys/kernel/unaligned-trap'' does not work on Alpha. The ''unaligned-trap'' sysctl exists only on architectures that opt in to it (parisc, arc, loongarch and riscv), takes only the values 0 and 1, and is not available on Alpha, which registers no sysctls of its own. Alpha's only controls are the two per-process interfaces described above. === Other operating systems === The other Alpha operating systems, NetBSD, FreeBSD, and Tru64 UNIX, also fix up user unaligned accesses by default and print a warning, with system-wide or per-process controls of their own. [(>[[https://github.com/alphalinux/mailing-list-archives/blob/main/netbsd-port-alpha-mbox/1998-May.mbox#L2901|"Re: unaligned access"]], Chris G. Demetriou, port-alpha, 19 May 1998)] [(>[[https://github.com/alphalinux/mailing-list-archives/blob/main/freebsd-alpha-mbox/1999-April.mbox#L7696|"Re: egcs ready for alpha?"]], Andrew Gallatin, freebsd-alpha, 18 Apr 1999)] ==== What an unaligned access costs ==== Each fixed-up access costs a trap into the kernel, and silencing the log message does not remove that cost. [(>"Re: Turning off 'unaligned trap' logging", Jay Estabrook, axp-list, 8 Nov 2000)] Reliable per-instruction figures are not available, but the total can be large: rendering a single web page with Ghostscript once incurred over 200 million unaligned accesses, and fixing them reduced the render time from more than 30 seconds to one or two. [(>[[https://github.com/alphalinux/mailing-list-archives/blob/main/axp-list-mbox/2008-May.mbox#L2761|"Getting rid of Unaligned Accesses (UA)"]], Jay Estabrook, axp-list, 23 May 2008)] The cost has the following structure. A fixed-up unaligned access pays for, at minimum: the PALcode unaligned trap; entry to ''do_entUnaUser()''; decode of the faulting instruction; two ''LDQ_U'' plus extract and merge for a load, or two ''LDQ_U'', mask, insert and two ''STQ_U'' for a store; a write back into the saved register file; and the return to user mode. On top of that come the instruction-cache and branch-predictor damage of the round trip, and a rate-limited ''printk'' if logging is still enabled. The sequence is orders of magnitude more expensive than the single instruction it replaces, being a trap into the operating system rather than a pipeline stall. A figure for particular hardware can be obtained with an ''rpcc''-timed loop comparing aligned against ''+1''-offset accesses, with ''UAC_NOPRINT'' set so that logging falls outside the measurement. ==== What the compiler emits instead ==== Alpha is a ''STRICT_ALIGNMENT'' target. When GCC cannot prove that a pointer is aligned, which is the default for a ''char *'' or a ''memcpy'' source, it emits the extract sequence rather than a faulting load. The Architecture Handbook gives the canonical form: LDQ_U R1, X(R11) ; Ignores va<2:0>, R1 = CBAx xxxx LDQ_U R2, X+3(R11) ; Ignores va<2:0>, R2 = yyyy yyyD LDA R3, X(R11) ; R3<2:0> = (X mod 8) = 5 EXTLL R1, R3, R1 ; R1 = 0000 0CBA EXTLH R2, R3, R2 ; R2 = 0000 D000 OR R2, R1, R1 ; R1 = 0000 DCBA In practice, for ''%%uint32_t v; memcpy(&v, p, sizeof v); return v;%%'', ''alpha-unknown-linux-gnu-gcc -O2'' produces the following, whatever the ''-mcpu'' setting: ldq_u $0,0($16) ldq_u $1,3($16) extll $0,$16,$0 extlh $1,$16,$1 bis $0,$1,$0 addl $31,$0,$0 /* sign-extend to canonical 64-bit form */ Six instructions instead of one. The aligned version, ''%%return *(const uint32_t *)p;%%'', is a single ''ldl''. An unaligned 32-bit //store// is worse still: two ''LDQ_U'', ''MSKLL''/''MSKLH'', ''INSLL''/''INSLH'', two ''BIS'' and two ''STQ_U''. Six instructions is the cheaper case, in which the compiler treated the pointer as possibly misaligned. The expensive case is a plain ''LDL'', emitted because the pointer was declared ''uint32_t *'', which the hardware then traps. In an optimized build ''memcpy'' of a scalar is not a function call on any target: on x86-64 it is a single ''movl'', and on Alpha it is the sequence above. ''%%memcpy(&v, p, sizeof v)%%'' is the portable spelling of a load performed however the machine requires, and unlike the cast it is well-defined C. ==== Finding unaligned accesses ==== === UBSan on a non-Alpha host === UBSan detects most alignment defects without Alpha hardware. gcc -g -fsanitize=undefined -fno-sanitize-recover=alignment myfile.c -o mybinary ./mybinary u.c:4:55: runtime error: store to misaligned address 0x561495a5f121 for type 'uint32_t', which requires 4 byte alignment 0x561495a5f121: note: pointer points here 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ^ ''-fsanitize=alignment'' is already part of ''-fsanitize=undefined''. ''-fno-sanitize-recover=alignment'' makes the first occurrence abort; by default UBSan prints a diagnostic and continues, which is unsuitable for a test suite. === -Wcast-align and -Wcast-align=strict === ''-Wcast-align'' warns only on targets that require alignment, and is a silent no-op on x86, so on an x86 build host it reports nothing. From ''gcc/doc/invoke.texi'': @item -Wcast-align Warn whenever a pointer is cast such that the required alignment of the target is increased. For example, warn if a @code{char *} is cast to an @code{int *} on machines where integers can only be accessed at two- or four-byte boundaries. @opindex Wcast-align=strict Warn whenever a pointer is cast such that the required alignment of the target is increased. For example, warn if a @code{char *} is cast to an @code{int *} regardless of the target machine. The difference is target sensitivity. Neither variant inspects the offset; both fire on the cast, which means ''=strict'' will also warn about casts that happen to be fine. It is a lint, not a proof. char buf[64]; uint32_t *p(void) { return (uint32_t *)(buf + 1); } $ gcc -c -Wcast-align x.c # x86-64: nothing $ gcc -c -Wcast-align=strict x.c x.c:3:27: warning: cast increases required alignment of target type [-Wcast-align] $ alpha-unknown-linux-gnu-gcc -c -Wcast-align x.c x.c:3:27: warning: cast increases required alignment of target type [-Wcast-align] === GDB === gdb ./mybinary (gdb) handle SIGBUS stop print (gdb) run (gdb) bt (gdb) x/i $pc # the faulting instruction (gdb) p $_siginfo.si_code # 1 == BUS_ADRALN (gdb) p/x $_siginfo._sifields._sigfault.si_addr # the misaligned address The kernel rewinds ''$pc'' to the faulting instruction before delivering the signal, and sets ''si_addr'' to the unaligned address: give_sigbus: regs->pc -= 4; send_sig_fault(SIGBUS, BUS_ADRALN, va, current); return; ''$_siginfo'' is populated when GDB intercepts a live signal under ''handle SIGBUS stop'', and also when it opens a core file, which carries the signal information in an ''NT_SIGINFO'' note. === Kernel log and addr2line === This method is limited by the hashing and rate limiting described above. The faulting PC is the field ending in a colon: dmesg -t | sed -n 's/.*unaligned trap at \([0-9a-f]*\):.*/0x\1/p' | sort -u | while read -r pc; do addr2line -e ./mybinary -f -C "$pc" done ''addr2line'' expects a link-time address, so for a PIE or a shared library the mapping base must be subtracted first. Function names come from the symbol table; file names and line numbers require a binary built with ''-g''. ==== Detection workflow ==== The steps below combine the tools described above. - Build with ''-g -fsanitize=undefined -fno-sanitize-recover=alignment -Wcast-align=strict''. The ''=strict'' form is required, since the plain form does nothing on x86. - Run the test suite on x86. UBSan finds the majority of alignment defects without Alpha hardware. - On Alpha, call ''prctl(PR_SET_UNALIGN, PR_UNALIGN_SIGBUS, 0, 0, 0)'' at the top of ''main()'' and run under ''gdb'' with ''handle SIGBUS stop print''. - Compare ''grep unaligned /proc/cpuinfo'' before and after a run as a regression check, on an otherwise idle system, since the counters are system-wide. - Repair the access with ''memcpy'', or with ''get_unaligned()'' in kernel code. Casting through ''void *'' silences the warning without removing the undefined behavior. ==== Common causes ==== === Packed structures === struct __attribute__((packed)) header { uint8_t type; uint32_t length; /* offset 1 */ uint64_t checksum; /* offset 5 */ }; Reading ''h->length'' through a ''struct header *'' is fine, because ''__packed'' tells the compiler the field is unaligned and it emits the extract sequence. The defect is taking the //address// of a packed field and passing it on as an ordinary pointer, which discards the alignment information. GCC's ''-Waddress-of-packed-member'' diagnoses this. === Pointer arithmetic on byte buffers === /* WRONG */ uint32_t val = *(uint32_t *)(buf + 3); /* RIGHT */ uint32_t val; memcpy(&val, buf + 3, sizeof val); === Network and file format parsing === Wire and file formats pack fields without padding, and a receive buffer offset by a header length is aligned only by chance. IP headers, DOS partition tables, and DWARF exception frame information all have unaligned fields. Casting a pointer into such a buffer to a pointer to a larger type creates a potential alignment defect, and, unless the original type is a character type, a strict aliasing defect as well; the two are separate problems in the same line of code (see [[#it_is_undefined_behavior_in_c|It is undefined behavior in C]]). [(>[[https://github.com/alphalinux/mailing-list-archives/blob/main/axp-list-mbox/2000-May.mbox#L10130|"Re: Fixing 'unaligned trap' errors"]], David Huggins-Daines, axp-list, 19 May 2000)] The same problem is the reason some network drivers copy received packets on Alpha and other strict-alignment architectures: a card that can only DMA to a 32-bit boundary leaves the IP header misaligned, so the driver copies each packet to an aligned buffer (''rx_copybreak'' set to the full MTU) rather than let the TCP/IP code trap on every access. [(>[[https://github.com/alphalinux/mailing-list-archives/blob/main/axp-list-mbox/2001-December.mbox#L6642|"Re: Kernel unaligned accesses"]], Ivan Kokshaysky, axp-list, 11 Dec 2001)] /* WRONG on Alpha, undefined everywhere */ struct ip_header *ip = (struct ip_header *)packet; uint16_t len = ntohs(ip->total_length); /* RIGHT */ struct ip_header ip; memcpy(&ip, packet, sizeof ip); uint16_t len = ntohs(ip.total_length); === Type punning through a union === union { char bytes[8]; uint64_t qword; } u; This is safe, and is a type-punning idiom that C (but not C++) explicitly permits. The union carries the alignment of its strictest member. Problems arise only when the union is embedded in a packed structure or placed at a computed offset. ==== In kernel code ==== Kernel code uses ''get_unaligned()'' and ''put_unaligned()'': #include ''asm/unaligned.h'' and ''asm-generic/unaligned.h'' no longer exist; they were consolidated into ''linux/unaligned.h''. The helpers were once implemented with a packed structure. The current implementation is based on ''memcpy'', and its comment gives the reason: /** * __get_unaligned_t - read an unaligned value from memory. * … * Use memcpy to affect an unaligned type sized load avoiding undefined behavior * from approaches like type punning that require -fno-strict-aliasing in order * to be correct. … */ ==== It is undefined behavior in C ==== The relevant rule is about the //cast//, not the dereference. **C11/C17 §6.3.2.3 paragraph 7**:
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer.
and **Annex J.2**, in the list of undefined behaviors:
Conversion between two pointer types produces a result that is incorrectly aligned (6.3.2.3).
Three consequences follow: - The undefined behavior occurs at the cast. ''%%(uint32_t *)(buf + 1)%%'' is undefined even if the pointer is never dereferenced, which is why ''-Wcast-align=strict'' diagnoses the cast and never examines the offset. - There is no exception for hardware that tolerates the access. x86 guarantees that the //hardware// will not trap on an ordinary integer load. It guarantees nothing about what the //compiler// may assume or emit. - The rule is distinct from strict aliasing, which is §6.5 paragraph 7, about effective types. ''%%*(uint32_t *)(buf + 1)%%'' on a ''char'' array violates both rules, and ''-fno-strict-aliasing'' disables exploitation of the aliasing rule only. It has no effect on §6.3.2.3p7. ''memcpy'' satisfies both rules at once. It accesses through character-type lvalues, which §6.5p7 explicitly permits, and it performs no misaligned pointer conversion, so §6.3.2.3p7 never engages. It is therefore the recommended construct, and compilers reduce it to the same instructions as a direct load. ==== Effects on x86 ==== === A misaligned pointer in plain C === The following program uses no intrinsics, no inline assembly and no ''-ffast-math''. It uses a type that the ABI requires to be 16-byte aligned, and a pointer that is not. /* v.c */ typedef int v4si __attribute__((vector_size(16))); void vcopy(v4si *d, const v4si *s) { *d = *s; } /* main.c */ #include typedef int v4si __attribute__((vector_size(16))); extern void vcopy(v4si *d, const v4si *s); static char dst[64], src[64]; int main(void) { vcopy((v4si *)(dst + 1), (v4si *)(src + 1)); printf("survived\n"); } ''gcc -O2'' compiles ''vcopy'' to aligned moves, because it is entitled to assume the pointer is aligned: vcopy: movdqa (%rsi), %xmm0 movaps %xmm0, (%rdi) ret $ gcc -O2 -c v.c && gcc -O2 main.c v.o -o t && ./t; echo "exit=$?" exit=139 Program received signal SIGSEGV, Segmentation fault. 0x0000555555555184 in vcopy () (gdb) x/i $pc => 0x555555555184 : movdqa xmm0,XMMWORD PTR [rsi] Exit status 139 is ''SIGSEGV''. The ''#GP'' fault from ''MOVDQA'' on a misaligned operand is delivered as ''SIGSEGV'', which is why ''si_addr'' is 0 rather than the offending address. The failure is the ordinary consequence of writing through a pointer that is not aligned as its type requires, on an architecture usually regarded as tolerant of misalignment. x86 can also trap every misaligned access: with the ''EFLAGS.AC'' flag set, it delivers ''SIGBUS'' with ''BUS_ADRALN'', the same signal and ''si_code'' as Alpha. x86 software tolerates misaligned access only because the flag is normally clear. ==== Summary of build flags ==== ^ Flag ^ Purpose ^ | ''-fsanitize=undefined'' | includes the alignment check; works on any architecture | | ''-fno-sanitize-recover=alignment'' | abort on the first misaligned access instead of printing and continuing | | ''-Wcast-align=strict'' | diagnose alignment-increasing casts regardless of target; the form to use on x86 hosts | | ''-Wcast-align'' | same, but only on strict-alignment targets; a no-op on x86 | | ''-Waddress-of-packed-member'' | catch pointers taken to packed fields | | ''-g'' | required for ''addr2line'' and useful backtraces | ==== References ==== * {{wiki:documentation:references:alpha_architecture_reference_manual_1st_edition.pdf?linkonly|Alpha Architecture Reference Manual, First Edition}}, §1.6.5 (aligned and unaligned data), §4.6.2 (the extract, insert and mask instructions, with the canonical unaligned load sequence), §4.8 (the load and store instructions). * {{wiki:documentation:references:alpha_architecture_handbook_v4.pdf?linkonly|Alpha Architecture Handbook, Version 4}}, §4.6.2, which reproduces the same sequence in cleaner typesetting. * ISO/IEC 9899:2011 §6.3.2.3p7, §6.5p7, Annex J.2. The free [[https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf|N1570 draft]] is textually identical in these clauses. * [[https://www.kernel.org/doc/html/latest/core-api/unaligned-memory-access.html|''Documentation/core-api/unaligned-memory-access.rst'']], Linux kernel. It argues entirely in machine terms and does not mention undefined behavior; for that, see the ''__get_unaligned_t'' comment in ''include/vdso/unaligned.h''. * [[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/alpha/kernel/traps.c|''arch/alpha/kernel/traps.c'']], [[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/alpha/kernel/osf_sys.c|''osf_sys.c'']], [[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/alpha/include/uapi/asm/sysinfo.h|''uapi/asm/sysinfo.h'']], [[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/alpha/include/asm/thread_info.h|''asm/thread_info.h'']]. * Raymond Chen, [[https://devblogs.microsoft.com/oldnewthing/20170815-00/?p=96816|"The Alpha AXP, part 7: Memory access, loading unaligned data"]] and [[https://devblogs.microsoft.com/oldnewthing/20170816-00/?p=96825|"part 8: Memory access, storing bytes and words and unaligned data"]]. * Richard Henderson, "Re: Unaligned traps on kernel startup", axp-list, 31 August 1998, on the compiler merging two byte loads in ''msdos_partition()'' and the ''__attribute__((packed))'' fix. See [[history:community#mailing_lists|Mailing Lists]] for archive locations. {{tag>documentation porting}}