Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Mark Rutland <mark.rutland@arm.com>
To: David Laight <david.laight.linux@gmail.com>
Cc: vladimir.murzin@arm.com, ryan.roberts@arm.com,
	peterz@infradead.org, catalin.marinas@arm.com,
	ruanjinjie@huawei.com, stable@vger.kernel.org,
	james.morse@arm.com, yang@os.amperecomputing.com, cl@gentwo.org,
	maz@kernel.org, david@kernel.org, ljs@kernel.org,
	will@kernel.org, ardb@kernel.org,
	linux-arm-kernel@lists.infradead.org
Subject: Re: [PATCH v2 02/20] arm64: percpu: Fix this_cpu_and() mask generation
Date: Wed, 5 Aug 2026 14:02:03 +0100	[thread overview]
Message-ID: <anM0S1jVmwO0mqEt@J2N7QTR9R3> (raw)
In-Reply-To: <20260805101416.454a49b6@pumpkin>

On Wed, Aug 05, 2026 at 10:14:16AM +0100, David Laight wrote:
> On Tue,  4 Aug 2026 18:04:45 +0100
> Mark Rutland <mark.rutland@arm.com> wrote:
> 
> > The arm64 implementation of this_cpu_and(pcp, val) is built in terms of
> > ANDNOT operations, which requires the 'val' argument to be bitwise
> > negated. The bitwise negation is not implemented correctly, with two
> > bugs described below.
> > 
> > (1) The bitwise negation is performed as '~val' rather than '~(val)'.
> >     This won't always generate the expected value when 'val' is an
> >     expression.
> > 
> >     For example, for this_cpu_and(pcp, 1 - 1):
> > 
> >     * 'val'    is  '1 - 1'   ===> (int) 0x00000000
> >     * '~val'   is '~1 - 1'   ===> (int) 0xfffffffd
> >     * '~(val)' is '~(1 - 1)' ===> (int) 0xffffffff
> > 
> >     ... and thus bit[1] of 'pcp' would be preserved unexpectedly by the
> >     ANDNOT operation.
> > 
> > (2) The bitwise negation is performed on 'val' before it has been cast
> >     to (at least) the width of 'pcp'. This won't always generate the
> >     expected value for the upper bits.
> > 
> >     For example, for this_cpu_and(pcp, zero), where 'pcp' is a u64 and
> >     'zero' is a u32:
> > 
> >     * 'zero'           ===> (u32) 0x00000000
> >     * '~(zero)'        ===> (u32) 0xffffffff
> >     * '(u64)~(zero)'   ===> (u64) 0x00000000ffffffff
> >     * '~((u64)(zero))' ===> (u64) 0xffffffffffffffff
> > 
> >     ... and thus bits[63:32] of 'pcp' would be preserved unexpectedly by
> >     the ANDNOT operation.
> > 
> > Fix these issues by adding brackets around 'val', and by casting 'val'
> > to an appropriately-sized type before bitwise negation.

> > diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h
> > index 63bbfd4944a37..31193bcf89a2b 100644
> > --- a/arch/arm64/include/asm/percpu.h
> > +++ b/arch/arm64/include/asm/percpu.h
> > @@ -206,13 +206,13 @@ PERCPU_RET_OP(add, add, ldadd)
> >  	_pcp_protect_return(__percpu_add_return_case_64, pcp, val)
> >  
> >  #define this_cpu_and_1(pcp, val)	\
> > -	_pcp_protect(__percpu_andnot_case_8, pcp, ~val)
> > +	_pcp_protect(__percpu_andnot_case_8, pcp, ~(u8)(val))
> >  #define this_cpu_and_2(pcp, val)	\
> > -	_pcp_protect(__percpu_andnot_case_16, pcp, ~val)
> > +	_pcp_protect(__percpu_andnot_case_16, pcp, ~(u16)(val))
> 
> I don't think the (u8) or (u16) casts are needed.

They're not strictly needed, but I added them for consistency with the
other cases.

> They force the high 24/16 bits to be ones, but the asm should
> ignore those bits (or possible even prefer they be zeros).

For 'sz' bits, the asm for this op only cares about val[sz-1:0], and
val[63:sz] is immaterial. There's no preference.

> They might also force the compiler to emit code to mask the high bits.

If __percpu_andnot_case_##sz() gets outlined, sure. When
__percpu_andnot_case_##sz() is inlined (which we expect in almost all cases
today), the compiler has visibility that bits [63:sz] are unused, and won't
generate redundant code.

That's a minor redundancy, not a functional issue. If we're worried about that,
we can have __percpu_andnot_case_##sz() take its argument as a u##sz (which TBH
we probably should anyway).

For example, see the code generated for:

| void this_cpu_and_u8__0xf0(u8 __percpu *p) 
| {
|         this_cpu_and(*p, 0xf0);
| }

At this point in the series, GCC 15.2.0 generates:

| <this_cpu_and_u8__0xf0>:
|        paciasp
|        stp     x29, x30, [sp, #-16]!
|        mrs     x1, sp_el0
|        mov     x29, sp
|        ldr     w2, [x1, #8]
|        add     w2, w2, #0x1
|        str     w2, [x1, #8]
|        mov     w3, #0xf       // <------ Low 8 bits only!
|        mrs     x2, tpidr_el1
|        add     x0, x0, x2
| 1:     ldxrb   w5, [x0]
|        bic     w5, w5, w3
|        stxrb   w4, w5, [x0]
|        cbnz    w4, 1b
|        ldr     x0, [x1, #8]
|        sub     x0, x0, #0x1
|        str     w0, [x1, #8]
|        cbz     x0, 2f
|        ldr     x0, [x1, #8]
|        cbnz    x0, 3f
| 2:     bl      preempt_schedule_notrace
| 3:     ldp     x29, x30, [sp], #16
|        autiasp
|        ret

> Actually the (u16) cast is wrong for (s8)128.
> That is tricky to fix, maybe:
> 	~(sizeof(val) == 1 ? (u8)(val) : (val))
> (Remember ?: promotes its operands to int.)

I do not follow, and I think you are wrong.

My understanding is that the value arguments to a this_cpu_*() operation
should be subject to the usual type promotion rules. For a u16 'pcp' and
an s8 'v', 'pcp & v' should result in sign-extension of v, and
this_cpu_and(pcp, v) should do the same.

What makes you believe the semantic you propose is correct, and the
semantic I've implemented is wrong? Is there some documentation?

Tvhe semantic youe propose doesn't match what __this_cpu_and() does, and
it doesn't match what this_cpu_and() does on x86_64.

Note how __this_cpu_and() behaves. For the following test case:

| void outline_and__u16__s8_128(u16 __percpu *p)
| {
|         s8 v = 128;
|         __this_cpu_and(*p, v);
| }
| 
| void outline_and__s16__s8_128(s16 __percpu *p)
| {
|         s8 v = 128;
|         __this_cpu_and(*p, v);
| }

For arm64 this generates:

| <outline_and__u16__s8_128>:
|        mrs     x2, tpidr_el1
|        ldrh    w1, [x0, x2]
|        and     w1, w1, #0xffffff80
|        strh    w1, [x0, x2]
|
| <outline_and__s16__s8_128>:
|        mrs     x2, tpidr_el1
|        ldrh    w1, [x0, x2]
|        and     w1, w1, #0xffffff80
|        strh    w1, [x0, x2]
|        ret

... where '(s8)128' is sign-extended to (at least) 16 bits.

Note that LDRH and STRH only use the low 16 bits of the register, the
upper bits of the AND are irrelevant.

Note that for x86, those tests generate:

|	andw   $0xff80,%gs:(%rdi)

... which is clearly sign-extending to 16 bits.

> >  #define this_cpu_and_4(pcp, val)	\
> > -	_pcp_protect(__percpu_andnot_case_32, pcp, ~val)
> > +	_pcp_protect(__percpu_andnot_case_32, pcp, ~(u32)(val))
> 
> The (u32) cast isn't needed (and has pretty much no effect).

As above, this is for consistency. I agree it happens to do nothing.

> >  #define this_cpu_and_8(pcp, val)	\
> > -	_pcp_protect(__percpu_andnot_case_64, pcp, ~val)
> > +	_pcp_protect(__percpu_andnot_case_64, pcp, ~(u64)(val))
> 
> This one still isn't right.
> If val is a signed int with a negative value then it is sign extended
> before being inverted.
> 	val             (int)0x80000000
> 	(u64)(val)   0xffffffff80000000
> 	~(u64)(val)  0x000000007fffffff
> Something like ~(u64)((val) + 0u) will DTRT.

As above, where have you got that idea from?

AFAICT, a smaller signed type *should* be sign extended, and that must
happen before bitwise negation, since that bitwise negation is to cancel
out the NOT part of the ANDNOT operation.
 
Think:

    'pcp'                    is (u64) 0x0123456789abcdef
    'val'                    is (int) 0x800000000
    '(u64)(val)'             is (u64) 0xffffffff80000000
    'pcp & (u64)(val)'       is (u64) 0x0123456780000000

    '~(u64)(val)'            is (u64) 0x000000007fffffff
    'pcp ANDNOT ~(u64)(val)' is (u64) 0x0123456780000000
    
See:

| void outline_and__u64__int_0x80000000(u64 __percpu *p)
| {
|         int v = 0x80000000;
|         __this_cpu_and(*p, v);
| }
| 
| void outline_and__s64__int_0x80000000(s64 __percpu *p)
| {
|         int v = 0x80000000;
|         __this_cpu_and(*p, v);
| }

For which GCC 15.2.0 generates the following:

| <outline_and__u64__int_0x80000000>:
|        mrs     x2, tpidr_el1
|        ldr     x1, [x0, x2]
|        and     x1, x1, #0xffffffff80000000
|        str     x1, [x0, x2]
|        ret
| 
| <outline_and__s64__int_0x80000000>:
|        mrs     x2, tpidr_el1
|        ldr     x1, [x0, x2]
|        and     x1, x1, #0xffffffff80000000
|        str     x1, [x0, x2]
|        ret

Likewise on x86 this generates:

|	andq   $0xffffffff80000000,%gs:(%rdi)

Mark.


  reply	other threads:[~2026-08-05 13:02 UTC|newest]

Thread overview: 48+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-04 17:04 [PATCH v2 00/20] arm64: Preemptible this_cpu_*() operations Mark Rutland
2026-08-04 17:04 ` [PATCH v2 01/20] arm64: percpu: Fix this_cpu_write() casting Mark Rutland
2026-08-05  8:37   ` David Laight
2026-08-07  4:00   ` Jinjie Ruan
2026-08-04 17:04 ` [PATCH v2 02/20] arm64: percpu: Fix this_cpu_and() mask generation Mark Rutland
2026-08-05  9:14   ` David Laight
2026-08-05 13:02     ` Mark Rutland [this message]
2026-08-06  8:28       ` David Laight
2026-08-06 10:23         ` Mark Rutland
2026-08-07  9:52   ` Jinjie Ruan
2026-08-04 17:04 ` [PATCH v2 03/20] arm64: cmpxchg: LL/SC: Avoid redundant extension Mark Rutland
2026-08-04 17:04 ` [PATCH v2 04/20] arm64: cmpxchg128: LSE: Remove redundant operands Mark Rutland
2026-09-07  8:42   ` Jinjie Ruan
2026-08-04 17:04 ` [PATCH v2 05/20] arm64: preempt: Simplify and optimize __preempt_count_dec_and_test() Mark Rutland
2026-08-04 17:04 ` [PATCH v2 06/20] arm64: preempt: Treat should_resched() as unlikely Mark Rutland
2026-08-04 17:04 ` [PATCH v2 07/20] arm64: ptrace: Always inline pt_regs_[read,write}_reg() Mark Rutland
2026-08-04 17:04 ` [PATCH v2 08/20] arm64: percpu: Factor out percpu offset asm Mark Rutland
2026-08-04 17:04 ` [PATCH v2 09/20] arm64: gpr-num: Add wxN aliases for wN registers Mark Rutland
2026-08-04 17:04 ` [PATCH v2 10/20] arm64: gpr-num: add __GPR_NUM() helper Mark Rutland
2026-08-04 17:04 ` [PATCH v2 11/20] arm64: entry: sdei: Restore all clobberable GPRs Mark Rutland
2026-08-07 11:35   ` Mark Rutland
2026-08-04 17:04 ` [PATCH v2 12/20] arm64: entry: sdei: Make 'tsk' available Mark Rutland
2026-08-04 17:04 ` [PATCH v2 13/20] arm64: percpu: Add infrastructure for preemptible this_cpu_*() ops Mark Rutland
2026-08-04 22:45   ` Pedro Falcato
2026-08-05 10:27     ` David Laight
2026-08-05 12:50       ` Pedro Falcato
2026-08-05  6:45   ` David Hildenbrand (Arm)
2026-08-05  6:47     ` David Hildenbrand (Arm)
2026-08-06 11:21       ` Mark Rutland
2026-08-06 11:32         ` David Hildenbrand (Arm)
2026-08-06 12:02           ` Mark Rutland
2026-08-06 13:25           ` David Laight
2026-08-06 13:30             ` David Hildenbrand (Arm)
2026-08-04 17:04 ` [PATCH v2 14/20] arm64: percpu: Implement preemptible read/write ops Mark Rutland
2026-08-05  9:24   ` Ryan Roberts
2026-08-05 12:08     ` David Laight
2026-08-05 13:34     ` Mark Rutland
2026-08-04 17:04 ` [PATCH v2 15/20] arm64: percpu: Implement preemptible void RMW ops Mark Rutland
2026-08-04 17:04 ` [PATCH v2 16/20] arm64: percpu: Implement preemptible return " Mark Rutland
2026-08-04 17:05 ` [PATCH v2 17/20] arm64: percpu: Implement preemptible XCHG ops Mark Rutland
2026-08-04 17:05 ` [PATCH v2 18/20] arm64: percpu: Implement preemptible CMPXCHG ops Mark Rutland
2026-08-04 17:05 ` [PATCH v2 19/20] arm64: percpu: Implement preemptible CMPXCHG128 ops Mark Rutland
2026-08-04 17:05 ` [PATCH v2 20/20] arm64: percpu: Remove _pcp_protect*() wrappers Mark Rutland
2026-09-02 11:55 ` [PATCH v2 00/20] arm64: Preemptible this_cpu_*() operations Usama Anjum
2026-09-02 13:16   ` Lorenzo Stoakes (ARM)
2026-09-02 13:32   ` Mark Rutland
2026-09-02 15:42   ` David Laight
2026-09-02 18:24     ` David Laight

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=anM0S1jVmwO0mqEt@J2N7QTR9R3 \
    --to=mark.rutland@arm.com \
    --cc=ardb@kernel.org \
    --cc=catalin.marinas@arm.com \
    --cc=cl@gentwo.org \
    --cc=david.laight.linux@gmail.com \
    --cc=david@kernel.org \
    --cc=james.morse@arm.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=ljs@kernel.org \
    --cc=maz@kernel.org \
    --cc=peterz@infradead.org \
    --cc=ruanjinjie@huawei.com \
    --cc=ryan.roberts@arm.com \
    --cc=stable@vger.kernel.org \
    --cc=vladimir.murzin@arm.com \
    --cc=will@kernel.org \
    --cc=yang@os.amperecomputing.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox