UniqueVoidPtr.h 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. #if !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)
  2. #pragma once
  3. #include <cstddef>
  4. #include <memory>
  5. #include <utility>
  6. #include <c10/macros/Export.h>
  7. #include <c10/macros/Macros.h>
  8. namespace c10 {
  9. using DeleterFnPtr = void (*)(void*);
  10. namespace detail {
  11. // Does not delete anything
  12. C10_API void deleteNothing(void* /*unused*/);
  13. // A detail::UniqueVoidPtr is an owning smart pointer like unique_ptr, but
  14. // with three major differences:
  15. //
  16. // 1) It is specialized to void
  17. //
  18. // 2) It is specialized for a function pointer deleter
  19. // void(void* ctx); i.e., the deleter doesn't take a
  20. // reference to the data, just to a context pointer
  21. // (erased as void*). In fact, internally, this pointer
  22. // is implemented as having an owning reference to
  23. // context, and a non-owning reference to data; this is why
  24. // you release_context(), not release() (the conventional
  25. // API for release() wouldn't give you enough information
  26. // to properly dispose of the object later.)
  27. //
  28. // 3) The deleter is guaranteed to be called when the unique
  29. // pointer is destructed and the context is non-null; this is different
  30. // from std::unique_ptr where the deleter is not called if the
  31. // data pointer is null.
  32. //
  33. // Some of the methods have slightly different types than std::unique_ptr
  34. // to reflect this.
  35. //
  36. class UniqueVoidPtr {
  37. private:
  38. // Lifetime tied to ctx_
  39. void* data_;
  40. std::unique_ptr<void, DeleterFnPtr> ctx_;
  41. public:
  42. UniqueVoidPtr() : data_(nullptr), ctx_(nullptr, &deleteNothing) {}
  43. explicit UniqueVoidPtr(void* data)
  44. : data_(data), ctx_(nullptr, &deleteNothing) {}
  45. UniqueVoidPtr(void* data, void* ctx, DeleterFnPtr ctx_deleter)
  46. : data_(data), ctx_(ctx, ctx_deleter ? ctx_deleter : &deleteNothing) {}
  47. void* operator->() const {
  48. return data_;
  49. }
  50. void clear() {
  51. ctx_ = nullptr;
  52. data_ = nullptr;
  53. }
  54. void* get() const {
  55. return data_;
  56. }
  57. bool /* success */ unsafe_reset_data_and_ctx(void* new_data_and_ctx) {
  58. if (C10_UNLIKELY(ctx_.get_deleter() != &deleteNothing)) {
  59. return false;
  60. }
  61. // seems quicker than calling the no-op deleter when we reset
  62. // NOLINTNEXTLINE(bugprone-unused-return-value)
  63. ctx_.release();
  64. ctx_.reset(new_data_and_ctx);
  65. data_ = new_data_and_ctx;
  66. return true;
  67. }
  68. void* get_context() const {
  69. return ctx_.get();
  70. }
  71. void* release_context() {
  72. return ctx_.release();
  73. }
  74. std::unique_ptr<void, DeleterFnPtr>&& move_context() {
  75. return std::move(ctx_);
  76. }
  77. [[nodiscard]] bool compare_exchange_deleter(
  78. DeleterFnPtr expected_deleter,
  79. DeleterFnPtr new_deleter) {
  80. if (get_deleter() != expected_deleter)
  81. return false;
  82. ctx_ = std::unique_ptr<void, DeleterFnPtr>(ctx_.release(), new_deleter);
  83. return true;
  84. }
  85. template <typename T>
  86. T* cast_context(DeleterFnPtr expected_deleter) const {
  87. if (get_deleter() != expected_deleter)
  88. return nullptr;
  89. return static_cast<T*>(get_context());
  90. }
  91. operator bool() const {
  92. return data_ || ctx_;
  93. }
  94. DeleterFnPtr get_deleter() const {
  95. return ctx_.get_deleter();
  96. }
  97. };
  98. // Note [How UniqueVoidPtr is implemented]
  99. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  100. // UniqueVoidPtr solves a common problem for allocators of tensor data, which
  101. // is that the data pointer (e.g., float*) which you are interested in, is not
  102. // the same as the context pointer (e.g., DLManagedTensor) which you need
  103. // to actually deallocate the data. Under a conventional deleter design, you
  104. // have to store extra context in the deleter itself so that you can actually
  105. // delete the right thing. Implementing this with standard C++ is somewhat
  106. // error-prone: if you use a std::unique_ptr to manage tensors, the deleter will
  107. // not be called if the data pointer is nullptr, which can cause a leak if the
  108. // context pointer is non-null (and the deleter is responsible for freeing both
  109. // the data pointer and the context pointer).
  110. //
  111. // So, in our reimplementation of unique_ptr, which just store the context
  112. // directly in the unique pointer, and attach the deleter to the context
  113. // pointer itself. In simple cases, the context pointer is just the pointer
  114. // itself.
  115. inline bool operator==(const UniqueVoidPtr& sp, std::nullptr_t) noexcept {
  116. return !sp;
  117. }
  118. inline bool operator==(std::nullptr_t, const UniqueVoidPtr& sp) noexcept {
  119. return !sp;
  120. }
  121. inline bool operator!=(const UniqueVoidPtr& sp, std::nullptr_t) noexcept {
  122. return sp;
  123. }
  124. inline bool operator!=(std::nullptr_t, const UniqueVoidPtr& sp) noexcept {
  125. return sp;
  126. }
  127. } // namespace detail
  128. } // namespace c10
  129. #else
  130. #error "This file should not be included when either TORCH_STABLE_ONLY or TORCH_TARGET_VERSION is defined."
  131. #endif // !defined(TORCH_STABLE_ONLY) && !defined(TORCH_TARGET_VERSION)