Skip to content

Commit 51f6adf

Browse files
committed
Added alignment tests for [[no_unique_address]].
1 parent 8d5df91 commit 51f6adf

File tree

2 files changed

+182
-1
lines changed

2 files changed

+182
-1
lines changed

test/test_alignment.cpp

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#include <catch2/catch_test_macros.hpp>
2+
#include <fmt/format.h>
3+
#include <tuplet/tuple.hpp>
4+
5+
// To get clang-cl: cmake -T ClangCL ...
6+
7+
template <typename Tuple, size_t... Indexes, typename Fn>
8+
constexpr decltype(auto) tuple_for_each_impl(
9+
Tuple&& tuple,
10+
std::integer_sequence<size_t, Indexes...>,
11+
Fn&& fn) {
12+
(fn(get<Indexes>(std::forward<Tuple>(tuple))), ...);
13+
14+
return std::forward<Fn>(fn);
15+
}
16+
template <typename Tuple, typename Fn>
17+
constexpr decltype(auto) tuple_for_each(Tuple&& tuple, Fn&& fn) {
18+
using indexes = std::make_index_sequence<
19+
std::tuple_size<std::decay_t<Tuple>>::value>;
20+
21+
return tuple_for_each_impl(
22+
std::forward<Tuple>(tuple),
23+
indexes {},
24+
std::forward<Fn>(fn));
25+
}
26+
27+
template <size_t Alignment, typename T>
28+
constexpr T align_value(T value) {
29+
constexpr auto alignment {static_cast<T>(Alignment)};
30+
31+
value += alignment - 1;
32+
value &= ~(alignment - 1);
33+
34+
return value;
35+
}
36+
37+
#if (defined _MSC_VER)
38+
constexpr bool is_cl_or_clang_cl = true;
39+
#else
40+
constexpr bool is_cl_or_clang_cl = false;
41+
#endif
42+
43+
#define DO_STRINGIZE(a) #a
44+
#define STRINGIZE(a) DO_STRINGIZE(a)
45+
46+
constexpr bool has_no_unique_address = sizeof(
47+
STRINGIZE(TUPLET_NO_UNIQUE_ADDRESS))
48+
> 1;
49+
50+
struct empty {};
51+
52+
struct struct_with_empty {
53+
alignas(64) int a;
54+
[[no_unique_address]] empty b;
55+
int c;
56+
};
57+
58+
// [[msvc::no_unique_address]] affects the offset of empty struct members.
59+
// In tuple, it doesn't enable padding "borrowing" and there's no empty element
60+
// removal.
61+
static_assert(
62+
offsetof(struct_with_empty, b)
63+
== (has_no_unique_address && !is_cl_or_clang_cl)
64+
? 0
65+
: sizeof(int));
66+
static_assert(sizeof(struct_with_empty) == 64);
67+
68+
template <typename Tuple>
69+
void test_tuple_alignment() {
70+
Tuple t;
71+
const auto base_addr {reinterpret_cast<uintptr_t>(&t)};
72+
size_t offset {0}, index {0};
73+
74+
INFO(fmt::format("{}", typeid(Tuple).name()));
75+
76+
tuple_for_each(t, [&](auto& element) {
77+
using element_type = std::decay_t<decltype(element)>;
78+
79+
INFO(fmt::format(
80+
"Element index {}, sizeof {}, alignof {}",
81+
index++,
82+
sizeof(element_type),
83+
alignof(element_type)));
84+
85+
const auto addr {reinterpret_cast<uintptr_t>(&element)};
86+
const auto element_offset {addr - base_addr};
87+
constexpr auto element_size {sizeof(element_type)};
88+
89+
offset = align_value<alignof(element_type)>(offset);
90+
91+
if (has_no_unique_address) {
92+
// cl with [[msvc::no_unique_address]] will not optimize out empty
93+
// tuple elements.
94+
if (is_cl_or_clang_cl || (element_type::s_size != 0)) {
95+
CHECK(element_offset == offset);
96+
97+
if (!is_cl_or_clang_cl && !std::is_aggregate_v<element_type>) {
98+
// Only non-aggregates allow their padding to be used for
99+
// the next element.
100+
// cl doesn't allow this either.
101+
offset += element_type::s_size;
102+
} else {
103+
offset += element_size;
104+
}
105+
} else {
106+
// gcc and clang will optimize away and the offset is 0, not the
107+
// offset of the previous element.
108+
CHECK(element_offset == 0);
109+
}
110+
} else {
111+
// Simple case; no [[no_unique_address]] affecting things.
112+
CHECK(element_offset == offset);
113+
offset += element_size;
114+
}
115+
});
116+
117+
offset = align_value<alignof(Tuple)>(offset);
118+
119+
CHECK(offset == sizeof(Tuple));
120+
}
121+
122+
template <size_t Size, size_t Alignment>
123+
struct alignas(Alignment) aligned_buffer_aggregate {
124+
static constexpr size_t s_size {Size};
125+
126+
uint8_t m_buff[s_size];
127+
};
128+
129+
template <size_t Alignment>
130+
struct alignas(Alignment) aligned_buffer_aggregate<0, Alignment> {
131+
static constexpr size_t s_size {0};
132+
};
133+
134+
template <size_t Size, size_t Alignment>
135+
struct alignas(Alignment) aligned_buffer {
136+
static constexpr size_t s_size {Size};
137+
138+
uint8_t m_buff[s_size];
139+
140+
aligned_buffer() {}
141+
};
142+
143+
template <size_t Alignment>
144+
struct alignas(Alignment) aligned_buffer<0, Alignment> {
145+
static constexpr size_t s_size {0};
146+
147+
aligned_buffer() {}
148+
};
149+
150+
TEST_CASE("Check alignment") {
151+
using buff40_64_a = aligned_buffer_aggregate<40, 64>;
152+
using buff10_16_a = aligned_buffer_aggregate<10, 16>;
153+
using buff15_32_a = aligned_buffer_aggregate<15, 32>;
154+
using buff0_16_a = aligned_buffer_aggregate<0, 16>;
155+
using buff13_8_a = aligned_buffer_aggregate<13, 8>;
156+
157+
using buff40_64 = aligned_buffer<40, 64>;
158+
using buff10_16 = aligned_buffer<10, 16>;
159+
using buff15_32 = aligned_buffer<15, 32>;
160+
using buff0_16 = aligned_buffer<0, 16>;
161+
using buff13_8 = aligned_buffer<13, 8>;
162+
163+
test_tuple_alignment<tuplet::tuple<
164+
buff40_64_a,
165+
buff10_16_a,
166+
buff15_32_a,
167+
buff0_16_a,
168+
buff13_8_a>>();
169+
170+
test_tuple_alignment<
171+
tuplet::tuple<buff40_64, buff10_16, buff15_32, buff0_16, buff13_8>>();
172+
173+
test_tuple_alignment<
174+
tuplet::
175+
tuple<buff40_64_a, buff10_16, buff15_32_a, buff0_16, buff13_8_a>>();
176+
177+
test_tuple_alignment<
178+
tuplet::
179+
tuple<buff40_64, buff10_16_a, buff15_32, buff0_16_a, buff13_8>>();
180+
}

test/test_tuplet.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
#include "test_alignment.cpp"
12
#include "test_apply.cpp"
23
#include "test_assignment.cpp"
34
#include "test_compare.cpp"
45
#include "test_structured_bindings.cpp"
56
#include "test_traits.cpp"
6-
#include "test_printing.cpp"
7+
#include "test_printing.cpp"

0 commit comments

Comments
 (0)