arm64: use subsections instead of function calls for LL/SC fallbacks
Subsystems (2)
cover arm64: use subsections instead of function calls for LL/SC fallbacks
Refactor the LL/SC atomics code so we can emit the LL/SC fallbacks for the LSE atomics as subsections that get instantiated at each call site rather than as out of line functions that get called from inline asm (without the awareness of the compiler)
This should allow slightly better LSE code, and removes stack spilling and potential PLT indirection for the LL/SC fallbacks.
Ard Biesheuvel (3):
arm64/atomics: refactor LL/SC base asm templates arm64/atomics: use subsections for out of line LL/SC alternatives arm64/atomics: remove out of line LL/SC alternatives
arch/arm64/include/asm/atomic_ll_sc.h | 190 ++++--- arch/arm64/include/asm/atomic_lse.h | 558 ++++++++++---------- arch/arm64/include/asm/lse.h | 13 - arch/arm64/lib/Makefile | 19 - arch/arm64/lib/atomic_ll_sc.c | 3 - 5 files changed, 372 insertions(+), 411 deletions(-) delete mode 100644 arch/arm64/lib/atomic_ll_sc.c
Signature · 1 line
2.17.1
PATCH 1/3 arm64/atomics: refactor LL/SC base asm templates
Refactor the asm templates that emit the LL/SC instruction sequences so that we will be able to reuse them in the LSE code, which will emit them out of line, but without the use of function calls.
This involves factoring out the core instruction sequences and using named operands throughout.
--- arch/arm64/include/asm/atomic_ll_sc.h | 139 ++++++++++---------- 1 file changed, 72 insertions(+), 67 deletions(-)
arch/arm64/include/asm/atomic_ll_sc.h +72 -67
@@ -36,6 +36,51 @@* this file, which unfortunately don't work on a per-function basis * (the optimize attribute silently ignores these options). */ +#define __LL_SC_ATOMIC_OP (asm_op, w) \ +" prfm pstl1strm, %[v] \n" \ +"1: ldxr %" #w "[res], %[v] \n" \ +" " #asm_op " %" #w "[res], %" #w "[res], %" #w "[i] \n" \ +" stxr %w[tmp], %" #w "[res], %[v] \n" \ +" cbnz %w[tmp], 1b" + +#define __LL_SC_ATOMIC_OP_RETURN (asm_op, mb, acq, rel, w) \ +" prfm pstl1strm, %[v] \n" \ +"1: ld" #acq "xr %" #w "[res], %[v] \n" \ +" " #asm_op " %" #w "[res], %" #w "[res], %" #w "[i] \n" \ +" st" #rel "xr %w[tmp], %" #w "[res], %[v] \n" \ +" cbnz %w[tmp], 1b \n" \ +" " #mb + +#define __LL_SC_ATOMIC_FETCH_OP (asm_op, mb, acq, rel, w) \ +" prfm pstl1strm, %[v] \n" \ +"1: ld" #acq "xr %" #w "[res], %[v] \n" \ +" " #asm_op " %" #w "[val], %" #w "[res], %" #w "[i] \n" \ +" st" #rel "xr %w[tmp], %" #w "[val], %[v] \n" \ +" cbnz %w[tmp], 1b \n" \ +" " #mb \ + +#define __LL_SC_CMPXCHG_BASE_OP (w, sz, name, mb, acq, rel) \ +" prfm pstl1strm, %[v] \n" \ +"1: ld" #acq "xr" #sz " %" #w "[oldval], %[v] \n" \ +" eor %" #w "[tmp], %" #w "[oldval], " \ +" %" #w "[old] \n" \ +" cbnz %" #w "[tmp], 2f \n" \ +" st" #rel "xr" #sz " %w[tmp], %" #w "[new], %[v] \n" \ +" cbnz %w[tmp], 1b \n" \ +" " #mb " \n" \ +"2:" + +#define __LL_SC_CMPXCHG_DBL_OP (mb, rel) \ +" prfm pstl1strm, %[v] \n" \ +"1: ldxp %[tmp], %[ret], %[v] \n" \ +" eor %[tmp], %[tmp], %[old1] \n" \ +" eor %[ret], %[ret], %[old2] \n" \ +" orr %[ret], %[tmp], %[ret] \n" \ +" cbnz %[ret], 2f \n" \ +" st" #rel "xp %w[tmp], %[new1], %[new2], %[v] \n" \ +" cbnz %w[tmp], 1b \n" \ +" " #mb " \n" \ +"2:" \#define ATOMIC_OP (op, asm_op) \ __LL_SC_INLINE void \
@@ -44,14 +89,10 @@ __LL_SC_PREFIX(atomic_##op(int i, atomic_t *v)) \unsigned long tmp; \int result; \ \ - asm volatile("// atomic_" #op "\n" \ -" prfm pstl1strm, %2\n" \ -"1: ldxr %w0, %2\n" \ -" " #asm_op " %w0, %w0, %w3\n" \ -" stxr %w1, %w0, %2\n" \ -" cbnz %w1, 1b" \ - : "=&r" (result), "=&r" (tmp), "+Q" (v->counter) \ - : "Ir" (i)); \ + asm volatile(" // atomic_" #op "\n" \ + __LL_SC_ATOMIC_OP(asm_op, w) \ + : [res]"=&r" (result), [tmp]"=&r" (tmp), [v]"+Q" (v->counter) \ + : [i]"Ir" (i)); \ } \ __LL_SC_EXPORT(atomic_##op);
@@ -63,14 +104,9 @@ __LL_SC_PREFIX(atomic_##op##_return##name(int i, atomic_t *v)) \int result; \ \ asm volatile("// atomic_" #op "_return" #name "\n" \ -" prfm pstl1strm, %2\n" \ -"1: ld" #acq "xr %w0, %2\n" \ -" " #asm_op " %w0, %w0, %w3\n" \ -" st" #rel "xr %w1, %w0, %2\n" \ -" cbnz %w1, 1b\n" \ -" " #mb \ - : "=&r" (result), "=&r" (tmp), "+Q" (v->counter) \ - : "Ir" (i) \ + __LL_SC_ATOMIC_OP_RETURN(asm_op, mb, acq, rel, w) \ + : [res]"=&r" (result), [tmp]"=&r" (tmp), [v]"+Q" (v->counter) \ + : [i]"Ir" (i) \ : cl); \ \ return result; \
@@ -85,14 +121,10 @@ __LL_SC_PREFIX(atomic_fetch_##op##name(int i, atomic_t *v)) \int val, result; \ \ asm volatile("// atomic_fetch_" #op #name "\n" \ -" prfm pstl1strm, %3\n" \ -"1: ld" #acq "xr %w0, %3\n" \ -" " #asm_op " %w1, %w0, %w4\n" \ -" st" #rel "xr %w2, %w1, %3\n" \ -" cbnz %w2, 1b\n" \ -" " #mb \ - : "=&r" (result), "=&r" (val), "=&r" (tmp), "+Q" (v->counter) \ - : "Ir" (i) \ + __LL_SC_ATOMIC_FETCH_OP(asm_op, mb, acq, rel, w) \ + : [res]"=&r" (result), [val]"=&r" (val), [tmp]"=&r" (tmp), \ + [v]"+Q" (v->counter) \ + : [i]"Ir" (i) \ : cl); \ \ return result; \
@@ -139,13 +171,9 @@ __LL_SC_PREFIX(atomic64_##op(long i, atomic64_t *v)) \unsigned long tmp; \ \ asm volatile("// atomic64_" #op "\n" \ -" prfm pstl1strm, %2\n" \ -"1: ldxr %0, %2\n" \ -" " #asm_op " %0, %0, %3\n" \ -" stxr %w1, %0, %2\n" \ -" cbnz %w1, 1b" \ - : "=&r" (result), "=&r" (tmp), "+Q" (v->counter) \ - : "Ir" (i)); \ + __LL_SC_ATOMIC_OP(asm_op, ) \ + : [res]"=&r" (result), [tmp]"=&r" (tmp), [v]"+Q" (v->counter) \ + : [i]"Ir" (i)); \ } \ __LL_SC_EXPORT(atomic64_##op);
@@ -157,14 +185,9 @@ __LL_SC_PREFIX(atomic64_##op##_return##name(long i, atomic64_t *v)) \unsigned long tmp; \ \ asm volatile("// atomic64_" #op "_return" #name "\n" \ -" prfm pstl1strm, %2\n" \ -"1: ld" #acq "xr %0, %2\n" \ -" " #asm_op " %0, %0, %3\n" \ -" st" #rel "xr %w1, %0, %2\n" \ -" cbnz %w1, 1b\n" \ -" " #mb \ - : "=&r" (result), "=&r" (tmp), "+Q" (v->counter) \ - : "Ir" (i) \ + __LL_SC_ATOMIC_OP_RETURN(asm_op, mb, acq, rel, ) \ + : [res]"=&r" (result), [tmp]"=&r" (tmp), [v]"+Q" (v->counter) \ + : [i]"Ir" (i) \ : cl); \ \ return result; \
@@ -179,14 +202,10 @@ __LL_SC_PREFIX(atomic64_fetch_##op##name(long i, atomic64_t *v)) \unsigned long tmp; \ \ asm volatile("// atomic64_fetch_" #op #name "\n" \ -" prfm pstl1strm, %3\n" \ -"1: ld" #acq "xr %0, %3\n" \ -" " #asm_op " %1, %0, %4\n" \ -" st" #rel "xr %w2, %1, %3\n" \ -" cbnz %w2, 1b\n" \ -" " #mb \ - : "=&r" (result), "=&r" (val), "=&r" (tmp), "+Q" (v->counter) \ - : "Ir" (i) \ + __LL_SC_ATOMIC_FETCH_OP(asm_op, mb, acq, rel, ) \ + : [res]"=&r" (result), [val]"=&r" (val), [tmp]"=&r" (tmp), \ + [v]"+Q" (v->counter) \ + : [i]"Ir" (i) \ : cl); \ \ return result; \
@@ -257,14 +276,7 @@ __LL_SC_PREFIX(__cmpxchg_case_##name(volatile void *ptr, \unsigned long tmp, oldval; \ \ asmvolatile ( \ - " prfm pstl1strm, %[v]\n" \ - "1: ld" #acq "xr" #sz "\t%" #w "[oldval], %[v]\n" \ - " eor %" #w "[tmp], %" #w "[oldval], %" #w "[old]\n" \ - " cbnz %" #w "[tmp], 2f\n" \ - " st" #rel "xr" #sz "\t%w[tmp], %" #w "[new], %[v]\n" \ - " cbnz %w[tmp], 1b\n" \ - " " #mb "\n" \ - "2:" \ + __LL_SC_CMPXCHG_BASE_OP(w, sz, name, mb, acq, rel) \ : [tmp] "=&r" (tmp), [oldval] "=&r" (oldval), \[v] " +Q " (*(unsigned long *)ptr) \ : [old] " Lr" (old), [new] " r" (new) \
@@ -304,18 +316,11 @@ __LL_SC_PREFIX(__cmpxchg_double##name(unsigned long old1, \unsigned long tmp, ret; \ \ asm volatile("// __cmpxchg_double" #name "\n" \ - " prfm pstl1strm, %2\n" \ - "1: ldxp %0, %1, %2\n" \ - " eor %0, %0, %3\n" \ - " eor %1, %1, %4\n" \ - " orr %1, %0, %1\n" \ - " cbnz %1, 2f\n" \ - " st" #rel "xp %w0, %5, %6, %2\n" \ - " cbnz %w0, 1b\n" \ - " " #mb "\n" \ - "2:" \ - : "=&r" (tmp), "=&r" (ret), "+Q" (*(unsigned long *)ptr) \ - : "r" (old1), "r" (old2), "r" (new1), "r" (new2) \ + __LL_SC_CMPXCHG_DBL_OP(mb, rel) \ + : [tmp]"=&r" (tmp), [ret]"=&r" (ret), \ + [v]"+Q" (*(unsigned long *)ptr) \ + : [old1]"r" (old1), [old2]"r" (old2), [new1]"r" (new1), \ + [new2]"r" (new2) \ : cl); \ \ return ret; \
Signature · 1 line
2.17.1
PATCH 2/3 arm64/atomics: use subsections for out of line LL/SC alternatives
In reply to Ard BiesheuvelWhen building with support for LSE atomics, the LL/SC alternative are emitted as functions, and 'bl' instructions are patched into the code stream to call them instead of the LSE instructions when the CPU does not support LSE.
This has some downsides: the LSE atomics are coded up in such a way that the asm input registers are always aligned with the prototypes of the out of line alternatives, limiting the freedom the compiler to allocate registers. Also, the registers x16, x17 and x30 need to be added to the clobber list, given that those may be corrupted by a function call. Unfortunately, we still end up with stack spills in the out of line alternatives that requires 3 temp registers, due to the fact that x30 needs to be preserved to be able to return.
Also, doing function calls from inline assembler is likely to become more of a maintenance burden going forward, due to the introduction of live patching (which requires reliable stack traces), code flow integrity, Clang support etc etc.
So instead, let's use subsections to emit the out of line alternatives, but as part of the inline asm expansion so that the compiler is in charge of the register allocation. The number of allocated registers still exceeds what LSE actually needs, but the compiler is free to select them, and the out of line alternatives will no longer require to spill x30.
--- arch/arm64/include/asm/atomic_ll_sc.h | 3 + arch/arm64/include/asm/atomic_lse.h | 558 ++++++++++---------- arch/arm64/include/asm/lse.h | 4 - 3 files changed, 285 insertions(+), 280 deletions(-)
arch/arm64/include/asm/atomic_ll_sc.h +3 -0
@@ -82,6 +82,8 @@ " " #mb " \n" \ "2:" \ +#if !(defined(CONFIG_ARM64_LSE_ATOMICS ) && defined(CONFIG_AS_LSE )) +#define ATOMIC_OP (op, asm_op) \ __LL_SC_INLINE void \ __LL_SC_PREFIX(atomic_##op(int i, atomic_t *v)) \
@@ -332,4 +334,5 @@ __CMPXCHG_DBL(_mb, dmb ish, l, "memory")#undef __CMPXCHG_DBL +#endif /* !(defined(CONFIG_ARM64_LSE_ATOMICS) && defined(CONFIG_AS_LSE)) */ #endif /* __ASM_ATOMIC_LL_SC_H */
arch/arm64/include/asm/atomic_lse.h +282 -276
792 lines not on this page · raw message
arch/arm64/include/asm/lse.h +0 -4
@@ -27,10 +27,6 @@ __asm__(".arch_extension lse");#define __LL_SC_PREFIX (x) __ll_sc_##x#define __LL_SC_EXPORT (x) EXPORT_SYMBOL(__LL_SC_PREFIX(x)) -/* Macro for constructing calls to out-of-line ll/sc atomics */ -#define __LL_SC_CALL (op) "bl\t" __stringify(__LL_SC_PREFIX(op)) "\n" -#define __LL_SC_CLOBBERS "x16", "x17", "x30" -/* In-line patching at runtime */ #define ARM64_LSE_ATOMIC_INSN (llsc, lse) \ ALTERNATIVE(llsc, lse, ARM64_HAS_LSE_ATOMICS)
Signature · 1 line
2.17.1
PATCH 3/3 arm64/atomics: remove out of line LL/SC alternatives
In reply to Ard BiesheuvelNow that we are no longer emitting calls to the out of line LL/SC alternatives from the LSE implementation, drop the exports, the prototype decorations and the Makefile rules that build the object file that contains them.
--- arch/arm64/include/asm/atomic_ll_sc.h | 48 ++++++-------------- arch/arm64/include/asm/lse.h | 9 ---- arch/arm64/lib/Makefile | 19 -------- arch/arm64/lib/atomic_ll_sc.c | 3 -- 4 files changed, 15 insertions(+), 64 deletions(-)
arch/arm64/include/asm/atomic_ll_sc.h +15 -33
@@ -85,8 +85,7 @@#if !(defined(CONFIG_ARM64_LSE_ATOMICS ) && defined(CONFIG_AS_LSE ))#define ATOMIC_OP (op, asm_op) \ -__LL_SC_INLINE void \ -__LL_SC_PREFIX(atomic_##op(int i, atomic_t *v)) \ +static inline void atomic_##op(int i, atomic_t *v)) \ { \ unsigned long tmp; \ int result; \
@@ -96,11 +95,9 @@ __LL_SC_PREFIX(atomic_##op(int i, atomic_t *v)) \ : [res]"=&r" (result), [tmp]"=&r" (tmp), [v]"+Q" (v->counter) \ : [i]"Ir" (i)); \ } \ -__LL_SC_EXPORT(atomic_##op);#define ATOMIC_OP_RETURN (name, mb, acq, rel, cl, op, asm_op) \ -__LL_SC_INLINE int \ -__LL_SC_PREFIX(atomic_##op##_return##name(int i, atomic_t *v)) \ +static inline int atomic_##op##_return##name(int i, atomic_t *v) \ { \ unsigned long tmp; \ int result; \
@@ -113,11 +110,9 @@ __LL_SC_PREFIX(atomic_##op##_return##name(int i, atomic_t *v)) \ \return result; \ } \ -__LL_SC_EXPORT(atomic_##op##_return##name);#define ATOMIC_FETCH_OP (name, mb, acq, rel, cl, op, asm_op) \ -__LL_SC_INLINE int \ -__LL_SC_PREFIX(atomic_fetch_##op##name(int i, atomic_t *v)) \ +static inline int atomic_fetch_##op##name(int i, atomic_t *v) \ { \ unsigned long tmp; \ int val, result; \
@@ -131,7 +126,6 @@ __LL_SC_PREFIX(atomic_fetch_##op##name(int i, atomic_t *v)) \ \return result; \ } \ -__LL_SC_EXPORT(atomic_fetch_##op##name);#define ATOMIC_OPS (...) \ ATOMIC_OP(__VA_ARGS__) \
@@ -166,8 +160,7 @@ ATOMIC_OPS(xor, eor)#undef ATOMIC_OP#define ATOMIC64_OP (op, asm_op) \ -__LL_SC_INLINE void \ -__LL_SC_PREFIX(atomic64_##op(long i, atomic64_t *v)) \ +static inline void atomic64_##op(long i, atomic64_t *v) \ { \ long result; \ unsigned long tmp; \
@@ -177,11 +170,9 @@ __LL_SC_PREFIX(atomic64_##op(long i, atomic64_t *v)) \ : [res]"=&r" (result), [tmp]"=&r" (tmp), [v]"+Q" (v->counter) \ : [i]"Ir" (i)); \ } \ -__LL_SC_EXPORT(atomic64_##op);#define ATOMIC64_OP_RETURN (name, mb, acq, rel, cl, op, asm_op) \ -__LL_SC_INLINE long \ -__LL_SC_PREFIX(atomic64_##op##_return##name(long i, atomic64_t *v)) \ +static inline long atomic64_##op##_return##name(long i, atomic64_t *v) \ { \ long result; \ unsigned long tmp; \
@@ -194,11 +185,9 @@ __LL_SC_PREFIX(atomic64_##op##_return##name(long i, atomic64_t *v)) \ \return result; \ } \ -__LL_SC_EXPORT(atomic64_##op##_return##name);#define ATOMIC64_FETCH_OP (name, mb, acq, rel, cl, op, asm_op) \ -__LL_SC_INLINE long \ -__LL_SC_PREFIX(atomic64_fetch_##op##name(long i, atomic64_t *v)) \ +static inline long atomic64_fetch_##op##name(long i, atomic64_t *v) \ { \ long result, val; \ unsigned long tmp; \
@@ -212,7 +201,6 @@ __LL_SC_PREFIX(atomic64_fetch_##op##name(long i, atomic64_t *v)) \ \return result; \ } \ -__LL_SC_EXPORT(atomic64_fetch_##op##name);#define ATOMIC64_OPS (...) \ ATOMIC64_OP(__VA_ARGS__) \
@@ -246,8 +234,7 @@ ATOMIC64_OPS(xor, eor)#undef ATOMIC64_OP_RETURN#undef ATOMIC64_OP -__LL_SC_INLINE long -__LL_SC_PREFIX (atomic64_dec_if_positive (atomic64_t *v)) +static inline long atomic64_dec_if_positive (atomic64_t *v) {long result;unsigned long tmp;
@@ -267,13 +254,11 @@ __LL_SC_PREFIX(atomic64_dec_if_positive(atomic64_t *v))return result; } -__LL_SC_EXPORT (atomic64_dec_if_positive);#define __CMPXCHG_CASE (w, sz, name, mb, acq, rel, cl) \ -__LL_SC_INLINE unsigned long \ -__LL_SC_PREFIX(__cmpxchg_case_##name(volatile void *ptr, \ - unsigned long old, \ - unsigned long new)) \ +static inline unsigned long __cmpxchg_case_##name(volatile void *ptr, \ + unsigned long old, \ + unsigned long new) \ { \ unsigned long tmp, oldval; \ \
@@ -286,7 +271,6 @@ __LL_SC_PREFIX(__cmpxchg_case_##name(volatile void *ptr, \ \return oldval; \ } \ -__LL_SC_EXPORT(__cmpxchg_case_##name); __CMPXCHG_CASE(w, b, 1, , , , ) __CMPXCHG_CASE(w, h, 2, , , , )
@@ -308,12 +292,11 @@ __CMPXCHG_CASE( , , mb_8, dmb ish, , l, "memory")#undef __CMPXCHG_CASE#define __CMPXCHG_DBL (name, mb, rel, cl) \ -__LL_SC_INLINE long \ -__LL_SC_PREFIX(__cmpxchg_double##name(unsigned long old1, \ - unsigned long old2, \ - unsigned long new1, \ - unsigned long new2, \ - volatile void *ptr)) \ +static inline long __cmpxchg_double##name(unsigned long old1, \ + unsigned long old2, \ + unsigned long new1, \ + unsigned long new2, \ + volatile void *ptr) \ { \ unsigned long tmp, ret; \ \
@@ -327,7 +310,6 @@ __LL_SC_PREFIX(__cmpxchg_double##name(unsigned long old1, \ \return ret; \ } \ -__LL_SC_EXPORT(__cmpxchg_double##name); __CMPXCHG_DBL( , , , ) __CMPXCHG_DBL(_mb, dmb ish, l, "memory")
arch/arm64/include/asm/lse.h +0 -9
@@ -22,11 +22,6 @@ __asm__(".arch_extension lse" ); -/* Move the ll/sc atomics out-of-line */ -#define __LL_SC_INLINE notrace -#define __LL_SC_PREFIX (x) __ll_sc_##x -#define __LL_SC_EXPORT (x) EXPORT_SYMBOL(__LL_SC_PREFIX(x)) -/* In-line patching at runtime */ #define ARM64_LSE_ATOMIC_INSN (llsc, lse) \ ALTERNATIVE(llsc, lse, ARM64_HAS_LSE_ATOMICS)
@@ -42,10 +37,6 @@ __asm__(".arch_extension lse");#else /* __ASSEMBLER__ */ -#define __LL_SC_INLINE static inline -#define __LL_SC_PREFIX (x) x -#define __LL_SC_EXPORT (x) -#define ARM64_LSE_ATOMIC_INSN (llsc, lse) llsc#endif /* __ASSEMBLER__ */
arch/arm64/lib/Makefile +0 -19
@@ -5,25 +5,6 @@ lib-y := clear_user.o delay.o copy_from_user.o \ memcmp.o strcmp.o strncmp.o strlen.o strnlen.o \ strchr.o strrchr.o tishift.o -# Tell the compiler to treat all general purpose registers (with the -# exception of the IP registers, which are already handled by the caller -# in case of a PLT) as callee-saved, which allows for efficient runtime -# patching of the bl instruction in the caller with an atomic instruction -# when supported by the CPU. Result and argument registers are handled -# correctly, based on the function prototype. -lib-$(CONFIG_ARM64_LSE_ATOMICS) += atomic_ll_sc.o -CFLAGS_atomic_ll_sc.o := -ffixed-x1 -ffixed-x2 \ - -ffixed-x3 -ffixed-x4 -ffixed-x5 -ffixed-x6 \ - -ffixed-x7 -fcall-saved-x8 -fcall-saved-x9 \ - -fcall-saved-x10 -fcall-saved-x11 -fcall-saved-x12 \ - -fcall-saved-x13 -fcall-saved-x14 -fcall-saved-x15 \ - -fcall-saved-x18 -fomit-frame-pointer -CFLAGS_REMOVE_atomic_ll_sc.o := -pg -GCOV_PROFILE_atomic_ll_sc.o := n -KASAN_SANITIZE_atomic_ll_sc.o := n -KCOV_INSTRUMENT_atomic_ll_sc.o := n -UBSAN_SANITIZE_atomic_ll_sc.o := n - lib-$(CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE) += uaccess_flushcache.o obj-$(CONFIG_CRC32) += crc32.o
arch/arm64/lib/atomic_ll_sc.c deleted +0 -3
@@ -1,3 +0,0 @@ -#include <asm/atomic.h> -#define __ARM64_IN_ATOMIC_IMPL -#include <asm/atomic_ll_sc.h>
Signature · 1 line
2.17.1
cover arm64: use subsections instead of function calls for LL/SC fallbacks
In reply to Ard BiesheuvelHi Ard,
Quoted 7 lines · Ard Biesheuvel
Refactor the LL/SC atomics code so we can emit the LL/SC fallbacks for the LSE atomics as subsections that get instantiated at each call site rather than as out of line functions that get called from inline asm (without the awareness of the compiler)
This should allow slightly better LSE code, and removes stack spilling and potential PLT indirection for the LL/SC fallbacks.
Thanks, I much prefer using subsections to the current approach. However, a downside of your patches is that the some of the asm operands passed to the LSE implementation are redundant, for example, in the fetch-ops:
" " #lse_op #ac #rl " %w[i], %w[res], %[v]") \ : [res]"=&r" (result), [val]"=&r" (val), [tmp]"=&r" (tmp), \ [v]"+Q" (v->counter) \
I'd have thought we could avoid this by splitting up the asms and using a static key to dispatch them. For example, the really crude hacking below resulted in reasonable code generation:
000000000000040 <will_atomic_add>:
40: 14000004 b 50 <will_atomic_add+0x10> // Patched with NOP once features are determined 44: 14000007 b 60 <will_atomic_add+0x20> // Patched with NOP if LSE 48: b820003f stadd w0, [x1] 4c: d65f03c0 ret 50: 90000002 adrp x2, 0 <cpu_hwcaps> 54: f9400042 ldr x2, [x2] 58: 721b005f tst w2, #0x20 5c: 54ffff61 b.ne 48 <will_atomic_add+0x8> // b.any 60: 14000002 b 68 <will_atomic_add+0x28> 64: d65f03c0 ret 68: f9800031 prfm pstl1strm, [x1] 6c: 885f7c22 ldxr w2, [x1] 70: 0b000042 add w2, w2, w0 74: 88037c22 stxr w3, w2, [x1] 78: 35ffffa3 cbnz w3, 6c <will_atomic_add+0x2c> 7c: 17fffffa b 64 <will_atomic_add+0x24>
So if we tweaked the existing code so that we can generate the LL/SC versions either in a subsection or not depending on LSE, then we could probably play this sort of trick using a static key.
What do you think?
Will
--->8
arch/arm64/include/asm/cpufeature.h +1 -1
@@ -369,7 +369,7 @@ static inline bool __cpus_have_const_cap(int num) {if (num >=ARM64_NCAPS )return false; -return static_branch_unlikely (&cpu_hwcap_keys[num]); +return static_branch_likely (&cpu_hwcap_keys[num]); }static inline bool cpus_have_cap (unsigned int num)
arch/arm64/kernel/setup.c +33 -0
@@ -405,3 +405,36 @@ static int __init register_kernel_offset_dumper(void)return 0 ; }__initcall (register_kernel_offset_dumper); + +static inline void ll_sc_atomic_add (int i,atomic_t *v) +{ +unsigned long tmp; +int result; + + asmvolatile ( +" b 3f\n" +" .subsection 1\n" +"3: prfm pstl1strm, %2\n" +"1: ldxr %w0, %2\n" +" add %w0, %w0, %w3\n" +" stxr %w1, %w0, %2\n" +" cbnz %w1, 1b\n" +" b 4f\n" +" .previous\n" +"4:" + :"=&r" (result),"=&r" (tmp),"+Q" (v->counter) + :"Ir" (i)); +} + +void will_atomic_add (int i,atomic_t *v) +{ +if (!cpus_have_const_cap (ARM64_HAS_LSE_ATOMICS )) { +ll_sc_atomic_add (i, v); + }else { + asmvolatile ("stadd %w[i], %[v]" + : [v]"+Q" (v->counter) + : [i]"r" (i)); + } + +return ; +}
cover arm64: use subsections instead of function calls for LL/SC fallbacks
Quoted 12 lines · Will Deacon
Hi Ard,
Quoted 7 lines · Ard Biesheuvel
Refactor the LL/SC atomics code so we can emit the LL/SC fallbacks for the LSE atomics as subsections that get instantiated at each call site rather than as out of line functions that get called from inline asm (without the awareness of the compiler)
This should allow slightly better LSE code, and removes stack spilling and potential PLT indirection for the LL/SC fallbacks.
Quoted 8 lines
Thanks, I much prefer using subsections to the current approach. However, a downside of your patches is that the some of the asm operands passed to the LSE implementation are redundant, for example, in the fetch-ops:
" " #lse_op #ac #rl " %w[i], %w[res], %[v]") \ : [res]"=&r" (result), [val]"=&r" (val), [tmp]"=&r" (tmp), \ [v]"+Q" (v->counter) \
That applies to most of the ops, but note that
- compared to the current situation, where x16, x17 and x30 are always considered clobbered, the situation actually improves in some cases, and doesn't get worse in any of them
- i tweaked the patterns a bit so that, for instance in the example you quote, [i] is now only an input register, which may permit the compiler to reuse a live register holding the value of i
Quoted 22 lines
I'd have thought we could avoid this by splitting up the asms and using a static key to dispatch them. For example, the really crude hacking below resulted in reasonable code generation:
000000000000040 <
will_atomic_add>:40: 14000004 b 50 <will_atomic_add+0x10> // Patched with NOP once features are determined 44: 14000007 b 60 <will_atomic_add+0x20> // Patched with NOP if LSE 48: b820003f stadd w0, [x1] 4c: d65f03c0 ret 50: 90000002 adrp x2, 0 <cpu_hwcaps> 54: f9400042 ldr x2, [x2] 58: 721b005f tst w2, #0x20 5c: 54ffff61 b.ne 48 <will_atomic_add+0x8> // b.any 60: 14000002 b 68 <will_atomic_add+0x28> 64: d65f03c0 ret 68: f9800031 prfm pstl1strm, [x1] 6c: 885f7c22 ldxr w2, [x1] 70: 0b000042 add w2, w2, w0 74: 88037c22 stxr w3, w2, [x1] 78: 35ffffa3 cbnz w3, 6c <will_atomic_add+0x2c> 7c: 17fffffa b 64 <will_atomic_add+0x24>
Quoted 6 lines
So if we tweaked the existing code so that we can generate the LL/SC versions either in a subsection or not depending on LSE, then we could probably play this sort of trick using a static key.
What do you think?
If you inline that function, the register allocation will be exactly the same, no? The compiler will still need to spill whatever is in the registers that the LL/SC code may use if executed. Or will it only do so when taking that path?
Also, AIUI, the point was to optimize for LSE, and fall back to LL/SC (potentially taking a slight performance hit in doing so). Does the compiler actually move the unlikely path out of line? I can't tell from just this function, but if it doesn't, the ret at 0x4c will become another branch in reality, branching over the LL/SC code
So it really depends on how well the compiler behaves when emitting these inline. Does it really help that much to use fewer registers? Enough to justify adding unconditional branches, and potentially lower I-cache utilization?
Quoted 56 lines
--->8
arch/arm64/include/asm/cpufeature.h +1
-1@@ -369,7 +369,7 @@ static inline bool __cpus_have_const_cap(int num) {if (num >=ARM64_NCAPS )return false; -return static_branch_unlikely (&cpu_hwcap_keys[num]); +return static_branch_likely (&cpu_hwcap_keys[num]); }static inline bool cpus_have_cap (unsigned int num)arch/arm64/kernel/setup.c +33
-0@@ -405,3 +405,36 @@ static int __init register_kernel_offset_dumper(void)return 0 ; }__initcall (register_kernel_offset_dumper); + +static inline void ll_sc_atomic_add (int i,atomic_t *v) +{ +unsigned long tmp; +int result; + + asmvolatile ( +" b 3f\n" +" .subsection 1\n" +"3: prfm pstl1strm, %2\n" +"1: ldxr %w0, %2\n" +" add %w0, %w0, %w3\n" +" stxr %w1, %w0, %2\n" +" cbnz %w1, 1b\n" +" b 4f\n" +" .previous\n" +"4:" + :"=&r" (result),"=&r" (tmp),"+Q" (v->counter) + :"Ir" (i)); +} + +void will_atomic_add (int i,atomic_t *v) +{ +if (!cpus_have_const_cap (ARM64_HAS_LSE_ATOMICS )) { +ll_sc_atomic_add (i, v); + }else { + asmvolatile ("stadd %w[i], %[v]" + : [v]"+Q" (v->counter) + : [i]"r" (i)); + } + +return ; +}
cover arm64: use subsections instead of function calls for LL/SC fallbacks
Quoted 57 lines · Ard Biesheuvel
Quoted 12 lines · Will Deacon
Hi Ard,
Quoted 7 lines · Ard Biesheuvel
Refactor the LL/SC atomics code so we can emit the LL/SC fallbacks for the LSE atomics as subsections that get instantiated at each call site rather than as out of line functions that get called from inline asm (without the awareness of the compiler)
This should allow slightly better LSE code, and removes stack spilling and potential PLT indirection for the LL/SC fallbacks.
Quoted 8 lines
Thanks, I much prefer using subsections to the current approach. However, a downside of your patches is that the some of the asm operands passed to the LSE implementation are redundant, for example, in the fetch-ops:
" " #lse_op #ac #rl " %w[i], %w[res], %[v]") \ : [res]"=&r" (result), [val]"=&r" (val), [tmp]"=&r" (tmp), \ [v]"+Q" (v->counter) \That applies to most of the ops, but note that
- compared to the current situation, where x16, x17 and x30 are always considered clobbered, the situation actually improves in some cases, and doesn't get worse in any of them
- i tweaked the patterns a bit so that, for instance in the example you quote, [i] is now only an input register, which may permit the compiler to reuse a live register holding the value of iQuoted 26 lines
I'd have thought we could avoid this by splitting up the asms and using a static key to dispatch them. For example, the really crude hacking below resulted in reasonable code generation:
000000000000040 <
will_atomic_add>:40: 14000004 b 50 <will_atomic_add+0x10> // Patched with NOP once features are determined 44: 14000007 b 60 <will_atomic_add+0x20> // Patched with NOP if LSE 48: b820003f stadd w0, [x1] 4c: d65f03c0 ret 50: 90000002 adrp x2, 0 <cpu_hwcaps> 54: f9400042 ldr x2, [x2] 58: 721b005f tst w2, #0x20 5c: 54ffff61 b.ne 48 <will_atomic_add+0x8> // b.any 60: 14000002 b 68 <will_atomic_add+0x28> 64: d65f03c0 ret 68: f9800031 prfm pstl1strm, [x1] 6c: 885f7c22 ldxr w2, [x1] 70: 0b000042 add w2, w2, w0 74: 88037c22 stxr w3, w2, [x1] 78: 35ffffa3 cbnz w3, 6c <will_atomic_add+0x2c> 7c: 17fffffa b 64 <will_atomic_add+0x24>So if we tweaked the existing code so that we can generate the LL/SC versions either in a subsection or not depending on LSE, then we could probably play this sort of trick using a static key.
Quoted 8 lines
Quoted 2 lines
What do you think?
If you inline that function, the register allocation will be exactly the same, no? The compiler will still need to spill whatever is in the registers that the LL/SC code may use if executed. Or will it only do so when taking that path?
Never mind, I just noticed that the LL/SC code is still in a subsection, so it will be moved out of the code path in any case.
Quoted 11 lines
Also, AIUI, the point was to optimize for LSE, and fall back to LL/SC (potentially taking a slight performance hit in doing so). Does the compiler actually move the unlikely path out of line? I can't tell from just this function, but if it doesn't, the ret at
0x4cwill become another branch in reality, branching over the LL/SC codeSo it really depends on how well the compiler behaves when emitting these inline. Does it really help that much to use fewer registers? Enough to justify adding unconditional branches, and potentially lower I-cache utilization?
OK, so the LL/SC code is not a concern. What about the const cap checks? Will they pollute the I-cache or be moved out of line as well?
In general, I think your approach looks cleaner and more maintainable in any case, I'm just skeptical that it will make a noticeable difference in terms of register pressure.
Quoted 56 lines
Quoted 56 lines
--->8
arch/arm64/include/asm/cpufeature.h +1
-1@@ -369,7 +369,7 @@ static inline bool __cpus_have_const_cap(int num) {if (num >=ARM64_NCAPS )return false; -return static_branch_unlikely (&cpu_hwcap_keys[num]); +return static_branch_likely (&cpu_hwcap_keys[num]); }static inline bool cpus_have_cap (unsigned int num)arch/arm64/kernel/setup.c +33
-0@@ -405,3 +405,36 @@ static int __init register_kernel_offset_dumper(void)return 0 ; }__initcall (register_kernel_offset_dumper); + +static inline void ll_sc_atomic_add (int i,atomic_t *v) +{ +unsigned long tmp; +int result; + + asmvolatile ( +" b 3f\n" +" .subsection 1\n" +"3: prfm pstl1strm, %2\n" +"1: ldxr %w0, %2\n" +" add %w0, %w0, %w3\n" +" stxr %w1, %w0, %2\n" +" cbnz %w1, 1b\n" +" b 4f\n" +" .previous\n" +"4:" + :"=&r" (result),"=&r" (tmp),"+Q" (v->counter) + :"Ir" (i)); +} + +void will_atomic_add (int i,atomic_t *v) +{ +if (!cpus_have_const_cap (ARM64_HAS_LSE_ATOMICS )) { +ll_sc_atomic_add (i, v); + }else { + asmvolatile ("stadd %w[i], %[v]" + : [v]"+Q" (v->counter) + : [i]"r" (i)); + } + +return ; +}