Sequoia
Loading...
Searching...
No Matches
AllocationCheckers.hpp
Go to the documentation of this file.
1
2// Copyright Oliver J. Rosten 2019. //
3// Distributed under the GNU GENERAL PUBLIC LICENSE, Version 3.0. //
4// (See accompanying file LICENSE.md or copy at //
5// https://www.gnu.org/licenses/gpl-3.0.en.html) //
7
8#pragma once
9
18
21
22namespace sequoia::testing
23{
24 enum class construction_allocation_event {
25 initialization,
26 copy,
27 move,
28 para_copy,
29 para_move
30 };
31
32 enum class assignment_allocation_event {
33 assign,
34 assign_no_prop,
35 move_assign,
36 move_assign_no_prop
37 };
38
39 enum class individual_allocation_event {
40 mutation
41 };
42
43 template<auto To, auto From>
44 [[nodiscard]]
45 constexpr alloc_prediction<To> convert(alloc_prediction<From> p) noexcept
46 {
47 return {p.unshifted(), p.value() - p.unshifted()};
48 }
49
50 using initialization_prediction = alloc_prediction<construction_allocation_event::initialization>;
51 using copy_prediction = alloc_prediction<construction_allocation_event::copy>;
52 using move_prediction = alloc_prediction<construction_allocation_event::move>;
53 using para_copy_prediction = alloc_prediction<construction_allocation_event::para_copy>;
54 using para_move_prediction = alloc_prediction<construction_allocation_event::para_move>;
55 using assign_prediction = alloc_prediction<assignment_allocation_event::assign>;
56 using assign_no_prop_prediction = alloc_prediction<assignment_allocation_event::assign_no_prop>;
57 using move_assign_prediction = alloc_prediction<assignment_allocation_event::move_assign>;
58 using move_assign_no_prop_prediction = alloc_prediction<assignment_allocation_event::move_assign_no_prop>;
59 using mutation_prediction = alloc_prediction<individual_allocation_event::mutation>;
60
61 [[nodiscard]]
62 consteval initialization_prediction operator ""_init(unsigned long long int n) noexcept
63 {
64 return initialization_prediction{static_cast<int>(n)};
65 }
66
67 [[nodiscard]]
68 consteval copy_prediction operator ""_c(unsigned long long int n) noexcept
69 {
70 return copy_prediction{ static_cast<int>(n) };
71 }
72
73 [[nodiscard]]
74 consteval mutation_prediction operator ""_mu(unsigned long long int n) noexcept
75 {
76 return mutation_prediction{ static_cast<int>(n) };
77 }
78
79 [[nodiscard]]
80 consteval para_copy_prediction operator ""_pc(unsigned long long int n) noexcept
81 {
82 return para_copy_prediction{ static_cast<int>(n) };
83 }
84
85 [[nodiscard]]
86 consteval para_move_prediction operator ""_pm(unsigned long long int n) noexcept
87 {
88 return para_move_prediction{ static_cast<int>(n) };
89 }
90
91 [[nodiscard]]
92 consteval assign_no_prop_prediction operator ""_anp(unsigned long long int n) noexcept
93 {
94 return assign_no_prop_prediction{ static_cast<int>(n) };
95 }
96
97 [[nodiscard]]
98 consteval assign_prediction operator ""_awp(unsigned long long int n) noexcept
99 {
100 return assign_prediction{ static_cast<int>(n) };
101 }
102
103 [[nodiscard]]
104 consteval move_assign_prediction operator ""_ma(unsigned long long int n) noexcept
105 {
106 return move_assign_prediction{static_cast<int>(n)};
107 }
108
109 [[nodiscard]]
110 consteval move_assign_no_prop_prediction operator ""_manp(unsigned long long int n) noexcept
111 {
112 return move_assign_no_prop_prediction{ static_cast<int>(n) };
113 }
114
115 enum class scoped_prediction_corrections { num_containers, post_mutation };
116
117 template<scoped_prediction_corrections CorrectionFlavour>
119 {
120 public:
121 constexpr explicit corrections(std::size_t n) : m_Num{n} {}
122
123 [[nodiscard]]
124 constexpr int value() const noexcept
125 {
126 return static_cast<int>(m_Num);
127 }
128
129 [[nodiscard]]
130 friend auto operator<=>(const corrections&, const corrections&) noexcept = default;
131
132 private:
133 std::size_t m_Num{};
134 };
135
138
139 [[nodiscard]]
140 consteval number_of_containers operator ""_containers(unsigned long long int n) noexcept
141 {
142 return number_of_containers{ static_cast<std::size_t>(n) };
143 }
144
145 [[nodiscard]]
146 consteval post_mutation_correction operator ""_postmutation(unsigned long long int n) noexcept
147 {
148 return post_mutation_correction{static_cast<std::size_t>(n)};
149 }
150
152 {
154 : num_x{x}
155 , num_y{y}
156 , mutation_correction{postMutation}
157 {}
158
159 number_of_containers num_x, num_y;
160 post_mutation_correction mutation_correction;
161 };
162
163 namespace allocation_equivalence_classes
164 {
166
174 template<class Allocator>
176
177 template<class Allocator>
179
181 }
182
183 template<auto AllocEvent>
184 [[nodiscard]]
185 constexpr alloc_prediction<AllocEvent> increment_msvc_debug_count(alloc_prediction<AllocEvent> p, int val) noexcept
186 {
187 if constexpr (with_msvc_v && (iterator_debug_level() > 0))
188 {
189 const int unshifted{p.unshifted()};
190 return alloc_prediction<AllocEvent>{unshifted, val};
191 }
192 else
193 {
194 return p;
195 }
196 }
197
198 template<class T>
200 {
201 template<auto NullAllocEvent>
202 [[nodiscard]]
203 constexpr static int shift(int count, const alloc_prediction<NullAllocEvent>&) noexcept
204 {
205 return count;
206 }
207 };
208
209 enum class top_level { no, yes };
210
216 template<class T>
218
219 template<class Allocator>
220 class alloc_prediction_shifter<allocation_equivalence_classes::container_of_values<Allocator>>
221 {
222 public:
223 alloc_prediction_shifter(const container_counts& data, top_level level)
224 : m_Counts{data}
225 , m_TopLevel{level}
226 {}
227
228 [[nodiscard]]
229 constexpr copy_prediction shift(copy_prediction p, container_tag tag) const noexcept
230 {
231 const auto& c{tag == container_tag::x ? m_Counts.num_x : m_Counts.num_y};
232 return increment_msvc_debug_count(p, c.value());
233 }
234
235 [[nodiscard]]
236 constexpr move_prediction shift(move_prediction p) const noexcept
237 {
238 return increment_msvc_debug_count(p, is_top_level() ? 1 : 0);
239 }
240
241 [[nodiscard]]
242 constexpr para_copy_prediction shift(para_copy_prediction p) const noexcept
243 {
244 return increment_msvc_debug_count(p, m_Counts.num_y.value());
245 }
246
247 [[nodiscard]]
248 constexpr para_move_prediction shift(para_move_prediction p, container_tag tag) const noexcept
249 {
250 const auto& c{tag == container_tag::x ? m_Counts.num_x : m_Counts.num_y};
251 return increment_msvc_debug_count(p, c.value());
252 }
253
254 [[nodiscard]]
255 constexpr mutation_prediction shift(mutation_prediction p) const noexcept
256 {
257 return increment_msvc_debug_count(p, m_Counts.mutation_correction.value());
258 }
259
260 [[nodiscard]]
261 constexpr assign_prediction shift(assign_prediction p) const noexcept
262 {
263 return increment_msvc_debug_count(p, m_Counts.num_y.value());
264 }
265
266 [[nodiscard]]
267 constexpr move_assign_prediction shift(move_assign_prediction p) const noexcept
268 {
269 return increment_msvc_debug_count(p, is_top_level() ? 1 : 0);
270 }
271
272 [[nodiscard]]
273 constexpr assign_no_prop_prediction shift(assign_no_prop_prediction p) const noexcept
274 {
275 return increment_msvc_debug_count(p, is_top_level() ? 0 : m_Counts.num_y.value());
276 }
277
278 [[nodiscard]]
279 constexpr move_assign_no_prop_prediction shift(move_assign_no_prop_prediction p) const noexcept
280 {
281 const bool doIncrement{!is_top_level() && (m_Counts.num_y.value() > m_Counts.num_x.value())};
282 return increment_msvc_debug_count(p, doIncrement ? m_Counts.num_y.value() : 0);
283 }
284
285 [[nodiscard]]
286 constexpr const container_counts& counts() const noexcept
287 {
288 return m_Counts;
289 }
290
291 [[nodiscard]]
292 constexpr bool is_top_level() const noexcept
293 {
294 return m_TopLevel == top_level::yes;
295 }
296 private:
297 container_counts m_Counts;
298 top_level m_TopLevel;
299 };
300
301 template<class Allocator>
302 class alloc_prediction_shifter<allocation_equivalence_classes::container_of_pointers<Allocator>>
303 : public alloc_prediction_shifter<allocation_equivalence_classes::container_of_values<Allocator>>
304 {
305 public:
308
309 [[nodiscard]]
310 constexpr assign_prediction shift(assign_prediction p) const noexcept
311 {
312 const auto val{
313 []() {
314 constexpr bool copyProp{std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value};
315 constexpr bool moveProp{std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value};
316 constexpr bool swapProp{std::allocator_traits<Allocator>::propagate_on_container_swap::value};
317 return !copyProp ? 1 :
318 moveProp ? 2 :
319 swapProp ? 1 : 3;
320 }()
321 };
322
323 return increment_msvc_debug_count(p, this->counts().num_y.value()*val);
324 }
325
326 [[nodiscard]]
327 constexpr assign_no_prop_prediction shift(assign_no_prop_prediction p) const noexcept
328 {
329 return increment_msvc_debug_count(p, this->counts().num_y.value());
330 }
331 };
332
333 template<movable_comparable T, alloc_getter<T> Getter>
335 {
336 using allocator = std::remove_cvref_t<std::invoke_result_t<Getter, T>>;
338 };
339
340 template<movable_comparable T, alloc_getter<T> Getter>
341 requires requires { typename Getter::alloc_equivalence_class; }
343 {
344 using type = typename Getter::alloc_equivalence_class;
345 };
346
347 template<movable_comparable T, alloc_getter<T> Getter>
348 using alloc_equivalence_class_generator_t = typename alloc_equivalence_class_generator<T, Getter>::type;
349
350 template<class T>
352
353 template<class T>
354 using type_to_allocation_predictions_t = typename type_to_allocation_predictions<T>::predictions_type;
355
356 template<class T>
357 using type_to_inner_allocation_predictions_t = typename type_to_allocation_predictions<T>::inner_predictions_type;
358
369 template<movable_comparable T, alloc_getter<T> Getter>
371 {
372 public:
373 using allocator_type = std::invoke_result_t<Getter, T>;
374
375 constexpr explicit allocation_info_base(Getter allocGetter)
376 : m_AllocatorGetter{std::move(allocGetter)}
377 {}
378
379 constexpr allocation_info_base(const allocation_info_base&) = default;
380
381 [[nodiscard]]
382 int count(const T& c) const noexcept
383 {
384 return m_AllocatorGetter(c).allocs();
385 }
386
387 template<class... Args>
388 [[nodiscard]]
389 allocator_type make_allocator(Args&&... args) const
390 {
391 return {std::forward<Args>(args)...};
392 }
393
394 [[nodiscard]]
395 allocator_type allocator(const T& c) const
396 {
397 return m_AllocatorGetter(c);
398 }
399 protected:
400 ~allocation_info_base() = default;
401
402 constexpr allocation_info_base(allocation_info_base&&) noexcept = default;
403
404 constexpr allocation_info_base& operator=(const allocation_info_base&) = default;
405 constexpr allocation_info_base& operator=(allocation_info_base&&) noexcept = default;
406
407 [[nodiscard]]
408 constexpr Getter make_getter() const
409 {
410 return m_AllocatorGetter;
411 }
412 private:
413
414 Getter m_AllocatorGetter;
415 };
416
423 template<movable_comparable T, alloc_getter<T> Getter>
424 class allocation_info : public allocation_info_base<T, Getter>
425 {
426 private:
428 public:
429 using value_type = T;
430 using allocator_type = typename base_t::allocator_type;
431 using predictions_type = type_to_allocation_predictions_t<T>;
432 using inner_predictions_type = type_to_inner_allocation_predictions_t<T>;
433
434 constexpr allocation_info(Getter allocGetter, const predictions_type& predictions)
435 : base_t{std::move(allocGetter)}
436 , m_Predictions{prediction_shifter{}(predictions)}
437 {}
438
439 constexpr allocation_info(Getter allocGetter, const inner_predictions_type& predictions)
440 : base_t{std::move(allocGetter)}
441 , m_Predictions{prediction_shifter{}(predictions)}
442 {}
443
444 [[nodiscard]]
445 constexpr const predictions_type& get_predictions() const noexcept
446 {
447 return m_Predictions;
448 }
449 private:
450 predictions_type m_Predictions;
451
452 struct prediction_shifter
453 {
454 using allocClass = alloc_equivalence_class_generator_t<T, Getter>;
455
456 [[nodiscard]]
457 constexpr predictions_type operator()(const predictions_type& predictions) const
458 {
459 return shift<allocClass>(predictions);
460 }
461
462 [[nodiscard]]
463 constexpr predictions_type operator()(const inner_predictions_type& predictions) const
464 {
465 return to_top_level(shift<allocClass>(predictions));
466 }
467 };
468 };
469
470 template
471 <
472 class Fn,
473 class T = std::remove_cvref_t<typename function_signature<decltype(&std::remove_cvref_t<Fn>::operator())>::arg>
474 >
475 allocation_info(Fn, const type_to_allocation_predictions_t<T>&)
477
478 template<movable_comparable T, alloc_getter<T> Getter>
480 {
481 using alloc_equivalence_class = alloc_equivalence_class_generator_t<T, Getter>;
482
483 outer_alloc_getter(Getter g) : getter{std::move(g)} {}
484
485 [[nodiscard]]
486 auto operator()(const T& c) const
487 {
488 return getter(c).outer_allocator();
489 }
490
491 Getter getter;
492 };
493
499 template<movable_comparable T, alloc_getter<T> Getter>
501 class allocation_info<T, Getter>
502 : public allocation_info_base<T, Getter>
503 {
504 private:
506 public:
507 using value_type = T;
508 using allocator_type = typename base_t::allocator_type;
509 using predictions_type = type_to_allocation_predictions_t<T>;
510 using inner_predictions_type = type_to_inner_allocation_predictions_t<T>;
511
512 constexpr static std::size_t size{alloc_count<allocator_type>::size};
513 static_assert(size > 0);
514
515 constexpr allocation_info(Getter allocGetter,
516 predictions_type predictions,
517 std::initializer_list<inner_predictions_type> innerPredictions)
518 : base_t{std::move(allocGetter)}
519 , m_Predictions{predictions}
520 , m_InnerPredictions{utilities::to_array<inner_predictions_type, size-1>(innerPredictions)}
521 {}
522
525 template<std::size_t I>
526 [[nodiscard]]
527 auto unpack() const
528 {
529 if constexpr(I == 0)
530 {
531 using getter = outer_alloc_getter<T, Getter>;
532 return allocation_info<T, getter>{getter{this->make_getter()}, m_Predictions};
533 }
534 else
535 {
536 auto scopedGetter{[getter{this->make_getter()}] (const T& c){
537 return get<I>(getter(c));
538 }
539 };
540 return allocation_info<T, decltype(scopedGetter)>{scopedGetter, m_InnerPredictions[I-1]};
541 }
542 }
543
544 private:
545 template<std::size_t I, counting_alloc... As>
546 [[nodiscard]]
547 static auto get(const std::scoped_allocator_adaptor<As...>& a)
548 {
549 if constexpr(I==0)
550 {
551 return a.outer_allocator();
552 }
553 else
554 {
555 return get<I-1>(a.inner_allocator());
556 }
557 }
558
559 predictions_type m_Predictions;
560 std::array<inner_predictions_type, size-1> m_InnerPredictions;
561 };
562
563 template
564 <
565 class Fn,
566 class T = std::remove_cvref_t<typename function_signature<decltype(&std::remove_cvref_t<Fn>::operator())>::arg>,
567 class Predictions = type_to_allocation_predictions_t<T>,
568 class InnerPredictions = type_to_inner_allocation_predictions_t<T>
569 >
570 allocation_info(Fn, Predictions, std::initializer_list<InnerPredictions>)
571 -> allocation_info<T, Fn>;
572
573 template<top_level TopLevel>
575 {
576 public:
577 [[nodiscard]]
578 constexpr container_counts containers() const noexcept { return m_Containers; }
579 protected:
581 : m_Containers{std::move(counts)}
582 {}
583
585 constexpr container_predictions_policy& operator=(const container_predictions_policy&) = default;
586
588 private:
589 container_counts m_Containers;
590 };
591
592 template<>
594 {
595 protected:
596 constexpr container_predictions_policy() = default;
598 constexpr container_predictions_policy& operator=(const container_predictions_policy&) = default;
599
601 };
602}
Core components for the Allocation Testing framework.
Utility to convert an initializer_list into an array, potentially transforming the initializer_list i...
Free functions for performing checks, together with the 'checker' class template which wraps them.
Meta-programming utilities.
class template for shifting allocation predictions, especially for MSVC debug builds.
Definition: AllocationCheckers.hpp:217
Definition: AllocationCheckersCore.hpp:73
auto unpack() const
Definition: AllocationCheckers.hpp:527
Base class for use with both plain (shared counting) allocators and std::scoped_allocator_adaptor.
Definition: AllocationCheckers.hpp:371
Class for use with a container possessing a (shared counting) allocator.
Definition: AllocationCheckers.hpp:425
Definition: AllocationCheckers.hpp:575
Definition: AllocationCheckers.hpp:119
A concept for scoped allocators.
Definition: Concepts.hpp:54
Definition: AllocationCheckersTraits.hpp:19
Definition: Utilities.hpp:78
Definition: Utilities.hpp:29
Definition: AllocationCheckers.hpp:335
Definition: AllocationCheckers.hpp:200
Definition: AllocationCheckers.hpp:152
Definition: AllocationCheckers.hpp:480
Definition: AllocationCheckers.hpp:351