1 | /* |
2 | * Copyright 2016-present Facebook, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | #include <folly/CachelinePadded.h> |
18 | |
19 | #include <type_traits> |
20 | |
21 | #include <folly/lang/Align.h> |
22 | #include <folly/portability/GTest.h> |
23 | |
24 | using folly::CachelinePadded; |
25 | |
26 | static_assert( |
27 | std::is_standard_layout<CachelinePadded<int>>::value, |
28 | "CachelinePadded<T> must be standard-layout if T is." ); |
29 | |
30 | static constexpr int kCachelineSize = |
31 | folly::hardware_destructive_interference_size; |
32 | |
33 | template <size_t dataSize, size_t alignment = alignof(void*)> |
34 | struct alignas(alignment) SizedData { |
35 | SizedData() { |
36 | size_t i = 0; |
37 | for (auto& datum : data) { |
38 | datum = i++; |
39 | } |
40 | } |
41 | |
42 | void doModifications() { |
43 | size_t i = 0; |
44 | for (auto& datum : data) { |
45 | EXPECT_EQ(static_cast<unsigned char>(i), datum); |
46 | ++i; |
47 | ++datum; |
48 | } |
49 | } |
50 | |
51 | ~SizedData() { |
52 | size_t i = 1; |
53 | for (auto& datum : data) { |
54 | EXPECT_EQ(static_cast<unsigned char>(i), datum); |
55 | ++i; |
56 | } |
57 | } |
58 | |
59 | unsigned char data[dataSize]; |
60 | }; |
61 | |
62 | template <typename T, size_t N = 1> |
63 | using SizedDataMimic = SizedData<N * sizeof(T), alignof(T)>; |
64 | |
65 | template <typename T> |
66 | struct CachelinePaddedTests : ::testing::Test {}; |
67 | |
68 | using CachelinePaddedTypes = ::testing::Types< |
69 | SizedData<kCachelineSize>, |
70 | SizedData<2 * kCachelineSize>, |
71 | SizedData<kCachelineSize / 2>, |
72 | SizedData<kCachelineSize + kCachelineSize / 2>, |
73 | // Mimic single basic types: |
74 | SizedDataMimic<folly::max_align_t>, |
75 | SizedDataMimic<void*>, |
76 | SizedDataMimic<long double>, |
77 | SizedDataMimic<double>, |
78 | SizedDataMimic<float>, |
79 | SizedDataMimic<long long>, |
80 | SizedDataMimic<long>, |
81 | SizedDataMimic<int>, |
82 | SizedDataMimic<short>, |
83 | SizedDataMimic<char>, |
84 | // Mimic small arrays of basic types: |
85 | SizedDataMimic<folly::max_align_t, 3>, |
86 | SizedDataMimic<void*, 3>, |
87 | SizedDataMimic<long double, 3>, |
88 | SizedDataMimic<double, 3>, |
89 | SizedDataMimic<float, 3>, |
90 | SizedDataMimic<long long, 3>, |
91 | SizedDataMimic<long, 3>, |
92 | SizedDataMimic<int, 3>, |
93 | SizedDataMimic<short, 3>, |
94 | SizedDataMimic<char, 3>, |
95 | // Mimic large arrays of basic types: |
96 | SizedDataMimic<folly::max_align_t, kCachelineSize + 3>, |
97 | SizedDataMimic<void*, kCachelineSize + 3>, |
98 | SizedDataMimic<long double, kCachelineSize + 3>, |
99 | SizedDataMimic<double, kCachelineSize + 3>, |
100 | SizedDataMimic<float, kCachelineSize + 3>, |
101 | SizedDataMimic<long long, kCachelineSize + 3>, |
102 | SizedDataMimic<long, kCachelineSize + 3>, |
103 | SizedDataMimic<int, kCachelineSize + 3>, |
104 | SizedDataMimic<short, kCachelineSize + 3>, |
105 | SizedDataMimic<char, kCachelineSize + 3>>; |
106 | TYPED_TEST_CASE(CachelinePaddedTests, CachelinePaddedTypes); |
107 | |
108 | TYPED_TEST(CachelinePaddedTests, alignment) { |
109 | EXPECT_EQ(alignof(TypeParam), alignof(CachelinePadded<TypeParam>)); |
110 | } |
111 | |
112 | TYPED_TEST(CachelinePaddedTests, integrity) { |
113 | CachelinePadded<TypeParam> item; |
114 | item.get()->doModifications(); |
115 | } |
116 | |
117 | TYPED_TEST(CachelinePaddedTests, size) { |
118 | EXPECT_GT( |
119 | sizeof(TypeParam) + 2 * kCachelineSize, |
120 | sizeof(CachelinePadded<TypeParam>)); |
121 | size_t const rawSize = sizeof(TypeParam); |
122 | size_t const rawAlign = alignof(TypeParam); |
123 | size_t const expectedPadding = kCachelineSize - (rawAlign % kCachelineSize); |
124 | size_t const expectedPaddedSize = rawSize + 2 * expectedPadding; |
125 | EXPECT_EQ(expectedPaddedSize, sizeof(CachelinePadded<TypeParam>)); |
126 | } |
127 | |
128 | TEST(CachelinePadded, PtrOperator) { |
129 | CachelinePadded<int> padded; |
130 | EXPECT_TRUE(padded.get() == padded.operator->()); |
131 | EXPECT_TRUE(&*padded == padded.get()); |
132 | const auto constPadded = CachelinePadded<int>{}; |
133 | EXPECT_TRUE(constPadded.get() == constPadded.operator->()); |
134 | EXPECT_TRUE(constPadded.get() == &*constPadded.get()); |
135 | } |
136 | |
137 | TEST(CachelinePadded, PropagatesConstness) { |
138 | struct OverloadedOnConst { |
139 | void assign(int* dst) { |
140 | *dst = 31415; |
141 | } |
142 | void assign(int* dst) const { |
143 | *dst = 271828; |
144 | } |
145 | }; |
146 | |
147 | CachelinePadded<OverloadedOnConst> padded; |
148 | |
149 | int i = 0; |
150 | padded->assign(&i); |
151 | EXPECT_EQ(31415, i); |
152 | |
153 | const auto constPadded = CachelinePadded<OverloadedOnConst>{}; |
154 | constPadded->assign(&i); |
155 | EXPECT_EQ(271828, i); |
156 | } |
157 | |
158 | TEST(CachelinePadded, ConstructsAndDestructs) { |
159 | enum LifetimeStatus { |
160 | kNone, |
161 | kConstructed, |
162 | kDestroyed, |
163 | }; |
164 | struct WriteOnLifetimeOp { |
165 | explicit WriteOnLifetimeOp(LifetimeStatus* dst) : dst_(dst) { |
166 | *dst = kConstructed; |
167 | } |
168 | ~WriteOnLifetimeOp() { |
169 | *dst_ = kDestroyed; |
170 | } |
171 | LifetimeStatus* dst_; |
172 | }; |
173 | LifetimeStatus status = kNone; |
174 | CachelinePadded<WriteOnLifetimeOp>* ptr = |
175 | new CachelinePadded<WriteOnLifetimeOp>(&status); |
176 | EXPECT_EQ(kConstructed, status); |
177 | delete ptr; |
178 | EXPECT_EQ(kDestroyed, status); |
179 | } |
180 | |
181 | TEST(CachelinePadded, ConstructsAndDestructsArrays) { |
182 | static thread_local int numConstructions; |
183 | static thread_local int numDestructions; |
184 | numConstructions = 0; |
185 | numDestructions = 0; |
186 | struct LifetimeCountingClass { |
187 | LifetimeCountingClass() { |
188 | ++numConstructions; |
189 | } |
190 | ~LifetimeCountingClass() { |
191 | ++numDestructions; |
192 | } |
193 | }; |
194 | const static int kNumItems = 123; |
195 | CachelinePadded<LifetimeCountingClass>* ptr = |
196 | new CachelinePadded<LifetimeCountingClass>[kNumItems]; |
197 | EXPECT_EQ(kNumItems, numConstructions); |
198 | delete[] ptr; |
199 | EXPECT_EQ(kNumItems, numDestructions); |
200 | } |
201 | |
202 | TEST(CachelinePadded, ForwardsCorrectly) { |
203 | struct RvalueOverloadedConstructor { |
204 | RvalueOverloadedConstructor(int* dst, int& /* ignored */) { |
205 | *dst = 0; |
206 | } |
207 | RvalueOverloadedConstructor(int* dst, int&& /* ignored */) { |
208 | *dst = 1; |
209 | } |
210 | }; |
211 | int shouldBeZero = 12345; |
212 | int shouldBeOne = 67890; |
213 | { |
214 | int ignored = 42; |
215 | CachelinePadded<RvalueOverloadedConstructor> padded1( |
216 | &shouldBeZero, ignored); |
217 | CachelinePadded<RvalueOverloadedConstructor> padded2( |
218 | &shouldBeOne, static_cast<int&&>(ignored)); |
219 | } |
220 | EXPECT_EQ(0, shouldBeZero); |
221 | EXPECT_EQ(1, shouldBeOne); |
222 | } |
223 | |