]> git.sesse.net Git - nageru/blob - shared/memcpy_interleaved.cpp
Add support for 10-bit AV1 encoding.
[nageru] / shared / memcpy_interleaved.cpp
1 #if (defined(__i386__) || defined(__x86_64__)) && defined(__GNUC__)
2 #define HAS_MULTIVERSIONING 1
3 #endif
4
5 #include <algorithm>
6 #include <assert.h>
7 #include <cstdint>
8 #if HAS_MULTIVERSIONING
9 #include <immintrin.h>
10 #endif
11
12 using namespace std;
13
14 // TODO: Support stride.
15 void memcpy_interleaved_slow(uint8_t *dest1, uint8_t *dest2, const uint8_t *src, size_t n)
16 {
17         assert(n % 2 == 0);
18         uint8_t *dptr1 = dest1;
19         uint8_t *dptr2 = dest2;
20
21         for (size_t i = 0; i < n; i += 2) {
22                 *dptr1++ = *src++;
23                 *dptr2++ = *src++;
24         }
25 }
26
27 void memcpy_interleaved_word_slow(uint16_t *dest1, uint16_t *dest2, const uint16_t *src, size_t n)
28 {
29         assert(n % 2 == 0);
30         uint16_t *dptr1 = dest1;
31         uint16_t *dptr2 = dest2;
32
33         for (size_t i = 0; i < n; i += 2) {
34                 *dptr1++ = *src++;
35                 *dptr2++ = *src++;
36         }
37 }
38
39 #if HAS_MULTIVERSIONING
40
41 // uint8_t version.
42
43 __attribute__((target("default")))
44 size_t memcpy_interleaved_fastpath_core(uint8_t *dest1, uint8_t *dest2, const uint8_t *src, const uint8_t *limit);
45
46 __attribute__((target("avx2")))
47 size_t memcpy_interleaved_fastpath_core(uint8_t *dest1, uint8_t *dest2, const uint8_t *src, const uint8_t *limit);
48
49 __attribute__((target("default")))
50 size_t memcpy_interleaved_fastpath_core(uint8_t *dest1, uint8_t *dest2, const uint8_t *src, const uint8_t *limit)
51 {
52         // No fast path supported unless we have AVX2.
53         return 0;
54 }
55
56 __attribute__((target("avx2")))
57 size_t memcpy_interleaved_fastpath_core(uint8_t *dest1, uint8_t *dest2, const uint8_t *src, const uint8_t *limit)
58 {
59         size_t consumed = 0;
60         const __m256i *__restrict in = (const __m256i *)src;
61         __m256i *__restrict out1 = (__m256i *)dest1;
62         __m256i *__restrict out2 = (__m256i *)dest2;
63
64         __m256i shuffle_cw = _mm256_set_epi8(
65                 15, 13, 11, 9, 7, 5, 3, 1, 14, 12, 10, 8, 6, 4, 2, 0,
66                 15, 13, 11, 9, 7, 5, 3, 1, 14, 12, 10, 8, 6, 4, 2, 0);
67         while (in < (const __m256i *)limit) {
68                 // Note: For brevity, comments show lanes as if they were 2x64-bit (they're actually 2x128).
69                 __m256i data1 = _mm256_stream_load_si256(in);         // AaBbCcDd EeFfGgHh
70                 __m256i data2 = _mm256_stream_load_si256(in + 1);     // IiJjKkLl MmNnOoPp
71
72                 data1 = _mm256_shuffle_epi8(data1, shuffle_cw);       // ABCDabcd EFGHefgh
73                 data2 = _mm256_shuffle_epi8(data2, shuffle_cw);       // IJKLijkl MNOPmnop
74         
75                 data1 = _mm256_permute4x64_epi64(data1, 0b11011000);  // ABCDEFGH abcdefgh
76                 data2 = _mm256_permute4x64_epi64(data2, 0b11011000);  // IJKLMNOP ijklmnop
77
78                 __m256i lo = _mm256_permute2x128_si256(data1, data2, 0b00100000);
79                 __m256i hi = _mm256_permute2x128_si256(data1, data2, 0b00110001);
80
81                 _mm256_storeu_si256(out1, lo);
82                 _mm256_storeu_si256(out2, hi);
83
84                 in += 2;
85                 ++out1;
86                 ++out2;
87                 consumed += 64;
88         }
89
90         return consumed;
91 }
92
93 // Returns the number of bytes consumed.
94 size_t memcpy_interleaved_fastpath(uint8_t *dest1, uint8_t *dest2, const uint8_t *src, size_t n)
95 {
96         const uint8_t *limit = src + n;
97         size_t consumed = 0;
98
99         // Align end to 32 bytes.
100         limit = (const uint8_t *)(intptr_t(limit) & ~31);
101
102         if (src >= limit) {
103                 return 0;
104         }
105
106         // Process [0,31] bytes, such that start gets aligned to 32 bytes.
107         const uint8_t *aligned_src = (const uint8_t *)(intptr_t(src + 31) & ~31);
108         if (aligned_src != src) {
109                 size_t n2 = aligned_src - src;
110                 memcpy_interleaved_slow(dest1, dest2, src, n2);
111                 dest1 += n2 / 2;
112                 dest2 += n2 / 2;
113                 if (n2 % 2) {
114                         swap(dest1, dest2);
115                 }
116                 src = aligned_src;
117                 consumed += n2;
118         }
119
120         // Make the length a multiple of 64.
121         if (((limit - src) % 64) != 0) {
122                 limit -= 32;
123         }
124         assert(((limit - src) % 64) == 0);
125
126         return consumed + memcpy_interleaved_fastpath_core(dest1, dest2, src, limit);
127 }
128
129 // uint16_t version.
130
131 __attribute__((target("default")))
132 size_t memcpy_interleaved_word_fastpath_core(uint16_t *dest1, uint16_t *dest2, const uint16_t *src, const uint16_t *limit);
133
134 __attribute__((target("avx2")))
135 size_t memcpy_interleaved_word_fastpath_core(uint16_t *dest1, uint16_t *dest2, const uint16_t *src, const uint16_t *limit);
136
137 __attribute__((target("default")))
138 size_t memcpy_interleaved_word_fastpath_core(uint16_t *dest1, uint16_t *dest2, const uint16_t *src, const uint16_t *limit)
139 {
140         // No fast path supported unless we have AVX2.
141         return 0;
142 }
143
144 __attribute__((target("avx2")))
145 size_t memcpy_interleaved_word_fastpath_core(uint16_t *dest1, uint16_t *dest2, const uint16_t *src, const uint16_t *limit)
146 {
147         size_t consumed = 0;
148         const __m256i *__restrict in = (const __m256i *)src;
149         __m256i *__restrict out1 = (__m256i *)dest1;
150         __m256i *__restrict out2 = (__m256i *)dest2;
151
152         __m256i shuffle_cw = _mm256_set_epi8(
153                 15, 14, 11, 10, 7, 6, 3, 2, 13, 12, 9, 8, 5, 4, 1, 0,
154                 15, 14, 11, 10, 7, 6, 3, 2, 13, 12, 9, 8, 5, 4, 1, 0);
155         while (in < (const __m256i *)limit) {
156                 // Note: Each element in these comments is 16 bits long (lanes are 2x128 bits).
157                 __m256i data1 = _mm256_stream_load_si256(in);         // AaBbCcDd EeFfGgHh
158                 __m256i data2 = _mm256_stream_load_si256(in + 1);     // IiJjKkLl MmNnOoPp
159
160                 data1 = _mm256_shuffle_epi8(data1, shuffle_cw);       // ABCDabcd EFGHefgh
161                 data2 = _mm256_shuffle_epi8(data2, shuffle_cw);       // IJKLijkl MNOPmnop
162
163                 data1 = _mm256_permute4x64_epi64(data1, 0b11011000);  // ABCDEFGH abcdefgh
164                 data2 = _mm256_permute4x64_epi64(data2, 0b11011000);  // IJKLMNOP ijklmnop
165
166                 __m256i lo = _mm256_permute2x128_si256(data1, data2, 0b00100000);
167                 __m256i hi = _mm256_permute2x128_si256(data1, data2, 0b00110001);
168
169                 _mm256_storeu_si256(out1, lo);
170                 _mm256_storeu_si256(out2, hi);
171
172                 in += 2;
173                 ++out1;
174                 ++out2;
175                 consumed += 32;
176         }
177
178         return consumed;
179 }
180
181 // Returns the number of bytes consumed.
182 size_t memcpy_interleaved_word_fastpath(uint16_t *dest1, uint16_t *dest2, const uint16_t *src, size_t n)
183 {
184         // We assume this to generally be the case, but just to be sure,
185         // drop down to the slow path.
186         if (intptr_t(dest1) % 2 != 0 || intptr_t(dest2) % 2 != 0 || intptr_t(src) % 2 != 0) {
187                 return 0;
188         }
189
190         const uint16_t *limit = src + n;
191         size_t consumed = 0;
192
193         // Align end to 32 bytes.
194         limit = (const uint16_t *)(intptr_t(limit) & ~31);
195
196         if (src >= limit) {
197                 return 0;
198         }
199
200         // Process [0,15] words, such that start gets aligned to 32 bytes (16 words).
201         const uint16_t *aligned_src = (const uint16_t *)(intptr_t(src + 31) & ~31);
202         if (aligned_src != src) {
203                 size_t n2 = aligned_src - src;
204                 memcpy_interleaved_word_slow(dest1, dest2, src, n2);
205                 dest1 += n2 / 2;
206                 dest2 += n2 / 2;
207                 if (n2 % 2) {
208                         swap(dest1, dest2);
209                 }
210                 src = aligned_src;
211                 consumed += n2;
212         }
213
214         // Make the length a multiple of 32 words (64 bytes).
215         if (((limit - src) % 32) != 0) {
216                 limit -= 16;
217         }
218         assert(((limit - src) % 32) == 0);
219
220         return consumed + memcpy_interleaved_word_fastpath_core(dest1, dest2, src, limit);
221 }
222
223 #endif  // defined(HAS_MULTIVERSIONING)
224
225 void memcpy_interleaved(uint8_t *dest1, uint8_t *dest2, const uint8_t *src, size_t n)
226 {
227 #if HAS_MULTIVERSIONING
228         size_t consumed = memcpy_interleaved_fastpath(dest1, dest2, src, n);
229         src += consumed;
230         dest1 += consumed / 2;
231         dest2 += consumed / 2;
232         if (consumed % 2) {
233                 swap(dest1, dest2);
234         }
235         n -= consumed;
236 #endif
237
238         if (n > 0) {
239                 memcpy_interleaved_slow(dest1, dest2, src, n);
240         }
241 }
242
243 void memcpy_interleaved_word(uint16_t *dest1, uint16_t *dest2, const uint16_t *src, size_t n)
244 {
245 #if HAS_MULTIVERSIONING
246         size_t consumed = memcpy_interleaved_word_fastpath(dest1, dest2, src, n);
247         src += consumed;
248         dest1 += consumed / 2;
249         dest2 += consumed / 2;
250         if (consumed % 2) {
251                 swap(dest1, dest2);
252         }
253         n -= consumed;
254 #endif
255
256         if (n > 0) {
257                 memcpy_interleaved_word_slow(dest1, dest2, src, n);
258         }
259 }