Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20261019155659.GA23438@brightrain.aerifal.cx>
Date: Mon, 19 Oct 2026 11:56:59 -0400
From: Rich Felker <dalias@...c.org>
To: musl@...ts.openwall.com
Subject: Re: [PATCH v2] aarch64: workaround for syscalls with sve

On Mon, Oct 19, 2026 at 04:50:10PM +0200, Szabolcs Nagy wrote:
> reported to gcc, attached v2 with external asm
> 

> >From 0493ed31c748f9b3725c62048a164419a11517c7 Mon Sep 17 00:00:00 2001
> From: Szabolcs Nagy <nsz@...t70.net>
> Date: Sun, 4 Oct 2026 15:04:29 +0000
> Subject: [PATCH v2] aarch64: workaround for syscalls with sve
> 
> SVE registers z0..z31 overlap with the base isa registers d0..d31
> (bottom 64bits) and q0..q31 (bottom 128bits). SVE also has an ffr
> and p0..p15 predicate registers.
> 
> arm64 linux syscalls preserve q0..q31 but not the top bits of z0..z31
> (if they are >128bits) nor ffr nor p0..p15. the c call abi requires
> preserving d8..d15. so the syscall inline asm requires sve z0..z31,
> p0..p15 and ffr clobbers, but then d8..d15 is spilled at every syscall
> call site defeating the purpose of inlining. unfortunately there is no
> inline asm syntax for specifying clobbers for z0..z31 top bits only.
> 
> the lack of sve clobbers affect clang, it uses p regs around syscalls.
> so use extern syscalls as a workaround. adding noinline is not enough
> due to inter-procedural optimizations and gives bigger code. using c
> code for the syscalls is not lto safe within libc.so. (for lto libc.a
> the workaround would be required even if musl is built without sve as
> user might use it). building musl with sve is not recommended for now
> (it does extra reg shuffling at syscalls). auto disabling sve is hard
> to do portably.
> 
> with gcc-16.1.0 -march=armv8-a+sve, libc.so .text increases by 3469
> and there are 486 new refs to __syscall[0-6] across *.lo
> 
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128010
> https://www.kernel.org/doc/html/latest/arch/arm64/sve.html#system-call-behaviour
> https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#simd-and-floating-point-registers
> ---
> ---
> v2:
> - use extern __syscall*.S
> - reword commit msg
> - gcc upstream bug
> - tested with libc-test + qemu-user
> 
>  arch/aarch64/syscall_arch.h       | 11 +++++++++++
>  src/internal/__syscall0.c         |  0
>  src/internal/__syscall1.c         |  0
>  src/internal/__syscall2.c         |  0
>  src/internal/__syscall3.c         |  0
>  src/internal/__syscall4.c         |  0
>  src/internal/__syscall5.c         |  0
>  src/internal/__syscall6.c         |  0

These aren't needed. src/dir/$ARCH/foo.[csS] replaces src/dir/foo.c if
the latter exists, but if it doesn't, src/dir/$ARCH/foo.[csS] just
gets added.

>  src/internal/aarch64/__syscall0.S |  8 ++++++++
>  src/internal/aarch64/__syscall1.S |  9 +++++++++
>  src/internal/aarch64/__syscall2.S | 10 ++++++++++
>  src/internal/aarch64/__syscall3.S | 11 +++++++++++
>  src/internal/aarch64/__syscall4.S | 12 ++++++++++++
>  src/internal/aarch64/__syscall5.S | 13 +++++++++++++
>  src/internal/aarch64/__syscall6.S | 14 ++++++++++++++

I'm not sure it makes sense to separate these out into 6 files when
they're each so small. It's a tradeoff between a few bytes in linked
output and moving further towards command line limits running ar and
the libc.so link. It might be okay, just something to consider.

> diff --git a/arch/aarch64/syscall_arch.h b/arch/aarch64/syscall_arch.h
> index 504983aa..33508456 100644
> --- a/arch/aarch64/syscall_arch.h
> +++ b/arch/aarch64/syscall_arch.h
> @@ -1,6 +1,16 @@
>  #define __SYSCALL_LL_E(x) (x)
>  #define __SYSCALL_LL_O(x) (x)
>  
> +#if __ARM_FEATURE_SVE
> +/* workaround for no clobber syntax for z reg top bits */
> +long __syscall0(long n);
> +long __syscall1(long n, long a);
> +long __syscall2(long n, long a, long b);
> +long __syscall3(long n, long a, long b, long c);
> +long __syscall4(long n, long a, long b, long c, long d);
> +long __syscall5(long n, long a, long b, long c, long d, long e);
> +long __syscall6(long n, long a, long b, long c, long d, long e, long f);
> +#else
> [...]
> diff --git a/src/internal/aarch64/__syscall6.S b/src/internal/aarch64/__syscall6.S
> new file mode 100644
> index 00000000..7d5801a0
> --- /dev/null
> +++ b/src/internal/aarch64/__syscall6.S
> @@ -0,0 +1,14 @@
> +#if __ARM_FEATURE_SVE
> +.global __syscall6
> +.type __syscall6,%function
> +__syscall6:
> +	mov x8, x0
> +	mov x0, x1
> +	mov x1, x2
> +	mov x2, x3
> +	mov x3, x4
> +	mov x4, x5
> +	mov x5, x6
> +	svc 0
> +	ret
> +#endif

I wonder if we should, instead of just using __syscallN as the
external name, have alt inlines of __syscallN that call __syscallN_asm
with nr moved to the end of the argument list. Then all __syscallN_asm
look like:

__syscall6:
	mov x8, x6
	svc 0
	ret

This probably also makes the callers nicer, where the arguments to the
public libc function are already in the right order for the syscall,
so we don't have, e.g., fchownat moving all the argument registers up
by one only to have __syscall5 moving them back down by 1.

In the big picture it doesn't make much difference, so I'm not really
particular on what we do here, and we could always change it later.

Rich

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.