1 #define LLVM_LIBC_USE_BUILTIN_MEMCPY_INLINE 0
2 #define LLVM_LIBC_USE_BUILTIN_MEMSET_INLINE 0
3
4 #include "utils/UnitTest/Test.h"
5 #include <src/__support/CPP/Array.h>
6 #include <src/string/memory_utils/algorithm.h>
7 #include <src/string/memory_utils/backends.h>
8
9 #include <sstream>
10
11 namespace __llvm_libc {
12
13 struct alignas(64) Buffer : cpp::Array<char, 128> {
contains__llvm_libc::Buffer14 bool contains(const char *ptr) const {
15 return ptr >= data() && ptr < (data() + size());
16 }
getOffset__llvm_libc::Buffer17 size_t getOffset(const char *ptr) const { return ptr - data(); }
fill__llvm_libc::Buffer18 void fill(char c) {
19 for (auto itr = begin(); itr != end(); ++itr)
20 *itr = c;
21 }
22 };
23
24 static Buffer buffer1;
25 static Buffer buffer2;
26 static std::ostringstream LOG;
27
28 struct TestBackend {
29 static constexpr bool IS_BACKEND_TYPE = true;
30
log__llvm_libc::TestBackend31 template <typename T> static void log(const char *Action, const char *ptr) {
32 LOG << Action << "<" << sizeof(T) << "> ";
33 if (buffer1.contains(ptr))
34 LOG << "a[" << buffer1.getOffset(ptr) << "]";
35 else if (buffer2.contains(ptr))
36 LOG << "b[" << buffer2.getOffset(ptr) << "]";
37 LOG << "\n";
38 }
39
40 template <typename T, Temporality TS, Aligned AS>
load__llvm_libc::TestBackend41 static T load(const T *src) {
42 log<T>((AS == Aligned::YES ? "LdA" : "LdU"),
43 reinterpret_cast<const char *>(src));
44 return Scalar64BitBackend::load<T, TS, AS>(src);
45 }
46
47 template <typename T, Temporality TS, Aligned AS>
store__llvm_libc::TestBackend48 static void store(T *dst, T value) {
49 log<T>((AS == Aligned::YES ? "StA" : "StU"),
50 reinterpret_cast<const char *>(dst));
51 Scalar64BitBackend::store<T, TS, AS>(dst, value);
52 }
53
splat__llvm_libc::TestBackend54 template <typename T> static inline T splat(ubyte value) {
55 LOG << "Splat<" << sizeof(T) << "> " << (unsigned)value << '\n';
56 return Scalar64BitBackend::splat<T>(value);
57 }
58
notEquals__llvm_libc::TestBackend59 template <typename T> static inline uint64_t notEquals(T v1, T v2) {
60 LOG << "Neq<" << sizeof(T) << ">\n";
61 return Scalar64BitBackend::notEquals<T>(v1, v2);
62 }
63
threeWayCmp__llvm_libc::TestBackend64 template <typename T> static inline int32_t threeWayCmp(T v1, T v2) {
65 LOG << "Diff<" << sizeof(T) << ">\n";
66 return Scalar64BitBackend::threeWayCmp<T>(v1, v2);
67 }
68
69 template <size_t Size>
70 using getNextType = Scalar64BitBackend::getNextType<Size>;
71 };
72
73 struct LlvmLibcAlgorithm : public testing::Test {
SetUp__llvm_libc::LlvmLibcAlgorithm74 void SetUp() override {
75 LOG = std::ostringstream();
76 LOG << '\n';
77 }
78
fillEqual__llvm_libc::LlvmLibcAlgorithm79 void fillEqual() {
80 buffer1.fill('a');
81 buffer2.fill('a');
82 }
83
fillDifferent__llvm_libc::LlvmLibcAlgorithm84 void fillDifferent() {
85 buffer1.fill('a');
86 buffer2.fill('b');
87 }
88
getTrace__llvm_libc::LlvmLibcAlgorithm89 const char *getTrace() {
90 trace_ = LOG.str();
91 return trace_.c_str();
92 }
93
stripComments__llvm_libc::LlvmLibcAlgorithm94 const char *stripComments(const char *expected) {
95 expected_.clear();
96 std::stringstream ss(expected);
97 std::string line;
98 while (std::getline(ss, line, '\n')) {
99 const auto pos = line.find('#');
100 if (pos == std::string::npos) {
101 expected_ += line;
102 } else {
103 auto log = line.substr(0, pos);
104 while (!log.empty() && std::isspace(log.back()))
105 log.pop_back();
106 expected_ += log;
107 }
108 expected_ += '\n';
109 }
110 return expected_.c_str();
111 }
112
buf1__llvm_libc::LlvmLibcAlgorithm113 template <size_t Align = 1> SrcAddr<Align> buf1(size_t offset = 0) const {
114 return buffer1.data() + offset;
115 }
buf2__llvm_libc::LlvmLibcAlgorithm116 template <size_t Align = 1> SrcAddr<Align> buf2(size_t offset = 0) const {
117 return buffer2.data() + offset;
118 }
dst__llvm_libc::LlvmLibcAlgorithm119 template <size_t Align = 1> DstAddr<Align> dst(size_t offset = 0) const {
120 return buffer1.data() + offset;
121 }
src__llvm_libc::LlvmLibcAlgorithm122 template <size_t Align = 1> SrcAddr<Align> src(size_t offset = 0) const {
123 return buffer2.data() + offset;
124 }
125
126 private:
127 std::string trace_;
128 std::string expected_;
129 };
130
131 using _8 = SizedOp<TestBackend, 8>;
132
133 ///////////////////////////////////////////////////////////////////////////////
134 //// Testing fixed fized forward operations
135 ///////////////////////////////////////////////////////////////////////////////
136
137 ///////////////////////////////////////////////////////////////////////////////
138 // Copy
139
TEST_F(LlvmLibcAlgorithm,copy_1)140 TEST_F(LlvmLibcAlgorithm, copy_1) {
141 SizedOp<TestBackend, 1>::copy(dst(), src());
142 EXPECT_STREQ(getTrace(), stripComments(R"(
143 LdU<1> b[0]
144 StU<1> a[0]
145 )"));
146 }
147
TEST_F(LlvmLibcAlgorithm,copy_15)148 TEST_F(LlvmLibcAlgorithm, copy_15) {
149 SizedOp<TestBackend, 15>::copy(dst(), src());
150 EXPECT_STREQ(getTrace(), stripComments(R"(
151 LdU<8> b[0]
152 StU<8> a[0]
153 LdU<4> b[8]
154 StU<4> a[8]
155 LdU<2> b[12]
156 StU<2> a[12]
157 LdU<1> b[14]
158 StU<1> a[14]
159 )"));
160 }
161
TEST_F(LlvmLibcAlgorithm,copy_16)162 TEST_F(LlvmLibcAlgorithm, copy_16) {
163 SizedOp<TestBackend, 16>::copy(dst(), src());
164 EXPECT_STREQ(getTrace(), stripComments(R"(
165 LdU<8> b[0]
166 StU<8> a[0]
167 LdU<8> b[8]
168 StU<8> a[8]
169 )"));
170 }
171
172 ///////////////////////////////////////////////////////////////////////////////
173 // Move
174
TEST_F(LlvmLibcAlgorithm,move_1)175 TEST_F(LlvmLibcAlgorithm, move_1) {
176 SizedOp<TestBackend, 1>::move(dst(), src());
177 EXPECT_STREQ(getTrace(), stripComments(R"(
178 LdU<1> b[0]
179 StU<1> a[0]
180 )"));
181 }
182
TEST_F(LlvmLibcAlgorithm,move_15)183 TEST_F(LlvmLibcAlgorithm, move_15) {
184 SizedOp<TestBackend, 15>::move(dst(), src());
185 EXPECT_STREQ(getTrace(), stripComments(R"(
186 LdU<8> b[0]
187 LdU<4> b[8]
188 LdU<2> b[12]
189 LdU<1> b[14]
190 StU<1> a[14]
191 StU<2> a[12]
192 StU<4> a[8]
193 StU<8> a[0]
194 )"));
195 }
196
TEST_F(LlvmLibcAlgorithm,move_16)197 TEST_F(LlvmLibcAlgorithm, move_16) {
198 SizedOp<TestBackend, 16>::move(dst(), src());
199 EXPECT_STREQ(getTrace(), stripComments(R"(
200 LdU<8> b[0]
201 LdU<8> b[8]
202 StU<8> a[8]
203 StU<8> a[0]
204 )"));
205 }
206
207 ///////////////////////////////////////////////////////////////////////////////
208 // set
209
TEST_F(LlvmLibcAlgorithm,set_1)210 TEST_F(LlvmLibcAlgorithm, set_1) {
211 SizedOp<TestBackend, 1>::set(dst(), ubyte{42});
212 EXPECT_STREQ(getTrace(), stripComments(R"(
213 Splat<1> 42
214 StU<1> a[0]
215 )"));
216 }
217
TEST_F(LlvmLibcAlgorithm,set_15)218 TEST_F(LlvmLibcAlgorithm, set_15) {
219 SizedOp<TestBackend, 15>::set(dst(), ubyte{42});
220 EXPECT_STREQ(getTrace(), stripComments(R"(
221 Splat<8> 42
222 StU<8> a[0]
223 Splat<4> 42
224 StU<4> a[8]
225 Splat<2> 42
226 StU<2> a[12]
227 Splat<1> 42
228 StU<1> a[14]
229 )"));
230 }
231
TEST_F(LlvmLibcAlgorithm,set_16)232 TEST_F(LlvmLibcAlgorithm, set_16) {
233 SizedOp<TestBackend, 16>::set(dst(), ubyte{42});
234 EXPECT_STREQ(getTrace(), stripComments(R"(
235 Splat<8> 42
236 StU<8> a[0]
237 Splat<8> 42
238 StU<8> a[8]
239 )"));
240 }
241
242 ///////////////////////////////////////////////////////////////////////////////
243 // different
244
TEST_F(LlvmLibcAlgorithm,different_1)245 TEST_F(LlvmLibcAlgorithm, different_1) {
246 fillEqual();
247 SizedOp<TestBackend, 1>::isDifferent(buf1(), buf2());
248 EXPECT_STREQ(getTrace(), stripComments(R"(
249 LdU<1> a[0]
250 LdU<1> b[0]
251 Neq<1>
252 )"));
253 }
254
TEST_F(LlvmLibcAlgorithm,different_15)255 TEST_F(LlvmLibcAlgorithm, different_15) {
256 fillEqual();
257 SizedOp<TestBackend, 15>::isDifferent(buf1(), buf2());
258 EXPECT_STREQ(getTrace(), stripComments(R"(
259 LdU<8> a[0]
260 LdU<8> b[0]
261 Neq<8>
262 LdU<4> a[8]
263 LdU<4> b[8]
264 Neq<4>
265 LdU<2> a[12]
266 LdU<2> b[12]
267 Neq<2>
268 LdU<1> a[14]
269 LdU<1> b[14]
270 Neq<1>
271 )"));
272 }
273
TEST_F(LlvmLibcAlgorithm,different_15_no_shortcircuit)274 TEST_F(LlvmLibcAlgorithm, different_15_no_shortcircuit) {
275 fillDifferent();
276 SizedOp<TestBackend, 15>::isDifferent(buf1(), buf2());
277 // If buffer compare isDifferent we continue to aggregate.
278 EXPECT_STREQ(getTrace(), stripComments(R"(
279 LdU<8> a[0]
280 LdU<8> b[0]
281 Neq<8>
282 LdU<4> a[8]
283 LdU<4> b[8]
284 Neq<4>
285 LdU<2> a[12]
286 LdU<2> b[12]
287 Neq<2>
288 LdU<1> a[14]
289 LdU<1> b[14]
290 Neq<1>
291 )"));
292 }
293
TEST_F(LlvmLibcAlgorithm,different_16)294 TEST_F(LlvmLibcAlgorithm, different_16) {
295 fillEqual();
296 SizedOp<TestBackend, 16>::isDifferent(buf1(), buf2());
297 EXPECT_STREQ(getTrace(), stripComments(R"(
298 LdU<8> a[0]
299 LdU<8> b[0]
300 Neq<8>
301 LdU<8> a[8]
302 LdU<8> b[8]
303 Neq<8>
304 )"));
305 }
306
307 ///////////////////////////////////////////////////////////////////////////////
308 // three_way_cmp
309
TEST_F(LlvmLibcAlgorithm,three_way_cmp_eq_1)310 TEST_F(LlvmLibcAlgorithm, three_way_cmp_eq_1) {
311 fillEqual();
312 SizedOp<TestBackend, 1>::threeWayCmp(buf1(), buf2());
313 // Buffer compare equal, returning 0 and no call to Diff.
314 EXPECT_STREQ(getTrace(), stripComments(R"(
315 LdU<1> a[0]
316 LdU<1> b[0]
317 Diff<1>
318 )"));
319 }
320
TEST_F(LlvmLibcAlgorithm,three_way_cmp_eq_15)321 TEST_F(LlvmLibcAlgorithm, three_way_cmp_eq_15) {
322 fillEqual();
323 SizedOp<TestBackend, 15>::threeWayCmp(buf1(), buf2());
324 // Buffer compare equal, returning 0 and no call to Diff.
325 EXPECT_STREQ(getTrace(), stripComments(R"(
326 LdU<8> a[0]
327 LdU<8> b[0]
328 Diff<8>
329 LdU<4> a[8]
330 LdU<4> b[8]
331 Diff<4>
332 LdU<2> a[12]
333 LdU<2> b[12]
334 Diff<2>
335 LdU<1> a[14]
336 LdU<1> b[14]
337 Diff<1>
338 )"));
339 }
340
TEST_F(LlvmLibcAlgorithm,three_way_cmp_neq_15_shortcircuit)341 TEST_F(LlvmLibcAlgorithm, three_way_cmp_neq_15_shortcircuit) {
342 fillDifferent();
343 SizedOp<TestBackend, 16>::threeWayCmp(buf1(), buf2());
344 // If buffer compare isDifferent we stop early.
345 EXPECT_STREQ(getTrace(), stripComments(R"(
346 LdU<8> a[0]
347 LdU<8> b[0]
348 Diff<8>
349 )"));
350 }
351
TEST_F(LlvmLibcAlgorithm,three_way_cmp_eq_16)352 TEST_F(LlvmLibcAlgorithm, three_way_cmp_eq_16) {
353 fillEqual();
354 SizedOp<TestBackend, 16>::threeWayCmp(buf1(), buf2());
355 // Buffer compare equal, returning 0 and no call to Diff.
356 EXPECT_STREQ(getTrace(), stripComments(R"(
357 LdU<8> a[0]
358 LdU<8> b[0]
359 Diff<8>
360 LdU<8> a[8]
361 LdU<8> b[8]
362 Diff<8>
363 )"));
364 }
365
366 ///////////////////////////////////////////////////////////////////////////////
367 //// Testing skip operations
368 ///////////////////////////////////////////////////////////////////////////////
369
TEST_F(LlvmLibcAlgorithm,skip_and_set)370 TEST_F(LlvmLibcAlgorithm, skip_and_set) {
371 Skip<11>::Then<SizedOp<TestBackend, 1>>::set(dst(), ubyte{42});
372 EXPECT_STREQ(getTrace(), stripComments(R"(
373 Splat<1> 42
374 StU<1> a[11]
375 )"));
376 }
377
TEST_F(LlvmLibcAlgorithm,skip_and_different_1)378 TEST_F(LlvmLibcAlgorithm, skip_and_different_1) {
379 Skip<11>::Then<SizedOp<TestBackend, 1>>::isDifferent(buf1(), buf2());
380 EXPECT_STREQ(getTrace(), stripComments(R"(
381 LdU<1> a[11]
382 LdU<1> b[11]
383 Neq<1>
384 )"));
385 }
386
TEST_F(LlvmLibcAlgorithm,skip_and_three_way_cmp_8)387 TEST_F(LlvmLibcAlgorithm, skip_and_three_way_cmp_8) {
388 Skip<11>::Then<SizedOp<TestBackend, 1>>::threeWayCmp(buf1(), buf2());
389 EXPECT_STREQ(getTrace(), stripComments(R"(
390 LdU<1> a[11]
391 LdU<1> b[11]
392 Diff<1>
393 )"));
394 }
395
396 ///////////////////////////////////////////////////////////////////////////////
397 //// Testing tail operations
398 ///////////////////////////////////////////////////////////////////////////////
399
TEST_F(LlvmLibcAlgorithm,tail_copy_8)400 TEST_F(LlvmLibcAlgorithm, tail_copy_8) {
401 Tail<_8>::copy(dst(), src(), 16);
402 EXPECT_STREQ(getTrace(), stripComments(R"(
403 LdU<8> b[8]
404 StU<8> a[8]
405 )"));
406 }
407
TEST_F(LlvmLibcAlgorithm,tail_move_8)408 TEST_F(LlvmLibcAlgorithm, tail_move_8) {
409 Tail<_8>::move(dst(), src(), 16);
410 EXPECT_STREQ(getTrace(), stripComments(R"(
411 LdU<8> b[8]
412 StU<8> a[8]
413 )"));
414 }
415
TEST_F(LlvmLibcAlgorithm,tail_set_8)416 TEST_F(LlvmLibcAlgorithm, tail_set_8) {
417 Tail<_8>::set(dst(), ubyte{42}, 16);
418 EXPECT_STREQ(getTrace(), stripComments(R"(
419 Splat<8> 42
420 StU<8> a[8]
421 )"));
422 }
423
TEST_F(LlvmLibcAlgorithm,tail_different_8)424 TEST_F(LlvmLibcAlgorithm, tail_different_8) {
425 fillEqual();
426 Tail<_8>::isDifferent(buf1(), buf2(), 16);
427 EXPECT_STREQ(getTrace(), stripComments(R"(
428 LdU<8> a[8]
429 LdU<8> b[8]
430 Neq<8>
431 )"));
432 }
433
TEST_F(LlvmLibcAlgorithm,tail_three_way_cmp_8)434 TEST_F(LlvmLibcAlgorithm, tail_three_way_cmp_8) {
435 fillEqual();
436 Tail<_8>::threeWayCmp(buf1(), buf2(), 16);
437 EXPECT_STREQ(getTrace(), stripComments(R"(
438 LdU<8> a[8]
439 LdU<8> b[8]
440 Diff<8>
441 )"));
442 }
443
444 ///////////////////////////////////////////////////////////////////////////////
445 //// Testing HeadTail operations
446 ///////////////////////////////////////////////////////////////////////////////
447
TEST_F(LlvmLibcAlgorithm,head_tail_copy_8)448 TEST_F(LlvmLibcAlgorithm, head_tail_copy_8) {
449 HeadTail<_8>::copy(dst(), src(), 16);
450 EXPECT_STREQ(getTrace(), stripComments(R"(
451 LdU<8> b[0]
452 StU<8> a[0]
453 LdU<8> b[8]
454 StU<8> a[8]
455 )"));
456 }
457
458 ///////////////////////////////////////////////////////////////////////////////
459 //// Testing Loop operations
460 ///////////////////////////////////////////////////////////////////////////////
461
TEST_F(LlvmLibcAlgorithm,loop_copy_one_iteration_and_tail)462 TEST_F(LlvmLibcAlgorithm, loop_copy_one_iteration_and_tail) {
463 Loop<_8>::copy(dst(), src(), 10);
464 EXPECT_STREQ(getTrace(), stripComments(R"(
465 LdU<8> b[0]
466 StU<8> a[0] # covers 0-7
467 LdU<8> b[2]
468 StU<8> a[2] # covers 2-9
469 )"));
470 }
471
TEST_F(LlvmLibcAlgorithm,loop_copy_two_iteration_and_tail)472 TEST_F(LlvmLibcAlgorithm, loop_copy_two_iteration_and_tail) {
473 Loop<_8>::copy(dst(), src(), 17);
474 EXPECT_STREQ(getTrace(), stripComments(R"(
475 LdU<8> b[0]
476 StU<8> a[0] # covers 0-7
477 LdU<8> b[8]
478 StU<8> a[8] # covers 8-15
479 LdU<8> b[9]
480 StU<8> a[9] # covers 9-16
481 )"));
482 }
483
TEST_F(LlvmLibcAlgorithm,loop_with_one_turn_is_inefficient_but_ok)484 TEST_F(LlvmLibcAlgorithm, loop_with_one_turn_is_inefficient_but_ok) {
485 Loop<_8>::copy(dst(), src(), 8);
486 EXPECT_STREQ(getTrace(), stripComments(R"(
487 LdU<8> b[0]
488 StU<8> a[0] # first iteration covers 0-7
489 LdU<8> b[0] # tail also covers 0-7 but since Loop is supposed to be used
490 StU<8> a[0] # with a sufficient number of iterations the tail cost is amortised
491 )"));
492 }
493
TEST_F(LlvmLibcAlgorithm,loop_with_round_number_of_turn)494 TEST_F(LlvmLibcAlgorithm, loop_with_round_number_of_turn) {
495 Loop<_8>::copy(dst(), src(), 24);
496 EXPECT_STREQ(getTrace(), stripComments(R"(
497 LdU<8> b[0]
498 StU<8> a[0] # first iteration covers 0-7
499 LdU<8> b[8]
500 StU<8> a[8] # second iteration covers 8-15
501 LdU<8> b[16]
502 StU<8> a[16]
503 )"));
504 }
505
TEST_F(LlvmLibcAlgorithm,dst_aligned_loop)506 TEST_F(LlvmLibcAlgorithm, dst_aligned_loop) {
507 Loop<_8>::copy(dst<16>(), src(), 23);
508 EXPECT_STREQ(getTrace(), stripComments(R"(
509 LdU<8> b[0]
510 StA<8> a[0] # store is aligned on 16B
511 LdU<8> b[8]
512 StA<8> a[8] # subsequent stores are aligned
513 LdU<8> b[15]
514 StU<8> a[15] # Tail is always unaligned
515 )"));
516 }
517
TEST_F(LlvmLibcAlgorithm,aligned_loop)518 TEST_F(LlvmLibcAlgorithm, aligned_loop) {
519 Loop<_8>::copy(dst<16>(), src<8>(), 23);
520 EXPECT_STREQ(getTrace(), stripComments(R"(
521 LdA<8> b[0] # load is aligned on 8B
522 StA<8> a[0] # store is aligned on 16B
523 LdA<8> b[8] # subsequent loads are aligned
524 StA<8> a[8] # subsequent stores are aligned
525 LdU<8> b[15] # Tail is always unaligned
526 StU<8> a[15] # Tail is always unaligned
527 )"));
528 }
529
530 ///////////////////////////////////////////////////////////////////////////////
531 //// Testing Align operations
532 ///////////////////////////////////////////////////////////////////////////////
533
TEST_F(LlvmLibcAlgorithm,align_dst_copy_8)534 TEST_F(LlvmLibcAlgorithm, align_dst_copy_8) {
535 Align<_8, Arg::Dst>::Then<Loop<_8>>::copy(dst(2), src(3), 31);
536 EXPECT_STREQ(getTrace(), stripComments(R"(
537 LdU<8> b[3]
538 StU<8> a[2] # First store covers unaligned bytes
539 LdU<8> b[9]
540 StA<8> a[8] # First aligned store
541 LdU<8> b[17]
542 StA<8> a[16] # Subsequent stores are aligned
543 LdU<8> b[25]
544 StA<8> a[24] # Subsequent stores are aligned
545 LdU<8> b[26]
546 StU<8> a[25] # Last store covers remaining bytes
547 )"));
548 }
549
TEST_F(LlvmLibcAlgorithm,align_src_copy_8)550 TEST_F(LlvmLibcAlgorithm, align_src_copy_8) {
551 Align<_8, Arg::Src>::Then<Loop<_8>>::copy(dst(2), src(3), 31);
552 EXPECT_STREQ(getTrace(), stripComments(R"(
553 LdU<8> b[3] # First load covers unaligned bytes
554 StU<8> a[2]
555 LdA<8> b[8] # First aligned load
556 StU<8> a[7]
557 LdA<8> b[16] # Subsequent loads are aligned
558 StU<8> a[15]
559 LdA<8> b[24] # Subsequent loads are aligned
560 StU<8> a[23]
561 LdU<8> b[26] # Last load covers remaining bytes
562 StU<8> a[25]
563 )"));
564 }
565
566 } // namespace __llvm_libc
567