• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Abseil Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "absl/crc/internal/crc_memcpy.h"
16 
17 #include <cstddef>
18 #include <cstdint>
19 #include <cstring>
20 #include <limits>
21 #include <memory>
22 #include <string>
23 #include <utility>
24 
25 #include "gtest/gtest.h"
26 #include "absl/crc/crc32c.h"
27 #include "absl/memory/memory.h"
28 #include "absl/random/distributions.h"
29 #include "absl/random/random.h"
30 #include "absl/strings/str_cat.h"
31 #include "absl/strings/string_view.h"
32 
33 namespace {
34 
35 enum CrcEngine {
36   ACCELERATED = 0,
37   NONTEMPORAL = 1,
38   FALLBACK = 2,
39 };
40 
41 // Correctness tests:
42 // - Every source/destination byte alignment 0-15, every size 0-511 bytes
43 // - Arbitrarily aligned source, large size
44 template <size_t max_size>
45 class CrcMemcpyTest : public testing::Test {
46  protected:
CrcMemcpyTest()47   CrcMemcpyTest() {
48     source_ = std::make_unique<char[]>(kSize);
49     destination_ = std::make_unique<char[]>(kSize);
50   }
51   static constexpr size_t kAlignment = 16;
52   static constexpr size_t kMaxCopySize = max_size;
53   static constexpr size_t kSize = kAlignment + kMaxCopySize;
54   std::unique_ptr<char[]> source_;
55   std::unique_ptr<char[]> destination_;
56 
57   absl::BitGen gen_;
58 };
59 
60 // Small test is slightly larger 4096 bytes to allow coverage of the "large"
61 // copy function.  The minimum size to exercise all code paths in that function
62 // would be around 256 consecutive tests (getting every possible tail value
63 // and 0-2 small copy loops after the main block), so testing from 4096-4500
64 // will cover all of those code paths multiple times.
65 typedef CrcMemcpyTest<4500> CrcSmallTest;
66 typedef CrcMemcpyTest<(1 << 24)> CrcLargeTest;
67 // Parametrize the small test so that it can be done with all configurations.
68 template <typename ParamsT>
69 class EngineParamTestTemplate : public CrcSmallTest,
70                                 public ::testing::WithParamInterface<ParamsT> {
71  protected:
EngineParamTestTemplate()72   EngineParamTestTemplate() {
73     if (GetParam().crc_engine_selector == FALLBACK) {
74       engine_ = std::make_unique<absl::crc_internal::FallbackCrcMemcpyEngine>();
75     } else if (GetParam().crc_engine_selector == NONTEMPORAL) {
76       engine_ =
77           std::make_unique<absl::crc_internal::CrcNonTemporalMemcpyEngine>();
78     } else {
79       engine_ = absl::crc_internal::CrcMemcpy::GetTestEngine(
80           GetParam().vector_lanes, GetParam().integer_lanes);
81     }
82   }
83 
84   // Convenience method.
GetParam() const85   ParamsT GetParam() const {
86     return ::testing::WithParamInterface<ParamsT>::GetParam();
87   }
88 
89   std::unique_ptr<absl::crc_internal::CrcMemcpyEngine> engine_;
90 };
91 struct TestParams {
92   CrcEngine crc_engine_selector = ACCELERATED;
93   int vector_lanes = 0;
94   int integer_lanes = 0;
95 };
96 using EngineParamTest = EngineParamTestTemplate<TestParams>;
97 // SmallCorrectness is designed to exercise every possible set of code paths
98 // in the memcpy code, not including the loop.
TEST_P(EngineParamTest,SmallCorrectnessCheckSourceAlignment)99 TEST_P(EngineParamTest, SmallCorrectnessCheckSourceAlignment) {
100   constexpr size_t kTestSizes[] = {0, 100, 255, 512, 1024, 4000, kMaxCopySize};
101 
102   for (size_t source_alignment = 0; source_alignment < kAlignment;
103        source_alignment++) {
104     for (auto size : kTestSizes) {
105       char* base_data = static_cast<char*>(source_.get()) + source_alignment;
106       for (size_t i = 0; i < size; i++) {
107         *(base_data + i) =
108             static_cast<char>(absl::Uniform<unsigned char>(gen_));
109       }
110       SCOPED_TRACE(absl::StrCat("engine=<", GetParam().vector_lanes, ",",
111                                 GetParam().integer_lanes, ">, ", "size=", size,
112                                 ", source_alignment=", source_alignment));
113       absl::crc32c_t initial_crc =
114           absl::crc32c_t{absl::Uniform<uint32_t>(gen_)};
115       absl::crc32c_t experiment_crc =
116           engine_->Compute(destination_.get(), source_.get() + source_alignment,
117                            size, initial_crc);
118       // Check the memory region to make sure it is the same
119       int mem_comparison =
120           memcmp(destination_.get(), source_.get() + source_alignment, size);
121       SCOPED_TRACE(absl::StrCat("Error in memcpy of size: ", size,
122                                 " with source alignment: ", source_alignment));
123       ASSERT_EQ(mem_comparison, 0);
124       absl::crc32c_t baseline_crc = absl::ExtendCrc32c(
125           initial_crc,
126           absl::string_view(
127               static_cast<char*>(source_.get()) + source_alignment, size));
128       ASSERT_EQ(baseline_crc, experiment_crc);
129     }
130   }
131 }
132 
TEST_P(EngineParamTest,SmallCorrectnessCheckDestAlignment)133 TEST_P(EngineParamTest, SmallCorrectnessCheckDestAlignment) {
134   constexpr size_t kTestSizes[] = {0, 100, 255, 512, 1024, 4000, kMaxCopySize};
135 
136   for (size_t dest_alignment = 0; dest_alignment < kAlignment;
137        dest_alignment++) {
138     for (auto size : kTestSizes) {
139       char* base_data = static_cast<char*>(source_.get());
140       for (size_t i = 0; i < size; i++) {
141         *(base_data + i) =
142             static_cast<char>(absl::Uniform<unsigned char>(gen_));
143       }
144       SCOPED_TRACE(absl::StrCat("engine=<", GetParam().vector_lanes, ",",
145                                 GetParam().integer_lanes, ">, ", "size=", size,
146                                 ", destination_alignment=", dest_alignment));
147       absl::crc32c_t initial_crc =
148           absl::crc32c_t{absl::Uniform<uint32_t>(gen_)};
149       absl::crc32c_t experiment_crc =
150           engine_->Compute(destination_.get() + dest_alignment, source_.get(),
151                            size, initial_crc);
152       // Check the memory region to make sure it is the same
153       int mem_comparison =
154           memcmp(destination_.get() + dest_alignment, source_.get(), size);
155       SCOPED_TRACE(absl::StrCat("Error in memcpy of size: ", size,
156                                 " with dest alignment: ", dest_alignment));
157       ASSERT_EQ(mem_comparison, 0);
158       absl::crc32c_t baseline_crc = absl::ExtendCrc32c(
159           initial_crc,
160           absl::string_view(static_cast<char*>(source_.get()), size));
161       ASSERT_EQ(baseline_crc, experiment_crc);
162     }
163   }
164 }
165 
166 INSTANTIATE_TEST_SUITE_P(EngineParamTest, EngineParamTest,
167                          ::testing::Values(
168                              // Tests for configurations that may occur in prod.
169                              TestParams{ACCELERATED, 3, 0},
170                              TestParams{ACCELERATED, 1, 2},
171                              TestParams{ACCELERATED, 1, 0},
172                              // Fallback test.
173                              TestParams{FALLBACK, 0, 0},
174                              // Non Temporal
175                              TestParams{NONTEMPORAL, 0, 0}));
176 
177 }  // namespace
178