task_custom_allocator.pass.cpp 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. // -*- C++ -*-
  2. //===----------------------------------------------------------------------===//
  3. //
  4. // The LLVM Compiler Infrastructure
  5. //
  6. // This file is dual licensed under the MIT and the University of Illinois Open
  7. // Source Licenses. See LICENSE.TXT for details.
  8. //
  9. //===----------------------------------------------------------------------===//
  10. // UNSUPPORTED: c++98, c++03, c++11, c++14
  11. #include <experimental/task>
  12. #include <cstdlib>
  13. #include <cassert>
  14. #include <vector>
  15. #include <memory>
  16. #include <experimental/memory_resource>
  17. #include "../sync_wait.hpp"
  18. namespace coro = std::experimental::coroutines_v1;
  19. namespace
  20. {
  21. static size_t allocator_instance_count = 0;
  22. // A custom allocator that tracks the number of allocator instances that
  23. // have been constructed/destructed as well as the number of bytes that
  24. // have been allocated/deallocated using the allocator.
  25. template<typename T>
  26. class my_allocator {
  27. public:
  28. using value_type = T;
  29. using size_type = std::size_t;
  30. using difference_type = std::ptrdiff_t;
  31. using is_always_equal = std::false_type;
  32. explicit my_allocator(
  33. std::shared_ptr<size_type> totalAllocated) noexcept
  34. : totalAllocated_(std::move(totalAllocated))
  35. {
  36. ++allocator_instance_count;
  37. assert(totalAllocated_);
  38. }
  39. my_allocator(const my_allocator& other)
  40. : totalAllocated_(other.totalAllocated_)
  41. {
  42. ++allocator_instance_count;
  43. }
  44. my_allocator(my_allocator&& other)
  45. : totalAllocated_(std::move(other.totalAllocated_))
  46. {
  47. ++allocator_instance_count;
  48. }
  49. template<typename U>
  50. my_allocator(const my_allocator<U>& other)
  51. : totalAllocated_(other.totalAllocated_)
  52. {
  53. ++allocator_instance_count;
  54. }
  55. template<typename U>
  56. my_allocator(my_allocator<U>&& other)
  57. : totalAllocated_(std::move(other.totalAllocated_))
  58. {
  59. ++allocator_instance_count;
  60. }
  61. ~my_allocator()
  62. {
  63. --allocator_instance_count;
  64. }
  65. char* allocate(size_t n) {
  66. const auto byteCount = n * sizeof(T);
  67. void* p = std::malloc(byteCount);
  68. if (!p) {
  69. throw std::bad_alloc{};
  70. }
  71. *totalAllocated_ += byteCount;
  72. return static_cast<char*>(p);
  73. }
  74. void deallocate(char* p, size_t n) {
  75. const auto byteCount = n * sizeof(T);
  76. *totalAllocated_ -= byteCount;
  77. std::free(p);
  78. }
  79. private:
  80. template<typename U>
  81. friend class my_allocator;
  82. std::shared_ptr<size_type> totalAllocated_;
  83. };
  84. }
  85. template<typename Allocator>
  86. coro::task<void> f(std::allocator_arg_t, [[maybe_unused]] Allocator alloc)
  87. {
  88. co_return;
  89. }
  90. void test_custom_allocator_is_destructed()
  91. {
  92. auto totalAllocated = std::make_shared<size_t>(0);
  93. assert(allocator_instance_count == 0);
  94. {
  95. std::vector<coro::task<>> tasks;
  96. tasks.push_back(
  97. f(std::allocator_arg, my_allocator<char>{ totalAllocated }));
  98. tasks.push_back(
  99. f(std::allocator_arg, my_allocator<char>{ totalAllocated }));
  100. assert(allocator_instance_count == 4);
  101. assert(*totalAllocated > 0);
  102. }
  103. assert(allocator_instance_count == 0);
  104. assert(*totalAllocated == 0);
  105. }
  106. void test_custom_allocator_type_rebinding()
  107. {
  108. auto totalAllocated = std::make_shared<size_t>(0);
  109. {
  110. std::vector<coro::task<>> tasks;
  111. tasks.emplace_back(
  112. f(std::allocator_arg, my_allocator<int>{ totalAllocated }));
  113. coro::sync_wait(tasks[0]);
  114. }
  115. assert(*totalAllocated == 0);
  116. assert(allocator_instance_count == 0);
  117. }
  118. void test_mixed_custom_allocator_type_erasure()
  119. {
  120. assert(allocator_instance_count == 0);
  121. // Show that different allocators can be used within a vector of tasks
  122. // of the same type. ie. that the allocator is type-erased inside the
  123. // coroutine.
  124. std::vector<coro::task<>> tasks;
  125. tasks.push_back(f(
  126. std::allocator_arg, std::allocator<char>{}));
  127. tasks.push_back(f(
  128. std::allocator_arg,
  129. std::experimental::pmr::polymorphic_allocator<char>{
  130. std::experimental::pmr::new_delete_resource() }));
  131. tasks.push_back(f(
  132. std::allocator_arg,
  133. my_allocator<char>{ std::make_shared<size_t>(0) }));
  134. assert(allocator_instance_count > 0);
  135. for (auto& t : tasks)
  136. {
  137. coro::sync_wait(t);
  138. }
  139. tasks.clear();
  140. assert(allocator_instance_count == 0);
  141. }
  142. template<typename Allocator>
  143. coro::task<int> add_async(std::allocator_arg_t, [[maybe_unused]] Allocator alloc, int a, int b)
  144. {
  145. co_return a + b;
  146. }
  147. void test_task_custom_allocator_with_extra_args()
  148. {
  149. std::vector<coro::task<int>> tasks;
  150. for (int i = 0; i < 5; ++i) {
  151. tasks.push_back(add_async(
  152. std::allocator_arg,
  153. std::allocator<char>{},
  154. i, 2 * i));
  155. }
  156. for (int i = 0; i < 5; ++i)
  157. {
  158. assert(sync_wait(std::move(tasks[i])) == 3 * i);
  159. }
  160. }
  161. struct some_type {
  162. template<typename Allocator>
  163. coro::task<int> get_async(std::allocator_arg_t, [[maybe_unused]] Allocator alloc) {
  164. co_return 42;
  165. }
  166. template<typename Allocator>
  167. coro::task<int> add_async(std::allocator_arg_t, [[maybe_unused]] Allocator alloc, int a, int b) {
  168. co_return a + b;
  169. }
  170. };
  171. void test_task_custom_allocator_on_member_function()
  172. {
  173. assert(allocator_instance_count == 0);
  174. auto totalAllocated = std::make_shared<size_t>(0);
  175. some_type obj;
  176. assert(sync_wait(obj.get_async(std::allocator_arg, std::allocator<char>{})) == 42);
  177. assert(sync_wait(obj.get_async(std::allocator_arg, my_allocator<char>{totalAllocated})) == 42);
  178. assert(sync_wait(obj.add_async(std::allocator_arg, std::allocator<char>{}, 2, 3)) == 5);
  179. assert(sync_wait(obj.add_async(std::allocator_arg, my_allocator<char>{totalAllocated}, 2, 3)) == 5);
  180. assert(allocator_instance_count == 0);
  181. assert(*totalAllocated == 0);
  182. }
  183. int main()
  184. {
  185. test_custom_allocator_is_destructed();
  186. test_custom_allocator_type_rebinding();
  187. test_mixed_custom_allocator_type_erasure();
  188. test_task_custom_allocator_with_extra_args();
  189. test_task_custom_allocator_on_member_function();
  190. return 0;
  191. }