[Patch, AArch64] Optimized strcpy

Richard Earnshaw rearnsha@arm.com
Wed Dec 17 12:12:00 GMT 2014


This patch contains an optimized implementation of strcpy for AArch64
systems.  Benchmarking shows that it is approximately 20-25% faster than
the generic implementation across the board.

R.

<date>  Richard Earnshaw  <rearnsha@arm.com>

	* sysdeps/aarch64/strcpy.S: New file.
-------------- next part --------------
diff --git a/sysdeps/aarch64/strcpy.S b/sysdeps/aarch64/strcpy.S
new file mode 100644
index 0000000..1cdf2a1
--- /dev/null
+++ b/sysdeps/aarch64/strcpy.S
@@ -0,0 +1,202 @@
+/* Copyright (C) 2013-2014 Free Software Foundation, Inc.
+
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library.  If not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <sysdep.h>
+
+/* Assumptions:
+ *
+ * ARMv8-a, AArch64, unaligned accesses
+ */
+
+/* Arguments and results.  */
+#define dstin		x0
+#define src		x1
+
+/* Locals and temporaries.  */
+#define dst		x2
+#define data1		x3
+#define data1w		w3
+#define data2		x4
+#define has_nul1	x5
+#define has_nul2	x6
+#define tmp1		x7
+#define tmp2		x8
+#define tmp3		x9
+#define tmp4		x10
+#define zeroones	x11
+
+#define REP8_01 0x0101010101010101
+#define REP8_7f 0x7f7f7f7f7f7f7f7f
+#define REP8_80 0x8080808080808080
+
+	/* Start of critial section -- keep to one 64Byte cache line.  */
+ENTRY_ALIGN (strcpy,6)
+	mov	zeroones, #REP8_01
+	mov	dst, dstin
+	ands	tmp1, src, #15
+	b.ne	L(misaligned)
+	/* NUL detection works on the principle that (X - 1) & (~X) & 0x80
+	   (=> (X - 1) & ~(X | 0x7f)) is non-zero iff a byte is zero, and
+	   can be done in parallel across the entire word.  */
+	/* The inner loop deals with two Dwords at a time.  This has a
+	   slightly higher start-up cost, but we should win quite quickly,
+	   especially on cores with a high number of issue slots per
+	   cycle, as we get much better parallelism out of the operations.  */
+	b	L(first_pass)
+L(main_loop):
+	stp	data1, data2, [dst], #16
+L(startloop_fast):
+	ldp	data1, data2, [src], #16
+	sub	tmp1, data1, zeroones
+	orr	tmp2, data1, #REP8_7f
+	sub	tmp3, data2, zeroones
+	orr	tmp4, data2, #REP8_7f
+	bic	has_nul1, tmp1, tmp2
+	bics	has_nul2, tmp3, tmp4
+	ccmp	has_nul1, #0, #0, eq	/* NZCV = 0000  */
+	b.eq	L(main_loop)
+	/* End of critical section -- keep to one 64Byte cache line.  */
+
+	cbnz	has_nul1, L(nul_in_data1_fast)
+L(nul_in_data2_fast):
+	str	data1, [dst], #8
+L(nul_in_data2_fast_after_d1):
+	/* For a NUL in data2, we always know that we've moved at least 8
+	   bytes, so no need for a slow path.  */
+#ifdef __AARCH64EB__
+	/* For big-endian only, carry propagation means we can't trust
+	   the MSB of the syndrome value calculated above (the byte
+	   sequence 01 00 will generate a syndrome of 80 80 rather than
+	   00 80).  We get around this by byte-swapping the data and
+	   re-calculating.  */
+	rev	data2, data2
+	sub	tmp1, data2, zeroones
+	orr	tmp2, data2, #REP8_7f
+	bic	has_nul2, tmp1, tmp2
+#endif
+	rev	has_nul2, has_nul2
+	sub	src, src, #(8+7)
+	clz	has_nul2, has_nul2
+	lsr	has_nul2, has_nul2, #3		/* Bits to bytes.  */
+	sub	dst, dst, #7
+	ldr	data2, [src, has_nul2]
+	str	data2, [dst, has_nul2]
+	ret
+
+L(nul_in_data1_fast):
+	/* Since we know we've already copied at least 8 bytes, we can
+	   safely handle the tail with one misaligned dword move.  To do this
+	   we calculate the location of the trailing NUL byte and go seven
+	   bytes back from that.  */
+#ifdef __AARCH64EB__
+	/* For big-endian only, carry propagation means we can't trust
+	   the MSB of the syndrome value calculated above (the byte
+	   sequence 01 00 will generate a syndrome of 80 80 rather than
+	   00 80).  We get around this by byte-swapping the data and
+	   re-calculating.  */
+	rev	data1, data1
+	sub	tmp1, data1, zeroones
+	orr	tmp2, data1, #REP8_7f
+	bic	has_nul1, tmp1, tmp2
+#endif
+	rev	has_nul1, has_nul1
+	sub	src, src, #(16+7)
+	clz	has_nul1, has_nul1
+	lsr	has_nul1, has_nul1, #3		/* Bits to bytes.  */
+	sub	dst, dst, #7
+	ldr	data1, [src, has_nul1]
+	str	data1, [dst, has_nul1]
+	ret
+
+L(first_pass):
+	ldp	data1, data2, [src], #16
+	sub	tmp1, data1, zeroones
+	orr	tmp2, data1, #REP8_7f
+	sub	tmp3, data2, zeroones
+	orr	tmp4, data2, #REP8_7f
+	bic	has_nul1, tmp1, tmp2
+	bics	has_nul2, tmp3, tmp4
+	ccmp	has_nul1, #0, #0, eq	/* NZCV = 0000  */
+	b.eq	L(main_loop)
+
+	cbz	has_nul1, L(nul_in_data2_fast)
+L(nul_in_data1):
+	/* Slow path.  We can't be sure we've moved at least 8 bytes, so
+	   fall back to a slow byte-by byte store of the bits already
+	   loaded.
+
+	   The worst case when coming through this path is that we've had
+	   to copy seven individual bytes to get to alignment and we then
+	   have to copy another seven (eight for big-endian) again here.
+	   We could try to detect that case (and any case where more than
+	   eight bytes have to be copied), but it really doesn't seem
+	   worth it.  */
+#ifdef __AARCH64EB__
+	rev	data1, data1
+#else
+	/* On little-endian, we can easily check if the NULL byte was
+	   in the last byte of the Dword.  For big-endian we'd have to
+	   recalculate the syndrome, which is unlikely to be worth it.  */
+	lsl	has_nul1, has_nul1, #8
+	cbnz	has_nul1, 1f
+	str	data1, [dst]
+	ret
+#endif
+1:
+	strb	data1w, [dst], #1
+	tst	data1, #0xff
+	lsr	data1, data1, #8
+	b.ne	1b
+L(done):
+	ret
+
+L(misaligned):
+	cmp	tmp1, #8
+	b.ge	2f
+	/* There's at least one Dword before we reach alignment, so we can
+	   deal with that efficiently.  */
+	ldr	data1, [src]
+	bic	src, src, #15
+	sub	tmp3, data1, zeroones
+	orr	tmp4, data1, #REP8_7f
+	bics	has_nul1, tmp3, tmp4
+	b.ne	L(nul_in_data1)
+	str	data1, [dst], #8
+	ldr	data2, [src, #8]
+	add	src, src, #16
+	sub	dst, dst, tmp1
+	sub	tmp3, data2, zeroones
+	orr	tmp4, data2, #REP8_7f
+	bics	has_nul2, tmp3, tmp4
+	b.ne	L(nul_in_data2_fast_after_d1)
+	str	data2, [dst], #8
+	/* We can by-pass the first-pass version of the loop in this case
+	   since we know that at least 8 bytes have already been copied.  */
+	b	L(startloop_fast)
+
+2:
+	sub	tmp1, tmp1, #16
+3:
+	ldrb	data1w, [src], #1
+	strb	data1w, [dst], #1
+	cbz	data1w, L(done)
+	add	tmp1, tmp1, #1
+	cbnz	tmp1, 3b
+	b	L(first_pass)
+END (strcpy)
+libc_hidden_builtin_def (strcpy)


More information about the Libc-alpha mailing list