about summary refs log tree commit diff
path: root/sysdeps/aarch64/multiarch/memcpy_falkor.S
blob: 2fe9937f1146ab732dab684f7045fdfb32482d40 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/* Optimized memcpy for Qualcomm Falkor processor.
   Copyright (C) 2017-2018 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, falkor, unaligned accesses.  */

#define dstin	x0
#define src	x1
#define count	x2
#define dst	x3
#define srcend	x4
#define dstend	x5
#define A_l	x6
#define A_lw	w6
#define A_h	x7
#define A_hw	w7
#define tmp1	x14

#define B_l	x8
#define B_lw	w8
#define B_h	x9
#define C_l	x10
#define C_h	x11
#define D_l	x12
#define D_h	x13
#define E_l	dst
#define E_h	tmp1
#define F_l	src
#define F_h	count
#define G_l	srcend
#define G_h	x15

/* Copies are split into 3 main cases:

   1. Small copies of up to 32 bytes
   2. Medium copies of 33..128 bytes which are fully unrolled
   3. Large copies of more than 128 bytes.

   Large copies align the sourceto a quad word and use an unrolled loop
   processing 64 bytes per iteration.

   FALKOR-SPECIFIC DESIGN:

   The smallest copies (32 bytes or less) focus on optimal pipeline usage,
   which is why the redundant copies of 0-3 bytes have been replaced with
   conditionals, since the former would unnecessarily break across multiple
   issue groups.  The medium copy group has been enlarged to 128 bytes since
   bumping up the small copies up to 32 bytes allows us to do that without
   cost and also allows us to reduce the size of the prep code before loop64.

   All copies are done only via two registers r6 and r7.  This is to ensure
   that all loads hit a single hardware prefetcher which can get correctly
   trained to prefetch a single stream.

   The non-temporal stores help optimize cache utilization.  */

#if IS_IN (libc)
ENTRY_ALIGN (__memcpy_falkor, 6)

	cmp	count, 32
	add	srcend, src, count
	add	dstend, dstin, count
	b.ls	L(copy32)
	ldp	A_l, A_h, [src]
	cmp	count, 128
	stp	A_l, A_h, [dstin]
	b.hi	L(copy_long)

	/* Medium copies: 33..128 bytes.  */
	sub	tmp1, count, 1
	ldp	A_l, A_h, [src, 16]
	ldp	B_l, B_h, [srcend, -32]
	ldp	C_l, C_h, [srcend, -16]
	tbz	tmp1, 6, 1f
	ldp	D_l, D_h, [src, 32]
	ldp	E_l, E_h, [src, 48]
	stp	D_l, D_h, [dstin, 32]
	stp	E_l, E_h, [dstin, 48]
	ldp	F_l, F_h, [srcend, -64]
	ldp	G_l, G_h, [srcend, -48]
	stp	F_l, F_h, [dstend, -64]
	stp	G_l, G_h, [dstend, -48]
1:
	stp	A_l, A_h, [dstin, 16]
	stp	B_l, B_h, [dstend, -32]
	stp	C_l, C_h, [dstend, -16]
	ret

	.p2align 4
	/* Small copies: 0..32 bytes.  */
L(copy32):
	/* 16-32 */
	cmp	count, 16
	b.lo	1f
	ldp	A_l, A_h, [src]
	ldp	B_l, B_h, [srcend, -16]
	stp	A_l, A_h, [dstin]
	stp	B_l, B_h, [dstend, -16]
	ret
	.p2align 4
1:
	/* 8-15 */
	tbz	count, 3, 1f
	ldr	A_l, [src]
	ldr	B_l, [srcend, -8]
	str	A_l, [dstin]
	str	B_l, [dstend, -8]
	ret
	.p2align 4
1:
	/* 4-7 */
	tbz	count, 2, 1f
	ldr	A_lw, [src]
	ldr	B_lw, [srcend, -4]
	str	A_lw, [dstin]
	str	B_lw, [dstend, -4]
	ret
	.p2align 4
1:
	/* 2-3 */
	tbz	count, 1, 1f
	ldrh	A_lw, [src]
	ldrh	B_lw, [srcend, -2]
	strh	A_lw, [dstin]
	strh	B_lw, [dstend, -2]
	ret
	.p2align 4
1:
	/* 0-1 */
	tbz	count, 0, 1f
	ldrb	A_lw, [src]
	strb	A_lw, [dstin]
1:
	ret

	/* Align SRC to 16 bytes and copy; that way at least one of the
	   accesses is aligned throughout the copy sequence.

	   The count is off by 0 to 15 bytes, but this is OK because we trim
	   off the last 64 bytes to copy off from the end.  Due to this the
	   loop never runs out of bounds.  */
	.p2align 6
L(copy_long):
	sub	count, count, 64 + 16
	and	tmp1, src, 15
	bic	src, src, 15
	sub	dst, dstin, tmp1
	add	count, count, tmp1

L(loop64):
	ldp	A_l, A_h, [src, 16]!
	stnp	A_l, A_h, [dst, 16]
	ldp	A_l, A_h, [src, 16]!
	subs	count, count, 64
	stnp	A_l, A_h, [dst, 32]
	ldp	A_l, A_h, [src, 16]!
	stnp	A_l, A_h, [dst, 48]
	ldp	A_l, A_h, [src, 16]!
	stnp	A_l, A_h, [dst, 64]
	add	dst, dst, 64
	b.hi	L(loop64)

	/* Write the last full set of 64 bytes.  The remainder is at most 64
	   bytes, so it is safe to always copy 64 bytes from the end even if
	   there is just 1 byte left.  */
L(last64):
	ldp	A_l, A_h, [srcend, -64]
	stnp	A_l, A_h, [dstend, -64]
	ldp	B_l, B_h, [srcend, -48]
	stnp	B_l, B_h, [dstend, -48]
	ldp	C_l, C_h, [srcend, -32]
	stnp	C_l, C_h, [dstend, -32]
	ldp	D_l, D_h, [srcend, -16]
	stnp	D_l, D_h, [dstend, -16]
	ret

END (__memcpy_falkor)
libc_hidden_builtin_def (__memcpy_falkor)
#endif