Hi Mathieu, I checked both implementations of ptr_eq().
The x86 version uses inline asm for the comparison, so the compiler cannot reason about it as an ordinary C pointer comparison. The generic version uses OPTIMIZER_HIDE_VAR() before the comparison to prevent the compiler from propagating the original pointer values through it. I verified this with GCC 13.3 on ARM64: the generated code uses the second load's result for the dependent access, rather than reusing the first load's result. Reviewed-by: Kunwu Chan <[email protected]> Thanks, Kunwu On Mon, Oct 12, 2026 at 3:49 AM Mathieu Desnoyers <[email protected]> wrote: > > Compiler CSE and SSA GVN optimizations can cause the address dependency > of addresses returned by rcu_dereference to be lost when comparing those > pointers with either constants or previously loaded pointers. > > Introduce ptr_eq() to compare two addresses while preserving the address > dependencies for later use of the address. It should be used when > comparing an address returned by rcu_dereference(). > > This is needed to prevent the compiler CSE and SSA GVN optimizations > from using @a (or @b) in places where the source refers to @b (or @a) > based on the fact that after the comparison, the two are known to be > equal, which does not preserve address dependencies and allows the > following misordering speculations: > > - If @b is a constant, the compiler can issue the loads which depend > on @a before loading @a. > - If @b is a register populated by a prior load, weakly-ordered > CPUs can speculate loads which depend on @a before loading @a. > > The same logic applies with @a and @b swapped. > > The header "linux/ptr_eq.h" is the expected include target. It > contains the documentation of the ptr_eq() API. > > An architecture specific implementation of the pointer comparison > can be implemented by each architecture as asm/ptr_eq.h. The x86 > implementation is provided initially. If no implementation header is > present for the architecture, an arch-agnostic fallback based on > OPTIMIZER_HIDE_VAR is included from asm-generic. > > The arguments to ptr_eq() are const volatile to take advantage of > implicit conversion that adds cv-qualifiers, which are thus added > implicitly. > > Suggested-by: Linus Torvalds <[email protected]> > Suggested-by: Boqun Feng <[email protected]> > Signed-off-by: Mathieu Desnoyers <[email protected]> > Reviewed-by: Boqun Feng <[email protected]> > Cc: Greg Kroah-Hartman <[email protected]> > Cc: Sebastian Andrzej Siewior <[email protected]> > Cc: "Paul E. McKenney" <[email protected]> > Cc: Will Deacon <[email protected]> > Cc: Peter Zijlstra <[email protected]> > Cc: Boqun Feng <[email protected]> > Cc: Alan Stern <[email protected]> > Cc: John Stultz <[email protected]> > Cc: Linus Torvalds <[email protected]> > Cc: Boqun Feng <[email protected]> > Cc: Frederic Weisbecker <[email protected]> > Cc: Joel Fernandes <[email protected]> > Cc: Josh Triplett <[email protected]> > Cc: Uladzislau Rezki <[email protected]> > Cc: Steven Rostedt <[email protected]> > Cc: Lai Jiangshan <[email protected]> > Cc: Zqiang <[email protected]> > Cc: Ingo Molnar <[email protected]> > Cc: Waiman Long <[email protected]> > Cc: Mark Rutland <[email protected]> > Cc: Thomas Gleixner <[email protected]> > Cc: Vlastimil Babka <[email protected]> > Cc: [email protected] > Cc: Mateusz Guzik <[email protected]> > Cc: Gary Guo <[email protected]> > Cc: [email protected] > Cc: [email protected] > Cc: [email protected] > Cc: Nikita Popov <[email protected]> > Cc: [email protected] > Cc: Lian Wang <[email protected]> > Cc: Kunwu Chan <[email protected]> > --- > Changes since v2: > - Rename ptreq.h to ptr_eq.h (x86, asm-generic and linux headers). > Adapt the include guards accordingly. > > Changes since v1: > - Move to linux/ptreq.h, implement x86-specific comparison > and asm-generic header (fallback). > > Changes since v0: > - Include feedback from Alan Stern. > --- > arch/x86/include/asm/ptr_eq.h | 16 +++++++++ > include/asm-generic/Kbuild | 1 + > include/asm-generic/ptr_eq.h | 16 +++++++++ > include/linux/ptr_eq.h | 63 +++++++++++++++++++++++++++++++++++ > 4 files changed, 96 insertions(+) > create mode 100644 arch/x86/include/asm/ptr_eq.h > create mode 100644 include/asm-generic/ptr_eq.h > create mode 100644 include/linux/ptr_eq.h > > diff --git a/arch/x86/include/asm/ptr_eq.h b/arch/x86/include/asm/ptr_eq.h > new file mode 100644 > index 000000000000..8e521cabe24b > --- /dev/null > +++ b/arch/x86/include/asm/ptr_eq.h > @@ -0,0 +1,16 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +#ifndef _ASM_X86_PTR_EQ_H > +#define _ASM_X86_PTR_EQ_H > + > +#include <linux/types.h> > +#include <asm/asm.h> > + > +static __always_inline > +bool ptr_eq(const volatile void *a, const volatile void *b) > +{ > + bool ret; > + asm(__ASM_SIZE(cmp) " %1,%2" : "=@ccz" (ret) : "r" (a), "r" (b)); > + return ret; > +} > + > +#endif /* _ASM_X86_PTR_EQ_H */ > diff --git a/include/asm-generic/Kbuild b/include/asm-generic/Kbuild > index 2bc00c67dc54..86cee0dfd47f 100644 > --- a/include/asm-generic/Kbuild > +++ b/include/asm-generic/Kbuild > @@ -47,6 +47,7 @@ mandatory-y += percpu.h > mandatory-y += percpu_types.h > mandatory-y += pgalloc.h > mandatory-y += preempt.h > +mandatory-y += ptr_eq.h > mandatory-y += rqspinlock.h > mandatory-y += runtime-const.h > mandatory-y += rwonce.h > diff --git a/include/asm-generic/ptr_eq.h b/include/asm-generic/ptr_eq.h > new file mode 100644 > index 000000000000..2bd1c3b8a56f > --- /dev/null > +++ b/include/asm-generic/ptr_eq.h > @@ -0,0 +1,16 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +#ifndef __ASM_GENERIC_PTR_EQ_H > +#define __ASM_GENERIC_PTR_EQ_H > + > +#include <linux/types.h> > +#include <linux/compiler.h> > + > +static __always_inline > +bool ptr_eq(const volatile void *a, const volatile void *b) > +{ > + OPTIMIZER_HIDE_VAR(a); > + OPTIMIZER_HIDE_VAR(b); > + return a == b; > +} > + > +#endif /* __ASM_GENERIC_PTR_EQ_H */ > diff --git a/include/linux/ptr_eq.h b/include/linux/ptr_eq.h > new file mode 100644 > index 000000000000..7a2bbf6e64b3 > --- /dev/null > +++ b/include/linux/ptr_eq.h > @@ -0,0 +1,63 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +#ifndef _LINUX_PTR_EQ_H > +#define _LINUX_PTR_EQ_H > + > +/* > + * Compare two addresses while preserving the address dependencies for > + * later use of the address. It should be used when comparing an address > + * returned by rcu_dereference(). > + * > + * This is needed to prevent the compiler CSE and SSA GVN optimizations > + * from using @a (or @b) in places where the source refers to @b (or @a) > + * based on the fact that after the comparison, the two are known to be > + * equal, which does not preserve address dependencies and allows the > + * following misordering speculations: > + * > + * - If @b is a constant, the compiler can issue the loads which depend > + * on @a before loading @a. > + * - If @b is a register populated by a prior load, weakly-ordered > + * CPUs can speculate loads which depend on @a before loading @a. > + * > + * The same logic applies with @a and @b swapped. > + * > + * Return value: true if pointers are equal, false otherwise. > + * > + * The compiler barrier() is ineffective at fixing this issue. It does > + * not prevent the compiler CSE from losing the address dependency: > + * > + * int fct_2_volatile_barriers(void) > + * { > + * int *a, *b; > + * > + * do { > + * a = READ_ONCE(p); > + * asm volatile ("" : : : "memory"); > + * b = READ_ONCE(p); > + * } while (a != b); > + * asm volatile ("" : : : "memory"); <-- barrier() > + * return *b; > + * } > + * > + * With gcc 14.2 (arm64): > + * > + * fct_2_volatile_barriers: > + * adrp x0, .LANCHOR0 > + * add x0, x0, :lo12:.LANCHOR0 > + * .L2: > + * ldr x1, [x0] <-- x1 populated by first load. > + * ldr x2, [x0] > + * cmp x1, x2 > + * bne .L2 > + * ldr w0, [x1] <-- x1 is used for access which should depend > on b. > + * ret > + * > + * On weakly-ordered architectures, this lets CPU speculation use the > + * result from the first load to speculate "ldr w0, [x1]" before > + * "ldr x2, [x0]". > + * Based on the RCU documentation, the control dependency does not > + * prevent the CPU from speculating loads. > + */ > + > +#include <asm/ptr_eq.h> > + > +#endif /* _LINUX_PTR_EQ_H */ > -- > 2.43.0 >

