All of lore.kernel.org
 help / color / mirror / Atom feed
From: Mark Rutland <mark.rutland@arm.com>
To: linux-arm-kernel@lists.infradead.org
Cc: mark.rutland@arm.com, vladimir.murzin@arm.com,
	usama.anjum@arm.com, peterz@infradead.org,
	catalin.marinas@arm.com, david.laight.linux@gmail.com,
	stable@vger.kernel.org, ruanjinjie@huawei.com,
	james.morse@arm.com, yang@os.amperecomputing.com, cl@gentwo.org,
	maz@kernel.org, will@kernel.org, ardb@kernel.org
Subject: [PATCH v3 06/21] arm64: preempt: Simplify and optimize __preempt_count_dec_and_test()
Date: Fri,  4 Sep 2026 17:57:09 +0100	[thread overview]
Message-ID: <20260904165709.376981-1-mark.rutland@arm.com> (raw)
In-Reply-To: <20260904161758.376504-1-mark.rutland@arm.com>

In arm64's __preempt_count_dec_and_test(), the final conditional load of
'ti->preempt_count' is always executed in the common case. The
conditional load leads to unfortunate code generation for
preempt_enable[_notrace](), and it would be better to unconditionally
load 'ti->preempt_count', as described below.

On arm64, struct thread_info contains the following union:

| union {
|         u64             preempt_count;
|         struct {
|                 u32     count;
|                 u32     need_resched;
|         } preempt;
| };

Note: 'need_resched' is encoded so that '0' means a reschedule is
needed, and '1' means a reschedule is NOT needed.

The core logic of __preempt_count_dec_and_test() is:

| static inline bool __preempt_count_dec_and_test(void)
| {
|         struct thread_info *ti = current_thread_info();
|         u64 pc = READ_ONCE(ti->preempt_count);
|
|         WRITE_ONCE(ti->preempt.count, --pc);
|
|         return !pc || !READ_ONCE(ti->preempt_count);
| }

The '!pc' condition can only be true when both:

* The initial value of 'need_resched' was 0, meaning that a reschedule
  is needed. This should be rare.

* The initial value of 'count' was exactly 1. This cannot be true for a
  nested preempt_disable() ... preempt_enable() sequence.

Hence in common cases, '!pc' will be false, and it's necessary to
execute the final READ_ONCE(ti->preempt_count).

This results in a conditional branch in the common case, as can be seen
when __preempt_count_dec_and_test() is outlined:

| <outline___preempt_count_dec_and_test>:
|        mrs     x2, sp_el0
|        ldr     x1, [x2, #8]
|        mov     w0, #0x1
|        sub     x1, x1, #0x1
|        str     w1, [x2, #8]
|        cbz     x1, 1f
|        ldr     x0, [x2, #8]
|        cmp     x0, #0x0
|        cset    w0, eq  // eq = none
| 1:     ret

It would be better to avoid the special case for 'pc == 0', and to
always load 'ti->preempt_count' after decrementing 'ti->preempt.count'.
For the common cases this will remove a conditional branch. For the rare
cases where preemption is needed initially, this only adds a single
load, whose cost should be dominated by other factors.

Remove the special case for 'pc == 0', and always load the combined
'ti->preempt_count' after decrementing 'ti->preempt.count'.

The removal of the conditional branch helps with code generation, as the
compiler can more easily move a dependent slow path out-of-line, as
demonstrated with the following compiled with GCC 15.2.0:

| void outline_preempt_enable_notrace(void)
| {
|        preempt_enable_notrace();
| }

Before this patch:

| <outline_preempt_enable_notrace>:
|        mrs     x1, sp_el0
|        ldr     x0, [x1, #8]
|        sub     x0, x0, #0x1
|        str     w0, [x1, #8]
|        cbz     x0, 1f
|        ldr     x0, [x1, #8]
|        cbnz    x0, 2f
| 1:     paciasp
|        stp     x29, x30, [sp, #-16]!
|        mov     x29, sp
|        bl      preempt_schedule_notrace
|        ldp     x29, x30, [sp], #16
|        autiasp
|        ret
| 2:     ret

After this patch:

| <outline_preempt_enable_notrace>:
|        mrs     x0, sp_el0
|        ldr     w1, [x0, #8]
|        sub     w1, w1, #0x1
|        str     w1, [x0, #8]
|        ldr     x0, [x0, #8]
|        cbz     x0, 1f
|        ret
| 1:     paciasp
|        stp     x29, x30, [sp, #-16]!
|        mov     x29, sp
|        bl      preempt_schedule_notrace
|        ldp     x29, x30, [sp], #16
|        autiasp
|        ret

Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Reviewed-by: Jinjie Ruan <ruanjinjie@huawei.com>
Tested-by: Muhammad Usama Anjum <usama.anjum@arm.com>
Cc: Ada Couprie Diaz <ada.coupriediaz@arm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Vladimir Murzin <vladimir.murzin@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
---
 arch/arm64/include/asm/preempt.h | 29 +++++++++++------------------
 1 file changed, 11 insertions(+), 18 deletions(-)

diff --git a/arch/arm64/include/asm/preempt.h b/arch/arm64/include/asm/preempt.h
index 9ecc2766a9f21..92ae2eb95a738 100644
--- a/arch/arm64/include/asm/preempt.h
+++ b/arch/arm64/include/asm/preempt.h
@@ -75,30 +75,23 @@ static inline int __preempt_count_sub_return(int val)
 	return pc;
 }
 
-static inline bool __preempt_count_dec_and_test(void)
-{
-	struct thread_info *ti = current_thread_info();
-	u64 pc = READ_ONCE(ti->preempt_count);
-
-	/* Update only the count field, leaving need_resched unchanged */
-	WRITE_ONCE(ti->preempt.count, --pc);
-
-	/*
-	 * If we wrote back all zeroes, then we're preemptible and in
-	 * need of a reschedule. Otherwise, we need to reload the
-	 * preempt_count in case the need_resched flag was cleared by an
-	 * interrupt occurring between the non-atomic READ_ONCE/WRITE_ONCE
-	 * pair.
-	 */
-	return !pc || !READ_ONCE(ti->preempt_count);
-}
-
 static inline bool should_resched(int preempt_offset)
 {
 	u64 pc = READ_ONCE(current_thread_info()->preempt_count);
 	return pc == preempt_offset;
 }
 
+static inline bool __preempt_count_dec_and_test(void)
+{
+	/*
+	 * We must load the combined 'prempt_count' after decrementing
+	 * 'preempt.count' as an interrupt could modify 'need_resched' before
+	 * __preempt_count_sub() writes back to 'preempt.count'.
+	 */
+	__preempt_count_sub(1);
+	return should_resched(0);
+}
+
 #ifdef CONFIG_PREEMPTION
 
 void preempt_schedule(void);
-- 
2.30.2



  parent reply	other threads:[~2026-09-04 16:57 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-04 16:17 [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland
2026-09-04 16:17 ` [PATCH v3 03/21] arm64: percpu: Fix LSE operations on {8,16}-bit types Mark Rutland
2026-09-07  8:48   ` Jinjie Ruan
2026-09-04 16:17 ` [PATCH v3 05/21] arm64: cmpxchg128: LSE: Remove redundant operands Mark Rutland
2026-09-04 16:17 ` [PATCH v3 07/21] arm64: preempt: Treat should_resched() as unlikely Mark Rutland
2026-09-04 16:17 ` [PATCH v3 08/21] arm64: ptrace: Always inline pt_regs_[read,write}_reg() Mark Rutland
2026-09-04 16:17 ` [PATCH v3 09/21] arm64: percpu: Factor out percpu offset asm Mark Rutland
2026-09-04 16:17 ` [PATCH v3 10/21] arm64: gpr-num: Add wxN aliases for wN registers Mark Rutland
2026-09-04 16:17 ` [PATCH v3 12/21] arm64: entry: sdei: Restore all clobberable GPRs Mark Rutland
2026-09-04 16:17 ` [PATCH v3 14/21] arm64: percpu: Add infrastructure for preemptible this_cpu_*() ops Mark Rutland
2026-09-04 16:17 ` [PATCH v3 15/21] arm64: percpu: Implement preemptible read/write ops Mark Rutland
2026-09-04 16:17 ` [PATCH v3 16/21] arm64: percpu: Implement preemptible void RMW ops Mark Rutland
2026-09-05  9:03   ` David Laight
2026-09-08 15:07     ` Mark Rutland
2026-09-08 20:52       ` David Laight
2026-09-04 16:17 ` [PATCH v3 17/21] arm64: percpu: Implement preemptible return " Mark Rutland
2026-09-04 16:17 ` [PATCH v3 18/21] arm64: percpu: Implement preemptible XCHG ops Mark Rutland
2026-09-04 16:17 ` [PATCH v3 19/21] arm64: percpu: Implement preemptible CMPXCHG ops Mark Rutland
2026-09-04 16:17 ` [PATCH v3 20/21] arm64: percpu: Implement preemptible CMPXCHG128 ops Mark Rutland
2026-09-04 16:17 ` [PATCH v3 21/21] arm64: percpu: Remove _pcp_protect*() wrappers Mark Rutland
2026-09-04 16:54 ` [PATCH v3 01/21] arm64: percpu: Fix this_cpu_write() casting Mark Rutland
2026-09-04 16:56 ` [PATCH v3 02/21] arm64: percpu: Fix this_cpu_and() mask generation Mark Rutland
2026-09-04 16:56 ` [PATCH v3 04/21] arm64: cmpxchg: LL/SC: Avoid redundant extension Mark Rutland
2026-09-04 16:57 ` Mark Rutland [this message]
2026-09-04 16:59 ` [PATCH v3 13/21] arm64: entry: sdei: Make 'tsk' available Mark Rutland
2026-09-04 17:43 ` [PATCH v3 00/21] arm64: Preemptible this_cpu_*() operations Mark Rutland

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=20260904165709.376981-1-mark.rutland@arm.com \
    --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=james.morse@arm.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=maz@kernel.org \
    --cc=peterz@infradead.org \
    --cc=ruanjinjie@huawei.com \
    --cc=stable@vger.kernel.org \
    --cc=usama.anjum@arm.com \
    --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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.