about summary refs log tree commit diff
path: root/sysdeps/aarch64/multiarch/memcpy_thunderx2.S
blob: 93993b9e03b95075dbc9aef13f44c2b9a2b7800f (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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
/* A Thunderx2 Optimized memcpy implementation for AARCH64.
   Copyright (C) 2018-2023 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
   <https://www.gnu.org/licenses/>.  */

#include <sysdep.h>

/* Assumptions:
 *
 * ARMv8-a, AArch64, unaligned accesses.
 *
 */

#define dstin	x0
#define src	x1
#define count	x2
#define dst	x3
#define srcend	x4
#define dstend	x5
#define tmp2	x6
#define tmp3	x7
#define tmp3w   w7
#define A_l	x6
#define A_lw	w6
#define A_h	x7
#define A_hw	w7
#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	src
#define E_h	count
#define F_l	srcend
#define F_h	dst
#define G_l	count
#define G_h	dst
#define tmp1	x14

#define A_q	q0
#define B_q	q1
#define C_q	q2
#define D_q	q3
#define E_q	q4
#define F_q	q5
#define G_q	q6
#define H_q	q7
#define I_q	q16
#define J_q	q17

#define A_v	v0
#define B_v	v1
#define C_v	v2
#define D_v	v3
#define E_v	v4
#define F_v	v5
#define G_v	v6
#define H_v	v7
#define I_v	v16
#define J_v	v17

/* Overlapping large forward memmoves use a loop that copies backwards.
   Otherwise memcpy is used. Small moves branch to memcopy16 directly.
   The longer memcpy cases fall through to the memcpy head.
*/

ENTRY (__memmove_thunderx2)

	PTR_ARG (0)
	PTR_ARG (1)
	SIZE_ARG (2)

	add	srcend, src, count
	cmp	count, 16
	b.ls	L(memcopy16)
	sub	tmp1, dstin, src
	cmp	count, 96
	ccmp	tmp1, count, 2, hi
	b.lo	L(move_long)

END (__memmove_thunderx2)


/* Copies are split into 3 main cases: small copies of up to 16 bytes,
   medium copies of 17..96 bytes which are fully unrolled. Large copies
   of more than 96 bytes align the destination and use load-and-merge
   approach in the case src and dst addresses are unaligned not evenly,
   so that, actual loads and stores are always aligned.
   Large copies use the loops processing 64 bytes per iteration for
   unaligned case and 128 bytes per iteration for aligned ones.
*/

#define MEMCPY_PREFETCH_LDR 640

ENTRY (__memcpy_thunderx2)

	PTR_ARG (0)
	PTR_ARG (1)
	SIZE_ARG (2)

	add	srcend, src, count
	cmp	count, 16
	b.ls	L(memcopy16)
	ldr	A_q, [src], #16
	add	dstend, dstin, count
	and	tmp1, src, 15
	cmp	count, 96
	b.hi	L(memcopy_long)

	/* Medium copies: 17..96 bytes.  */
	ldr	E_q, [srcend, -16]
	cmp	count, 64
	b.gt	L(memcpy_copy96)
	cmp	count, 48
	b.le	L(bytes_17_to_48)
	/* 49..64 bytes */
	ldp	B_q, C_q, [src]
	str	E_q, [dstend, -16]
	stp	A_q, B_q, [dstin]
	str	C_q, [dstin, 32]
	ret

L(bytes_17_to_48):
	/* 17..48 bytes*/
	cmp	count, 32
	b.gt	L(bytes_32_to_48)
	/* 17..32 bytes*/
	str	A_q, [dstin]
	str	E_q, [dstend, -16]
	ret

L(bytes_32_to_48):
	/* 32..48 */
	ldr	B_q, [src]
	str	A_q, [dstin]
	str	E_q, [dstend, -16]
	str	B_q, [dstin, 16]
	ret

	.p2align 4
	/* Small copies: 0..16 bytes.  */
L(memcopy16):
	cmp	count, 8
	b.lo	L(bytes_0_to_8)
	ldr	A_l, [src]
	ldr	A_h, [srcend, -8]
	add	dstend, dstin, count
	str	A_l, [dstin]
	str	A_h, [dstend, -8]
	ret
	.p2align 4

L(bytes_0_to_8):
	tbz	count, 2, L(bytes_0_to_3)
	ldr	A_lw, [src]
	ldr	A_hw, [srcend, -4]
	add	dstend, dstin, count
	str	A_lw, [dstin]
	str	A_hw, [dstend, -4]
	ret

	/* Copy 0..3 bytes.  Use a branchless sequence that copies the same
	   byte 3 times if count==1, or the 2nd byte twice if count==2.  */
L(bytes_0_to_3):
	cbz	count, 1f
	lsr	tmp1, count, 1
	ldrb	A_lw, [src]
	ldrb	A_hw, [srcend, -1]
	add	dstend, dstin, count
	ldrb	B_lw, [src, tmp1]
	strb	B_lw, [dstin, tmp1]
	strb	A_hw, [dstend, -1]
	strb	A_lw, [dstin]
1:
	ret

	.p2align 4

L(memcpy_copy96):
	/* Copying 65..96 bytes. A_q (first 16 bytes) and
	   E_q(last 16 bytes) are already loaded. The size
	   is large enough to benefit from aligned loads */
	bic	src, src, 15
	ldp	B_q, C_q, [src]
	/* Loaded 64 bytes, second 16-bytes chunk can be
	   overlapping with the first chunk by tmp1 bytes.
	   Stored 16 bytes. */
	sub	dst, dstin, tmp1
	add	count, count, tmp1
	/* The range of count being [65..96] becomes [65..111]
	   after tmp [0..15] gets added to it,
	   count now is <bytes-left-to-load>+48 */
	cmp	count, 80
	b.gt	L(copy96_medium)
	ldr	D_q, [src, 32]
	stp	B_q, C_q, [dst, 16]
	str	D_q, [dst, 48]
	str	A_q, [dstin]
	str	E_q, [dstend, -16]
	ret

	.p2align 4
L(copy96_medium):
	ldp	D_q, G_q, [src, 32]
	cmp	count, 96
	b.gt	L(copy96_large)
	stp	B_q, C_q, [dst, 16]
	stp	D_q, G_q, [dst, 48]
	str	A_q, [dstin]
	str	E_q, [dstend, -16]
	ret

L(copy96_large):
	ldr	F_q, [src, 64]
	str	B_q, [dst, 16]
	stp	C_q, D_q, [dst, 32]
	stp	G_q, F_q, [dst, 64]
	str	A_q, [dstin]
	str	E_q, [dstend, -16]
	ret

	.p2align 4
L(memcopy_long):
	bic	src, src, 15
	ldp	B_q, C_q, [src], #32
	sub	dst, dstin, tmp1
	add	count, count, tmp1
	add	dst, dst, 16
	and	tmp1, dst, 15
	ldp	D_q, E_q, [src], #32
	str	A_q, [dstin]

	/* Already loaded 64+16 bytes. Check if at
	   least 64 more bytes left */
	subs	count, count, 64+64+16
	b.lt	L(loop128_exit0)
	cmp	count, MEMCPY_PREFETCH_LDR + 64 + 32
	b.lt	L(loop128)
	cbnz	tmp1, L(dst_unaligned)
	sub	count, count, MEMCPY_PREFETCH_LDR + 64 + 32

	.p2align 4

L(loop128_prefetch):
	prfm	pldl1strm, [src, MEMCPY_PREFETCH_LDR]
	ldp	F_q, G_q, [src], #32
	stp	B_q, C_q, [dst], #32
	ldp	H_q, I_q, [src], #32
	prfm	pldl1strm, [src, MEMCPY_PREFETCH_LDR]
	ldp	B_q, C_q, [src], #32
	stp	D_q, E_q, [dst], #32
	ldp	D_q, E_q, [src], #32
	stp	F_q, G_q, [dst], #32
	stp	H_q, I_q, [dst], #32
	subs	count, count, 128
	b.ge	L(loop128_prefetch)

	add	count, count, MEMCPY_PREFETCH_LDR + 64 + 32
	.p2align 4
L(loop128):
	ldp	F_q, G_q, [src], #32
	ldp	H_q, I_q, [src], #32
	stp	B_q, C_q, [dst], #32
	stp	D_q, E_q, [dst], #32
	subs	count, count, 64
	b.lt	L(loop128_exit1)
	ldp	B_q, C_q, [src], #32
	ldp	D_q, E_q, [src], #32
	stp	F_q, G_q, [dst], #32
	stp	H_q, I_q, [dst], #32
	subs	count, count, 64
	b.ge	L(loop128)
L(loop128_exit0):
	ldp	F_q, G_q, [srcend, -64]
	ldp	H_q, I_q, [srcend, -32]
	stp	B_q, C_q, [dst], #32
	stp	D_q, E_q, [dst]
	stp	F_q, G_q, [dstend, -64]
	stp	H_q, I_q, [dstend, -32]
	ret
L(loop128_exit1):
	ldp	B_q, C_q, [srcend, -64]
	ldp	D_q, E_q, [srcend, -32]
	stp	F_q, G_q, [dst], #32
	stp	H_q, I_q, [dst]
	stp	B_q, C_q, [dstend, -64]
	stp	D_q, E_q, [dstend, -32]
	ret

L(dst_unaligned_tail):
	ldp	C_q, D_q, [srcend, -64]
	ldp	E_q, F_q, [srcend, -32]
	stp	A_q, B_q, [dst], #32
	stp	H_q, I_q, [dst], #16
	str	G_q, [dst, tmp1]
	stp	C_q, D_q, [dstend, -64]
	stp	E_q, F_q, [dstend, -32]
	ret

L(dst_unaligned):
	/* For the unaligned store case the code loads two
	   aligned chunks and then merges them using ext
	   instruction. This can be up to 30% faster than
	   the the simple unaligned store access.

	   Current state: tmp1 = dst % 16; C_q, D_q, E_q
	   contains data yet to be stored. src and dst points
	   to next-to-be-processed data. A_q, B_q contains
	   data already stored before, count = bytes left to
	   be load decremented by 64.

	   The control is passed here if at least 64 bytes left
	   to be loaded. The code does two aligned loads and then
	   extracts (16-tmp1) bytes from the first register and
	   tmp1 bytes from the next register forming the value
	   for the aligned store.

	   As ext instruction can only have it's index encoded
	   as immediate. 15 code chunks process each possible
	   index value. Computed goto is used to reach the
	   required code. */

	/* Store the 16 bytes to dst and align dst for further
	   operations, several bytes will be stored at this
	   address once more */

	ldp	F_q, G_q, [src], #32
	stp	B_q, C_q, [dst], #32
	bic	dst, dst, 15
	sub	count, count, 32
	adrp	tmp2, L(ext_table)
	add	tmp2, tmp2, :lo12:L(ext_table)
	add	tmp2, tmp2, tmp1, LSL #2
	ldr	tmp3w, [tmp2]
	add	tmp2, tmp2, tmp3w, SXTW
	br	tmp2

.p2align 4
	/* to make the loop in each chunk 16-bytes aligned */
	nop
#define EXT_CHUNK(shft) \
L(ext_size_ ## shft):;\
	ext     A_v.16b, C_v.16b, D_v.16b, 16-shft;\
	ext     B_v.16b, D_v.16b, E_v.16b, 16-shft;\
	ext     H_v.16b, E_v.16b, F_v.16b, 16-shft;\
1:;\
	stp     A_q, B_q, [dst], #32;\
	prfm    pldl1strm, [src, MEMCPY_PREFETCH_LDR];\
	ldp     C_q, D_q, [src], #32;\
	ext     I_v.16b, F_v.16b, G_v.16b, 16-shft;\
	stp     H_q, I_q, [dst], #32;\
	ext     A_v.16b, G_v.16b, C_v.16b, 16-shft;\
	ext     B_v.16b, C_v.16b, D_v.16b, 16-shft;\
	ldp     F_q, G_q, [src], #32;\
	ext     H_v.16b, D_v.16b, F_v.16b, 16-shft;\
	subs    count, count, 64;\
	b.ge    1b;\
2:;\
	ext     I_v.16b, F_v.16b, G_v.16b, 16-shft;\
	b	L(dst_unaligned_tail);

EXT_CHUNK(1)
EXT_CHUNK(2)
EXT_CHUNK(3)
EXT_CHUNK(4)
EXT_CHUNK(5)
EXT_CHUNK(6)
EXT_CHUNK(7)
EXT_CHUNK(8)
EXT_CHUNK(9)
EXT_CHUNK(10)
EXT_CHUNK(11)
EXT_CHUNK(12)
EXT_CHUNK(13)
EXT_CHUNK(14)
EXT_CHUNK(15)

L(move_long):
	.p2align 4
1:
	cbz	tmp1, 3f

	add	srcend, src, count
	add	dstend, dstin, count

	and	tmp1, srcend, 15
	ldr	D_q, [srcend, -16]
	sub	srcend, srcend, tmp1
	sub	count, count, tmp1
	ldp	A_q, B_q, [srcend, -32]
	str	D_q, [dstend, -16]
	ldp	C_q, D_q, [srcend, -64]!
	sub	dstend, dstend, tmp1
	subs	count, count, 128
	b.ls	2f

	.p2align 4
1:
	subs	count, count, 64
	stp	A_q, B_q, [dstend, -32]
	ldp	A_q, B_q, [srcend, -32]
	stp	C_q, D_q, [dstend, -64]!
	ldp	C_q, D_q, [srcend, -64]!
	b.hi	1b

	/* 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 start even if
	   there is just 1 byte left.  */
2:
	ldp	E_q, F_q, [src, 32]
	ldp	G_q, H_q, [src]
	stp	A_q, B_q, [dstend, -32]
	stp	C_q, D_q, [dstend, -64]
	stp	E_q, F_q, [dstin, 32]
	stp	G_q, H_q, [dstin]
3:	ret


END (__memcpy_thunderx2)
	.section	.rodata
	.p2align	4

L(ext_table):
	/* The first entry is for the alignment of 0 and is never
	   actually used (could be any value).  */
	.word	0
	.word	L(ext_size_1) -.
	.word	L(ext_size_2) -.
	.word	L(ext_size_3) -.
	.word	L(ext_size_4) -.
	.word	L(ext_size_5) -.
	.word	L(ext_size_6) -.
	.word	L(ext_size_7) -.
	.word	L(ext_size_8) -.
	.word	L(ext_size_9) -.
	.word	L(ext_size_10) -.
	.word	L(ext_size_11) -.
	.word	L(ext_size_12) -.
	.word	L(ext_size_13) -.
	.word	L(ext_size_14) -.
	.word	L(ext_size_15) -.