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
next prev 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.