Skip to content

Commit e7816df

Browse files
paulburtongregkh
authored andcommitted
MIPS: Workaround GCC __builtin_unreachable reordering bug
[ Upstream commit 906d441 ] Some versions of GCC for the MIPS architecture suffer from a bug which can lead to instructions from beyond an unreachable statement being incorrectly reordered into earlier branch delay slots if the unreachable statement is the only content of a case in a switch statement. This can lead to seemingly random behaviour, such as invalid memory accesses from incorrectly reordered loads or stores, and link failures on microMIPS builds. See this potential GCC fix for details: https://gcc.gnu.org/ml/gcc-patches/2015-09/msg00360.html Runtime problems resulting from this bug were initially observed using a maltasmvp_defconfig v4.4 kernel built using GCC 4.9.2 (from a Codescape SDK 2015.06-05 toolchain), with the result being an address exception taken after log messages about the L1 caches (during probe of the L2 cache): Initmem setup node 0 [mem 0x0000000080000000-0x000000009fffffff] VPE topology {2,2} total 4 Primary instruction cache 64kB, VIPT, 4-way, linesize 32 bytes. Primary data cache 64kB, 4-way, PIPT, no aliases, linesize 32 bytes <AdEL exception here> This is early enough that the kernel exception vectors are not in use, so any further output depends upon the bootloader. This is reproducible in QEMU where no further output occurs - ie. the system hangs here. Given the nature of the bug it may potentially be hit with differing symptoms. The bug is known to affect GCC versions as recent as 7.3, and it is unclear whether GCC 8 fixed it or just happens not to encounter the bug in the testcase found at the link above due to differing optimizations. This bug can be worked around by placing a volatile asm statement, which GCC is prevented from reordering past, prior to the __builtin_unreachable call. That was actually done already for other reasons by commit 173a3ef ("bug.h: work around GCC PR82365 in BUG()"), but creates problems for microMIPS builds due to the lack of a .insn directive. The microMIPS ISA allows for interlinking with regular MIPS32 code by repurposing bit 0 of the program counter as an ISA mode bit. To switch modes one changes the value of this bit in the PC. However typical branch instructions encode their offsets as multiples of 2-byte instruction halfwords, which means they cannot change ISA mode - this must be done using either an indirect branch (a jump-register in MIPS terminology) or a dedicated jalx instruction. In order to ensure that regular branches don't attempt to target code in a different ISA which they can't actually switch to, the linker will check that branch targets are code in the same ISA as the branch. Unfortunately our empty asm volatile statements don't qualify as code, and the link for microMIPS builds fails with errors such as: arch/mips/mm/dma-default.s:3265: Error: branch to a symbol in another ISA mode arch/mips/mm/dma-default.s:5027: Error: branch to a symbol in another ISA mode Resolve this by adding a .insn directive within the asm statement which declares that what comes next is code. This may or may not be true, since we don't really know what comes next, but as this code is in an unreachable path anyway that doesn't matter since we won't execute it. We do this in asm/compiler.h & select CONFIG_HAVE_ARCH_COMPILER_H in order to have this included by linux/compiler_types.h after linux/compiler-gcc.h. This will result in asm/compiler.h being included in all C compilations via the -include linux/compiler_types.h argument in c_flags, which should be harmless. Signed-off-by: Paul Burton <[email protected]> Fixes: 173a3ef ("bug.h: work around GCC PR82365 in BUG()") Patchwork: https://patchwork.linux-mips.org/patch/20270/ Cc: James Hogan <[email protected]> Cc: Ralf Baechle <[email protected]> Cc: Arnd Bergmann <[email protected]> Cc: [email protected] Signed-off-by: Sasha Levin <[email protected]>
1 parent 0a11270 commit e7816df

File tree

2 files changed

+36
-0
lines changed

2 files changed

+36
-0
lines changed

arch/mips/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ config MIPS
1313
select HAVE_OPROFILE
1414
select HAVE_PERF_EVENTS
1515
select PERF_USE_VMALLOC
16+
select HAVE_ARCH_COMPILER_H
1617
select HAVE_ARCH_KGDB
1718
select HAVE_ARCH_SECCOMP_FILTER
1819
select HAVE_ARCH_TRACEHOOK

arch/mips/include/asm/compiler.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,41 @@
88
#ifndef _ASM_COMPILER_H
99
#define _ASM_COMPILER_H
1010

11+
/*
12+
* With GCC 4.5 onwards we can use __builtin_unreachable to indicate to the
13+
* compiler that a particular code path will never be hit. This allows it to be
14+
* optimised out of the generated binary.
15+
*
16+
* Unfortunately at least GCC 4.6.3 through 7.3.0 inclusive suffer from a bug
17+
* that can lead to instructions from beyond an unreachable statement being
18+
* incorrectly reordered into earlier delay slots if the unreachable statement
19+
* is the only content of a case in a switch statement. This can lead to
20+
* seemingly random behaviour, such as invalid memory accesses from incorrectly
21+
* reordered loads or stores. See this potential GCC fix for details:
22+
*
23+
* https://gcc.gnu.org/ml/gcc-patches/2015-09/msg00360.html
24+
*
25+
* It is unclear whether GCC 8 onwards suffer from the same issue - nothing
26+
* relevant is mentioned in GCC 8 release notes and nothing obviously relevant
27+
* stands out in GCC commit logs, but these newer GCC versions generate very
28+
* different code for the testcase which doesn't exhibit the bug.
29+
*
30+
* GCC also handles stack allocation suboptimally when calling noreturn
31+
* functions or calling __builtin_unreachable():
32+
*
33+
* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82365
34+
*
35+
* We work around both of these issues by placing a volatile asm statement,
36+
* which GCC is prevented from reordering past, prior to __builtin_unreachable
37+
* calls.
38+
*
39+
* The .insn statement is required to ensure that any branches to the
40+
* statement, which sadly must be kept due to the asm statement, are known to
41+
* be branches to code and satisfy linker requirements for microMIPS kernels.
42+
*/
43+
#undef barrier_before_unreachable
44+
#define barrier_before_unreachable() asm volatile(".insn")
45+
1146
#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
1247
#define GCC_IMM_ASM() "n"
1348
#define GCC_REG_ACCUM "$0"

0 commit comments

Comments
 (0)