[PATCH] MIPS: Work around LLVM bug when gp is used as global register variable

Yao Zi posted 1 patch 2 weeks, 5 days ago
There is a newer version of this series
arch/mips/kernel/relocate.c | 11 +++++++++++
1 file changed, 11 insertions(+)
[PATCH] MIPS: Work around LLVM bug when gp is used as global register variable
Posted by Yao Zi 2 weeks, 5 days ago
On MIPS, __current_thread_info is defined as global register variable
locating in $gp, and is simply assigned with new address during kernel
relocation.

This however is broken with LLVM, which always restores $gp if it finds
$gp is clobbered in any form, including when intentionally through a
global register variable. This is against GCC's documentation[1], which
requires a callee-saved register used as global register variable not to
be restored if it's clobbered.

As a result, $gp will continue to point to the unrelocated kernel after
the epilog of relocate_kernel(), leading to an early crash in init_idle,

[    0.000000] CPU 0 Unable to handle kernel paging request at virtual address 0000000000000000, epc == ffffffff81afada8, ra == ffffffff81afad90
[    0.000000] Oops[#1]:
[    0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper Tainted: G        W           6.19.0-rc5-00262-gd3eeb99bbc99-dirty #188 VOLUNTARY
[    0.000000] Tainted: [W]=WARN
[    0.000000] Hardware name: loongson,loongson64v-4core-virtio
[    0.000000] $ 0   : 0000000000000000 0000000000000000 0000000000000001 0000000000000000
[    0.000000] $ 4   : ffffffff80b80ec0 ffffffff80b53d48 0000000000000000 00000000000f4240
[    0.000000] $ 8   : 0000000000000100 ffffffff81d82f80 ffffffff81d82f80 0000000000000001
[    0.000000] $12   : 0000000000000000 ffffffff81776f58 00000000000005da 0000000000000002
[    0.000000] $16   : ffffffff80b80e40 0000000000000000 ffffffff80b81614 9800000005dfbe80
[    0.000000] $20   : 00000000540000e0 ffffffff81980000 0000000000000000 ffffffff80f81c80
[    0.000000] $24   : 0000000000000a26 ffffffff8114fb90
[    0.000000] $28   : ffffffff80b50000 ffffffff80b53d40 0000000000000000 ffffffff81afad90
[    0.000000] Hi    : 0000000000000000
[    0.000000] Lo    : 0000000000000000
[    0.000000] epc   : ffffffff81afada8 init_idle+0x130/0x270
[    0.000000] ra    : ffffffff81afad90 init_idle+0x118/0x270
[    0.000000] Status: 540000e2	KX SX UX KERNEL EXL
[    0.000000] Cause : 00000008 (ExcCode 02)
[    0.000000] BadVA : 0000000000000000
[    0.000000] PrId  : 00006305 (ICT Loongson-3)
[    0.000000] Process swapper (pid: 0, threadinfo=(____ptrval____), task=(____ptrval____), tls=0000000000000000)
[    0.000000] Stack : 9800000005dfbf00 ffffffff8178e950 0000000000000000 0000000000000000
[    0.000000]         0000000000000000 ffffffff81970000 000000000000003f ffffffff810a6528
[    0.000000]         0000000000000001 9800000005dfbe80 9800000005dfbf00 ffffffff81980000
[    0.000000]         ffffffff810a6450 ffffffff81afb6c0 0000000000000000 ffffffff810a2258
[    0.000000]         ffffffff81d82ec8 ffffffff8198d010 ffffffff81b67e80 ffffffff8197dd98
[    0.000000]         ffffffff81d81c80 ffffffff81930000 0000000000000040 0000000000000000
[    0.000000]         0000000000000000 0000000000000000 0000000000000000 0000000000000000
[    0.000000]         0000000000000000 000000000000009e ffffffff9fc01000 0000000000000000
[    0.000000]         0000000000000000 0000000000000000 0000000000000000 0000000000000000
[    0.000000]         0000000000000000 ffffffff81ae86dc ffffffff81b3c741 0000000000000002
[    0.000000]         ...
[    0.000000] Call Trace:
[    0.000000] [<ffffffff81afada8>] init_idle+0x130/0x270
[    0.000000] [<ffffffff81afb6c0>] sched_init+0x5c8/0x6c0
[    0.000000] [<ffffffff81ae86dc>] start_kernel+0x27c/0x7a8

This bug has been reported to LLVM[2] and affects version from (at
least) 18 to 21. Let's work around this by using inline assembly to
assign $gp before a fix is widely available.

Cc: stable@vger.kernel.org
Link: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Global-Register-Variables.html # [1]
Link: https://github.com/llvm/llvm-project/issues/176546 # [2]
Signed-off-by: Yao Zi <me@ziyao.cc>
---
 arch/mips/kernel/relocate.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/arch/mips/kernel/relocate.c b/arch/mips/kernel/relocate.c
index 7f1c136ad850..12aa0bbdd65e 100644
--- a/arch/mips/kernel/relocate.c
+++ b/arch/mips/kernel/relocate.c
@@ -420,7 +420,18 @@ void *__init relocate_kernel(void)
 			goto out;
 
 		/* The current thread is now within the relocated image */
+#ifndef CONFIG_CC_IS_CLANG
 		__current_thread_info = RELOCATED(&init_thread_union);
+#else
+		/*
+		 * LLVM may wrongly restore $gp ($28) in epilog even if it's
+		 * intentionally modified. Work around this by using inline
+		 * assembly to assign $gp. $gp couldn't be listed as output or
+		 * clobber, or LLVM will still restore its original value.
+		 */
+		asm volatile("move $28, %0" : :
+			     "r" (RELOCATED(&init_thread_union)));
+#endif
 
 		/* Return the new kernel's entry point */
 		kernel_entry = RELOCATED(start_kernel);
-- 
2.52.0
Re: [PATCH] MIPS: Work around LLVM bug when gp is used as global register variable
Posted by Nathan Chancellor 4 days, 4 hours ago
On Sun, Jan 18, 2026 at 09:02:35AM +0000, Yao Zi wrote:
> On MIPS, __current_thread_info is defined as global register variable
> locating in $gp, and is simply assigned with new address during kernel
> relocation.
> 
> This however is broken with LLVM, which always restores $gp if it finds
> $gp is clobbered in any form, including when intentionally through a
> global register variable. This is against GCC's documentation[1], which
> requires a callee-saved register used as global register variable not to
> be restored if it's clobbered.
> 
> As a result, $gp will continue to point to the unrelocated kernel after
> the epilog of relocate_kernel(), leading to an early crash in init_idle,
> 
> [    0.000000] CPU 0 Unable to handle kernel paging request at virtual address 0000000000000000, epc == ffffffff81afada8, ra == ffffffff81afad90
> [    0.000000] Oops[#1]:
> [    0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper Tainted: G        W           6.19.0-rc5-00262-gd3eeb99bbc99-dirty #188 VOLUNTARY
> [    0.000000] Tainted: [W]=WARN
> [    0.000000] Hardware name: loongson,loongson64v-4core-virtio
> [    0.000000] $ 0   : 0000000000000000 0000000000000000 0000000000000001 0000000000000000
> [    0.000000] $ 4   : ffffffff80b80ec0 ffffffff80b53d48 0000000000000000 00000000000f4240
> [    0.000000] $ 8   : 0000000000000100 ffffffff81d82f80 ffffffff81d82f80 0000000000000001
> [    0.000000] $12   : 0000000000000000 ffffffff81776f58 00000000000005da 0000000000000002
> [    0.000000] $16   : ffffffff80b80e40 0000000000000000 ffffffff80b81614 9800000005dfbe80
> [    0.000000] $20   : 00000000540000e0 ffffffff81980000 0000000000000000 ffffffff80f81c80
> [    0.000000] $24   : 0000000000000a26 ffffffff8114fb90
> [    0.000000] $28   : ffffffff80b50000 ffffffff80b53d40 0000000000000000 ffffffff81afad90
> [    0.000000] Hi    : 0000000000000000
> [    0.000000] Lo    : 0000000000000000
> [    0.000000] epc   : ffffffff81afada8 init_idle+0x130/0x270
> [    0.000000] ra    : ffffffff81afad90 init_idle+0x118/0x270
> [    0.000000] Status: 540000e2	KX SX UX KERNEL EXL
> [    0.000000] Cause : 00000008 (ExcCode 02)
> [    0.000000] BadVA : 0000000000000000
> [    0.000000] PrId  : 00006305 (ICT Loongson-3)
> [    0.000000] Process swapper (pid: 0, threadinfo=(____ptrval____), task=(____ptrval____), tls=0000000000000000)
> [    0.000000] Stack : 9800000005dfbf00 ffffffff8178e950 0000000000000000 0000000000000000
> [    0.000000]         0000000000000000 ffffffff81970000 000000000000003f ffffffff810a6528
> [    0.000000]         0000000000000001 9800000005dfbe80 9800000005dfbf00 ffffffff81980000
> [    0.000000]         ffffffff810a6450 ffffffff81afb6c0 0000000000000000 ffffffff810a2258
> [    0.000000]         ffffffff81d82ec8 ffffffff8198d010 ffffffff81b67e80 ffffffff8197dd98
> [    0.000000]         ffffffff81d81c80 ffffffff81930000 0000000000000040 0000000000000000
> [    0.000000]         0000000000000000 0000000000000000 0000000000000000 0000000000000000
> [    0.000000]         0000000000000000 000000000000009e ffffffff9fc01000 0000000000000000
> [    0.000000]         0000000000000000 0000000000000000 0000000000000000 0000000000000000
> [    0.000000]         0000000000000000 ffffffff81ae86dc ffffffff81b3c741 0000000000000002
> [    0.000000]         ...
> [    0.000000] Call Trace:
> [    0.000000] [<ffffffff81afada8>] init_idle+0x130/0x270
> [    0.000000] [<ffffffff81afb6c0>] sched_init+0x5c8/0x6c0
> [    0.000000] [<ffffffff81ae86dc>] start_kernel+0x27c/0x7a8
> 
> This bug has been reported to LLVM[2] and affects version from (at
> least) 18 to 21. Let's work around this by using inline assembly to
> assign $gp before a fix is widely available.
> 
> Cc: stable@vger.kernel.org
> Link: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Global-Register-Variables.html # [1]
> Link: https://github.com/llvm/llvm-project/issues/176546 # [2]
> Signed-off-by: Yao Zi <me@ziyao.cc>

Acked-by: Nathan Chancellor <nathan@kernel.org>

Hopefully the MIPS LLVM folks can look into this (even though I think
the MIPS backend is one of the less maintained backends in LLVM).

> ---
>  arch/mips/kernel/relocate.c | 11 +++++++++++
>  1 file changed, 11 insertions(+)
> 
> diff --git a/arch/mips/kernel/relocate.c b/arch/mips/kernel/relocate.c
> index 7f1c136ad850..12aa0bbdd65e 100644
> --- a/arch/mips/kernel/relocate.c
> +++ b/arch/mips/kernel/relocate.c
> @@ -420,7 +420,18 @@ void *__init relocate_kernel(void)
>  			goto out;
>  
>  		/* The current thread is now within the relocated image */
> +#ifndef CONFIG_CC_IS_CLANG

I find

  #ifdef FOO
  <FOO block>
  #else
  <!FOO block>
  #endif

to be easier to read and process

  #ifndef FOO
  <!FOO block>
  #else
  <FOO block>
  #endif

but maybe that it is just personal preference.

>  		__current_thread_info = RELOCATED(&init_thread_union);
> +#else
> +		/*
> +		 * LLVM may wrongly restore $gp ($28) in epilog even if it's
> +		 * intentionally modified. Work around this by using inline
> +		 * assembly to assign $gp. $gp couldn't be listed as output or
> +		 * clobber, or LLVM will still restore its original value.

This comment should likely include a link to the LLVM upstream report to
make it easier to version restrict this workaround when fixed in the
future.

> +		 */
> +		asm volatile("move $28, %0" : :
> +			     "r" (RELOCATED(&init_thread_union)));
> +#endif
>  
>  		/* Return the new kernel's entry point */
>  		kernel_entry = RELOCATED(start_kernel);
> -- 
> 2.52.0
>
Re: [PATCH] MIPS: Work around LLVM bug when gp is used as global register variable
Posted by Yao Zi 3 days, 23 hours ago
On Mon, Feb 02, 2026 at 04:23:09PM -0700, Nathan Chancellor wrote:
> On Sun, Jan 18, 2026 at 09:02:35AM +0000, Yao Zi wrote:
> > On MIPS, __current_thread_info is defined as global register variable
> > locating in $gp, and is simply assigned with new address during kernel
> > relocation.
> > 
> > This however is broken with LLVM, which always restores $gp if it finds
> > $gp is clobbered in any form, including when intentionally through a
> > global register variable. This is against GCC's documentation[1], which
> > requires a callee-saved register used as global register variable not to
> > be restored if it's clobbered.
> > 
> > As a result, $gp will continue to point to the unrelocated kernel after
> > the epilog of relocate_kernel(), leading to an early crash in init_idle,
> > 
> > [    0.000000] CPU 0 Unable to handle kernel paging request at virtual address 0000000000000000, epc == ffffffff81afada8, ra == ffffffff81afad90
> > [    0.000000] Oops[#1]:
> > [    0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper Tainted: G        W           6.19.0-rc5-00262-gd3eeb99bbc99-dirty #188 VOLUNTARY
> > [    0.000000] Tainted: [W]=WARN
> > [    0.000000] Hardware name: loongson,loongson64v-4core-virtio
> > [    0.000000] $ 0   : 0000000000000000 0000000000000000 0000000000000001 0000000000000000
> > [    0.000000] $ 4   : ffffffff80b80ec0 ffffffff80b53d48 0000000000000000 00000000000f4240
> > [    0.000000] $ 8   : 0000000000000100 ffffffff81d82f80 ffffffff81d82f80 0000000000000001
> > [    0.000000] $12   : 0000000000000000 ffffffff81776f58 00000000000005da 0000000000000002
> > [    0.000000] $16   : ffffffff80b80e40 0000000000000000 ffffffff80b81614 9800000005dfbe80
> > [    0.000000] $20   : 00000000540000e0 ffffffff81980000 0000000000000000 ffffffff80f81c80
> > [    0.000000] $24   : 0000000000000a26 ffffffff8114fb90
> > [    0.000000] $28   : ffffffff80b50000 ffffffff80b53d40 0000000000000000 ffffffff81afad90
> > [    0.000000] Hi    : 0000000000000000
> > [    0.000000] Lo    : 0000000000000000
> > [    0.000000] epc   : ffffffff81afada8 init_idle+0x130/0x270
> > [    0.000000] ra    : ffffffff81afad90 init_idle+0x118/0x270
> > [    0.000000] Status: 540000e2	KX SX UX KERNEL EXL
> > [    0.000000] Cause : 00000008 (ExcCode 02)
> > [    0.000000] BadVA : 0000000000000000
> > [    0.000000] PrId  : 00006305 (ICT Loongson-3)
> > [    0.000000] Process swapper (pid: 0, threadinfo=(____ptrval____), task=(____ptrval____), tls=0000000000000000)
> > [    0.000000] Stack : 9800000005dfbf00 ffffffff8178e950 0000000000000000 0000000000000000
> > [    0.000000]         0000000000000000 ffffffff81970000 000000000000003f ffffffff810a6528
> > [    0.000000]         0000000000000001 9800000005dfbe80 9800000005dfbf00 ffffffff81980000
> > [    0.000000]         ffffffff810a6450 ffffffff81afb6c0 0000000000000000 ffffffff810a2258
> > [    0.000000]         ffffffff81d82ec8 ffffffff8198d010 ffffffff81b67e80 ffffffff8197dd98
> > [    0.000000]         ffffffff81d81c80 ffffffff81930000 0000000000000040 0000000000000000
> > [    0.000000]         0000000000000000 0000000000000000 0000000000000000 0000000000000000
> > [    0.000000]         0000000000000000 000000000000009e ffffffff9fc01000 0000000000000000
> > [    0.000000]         0000000000000000 0000000000000000 0000000000000000 0000000000000000
> > [    0.000000]         0000000000000000 ffffffff81ae86dc ffffffff81b3c741 0000000000000002
> > [    0.000000]         ...
> > [    0.000000] Call Trace:
> > [    0.000000] [<ffffffff81afada8>] init_idle+0x130/0x270
> > [    0.000000] [<ffffffff81afb6c0>] sched_init+0x5c8/0x6c0
> > [    0.000000] [<ffffffff81ae86dc>] start_kernel+0x27c/0x7a8
> > 
> > This bug has been reported to LLVM[2] and affects version from (at
> > least) 18 to 21. Let's work around this by using inline assembly to
> > assign $gp before a fix is widely available.
> > 
> > Cc: stable@vger.kernel.org
> > Link: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Global-Register-Variables.html # [1]
> > Link: https://github.com/llvm/llvm-project/issues/176546 # [2]
> > Signed-off-by: Yao Zi <me@ziyao.cc>
> 
> Acked-by: Nathan Chancellor <nathan@kernel.org>
> 
> Hopefully the MIPS LLVM folks can look into this (even though I think
> the MIPS backend is one of the less maintained backends in LLVM).

Sigh...

> > ---
> >  arch/mips/kernel/relocate.c | 11 +++++++++++
> >  1 file changed, 11 insertions(+)
> > 
> > diff --git a/arch/mips/kernel/relocate.c b/arch/mips/kernel/relocate.c
> > index 7f1c136ad850..12aa0bbdd65e 100644
> > --- a/arch/mips/kernel/relocate.c
> > +++ b/arch/mips/kernel/relocate.c
> > @@ -420,7 +420,18 @@ void *__init relocate_kernel(void)
> >  			goto out;
> >  
> >  		/* The current thread is now within the relocated image */
> > +#ifndef CONFIG_CC_IS_CLANG
> 
> I find
> 
>   #ifdef FOO
>   <FOO block>
>   #else
>   <!FOO block>
>   #endif
> 
> to be easier to read and process
> 
>   #ifndef FOO
>   <!FOO block>
>   #else
>   <FOO block>
>   #endif
> 
> but maybe that it is just personal preference.

I preferred to put Clang's workaround later, since

> >  		__current_thread_info = RELOCATED(&init_thread_union);

this simple assignment is easier to read and more clear than the inline
assembly, and matching the comment

	/* The current thread is now within the relocated image */

better. But yes, this is basically a personal preference, and I'm happy
with both. Please tell me if you do prefer the other.

> > +#else
> > +		/*
> > +		 * LLVM may wrongly restore $gp ($28) in epilog even if it's
> > +		 * intentionally modified. Work around this by using inline
> > +		 * assembly to assign $gp. $gp couldn't be listed as output or
> > +		 * clobber, or LLVM will still restore its original value.
> 
> This comment should likely include a link to the LLVM upstream report to
> make it easier to version restrict this workaround when fixed in the
> future.

Sure, will do it in v2. Thanks for the feedback!

Best regards,
Yao Zi
Re: [PATCH] MIPS: Work around LLVM bug when gp is used as global register variable
Posted by Yao Zi 4 days, 12 hours ago
On Sun, Jan 18, 2026 at 09:02:35AM +0000, Yao Zi wrote:
> On MIPS, __current_thread_info is defined as global register variable
> locating in $gp, and is simply assigned with new address during kernel
> relocation.
> 
> This however is broken with LLVM, which always restores $gp if it finds
> $gp is clobbered in any form, including when intentionally through a
> global register variable. This is against GCC's documentation[1], which
> requires a callee-saved register used as global register variable not to
> be restored if it's clobbered.
> 
> As a result, $gp will continue to point to the unrelocated kernel after
> the epilog of relocate_kernel(), leading to an early crash in init_idle,
> 
> [    0.000000] CPU 0 Unable to handle kernel paging request at virtual address 0000000000000000, epc == ffffffff81afada8, ra == ffffffff81afad90
> [    0.000000] Oops[#1]:
> [    0.000000] CPU: 0 UID: 0 PID: 0 Comm: swapper Tainted: G        W           6.19.0-rc5-00262-gd3eeb99bbc99-dirty #188 VOLUNTARY
> [    0.000000] Tainted: [W]=WARN
> [    0.000000] Hardware name: loongson,loongson64v-4core-virtio
> [    0.000000] $ 0   : 0000000000000000 0000000000000000 0000000000000001 0000000000000000
> [    0.000000] $ 4   : ffffffff80b80ec0 ffffffff80b53d48 0000000000000000 00000000000f4240
> [    0.000000] $ 8   : 0000000000000100 ffffffff81d82f80 ffffffff81d82f80 0000000000000001
> [    0.000000] $12   : 0000000000000000 ffffffff81776f58 00000000000005da 0000000000000002
> [    0.000000] $16   : ffffffff80b80e40 0000000000000000 ffffffff80b81614 9800000005dfbe80
> [    0.000000] $20   : 00000000540000e0 ffffffff81980000 0000000000000000 ffffffff80f81c80
> [    0.000000] $24   : 0000000000000a26 ffffffff8114fb90
> [    0.000000] $28   : ffffffff80b50000 ffffffff80b53d40 0000000000000000 ffffffff81afad90
> [    0.000000] Hi    : 0000000000000000
> [    0.000000] Lo    : 0000000000000000
> [    0.000000] epc   : ffffffff81afada8 init_idle+0x130/0x270
> [    0.000000] ra    : ffffffff81afad90 init_idle+0x118/0x270
> [    0.000000] Status: 540000e2	KX SX UX KERNEL EXL
> [    0.000000] Cause : 00000008 (ExcCode 02)
> [    0.000000] BadVA : 0000000000000000
> [    0.000000] PrId  : 00006305 (ICT Loongson-3)
> [    0.000000] Process swapper (pid: 0, threadinfo=(____ptrval____), task=(____ptrval____), tls=0000000000000000)
> [    0.000000] Stack : 9800000005dfbf00 ffffffff8178e950 0000000000000000 0000000000000000
> [    0.000000]         0000000000000000 ffffffff81970000 000000000000003f ffffffff810a6528
> [    0.000000]         0000000000000001 9800000005dfbe80 9800000005dfbf00 ffffffff81980000
> [    0.000000]         ffffffff810a6450 ffffffff81afb6c0 0000000000000000 ffffffff810a2258
> [    0.000000]         ffffffff81d82ec8 ffffffff8198d010 ffffffff81b67e80 ffffffff8197dd98
> [    0.000000]         ffffffff81d81c80 ffffffff81930000 0000000000000040 0000000000000000
> [    0.000000]         0000000000000000 0000000000000000 0000000000000000 0000000000000000
> [    0.000000]         0000000000000000 000000000000009e ffffffff9fc01000 0000000000000000
> [    0.000000]         0000000000000000 0000000000000000 0000000000000000 0000000000000000
> [    0.000000]         0000000000000000 ffffffff81ae86dc ffffffff81b3c741 0000000000000002
> [    0.000000]         ...
> [    0.000000] Call Trace:
> [    0.000000] [<ffffffff81afada8>] init_idle+0x130/0x270
> [    0.000000] [<ffffffff81afb6c0>] sched_init+0x5c8/0x6c0
> [    0.000000] [<ffffffff81ae86dc>] start_kernel+0x27c/0x7a8
> 
> This bug has been reported to LLVM[2] and affects version from (at
> least) 18 to 21. Let's work around this by using inline assembly to
> assign $gp before a fix is widely available.
> 
> Cc: stable@vger.kernel.org
> Link: https://gcc.gnu.org/onlinedocs/gcc-15.2.0/gcc/Global-Register-Variables.html # [1]
> Link: https://github.com/llvm/llvm-project/issues/176546 # [2]
> Signed-off-by: Yao Zi <me@ziyao.cc>

Gently ping on this patch. Thanks for your time and review.

Regards,
Yao Zi