diff --git a/CHANGELOG.md b/CHANGELOG.md index 82a2bcf5b4e41..205f8d0758c5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ * Added Castable class: [#1634](https://github.com/dartsim/dart/pull/1634) * Added spdlog support as underlying logging framework: [#1633](https://github.com/dartsim/dart/pull/1633) - * Added custom memory allocators: [#1636](https://github.com/dartsim/dart/pull/1636), [#1637](https://github.com/dartsim/dart/pull/1637), [#1639](https://github.com/dartsim/dart/pull/1639), [#1645](https://github.com/dartsim/dart/pull/1645) + * Added custom memory allocators: [#1636](https://github.com/dartsim/dart/pull/1636), [#1637](https://github.com/dartsim/dart/pull/1637), [#1639](https://github.com/dartsim/dart/pull/1639), [#1645](https://github.com/dartsim/dart/pull/1645), [#1646](https://github.com/dartsim/dart/pull/1646) * Added Stopwatch class to replace Timer: [#1638](https://github.com/dartsim/dart/pull/1638) * Dynamics diff --git a/dart/common/CAllocator.cpp b/dart/common/CAllocator.cpp index 15609d3599542..da72478284ef2 100644 --- a/dart/common/CAllocator.cpp +++ b/dart/common/CAllocator.cpp @@ -47,117 +47,29 @@ CAllocator::CAllocator() noexcept //============================================================================== CAllocator::~CAllocator() { -#ifndef NDEBUG - std::lock_guard lock(mMutex); - if (!mMapPointerToSize.empty()) - { - size_t totalSize = 0; - for (auto it : mMapPointerToSize) - { - void* pointer = it.first; - size_t size = it.second; - totalSize += size; - dterr << "Found memory leak of " << size << " bytes at " << pointer - << "\n"; - // TODO(JS): Change to DART_FATAL once the issue of calling spdlog in - // destructor is resolved. - } - dterr << "Found potential memory leak of total " << totalSize - << " bytes!\n"; - // TODO(JS): Change to DART_FATAL once the issue of calling spdlog in - // destructor is resolved. - } -#endif + // Do nothing } //============================================================================== -void* CAllocator::allocate(size_t size) noexcept +void* CAllocator::allocate(size_t bytes) noexcept { - if (size == 0) + if (bytes == 0) { return nullptr; } - DART_TRACE("Allocated {} bytes.", size); -#ifndef NDEBUG - std::lock_guard lock(mMutex); - auto newPtr = std::malloc(size); - if (newPtr) - { - mSize += size; - mPeak = std::max(mPeak, mSize); - mMapPointerToSize[newPtr] = size; - } - return newPtr; -#else - return std::malloc(size); -#endif + DART_TRACE("Allocated {} bytes.", bytes); + return std::malloc(bytes); } //============================================================================== -void CAllocator::deallocate(void* pointer, size_t size) +void CAllocator::deallocate(void* pointer, size_t bytes) { - (void)size; -#ifndef NDEBUG // debug - std::lock_guard lock(mMutex); - auto it = mMapPointerToSize.find(pointer); - if (it != mMapPointerToSize.end()) - { - auto allocatedSize = it->second; - if (size != allocatedSize) - { - DART_FATAL( - "Attempting to deallocate memory at {} of {} bytes that is different " - "from the allocated size {}, which is a critical bug. Deallocating " - "{} bytes.", - pointer, - size, - allocatedSize, - allocatedSize); - size = allocatedSize; - } - mSize -= size; - mMapPointerToSize.erase(it); - DART_TRACE("Deallocated {} bytes.", size); - } - else - { - DART_FATAL( - "Cannot deallocate memory {} that is not allocated by this allocator!", - pointer); - return; - } -#else - DART_TRACE("Deallocated."); -#endif + DART_UNUSED(bytes); std::free(pointer); + DART_TRACE("Deallocated."); } -#ifndef NDEBUG -//============================================================================== -bool CAllocator::isAllocated(void* pointer, size_t size) const noexcept -{ - std::lock_guard lock(mMutex); - - const auto it = mMapPointerToSize.find(pointer); - if (it == mMapPointerToSize.end()) - return false; - - const auto& allocatedSize = it->second; - if (size != allocatedSize) - return false; - - return true; -} - -//============================================================================== -bool CAllocator::isEmpty() const noexcept -{ - std::lock_guard lock(mMutex); - return mMapPointerToSize.empty(); -} -#endif - //============================================================================== void CAllocator::print(std::ostream& os, int indent) const { @@ -170,11 +82,6 @@ void CAllocator::print(std::ostream& os, int indent) const { os << spaces << "type: " << getType() << "\n"; } -#ifndef NDEBUG - std::lock_guard lock(mMutex); - os << spaces << "size_in_bytes: " << mSize << "\n"; - os << spaces << "peak: " << mPeak << "\n"; -#endif } } // namespace dart::common diff --git a/dart/common/CAllocator.hpp b/dart/common/CAllocator.hpp index 6d644917f711b..5533da81348f0 100644 --- a/dart/common/CAllocator.hpp +++ b/dart/common/CAllocator.hpp @@ -33,11 +33,6 @@ #ifndef DART_COMMON_CALLOCATOR_HPP_ #define DART_COMMON_CALLOCATOR_HPP_ -#ifndef NDEBUG - #include - #include -#endif - #include "dart/common/MemoryAllocator.hpp" namespace dart::common { @@ -56,30 +51,13 @@ class CAllocator : public MemoryAllocator DART_STRING_TYPE(CAllocator); // Documentation inherited - [[nodiscard]] void* allocate(size_t size) noexcept override; - - // Documentation inherited - void deallocate(void* pointer, size_t size) override; + [[nodiscard]] void* allocate(size_t bytes) noexcept override; -#ifndef NDEBUG // Documentation inherited - [[nodiscard]] bool isAllocated(void* pointer, size_t size) const - noexcept override; - - // Documentation inherited - [[nodiscard]] bool isEmpty() const noexcept override; -#endif + void deallocate(void* pointer, size_t bytes) override; // Documentation inherited void print(std::ostream& os = std::cout, int indent = 0) const override; - -#ifndef NDEBUG -private: - size_t mSize = 0; - size_t mPeak = 0; - std::unordered_map mMapPointerToSize; - mutable std::mutex mMutex; -#endif }; } // namespace dart::common diff --git a/dart/common/FreeListAllocator.cpp b/dart/common/FreeListAllocator.cpp index 2dcd67d06ca5a..1dea221672b53 100644 --- a/dart/common/FreeListAllocator.cpp +++ b/dart/common/FreeListAllocator.cpp @@ -52,32 +52,48 @@ FreeListAllocator::~FreeListAllocator() // Lock the mutex std::lock_guard lock(mMutex); -#ifndef NDEBUG - if (!mMapPointerToSize.empty()) + // Forcefully deallocate all the memory blocks if destructing this allocator + // without deallocating individual memories allocated by this allocator. + if (mTotalAllocatedSize != 0) { - size_t totalSize = 0; - for (auto it : mMapPointerToSize) + MemoryBlockHeader* currBlock = mFirstMemoryBlock; + while (currBlock) { - void* pointer = it.first; - size_t size = it.second; - totalSize += size; - dterr << "Found memory leak of " << size << " bytes at " << pointer - << "\n"; - // TODO(JS): Change to DART_FATAL once the issue of calling spdlog in - // destructor is resolved. + MemoryBlockHeader* currSubBlock = currBlock; + MemoryBlockHeader* next = currBlock->mNext; + size_t sizeToDeallocate = 0; + + while (currSubBlock) + { + sizeToDeallocate += currSubBlock->mSize + sizeof(MemoryBlockHeader); + if (!currSubBlock->mIsNextContiguous) + { + next = currSubBlock->mNext; + break; + } + currSubBlock = currSubBlock->mNext; + } + + mBaseAllocator.deallocate(currBlock, sizeToDeallocate); + currBlock = next; } - dterr << "Found potential memory leak of total " << totalSize - << " bytes!\n"; + + dterr + << "Forcefully deallocated memory " << mTotalAllocatedSize + << " of byte(s) that is not deallocated before destructing this memory " + << "allocator.\n"; // TODO(JS): Change to DART_FATAL once the issue of calling spdlog in // destructor is resolved. + + return; } -#endif - MemoryBlockHeader* curr = mBlockHead; + // Deallocate memory blocks + MemoryBlockHeader* curr = mFirstMemoryBlock; while (curr) { DART_ASSERT(!curr->mIsAllocated); // TODO(JS): This means some of pointers - // are not deallocated + // are not deallocated MemoryBlockHeader* next = curr->mNext; const auto size = curr->mSize; @@ -89,11 +105,22 @@ FreeListAllocator::~FreeListAllocator() } //============================================================================== -void* FreeListAllocator::allocate(size_t size) noexcept +const MemoryAllocator& FreeListAllocator::getBaseAllocator() const +{ + return mBaseAllocator; +} + +//============================================================================== +MemoryAllocator& FreeListAllocator::getBaseAllocator() { - DART_UNUSED(size); + return mBaseAllocator; +} - if (size == 0) +//============================================================================== +void* FreeListAllocator::allocate(size_t bytes) noexcept +{ + // Not allowed to allocate zero bytes + if (bytes == 0) { return nullptr; } @@ -101,33 +128,44 @@ void* FreeListAllocator::allocate(size_t size) noexcept // Lock the mutex std::lock_guard lock(mMutex); - MemoryBlockHeader* curr = mBlockHead; - DART_ASSERT(mBlockHead->mPrev == nullptr); + // Ensure that the first memory block doesn't have the previous block + DART_ASSERT(mFirstMemoryBlock->mPrev == nullptr); + // Iterate from the first memory block + MemoryBlockHeader* curr = mFirstMemoryBlock; + + // Use free block if available if (mFreeBlock) { + // Ensure the free block is not in use DART_ASSERT(!mFreeBlock->mIsAllocated); - if (size <= mFreeBlock->mSize) + + // Use the free block if the requested size is equal to or smaller than + // the free block + if (bytes <= mFreeBlock->mSize) { curr = mFreeBlock; mFreeBlock = nullptr; } } + // Search for a memory block that is not used and has sufficient free space while (curr) { - if (!curr->mIsAllocated && size <= curr->mSize) + if (!curr->mIsAllocated && bytes <= curr->mSize) { - curr->split(size); + curr->split(bytes); break; } curr = curr->mNext; } + // If failed to find an avaliable memory block, allocate a new memory block if (curr == nullptr) { - if (!allocateMemoryBlock((mAllocatedSize + size) * 2)) + // Allocate a sufficient size + if (!allocateMemoryBlock((mTotalAllocatedBlockSize + bytes) * 2)) { return nullptr; } @@ -136,38 +174,33 @@ void* FreeListAllocator::allocate(size_t size) noexcept DART_ASSERT(!mFreeBlock->mIsAllocated); curr = mFreeBlock; - DART_ASSERT(curr->mSize >= size); + DART_ASSERT(curr->mSize >= bytes); - curr->split(size); + // Split the new memory block for the requested size + curr->split(bytes); } + // Mark the current block is allocated curr->mIsAllocated = true; + // Set free block if the next block is free if (curr->mNext != nullptr && !curr->mNext->mIsAllocated) { mFreeBlock = curr->mNext; } -#ifndef NDEBUG - auto out = static_cast(curr->asCharPtr() + sizeof(MemoryBlockHeader)); - if (out) - { - mSize += size; - mPeak = std::max(mPeak, mSize); - mMapPointerToSize[out] = size; - } - return out; -#else + mTotalAllocatedSize += bytes; + return static_cast(curr->asCharPtr() + sizeof(MemoryBlockHeader)); -#endif } //============================================================================== -void FreeListAllocator::deallocate(void* pointer, size_t size) +void FreeListAllocator::deallocate(void* pointer, size_t bytes) { - DART_UNUSED(size, pointer); + DART_UNUSED(bytes, pointer); - if (pointer == nullptr || size == 0) + // Cannot deallocate nullptr or zero bytes + if (pointer == nullptr || bytes == 0) { return; } @@ -175,34 +208,6 @@ void FreeListAllocator::deallocate(void* pointer, size_t size) // Lock the mutex std::lock_guard lock(mMutex); -#ifndef NDEBUG - auto it = mMapPointerToSize.find(pointer); - if (it != mMapPointerToSize.end()) - { - auto allocatedSize = it->second; - if (size != allocatedSize) - { - DART_FATAL( - "Cannot deallocated memory {} because the deallocating size {} is " - "different from the allocated size {}.", - pointer, - size, - allocatedSize); - return; - } - mSize -= size; - mMapPointerToSize.erase(it); - DART_TRACE("Deallocated {} bytes.", size); - } - else - { - DART_FATAL( - "Cannot deallocate memory {} that is not allocated by this allocator!", - pointer); - return; - } -#endif - unsigned char* block_addr = static_cast(pointer) - sizeof(MemoryBlockHeader); MemoryBlockHeader* block = reinterpret_cast(block_addr); @@ -225,49 +230,38 @@ void FreeListAllocator::deallocate(void* pointer, size_t size) } mFreeBlock = curr; -} - -#ifndef NDEBUG -//============================================================================== -bool FreeListAllocator::isAllocated(void* pointer, size_t size) const noexcept -{ - std::lock_guard lock(mMutex); - - const auto it = mMapPointerToSize.find(pointer); - if (it == mMapPointerToSize.end()) - return false; - - const auto& allocatedSize = it->second; - if (size != allocatedSize) - return false; - return true; -} + mTotalAllocatedSize -= bytes; -//============================================================================== -bool FreeListAllocator::isEmpty() const noexcept -{ - std::lock_guard lock(mMutex); - return mMapPointerToSize.empty(); + DART_TRACE("Deallocated {} bytes.", bytes); } -#endif //============================================================================== bool FreeListAllocator::allocateMemoryBlock(size_t sizeToAllocate) { + // Allocate memory chunck for header and the actual requested size void* memory = mBaseAllocator.allocate(sizeToAllocate + sizeof(MemoryBlockHeader)); + + // Return false if failed to allocate if (memory == nullptr) - { return false; - } - mBlockHead = mBaseAllocator.constructAt( - memory, sizeToAllocate, nullptr, mBlockHead, false); + // Construct the memory block header, linking the current block as the next + // block + mFirstMemoryBlock = mBaseAllocator.constructAt( + memory, // address to construct + sizeToAllocate, // size of the memory block + nullptr, // previous memory block + mFirstMemoryBlock, // next memory block + false // whether the next memory block is contiguous + ); - mFreeBlock = mBlockHead; + // Set the new memory block as free block + mFreeBlock = mFirstMemoryBlock; - mAllocatedSize += sizeToAllocate; + // Update the allocated size (without memory size for the headers of blocks) + mTotalAllocatedBlockSize += sizeToAllocate; return true; } @@ -287,9 +281,9 @@ void FreeListAllocator::print(std::ostream& os, int indent) const { os << spaces << "type: " << getType() << "\n"; } - os << spaces << "reserved_size: " << mAllocatedSize << "\n"; + os << spaces << "reserved_size: " << mTotalAllocatedBlockSize << "\n"; os << spaces << "memory_blocks:\n"; - auto curr = mBlockHead; + auto curr = mFirstMemoryBlock; while (curr) { os << spaces << "- block_addr: " << curr << "\n"; @@ -302,7 +296,7 @@ void FreeListAllocator::print(std::ostream& os, int indent) const } os << spaces << "free_block_addr: " << mFreeBlock << "\n"; os << spaces << "header_size: " << sizeof(MemoryBlockHeader) << "\n"; - os << spaces << "baseAllocator:\n"; + os << spaces << "base_allocator:\n"; mBaseAllocator.print(os, indent + 2); } diff --git a/dart/common/FreeListAllocator.hpp b/dart/common/FreeListAllocator.hpp index 133b8d9092877..c2f91e95e9243 100644 --- a/dart/common/FreeListAllocator.hpp +++ b/dart/common/FreeListAllocator.hpp @@ -34,11 +34,9 @@ #define DART_COMMON_FREELISTALLOCATOR_HPP_ #include -#ifndef NDEBUG - #include -#endif #include "dart/common/MemoryAllocator.hpp" +#include "dart/common/MemoryAllocatorDebugger.hpp" namespace dart::common { @@ -58,6 +56,8 @@ namespace dart::common { class FreeListAllocator : public MemoryAllocator { public: + using Debug = MemoryAllocatorDebugger; + /// Constructor /// /// \param[in] baseAllocator: (optional) Base memory allocator. @@ -71,20 +71,17 @@ class FreeListAllocator : public MemoryAllocator DART_STRING_TYPE(FreeListAllocator); - // Documentation inherited - [[nodiscard]] void* allocate(size_t size) noexcept override; + /// Returns the base allocator + [[nodiscard]] const MemoryAllocator& getBaseAllocator() const; - // Documentation inherited - void deallocate(void* pointer, size_t size) override; + /// Returns the base allocator + [[nodiscard]] MemoryAllocator& getBaseAllocator(); -#ifndef NDEBUG // Documentation inherited - [[nodiscard]] bool isAllocated(void* pointer, size_t size) const - noexcept override; + [[nodiscard]] void* allocate(size_t bytes) noexcept override; // Documentation inherited - [[nodiscard]] bool isEmpty() const noexcept override; -#endif + void deallocate(void* pointer, size_t bytes) override; // Documentation inherited void print(std::ostream& os = std::cout, int indent = 0) const override; @@ -130,12 +127,15 @@ class FreeListAllocator : public MemoryAllocator void merge(MemoryBlockHeader* other); #ifndef NDEBUG - /// Returns whether this memory block is valid + /// [Debug only] Returns whether this memory block is valid bool isValid() const; #endif }; - /// Allocates + /// Allocates a new memory block for \c sizeToAllocate bytes + /// + /// \param[in] sizeToAllocate: The bytes to allocate. + /// \return The success bool allocateMemoryBlock(size_t sizeToAllocate); /// The base allocator @@ -145,20 +145,16 @@ class FreeListAllocator : public MemoryAllocator mutable std::mutex mMutex; /// Pointer to the first memory block - MemoryBlockHeader* mBlockHead{nullptr}; + MemoryBlockHeader* mFirstMemoryBlock{nullptr}; /// Pointer to the current free memory block MemoryBlockHeader* mFreeBlock{nullptr}; - /// The allocated size in bytes - size_t mAllocatedSize{0}; + /// The total allocated block size in bytes + size_t mTotalAllocatedBlockSize{0}; -#ifndef NDEBUG -private: - size_t mSize = 0; - size_t mPeak = 0; - std::unordered_map mMapPointerToSize; -#endif + /// The total allocated size in bytes + size_t mTotalAllocatedSize{0}; }; } // namespace dart::common diff --git a/dart/common/Macros.hpp b/dart/common/Macros.hpp index aaf7a8290ec79..5c90e3ebcb539 100644 --- a/dart/common/Macros.hpp +++ b/dart/common/Macros.hpp @@ -35,6 +35,8 @@ #include +#include "dart/common/Logging.hpp" + // DART_NUM_ARGS( [, [, ...]]) #define DETAIL_DART_NUM_ARGS(z, a, b, c, d, e, f, cnt, ...) cnt #define DART_NUM_ARGS(...) \ @@ -68,4 +70,9 @@ DART_CONCAT(DETAIL_DART_ASSERT_, DART_NUM_ARGS(__VA_ARGS__)) \ (__VA_ARGS__) +// Macro to mark the function is not implemented +#define DART_NOT_IMPLEMENTED \ + DART_FATAL("Not implemented: {}:{}", __FILE__, __LINE__); \ + void(0) + #endif diff --git a/dart/common/MemoryAllocator.cpp b/dart/common/MemoryAllocator.cpp index e51018d8514d8..92a0f38f05778 100644 --- a/dart/common/MemoryAllocator.cpp +++ b/dart/common/MemoryAllocator.cpp @@ -40,8 +40,8 @@ namespace dart::common { //============================================================================== MemoryAllocator& MemoryAllocator::GetDefault() { - static CAllocator default_allocator; - return default_allocator; + static CAllocator defaultAllocator; + return defaultAllocator; } //============================================================================== diff --git a/dart/common/MemoryAllocator.hpp b/dart/common/MemoryAllocator.hpp index 23a78178318a9..4644f1df0fe4e 100644 --- a/dart/common/MemoryAllocator.hpp +++ b/dart/common/MemoryAllocator.hpp @@ -57,29 +57,38 @@ class MemoryAllocator : public Castable /// Returns type string. [[nodiscard]] virtual const std::string& getType() const = 0; - /// Allocates @c size bytes of uninitialized storage. + /// Allocates \c size bytes of uninitialized storage. /// - /// @param[in] size: The byte size to allocate sotrage for. - /// @return On success, the pointer to the beginning of newly allocated + /// \param[in] bytes: The byte size to allocate sotrage for. + /// \return On success, the pointer to the beginning of newly allocated /// memory. - /// @return On failure, a null pointer - [[nodiscard]] virtual void* allocate(size_t size) noexcept = 0; + /// \return On failure, a null pointer + [[nodiscard]] virtual void* allocate(size_t bytes) noexcept = 0; // TODO(JS): Make this constexpr once migrated to C++20 + /// Allocates object(s) without calling the constructor. + /// + /// This is identical to \c static_cast(allocate(n * sizeof(T))). + /// + /// \param[in] n: The number of objects to allocate. template [[nodiscard]] T* allocateAs(size_t n = 1) noexcept; - /// Deallocates the storage referenced by the pointer @c p, which must be a + /// Deallocates the storage referenced by the pointer \c p, which must be a /// pointer obtained by an earlier cal to allocate(). /// - /// @param[in] pointer: Pointer obtained from allocate(). - virtual void deallocate(void* pointer, size_t size) = 0; + /// \param[in] pointer: Pointer obtained from allocate(). + /// \param[in] bytes: The bytes of the allocated memory. + virtual void deallocate(void* pointer, size_t bytes) = 0; // TODO(JS): Make this constexpr once migrated to C++20 /// Allocates uninitialized storage and constructs an object of type T to the /// allocated storage. /// - /// @param[in] args...: The constructor arguments to use. + /// \tparam T: The object type to construct. + /// \tparam Args...: The argument types to pass to the object constructor. + /// + /// \param[in] args: The constructor arguments to use. template [[nodiscard]] T* construct(Args&&... args) noexcept; @@ -93,16 +102,6 @@ class MemoryAllocator : public Castable template void destroy(T* object) noexcept; -#ifndef NDEBUG - /// Returns true if a pointer is allocated by this allocator. - [[nodiscard]] virtual bool isAllocated(void* pointer, size_t size) const - noexcept - = 0; - - /// Returns true if there is no memory allocated by this allocator. - [[nodiscard]] virtual bool isEmpty() const noexcept = 0; -#endif - /// Prints state of the memory allocator virtual void print(std::ostream& os = std::cout, int indent = 0) const; diff --git a/dart/common/MemoryAllocatorDebugger.hpp b/dart/common/MemoryAllocatorDebugger.hpp new file mode 100644 index 0000000000000..c880d17797db4 --- /dev/null +++ b/dart/common/MemoryAllocatorDebugger.hpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2011-2022, The DART development contributors + * All rights reserved. + * + * The list of contributors can be found at: + * https://github.com/dartsim/dart/blob/master/LICENSE + * + * This file is provided under the following "BSD-style" License: + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DART_COMMON_MEMORYALLOCATORDEBUGGER_HPP_ +#define DART_COMMON_MEMORYALLOCATORDEBUGGER_HPP_ + +#include +#include +#include + +#include "dart/common/MemoryAllocator.hpp" + +namespace dart::common { + +template +class MemoryAllocatorDebugger : public MemoryAllocator +{ +public: + /// Constructor + template + MemoryAllocatorDebugger(Args&&... args); + + /// Destructor + ~MemoryAllocatorDebugger(); + + /// Returns type string. + [[nodiscard]] static const std::string& getStaticType(); + + // Documentation inherited + [[nodiscard]] const std::string& getType() const override; + + // Documentation inherited + [[nodiscard]] void* allocate(size_t bytes) noexcept override; + + // Documentation inherited + void deallocate(void* pointer, size_t bytes) override; + + /// Returns true if there is no memory allocated by the internal allocator. + [[nodiscard]] bool isEmpty() const; + + /// Returns true if a pointer is allocated by the internal allocator. + [[nodiscard]] bool hasAllocated(void* pointer, size_t size) const; + + /// Returns the internal allocator + [[nodiscard]] const T& getInternalAllocator() const; + + /// Returns the internal allocator + [[nodiscard]] T& getInternalAllocator(); + + // Documentation inherited + void print(std::ostream& os = std::cout, int indent = 0) const override; + +private: + T mInternalAllocator; + + size_t mSize = 0; + + size_t mPeak = 0; + + std::unordered_map mMapPointerToSize; + + mutable std::mutex mMutex; +}; + +} // namespace dart::common + +#include "dart/common/detail/MemoryAllocatorDebugger-impl.hpp" + +#endif // DART_COMMON_MEMORYALLOCATORDEBUGGER_HPP_ diff --git a/dart/common/MemoryManager.cpp b/dart/common/MemoryManager.cpp new file mode 100644 index 0000000000000..9252cd725ec89 --- /dev/null +++ b/dart/common/MemoryManager.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2011-2022, The DART development contributors + * All rights reserved. + * + * The list of contributors can be found at: + * https://github.com/dartsim/dart/blob/master/LICENSE + * + * This file is provided under the following "BSD-style" License: + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "dart/common/MemoryManager.hpp" + +#ifndef NDEBUG // debug + #include "dart/common/Logging.hpp" +#endif + +namespace dart::common { + +//============================================================================== +MemoryManager& MemoryManager::GetDefault() +{ + static MemoryManager defaultMemoryManager(MemoryAllocator::GetDefault()); + return defaultMemoryManager; +} + +//============================================================================== +MemoryManager::MemoryManager(MemoryAllocator& baseAllocator) + : mBaseAllocator(baseAllocator), + mFreeListAllocator(mBaseAllocator), +#ifdef NDEBUG + mPoolAllocator(mFreeListAllocator) +#else + mPoolAllocator(mFreeListAllocator.getInternalAllocator()) +#endif +{ + // Do nothing +} + +//============================================================================== +MemoryManager::~MemoryManager() +{ + // Do nothing +} + +//============================================================================== +MemoryAllocator& MemoryManager::getBaseAllocator() +{ + return mBaseAllocator; +} + +//============================================================================== +FreeListAllocator& MemoryManager::getFreeListAllocator() +{ +#ifdef NDEBUG + return mFreeListAllocator; +#else + return mFreeListAllocator.getInternalAllocator(); +#endif +} + +//============================================================================== +PoolAllocator& MemoryManager::getPoolAllocator() +{ +#ifdef NDEBUG + return mPoolAllocator; +#else + return mPoolAllocator.getInternalAllocator(); +#endif +} + +//============================================================================== +void* MemoryManager::allocate(Type type, size_t bytes) +{ + switch (type) + { + case Type::Base: + return mBaseAllocator.allocate(bytes); + case Type::Free: + return mFreeListAllocator.allocate(bytes); + case Type::Pool: + return mPoolAllocator.allocate(bytes); + } + return nullptr; +} + +//============================================================================== +void* MemoryManager::allocateUsingFree(size_t bytes) +{ + return allocate(Type::Free, bytes); +} + +//============================================================================== +void* MemoryManager::allocateUsingPool(size_t bytes) +{ + return allocate(Type::Pool, bytes); +} + +//============================================================================== +void MemoryManager::deallocate(Type type, void* pointer, size_t bytes) +{ + switch (type) + { + case Type::Base: + mBaseAllocator.deallocate(pointer, bytes); + break; + case Type::Free: + mFreeListAllocator.deallocate(pointer, bytes); + break; + case Type::Pool: + mPoolAllocator.deallocate(pointer, bytes); + break; + } +} + +//============================================================================== +void MemoryManager::deallocateUsingFree(void* pointer, size_t bytes) +{ + deallocate(Type::Free, pointer, bytes); +} + +//============================================================================== +void MemoryManager::deallocateUsingPool(void* pointer, size_t bytes) +{ + deallocate(Type::Pool, pointer, bytes); +} + +#ifndef NDEBUG +//============================================================================== +bool MemoryManager::hasAllocated(void* pointer, size_t size) const noexcept +{ + if (mFreeListAllocator.hasAllocated(pointer, size)) + return true; + + if (mPoolAllocator.hasAllocated(pointer, size)) + return true; + + return false; +} +#endif + +//============================================================================== +void MemoryManager::print(std::ostream& os, int indent) const +{ + if (indent == 0) + { + os << "[MemoryManager]\n"; + } + const std::string spaces(indent, ' '); + os << spaces << "free_allocator:\n"; + mFreeListAllocator.print(os, indent + 2); + os << spaces << "pool_allocator:\n"; + mPoolAllocator.print(os, indent + 2); + os << spaces << "base_allocator:\n"; + mBaseAllocator.print(os, indent + 2); +} + +//============================================================================== +std::ostream& operator<<(std::ostream& os, const MemoryManager& memoryManager) +{ + memoryManager.print(os); + return os; +} + +} // namespace dart::common diff --git a/dart/common/MemoryManager.hpp b/dart/common/MemoryManager.hpp new file mode 100644 index 0000000000000..1e61c15b4ced5 --- /dev/null +++ b/dart/common/MemoryManager.hpp @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2011-2022, The DART development contributors + * All rights reserved. + * + * The list of contributors can be found at: + * https://github.com/dartsim/dart/blob/master/LICENSE + * + * This file is provided under the following "BSD-style" License: + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DART_COMMON_MEMORYMANAGER_HPP_ +#define DART_COMMON_MEMORYMANAGER_HPP_ + +#ifndef NDEBUG + #include +#endif +#include + +#include "dart/common/FreeListAllocator.hpp" +#include "dart/common/PoolAllocator.hpp" + +namespace dart::common { + +/// A composite memory allocator that contains various memory allocators that +/// are optimized for different use cases. +class MemoryManager final +{ +public: + /// Type of the memory allocators + enum class Type + { + Base, + Free, + Pool, + }; + + /// Returns the default memory manager + [[nodiscard]] static MemoryManager& GetDefault(); + + /// Constructor + /// + /// \param[in] baseAllocator: (optional) The most low level allocator to be + /// used by all the underlying memory allocators. + explicit MemoryManager( + MemoryAllocator& baseAllocator = MemoryAllocator::GetDefault()); + + /// Destructor + ~MemoryManager(); + + /// Returns the base allocator + [[nodiscard]] MemoryAllocator& getBaseAllocator(); + + /// Returns the free list allocator + [[nodiscard]] FreeListAllocator& getFreeListAllocator(); + + /// Returns the pool allocator + [[nodiscard]] PoolAllocator& getPoolAllocator(); + + /// Allocates \c size bytes of uninitialized storage. + /// + /// \param[in] type: The memory allocator type. + /// \param[in] bytes: The byte size to allocate sotrage for. + /// \return On success, the pointer to the beginning of newly allocated + /// memory. + /// \return On failure, a null pointer + [[nodiscard]] void* allocate(Type type, size_t bytes); + + /// Allocates \c size bytes of uninitialized storage using FreeListAllocator. + /// + /// \param[in] bytes: The byte size to allocate sotrage for. + /// \return On success, the pointer to the beginning of newly allocated + /// memory. + /// \return On failure, a null pointer + [[nodiscard]] void* allocateUsingFree(size_t bytes); + + /// Allocates \c size bytes of uninitialized storage using PoolAllocator. + /// + /// \param[in] bytes: The byte size to allocate sotrage for. + /// \return On success, the pointer to the beginning of newly allocated + /// memory. + /// \return On failure, a null pointer + [[nodiscard]] void* allocateUsingPool(size_t bytes); + + /// Deallocates the storage referenced by the pointer \c p, which must be a + /// pointer obtained by an earlier cal to allocate(). + /// + /// \param[in] type: The memory allocator type. + /// \param[in] pointer: Pointer obtained from allocate(). + /// \param[in] bytes: The bytes of the allocated memory. + void deallocate(Type type, void* pointer, size_t bytes); + // TODO(JS): Make this constexpr once migrated to C++20 + + void deallocateUsingFree(void* pointer, size_t bytes); + + void deallocateUsingPool(void* pointer, size_t bytes); + + /// Allocates uninitialized storage and constructs an object of type T to the + /// allocated storage. + /// + /// \tparam T: The object type to construct. + /// \tparam Args...: The argument types to pass to the object constructor. + /// + /// \param[in] type: The memory allocator type. + /// \param[in] args: The constructor arguments to use. + template + [[nodiscard]] T* construct(Type type, Args&&... args) noexcept; + + /// Allocates uninitialized storage using FreeListAllocator and constructs an + /// object of type T to the allocated storage. + template + [[nodiscard]] T* constructUsingFree(Args&&... args) noexcept; + + /// Allocates uninitialized storage using PoolAllocator and constructs an + /// object of type T to the allocated storage. + template + [[nodiscard]] T* constructUsingPool(Args&&... args) noexcept; + + /// Calls the destructor of the object and deallocate the storage. + template + void destroy(Type type, T* object) noexcept; + + /// Calls the destructor of the object and deallocate the storage using + /// FreeListAllocator. + template + void destroyUsingFree(T* pointer) noexcept; + + /// Calls the destructor of the object and deallocate the storage using + /// PoolAllocator. + template + void destroyUsingPool(T* pointer) noexcept; + +#ifndef NDEBUG + /// Returns true if a pointer is allocated by the internal allocator. + [[nodiscard]] bool hasAllocated(void* pointer, size_t size) const noexcept; +#endif + + /// Prints state of the memory manager. + void print(std::ostream& os = std::cout, int indent = 0) const; + + /// Prints state of the memory manager. + friend std::ostream& operator<<( + std::ostream& os, const MemoryManager& memoryManager); + +private: + /// The base allocator to allocate memory chunck. + MemoryAllocator& mBaseAllocator; + +#ifdef NDEBUG + /// The free list allocator. + FreeListAllocator mFreeListAllocator; + + /// The pool allocator. + PoolAllocator mPoolAllocator; +#else + /// The free list allocator. + FreeListAllocator::Debug mFreeListAllocator; + + /// The pool allocator. + PoolAllocator::Debug mPoolAllocator; +#endif +}; + +} // namespace dart::common + +#include "dart/common/detail/MemoryManager-impl.hpp" + +#endif // DART_COMMON_MEMORYMANAGER_HPP_ diff --git a/dart/common/PoolAllocator.cpp b/dart/common/PoolAllocator.cpp index 1ba21ed80a6ad..9756e9a01fb38 100644 --- a/dart/common/PoolAllocator.cpp +++ b/dart/common/PoolAllocator.cpp @@ -41,8 +41,8 @@ namespace dart::common { //============================================================================== -PoolAllocator::PoolAllocator(MemoryAllocator& base_allocator) - : mBaseAllocator(base_allocator) +PoolAllocator::PoolAllocator(MemoryAllocator& baseAllocator) + : mBaseAllocator(baseAllocator) { static_assert( 8 <= sizeof(MemoryUnit), @@ -90,21 +90,6 @@ PoolAllocator::~PoolAllocator() // Lock the mutex std::lock_guard lock(mMutex); -#ifndef NDEBUG - if (!mMapPointerToSize.empty()) - { - size_t totalSize = 0; - for (auto it : mMapPointerToSize) - { - void* pointer = it.first; - size_t size = it.second; - totalSize += size; - DART_FATAL("Found memory leak of {} bytes at {}!", size, pointer); - } - DART_FATAL("Found potential memory leak of total {} bytes!", totalSize); - } -#endif - for (int i = 0; i < mCurrentMemoryBlockIndex; ++i) { mBaseAllocator.deallocate(mMemoryBlocks[i].mMemoryUnits, BLOCK_SIZE); @@ -132,43 +117,33 @@ int PoolAllocator::getNumAllocatedMemoryBlocks() const } //============================================================================== -void* PoolAllocator::allocate(size_t size) noexcept +void* PoolAllocator::allocate(size_t bytes) noexcept { // Cannot allocate zero bytes - if (size == 0) + if (bytes == 0) { return nullptr; } // Use the default allocator to allocate memory that is greater than // MAX_UNIT_SIZE - if (size > MAX_UNIT_SIZE) + if (bytes > MAX_UNIT_SIZE) { DART_TRACE( "Cannot allocate memory of size > {} using PoolAllocator.", MAX_UNIT_SIZE); - return mBaseAllocator.allocate(size); + return mBaseAllocator.allocate(bytes); } // Lock the mutex std::lock_guard lock(mMutex); - const int heapIndex = mMapSizeToHeapIndex[size]; + const int heapIndex = mMapSizeToHeapIndex[bytes]; if (MemoryUnit* unit = mFreeMemoryUnits[heapIndex]) { mFreeMemoryUnits[heapIndex] = unit->mNext; -#ifndef NDEBUG - if (unit) - { - mSize += size; - mPeak = std::max(mPeak, mSize); - mMapPointerToSize[unit] = size; - } - return unit; -#else return unit; -#endif } if (mCurrentMemoryBlockIndex == mMemoryBlocksSize) @@ -211,96 +186,33 @@ void* PoolAllocator::allocate(size_t size) noexcept mFreeMemoryUnits[heapIndex] = newBlock->mMemoryUnits->mNext; mCurrentMemoryBlockIndex++; -#ifndef NDEBUG - if (newBlock->mMemoryUnits) - { - mSize += size; - mPeak = std::max(mPeak, mSize); - mMapPointerToSize[newBlock->mMemoryUnits] = size; - } return newBlock->mMemoryUnits; -#else - return newBlock->mMemoryUnits; -#endif } //============================================================================== -void PoolAllocator::deallocate(void* pointer, size_t size) +void PoolAllocator::deallocate(void* pointer, size_t bytes) { - if (size > MAX_UNIT_SIZE) + // Cannot deallocate nullptr or zero bytes + if (pointer == nullptr || bytes == 0) { - mBaseAllocator.deallocate(pointer, size); return; } - // Lock the mutex - std::lock_guard lock(mMutex); - -#ifndef NDEBUG // debug - auto it = mMapPointerToSize.find(pointer); - if (it != mMapPointerToSize.end()) + if (bytes > MAX_UNIT_SIZE) { - auto allocatedSize = it->second; - if (size != allocatedSize) - { - DART_FATAL( - "Cannot deallocated memory {} because the deallocating size {} is " - "different from the allocated size {}.", - pointer, - size, - allocatedSize); - return; - } - mSize -= size; - mMapPointerToSize.erase(it); - DART_TRACE("Deallocated {} bytes.", size); - } - else - { - DART_FATAL( - "Cannot deallocate memory {} that is not allocated by this allocator!", - pointer); - return; + return mBaseAllocator.deallocate(pointer, bytes); } -#endif - const int heapIndex = mMapSizeToHeapIndex[size]; + // Lock the mutex + std::lock_guard lock(mMutex); + + const int heapIndex = mMapSizeToHeapIndex[bytes]; MemoryUnit* releasedUnit = static_cast(pointer); releasedUnit->mNext = mFreeMemoryUnits[heapIndex]; mFreeMemoryUnits[heapIndex] = releasedUnit; } -#ifndef NDEBUG -//============================================================================== -bool PoolAllocator::isAllocated(void* pointer, size_t size) const noexcept -{ - if (size > MAX_UNIT_SIZE) - { - return mBaseAllocator.isAllocated(pointer, size); - } - - std::lock_guard lock(mMutex); - - const auto it = mMapPointerToSize.find(pointer); - if (it == mMapPointerToSize.end()) - return false; - - const auto& allocatedSize = it->second; - if (size != allocatedSize) - return false; - - return true; -} - -//============================================================================== -bool PoolAllocator::isEmpty() const noexcept -{ - std::lock_guard lock(mMutex); - return mMapPointerToSize.empty(); -} -#endif - //============================================================================== void PoolAllocator::print(std::ostream& os, int indent) const { diff --git a/dart/common/PoolAllocator.hpp b/dart/common/PoolAllocator.hpp index 1d87b911b3680..f27224813cc11 100644 --- a/dart/common/PoolAllocator.hpp +++ b/dart/common/PoolAllocator.hpp @@ -35,11 +35,9 @@ #include #include -#ifndef NDEBUG - #include -#endif #include "dart/common/MemoryAllocator.hpp" +#include "dart/common/MemoryAllocatorDebugger.hpp" namespace dart::common { @@ -48,6 +46,8 @@ namespace dart::common { class PoolAllocator : public MemoryAllocator { public: + using Debug = MemoryAllocatorDebugger; + /// Constructor /// /// \param[in] baseAllocator: (optional) Base memory allocator. @@ -61,28 +61,19 @@ class PoolAllocator : public MemoryAllocator DART_STRING_TYPE(PoolAllocator); /// Returns the base allocator - const MemoryAllocator& getBaseAllocator() const; + [[nodiscard]] const MemoryAllocator& getBaseAllocator() const; /// Returns the base allocator - MemoryAllocator& getBaseAllocator(); + [[nodiscard]] MemoryAllocator& getBaseAllocator(); /// Returns the count of allocated memory blocks - int getNumAllocatedMemoryBlocks() const; - - // Documentation inherited - [[nodiscard]] void* allocate(size_t size) noexcept override; + [[nodiscard]] int getNumAllocatedMemoryBlocks() const; // Documentation inherited - void deallocate(void* pointer, size_t size) override; + [[nodiscard]] void* allocate(size_t bytes) noexcept override; -#ifndef NDEBUG // Documentation inherited - [[nodiscard]] bool isAllocated(void* pointer, size_t size) const - noexcept override; - - // Documentation inherited - [[nodiscard]] bool isEmpty() const noexcept override; -#endif + void deallocate(void* pointer, size_t bytes) override; // Documentation inherited void print(std::ostream& os = std::cout, int indent = 0) const override; @@ -143,12 +134,6 @@ class PoolAllocator : public MemoryAllocator /// /// The size must be the same of mUnitSizes. std::array mFreeMemoryUnits; - -#ifndef NDEBUG - size_t mSize = 0; - size_t mPeak = 0; - std::unordered_map mMapPointerToSize; -#endif }; } // namespace dart::common diff --git a/dart/common/StlAllocator.hpp b/dart/common/StlAllocator.hpp index 65ef559697cc0..1b88339336dd5 100644 --- a/dart/common/StlAllocator.hpp +++ b/dart/common/StlAllocator.hpp @@ -73,18 +73,18 @@ class StlAllocator : public std::allocator /// Allocates n * sizeof(T) bytes of uninitialized storage. /// - /// @param[in] n: The number of objects to allocate sotrage for. - /// @param[in] hint: Point to a nearby memory location. - /// @return On success, the pointer to the beginning of newly allocated + /// \param[in] n: The number of objects to allocate sotrage for. + /// \param[in] hint: Point to a nearby memory location. + /// \return On success, the pointer to the beginning of newly allocated /// memory. - /// @return On failure, a null pointer + /// \return On failure, a null pointer [[nodiscard]] pointer allocate(size_type n, const void* hint = 0); - /// Deallocates the storage referenced by the pointer @c p, which must be a + /// Deallocates the storage referenced by the pointer \c p, which must be a /// pointer obtained by an earlier cal to allocate(). /// - /// @param[in] pointer: Pointer obtained from allocate(). - /// @param[in] n: Number of objects earlier passed to allocate(). + /// \param[in] pointer: Pointer obtained from allocate(). + /// \param[in] n: Number of objects earlier passed to allocate(). void deallocate(pointer pointer, size_type n); // TODO(JS): Make this constexpr once migrated to C++20 diff --git a/dart/common/detail/MemoryAllocatorDebugger-impl.hpp b/dart/common/detail/MemoryAllocatorDebugger-impl.hpp new file mode 100644 index 0000000000000..501b8a1454363 --- /dev/null +++ b/dart/common/detail/MemoryAllocatorDebugger-impl.hpp @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2011-2022, The DART development contributors + * All rights reserved. + * + * The list of contributors can be found at: + * https://github.com/dartsim/dart/blob/master/LICENSE + * + * This file is provided under the following "BSD-style" License: + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DART_COMMON_DETAIL_MEMORYALLOCATORDEBUGGER_IMPL_HPP_ +#define DART_COMMON_DETAIL_MEMORYALLOCATORDEBUGGER_IMPL_HPP_ + +#include "dart/common/Console.hpp" +#include "dart/common/Logging.hpp" +#include "dart/common/MemoryAllocatorDebugger.hpp" + +namespace dart::common { + +//============================================================================== +template +template +MemoryAllocatorDebugger::MemoryAllocatorDebugger(Args&&... args) + : mInternalAllocator(std::forward(args)...) +{ + // Do nothing +} + +//============================================================================== +template +MemoryAllocatorDebugger::~MemoryAllocatorDebugger() +{ + // Lock the mutex + std::lock_guard lock(mMutex); + + if (!mMapPointerToSize.empty()) + { + size_t totalSize = 0; + for (auto it : mMapPointerToSize) + { + void* pointer = it.first; + size_t size = it.second; + totalSize += size; + dtdbg << "Found potential memory leak at " << pointer << " (" << size + << " bytes).\n"; + // TODO(JS): Change to DART_FATAL once the issue of calling spdlog in + // destructor is resolved. + } + + dtdbg << "Found potential memory leak of total " << totalSize + << " bytes. The internal allocator will try to forcefully " + << "deallocate it but it's is not guaranteed.\n"; + // TODO(JS): Change to DART_FATAL once the issue of calling spdlog in + // destructor is resolved. + } +} + +//============================================================================== +template +const std::string& MemoryAllocatorDebugger::getStaticType() +{ + static const std::string type + = "MemoryAllocatorDebugger<" + T::getStaticType() + ">"; + return type; +} + +//============================================================================== +template +const std::string& MemoryAllocatorDebugger::getType() const +{ + return getStaticType(); +} + +//============================================================================== +template +void* MemoryAllocatorDebugger::allocate(size_t bytes) noexcept +{ + void* newPtr = mInternalAllocator.allocate(bytes); + + if (newPtr) + { + std::lock_guard lock(mMutex); + mSize += bytes; + mPeak = std::max(mPeak, mSize); + mMapPointerToSize[newPtr] = bytes; + } + + return newPtr; +} + +//============================================================================== +template +void MemoryAllocatorDebugger::deallocate(void* pointer, size_t bytes) +{ + std::lock_guard lock(mMutex); + + auto it = mMapPointerToSize.find(pointer); + if (it == mMapPointerToSize.end()) + { + DART_DEBUG( + "Cannot deallocate memory {} not allocated by this allocator.", + pointer); + return; + } + + auto allocatedSize = it->second; + if (bytes != allocatedSize) + { + DART_DEBUG( + "Cannot deallocate memory at {} of {} bytes that is different from the " + "allocated size {}, which is a critical bug.", + pointer, + bytes, + allocatedSize); + return; + } + + mInternalAllocator.deallocate(pointer, bytes); + mMapPointerToSize.erase(it); + mSize -= bytes; +} + +//============================================================================== +template +bool MemoryAllocatorDebugger::isEmpty() const +{ + std::lock_guard lock(mMutex); + return mMapPointerToSize.empty(); +} + +//============================================================================== +template +bool MemoryAllocatorDebugger::hasAllocated(void* pointer, size_t size) const +{ + std::lock_guard lock(mMutex); + + const auto it = mMapPointerToSize.find(pointer); + if (it == mMapPointerToSize.end()) + { + return false; + } + + const auto& allocatedSize = it->second; + if (size != allocatedSize) + { + return false; + } + + return true; +} + +//============================================================================== +template +const T& MemoryAllocatorDebugger::getInternalAllocator() const +{ + return mInternalAllocator; +} + +//============================================================================== +template +T& MemoryAllocatorDebugger::getInternalAllocator() +{ + return mInternalAllocator; +} + +//============================================================================== +template +void MemoryAllocatorDebugger::print(std::ostream& os, int indent) const +{ + if (indent == 0) + { + os << "[" << getType() << "]\n"; + } + const std::string spaces(indent, ' '); + if (indent != 0) + { + os << spaces << "type: " << getType() << "\n"; + } + std::lock_guard lock(mMutex); + os << spaces << "size_in_bytes: " << mSize << "\n"; + os << spaces << "peak: " << mPeak << "\n"; + os << spaces << "internal_allocator:\n"; + mInternalAllocator.print(os, indent + 2); +} + +} // namespace dart::common + +#endif // DART_COMMON_DETAIL_MEMORYALLOCATORDEBUGGER_IMPL_HPP_ diff --git a/dart/common/detail/MemoryManager-impl.hpp b/dart/common/detail/MemoryManager-impl.hpp new file mode 100644 index 0000000000000..862e2f0a28e60 --- /dev/null +++ b/dart/common/detail/MemoryManager-impl.hpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2011-2022, The DART development contributors + * All rights reserved. + * + * The list of contributors can be found at: + * https://github.com/dartsim/dart/blob/master/LICENSE + * + * This file is provided under the following "BSD-style" License: + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DART_COMMON_DETAIL_MEMORYMANAGER_IMPL_HPP_ +#define DART_COMMON_DETAIL_MEMORYMANAGER_IMPL_HPP_ + +#include "dart/common/MemoryManager.hpp" + +namespace dart::common { + +//============================================================================== +template +T* MemoryManager::construct(Type type, Args&&... args) noexcept +{ + // Allocate new memory for a new object (without calling the constructor) + void* object = allocate(type, sizeof(T)); + if (!object) + { + return nullptr; + } + + // Call constructor. Return nullptr if failed. + try + { + new (object) T(std::forward(args)...); + } + catch (...) + { + deallocate(type, object, sizeof(T)); + return nullptr; + } + + return reinterpret_cast(object); +} + +//============================================================================== +template +T* MemoryManager::constructUsingFree(Args&&... args) noexcept +{ + return construct(Type::Free, std::forward(args)...); +} + +//============================================================================== +template +T* MemoryManager::constructUsingPool(Args&&... args) noexcept +{ + return construct(Type::Pool, std::forward(args)...); +} + +//============================================================================== +template +void MemoryManager::destroy(Type type, T* object) noexcept +{ + if (!object) + { + return; + } + object->~T(); + deallocate(type, object, sizeof(T)); +} + +//============================================================================== +template +void MemoryManager::destroyUsingFree(T* pointer) noexcept +{ + destroy(Type::Free, pointer); +} + +//============================================================================== +template +void MemoryManager::destroyUsingPool(T* pointer) noexcept +{ + destroy(Type::Pool, pointer); +} + +} // namespace dart::common + +#endif // DART_COMMON_DETAIL_MEMORYMANAGER_IMPL_HPP_ diff --git a/dart/common/detail/StlAllocator-impl.hpp b/dart/common/detail/StlAllocator-impl.hpp index 37928897c5d42..03d3dd681dcc9 100644 --- a/dart/common/detail/StlAllocator-impl.hpp +++ b/dart/common/detail/StlAllocator-impl.hpp @@ -95,7 +95,7 @@ void StlAllocator::print(std::ostream& os, int indent) const os << "[StlAllocator]\n"; } const std::string spaces(indent, ' '); - os << spaces << "baseAllocator:\n"; + os << spaces << "base_allocator:\n"; mBaseAllocator.print(os, indent + 2); } diff --git a/dart/constraint/ConstraintSolver.cpp b/dart/constraint/ConstraintSolver.cpp index 573a9f8a3865d..40388060f422e 100644 --- a/dart/constraint/ConstraintSolver.cpp +++ b/dart/constraint/ConstraintSolver.cpp @@ -41,6 +41,7 @@ #include "dart/collision/dart/DARTCollisionDetector.hpp" #include "dart/collision/fcl/FCLCollisionDetector.hpp" #include "dart/common/Console.hpp" +#include "dart/common/Macros.hpp" #include "dart/constraint/ConstrainedGroup.hpp" #include "dart/constraint/ContactConstraint.hpp" #include "dart/constraint/JointConstraint.hpp" @@ -101,7 +102,7 @@ void ConstraintSolver::addSkeleton(const SkeletonPtr& skeleton) skeleton && "Null pointer skeleton is now allowed to add to ConstraintSover."); - if (containSkeleton(skeleton)) + if (hasSkeleton(skeleton)) { dtwarn << "[ConstraintSolver::addSkeleton] Attempting to add " << "skeleton '" << skeleton->getName() @@ -135,7 +136,7 @@ void ConstraintSolver::removeSkeleton(const SkeletonPtr& skeleton) skeleton && "Null pointer skeleton is now allowed to add to ConstraintSover."); - if (!containSkeleton(skeleton)) + if (!hasSkeleton(skeleton)) { dtwarn << "[ConstraintSolver::removeSkeleton] Attempting to remove " << "skeleton '" << skeleton->getName() @@ -233,7 +234,7 @@ std::vector ConstraintSolver::getConstraints() const { std::vector constraints; constraints.reserve(mManualConstraints.size()); - for (auto constraint : mManualConstraints) + for (auto& constraint : mManualConstraints) constraints.push_back(constraint); return constraints; @@ -392,16 +393,20 @@ void ConstraintSolver::setFromOtherConstraintSolver( } //============================================================================== -bool ConstraintSolver::containSkeleton(const ConstSkeletonPtr& _skeleton) const +bool ConstraintSolver::containSkeleton(const ConstSkeletonPtr& skeleton) const { - assert( - _skeleton != nullptr && "Not allowed to insert null pointer skeleton."); + return hasSkeleton(skeleton); +} + +//============================================================================== +bool ConstraintSolver::hasSkeleton(const ConstSkeletonPtr& skeleton) const +{ + DART_ASSERT( + skeleton != nullptr, "Not allowed to insert null pointer skeleton."); - for (std::vector::const_iterator it = mSkeletons.begin(); - it != mSkeletons.end(); - ++it) + for (const auto& itrSkel : mSkeletons) { - if ((*it) == _skeleton) + if (itrSkel == skeleton) return true; } @@ -411,7 +416,7 @@ bool ConstraintSolver::containSkeleton(const ConstSkeletonPtr& _skeleton) const //============================================================================== bool ConstraintSolver::checkAndAddSkeleton(const SkeletonPtr& skeleton) { - if (!containSkeleton(skeleton)) + if (!hasSkeleton(skeleton)) { mSkeletons.push_back(skeleton); return true; diff --git a/dart/constraint/ConstraintSolver.hpp b/dart/constraint/ConstraintSolver.hpp index dfdd1822fa815..6c8242ae7e1f1 100644 --- a/dart/constraint/ConstraintSolver.hpp +++ b/dart/constraint/ConstraintSolver.hpp @@ -117,11 +117,41 @@ class ConstraintSolver constraint::ConstConstraintBasePtr getConstraint(std::size_t index) const; /// Returns all the constraints added to this ConstraintSolver. + /// + /// \deprecated Use eachConstraint() instead + DART_DEPRECATED(6.13) std::vector getConstraints(); /// Returns all the constraints added to this ConstraintSolver. + /// + /// \deprecated Use eachConstraint() instead + DART_DEPRECATED(6.13) std::vector getConstraints() const; + /// Iterates all the constraints and invokes the callback function. + /// + /// \tparam Func: The callback function type. The function signature should be + /// equivalent to \c void(const ConstraintBase*) or \c bool(const + /// ConstraintBase*). If you want to conditionally iterate, use \c bool(const + /// ConstraintBase*) and return false when to stop iterating. + /// + /// \param[in] func: The callback function to be called for each + /// ConstraintBase. + template + void eachConstraint(Func func) const; + + /// Iterates all the constraints and invokes the callback function. + /// + /// \tparam Func: The callback function type. The function signature should be + /// equivalent to \c void(ConstraintBase*) or \c bool(ConstraintBase*). If + /// you want to conditionally iterate, use \c bool(ConstraintBase*) and + /// return false when to stop iterating. + /// + /// \param[in] func: The callback function to be called for each + /// ConstraintBase. + template + void eachConstraint(Func func); + /// Clears the last collision result void clearLastCollisionResult(); @@ -188,9 +218,15 @@ class ConstraintSolver // TODO(JS): Docstring virtual void solveConstrainedGroup(ConstrainedGroup& group) = 0; - /// Check if the skeleton is contained in this solver + /// Checks if the skeleton is contained in this solver + /// + /// \deprecated Use hasSkeleton() instead. + DART_DEPRECATED(6.13) bool containSkeleton(const dynamics::ConstSkeletonPtr& skeleton) const; + /// Checks if the skeleton is contained in this solver + bool hasSkeleton(const dynamics::ConstSkeletonPtr& skeleton) const; + /// Add skeleton if the constraint is not contained in this solver bool checkAndAddSkeleton(const dynamics::SkeletonPtr& skeleton); @@ -261,4 +297,6 @@ class ConstraintSolver } // namespace constraint } // namespace dart +#include "dart/constraint/detail/ConstraintSolver-impl.hpp" + #endif // DART_CONSTRAINT_CONSTRAINTSOVER_HPP_ diff --git a/dart/constraint/detail/ConstraintSolver-impl.hpp b/dart/constraint/detail/ConstraintSolver-impl.hpp new file mode 100644 index 0000000000000..bfae38295f8df --- /dev/null +++ b/dart/constraint/detail/ConstraintSolver-impl.hpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2011-2022, The DART development contributors + * All rights reserved. + * + * The list of contributors can be found at: + * https://github.com/dartsim/dart/blob/master/LICENSE + * + * This file is provided under the following "BSD-style" License: + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DART_CONSTRAINT_DETAIL_CONSTRAINTSOVER_IMPL_HPP_ +#define DART_CONSTRAINT_DETAIL_CONSTRAINTSOVER_IMPL_HPP_ + +#include "dart/constraint/ConstraintSolver.hpp" + +namespace dart::constraint { + +//============================================================================== +template +void ConstraintSolver::eachConstraint(Func func) const +{ + if constexpr (std::is_same_v< + std::invoke_result_t, + bool>) + { + for (auto i = 0u; i < getNumConstraints(); ++i) + { + if (!func(getConstraint(i))) + return; + } + } + else + { + for (auto i = 0u; i < getNumConstraints(); ++i) + { + func(getConstraint(i)); + } + } +} + +//============================================================================== +template +void ConstraintSolver::eachConstraint(Func func) +{ + if constexpr (std::is_same_v< + std::invoke_result_t, + bool>) + { + for (auto i = 0u; i < getNumConstraints(); ++i) + { + if (!func(getConstraint(i))) + return; + } + } + else + { + for (auto i = 0u; i < getNumConstraints(); ++i) + { + func(getConstraint(i)); + } + } +} + +} // namespace dart::constraint + +#endif // DART_CONSTRAINT_DETAIL_CONSTRAINTSOVER_IMPL_HPP_ diff --git a/dart/dynamics/BodyNode.hpp b/dart/dynamics/BodyNode.hpp index f4b1313b9876d..1ff3340b625f9 100644 --- a/dart/dynamics/BodyNode.hpp +++ b/dart/dynamics/BodyNode.hpp @@ -565,7 +565,8 @@ class BodyNode template void removeAllShapeNodesWith(); - /// Iterates all the ShapeNodes that has a specific aspect. + /// Iterates all the ShapeNodes that has a specific aspect and invokes the + /// callback function. /// /// Example: /// \code{.cpp} @@ -585,7 +586,8 @@ class BodyNode template void eachShapeNodeWith(Func func) const; - /// Iterates all the ShapeNodes that has a specific aspect. + /// Iterates all the ShapeNodes that has a specific aspect and invokes the + /// callback function. /// /// Example: /// \code{.cpp} diff --git a/unittests/integration/test_World.cpp b/unittests/integration/test_World.cpp index 4649045a61ee6..3d79cfe5b279e 100644 --- a/unittests/integration/test_World.cpp +++ b/unittests/integration/test_World.cpp @@ -378,23 +378,23 @@ TEST(World, SetNewConstraintSolver) { auto world = createWorld(); EXPECT_TRUE(world->getConstraintSolver()->getSkeletons().size() == 1); - EXPECT_TRUE(world->getConstraintSolver()->getConstraints().size() == 1); + EXPECT_TRUE(world->getConstraintSolver()->getNumConstraints() == 1); auto solver1 = std::make_unique( std::make_shared()); EXPECT_TRUE(solver1->getSkeletons().size() == 0); - EXPECT_TRUE(solver1->getConstraints().size() == 0); + EXPECT_TRUE(solver1->getNumConstraints() == 0); world->setConstraintSolver(std::move(solver1)); EXPECT_TRUE(world->getConstraintSolver()->getSkeletons().size() == 1); - EXPECT_TRUE(world->getConstraintSolver()->getConstraints().size() == 1); + EXPECT_TRUE(world->getConstraintSolver()->getNumConstraints() == 1); auto solver2 = std::make_unique( std::make_shared()); EXPECT_TRUE(solver2->getSkeletons().size() == 0); - EXPECT_TRUE(solver2->getConstraints().size() == 0); + EXPECT_TRUE(solver2->getNumConstraints() == 0); world->setConstraintSolver(std::move(solver2)); EXPECT_TRUE(world->getConstraintSolver()->getSkeletons().size() == 1); - EXPECT_TRUE(world->getConstraintSolver()->getConstraints().size() == 1); + EXPECT_TRUE(world->getConstraintSolver()->getNumConstraints() == 1); } diff --git a/unittests/unit/common/test_FreeListAllocator.cpp b/unittests/unit/common/test_FreeListAllocator.cpp index e52ea7cb25d97..d8edc67b364b6 100644 --- a/unittests/unit/common/test_FreeListAllocator.cpp +++ b/unittests/unit/common/test_FreeListAllocator.cpp @@ -38,17 +38,55 @@ using namespace dart; using namespace common; +//============================================================================== +TEST(FreeListAllocatorTest, Constructors) +{ + auto a = FreeListAllocator::Debug(); + EXPECT_EQ( + &a.getInternalAllocator().getBaseAllocator(), + &MemoryAllocator::GetDefault()); + + auto b = FreeListAllocator::Debug(MemoryAllocator::GetDefault()); + EXPECT_EQ( + &b.getInternalAllocator().getBaseAllocator(), + &MemoryAllocator::GetDefault()); + + EXPECT_TRUE(a.isEmpty()); + EXPECT_TRUE(b.isEmpty()); +} + //============================================================================== TEST(FreeListAllocatorTest, Basics) { - auto a = FreeListAllocator(); - a.print(); + auto a = FreeListAllocator::Debug(); + EXPECT_TRUE(a.isEmpty()); + + // Cannot allocate 0 bytes + EXPECT_EQ(a.allocate(0), nullptr); + + // Allocate small memory + auto ptr1 = a.allocate(1); + EXPECT_NE(ptr1, nullptr); + EXPECT_TRUE(a.hasAllocated(ptr1, 1)); + EXPECT_FALSE(a.hasAllocated(0, 1)); // incorrect address + EXPECT_FALSE(a.hasAllocated(ptr1, 1 * 2)); // incorrect size + + a.deallocate(ptr1, 1); + + EXPECT_TRUE(a.isEmpty()); +} + +//============================================================================== +TEST(FreeListAllocatorTest, MemoryLeak) +{ + auto a = FreeListAllocator::Debug(); + EXPECT_TRUE(a.isEmpty()); - auto mem1 = a.allocate(10); - auto mem2 = a.allocate(10); - a.print(); + // Allocate small memory + auto ptr1 = a.allocate(1); + EXPECT_NE(ptr1, nullptr); - a.deallocate(mem1, 10); - a.deallocate(mem2, 10); - a.print(); + EXPECT_FALSE(a.isEmpty()); + // Expect that FreeListAllocator complains that not all the memory is + // deallocated } diff --git a/unittests/unit/common/test_MemoryManager.cpp b/unittests/unit/common/test_MemoryManager.cpp new file mode 100644 index 0000000000000..76e116100d843 --- /dev/null +++ b/unittests/unit/common/test_MemoryManager.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2011-2022, The DART development contributors + * All rights reserved. + * + * The list of contributors can be found at: + * https://github.com/dartsim/dart/blob/master/LICENSE + * + * This file is provided under the following "BSD-style" License: + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "TestHelpers.hpp" + +using namespace dart; +using namespace common; + +//============================================================================== +TEST(MemoryManagerTest, BaseAllocator) +{ + auto mm = MemoryManager(); + auto& baseAllocator = mm.getBaseAllocator(); + auto& freeListAllocator = mm.getFreeListAllocator(); + auto& poolAllocator = mm.getPoolAllocator(); + + EXPECT_EQ(&freeListAllocator.getBaseAllocator(), &baseAllocator); + EXPECT_EQ(&poolAllocator.getBaseAllocator(), &freeListAllocator); +} + +//============================================================================== +TEST(MemoryManagerTest, Allocate) +{ + auto mm = MemoryManager(); + + // Cannot allocate 0 bytes + EXPECT_EQ(mm.allocateUsingFree(0), nullptr); + EXPECT_EQ(mm.allocateUsingPool(0), nullptr); + + // Allocate 1 byte using FreeListAllocator + auto ptr1 = mm.allocateUsingFree(1); + EXPECT_NE(ptr1, nullptr); +#ifndef NDEBUG + EXPECT_TRUE(mm.hasAllocated(ptr1, 1)); + EXPECT_FALSE(mm.hasAllocated(nullptr, 1)); + EXPECT_FALSE(mm.hasAllocated(ptr1, 1 * 2)); +#endif + + // Allocate 1 byte using PoolAllocator + auto ptr2 = mm.allocateUsingPool(1); + EXPECT_NE(ptr2, nullptr); +#ifndef NDEBUG + EXPECT_TRUE(mm.hasAllocated(ptr2, 1)); + EXPECT_FALSE(mm.hasAllocated(nullptr, 1)); + EXPECT_FALSE(mm.hasAllocated(ptr2, 1 * 2)); +#endif + + // Deallocate all + mm.deallocateUsingFree(ptr1, 1); + mm.deallocateUsingPool(ptr2, 1); +} + +//============================================================================== +TEST(MemoryManagerTest, MemoryLeak) +{ + auto a = MemoryManager(); + + // Allocate small memory + auto ptr1 = a.allocateUsingPool(1); + EXPECT_NE(ptr1, nullptr); + + // Allocate small memory + auto ptr2 = a.allocateUsingFree(1); + EXPECT_NE(ptr2, nullptr); + + // Expect that MemoryManager complains that not all the memory is deallocated +} diff --git a/unittests/unit/common/test_PoolAllocator.cpp b/unittests/unit/common/test_PoolAllocator.cpp index 7cd77ea92c608..8551b0727a7fe 100644 --- a/unittests/unit/common/test_PoolAllocator.cpp +++ b/unittests/unit/common/test_PoolAllocator.cpp @@ -41,73 +41,66 @@ using namespace common; //============================================================================== TEST(PoolAllocatorTest, Constructors) { - auto a = PoolAllocator(); - EXPECT_EQ(&a.getBaseAllocator(), &MemoryAllocator::GetDefault()); + auto a = PoolAllocator::Debug(); + EXPECT_EQ( + &a.getInternalAllocator().getBaseAllocator(), + &MemoryAllocator::GetDefault()); - auto b = PoolAllocator(MemoryAllocator::GetDefault()); - EXPECT_EQ(&b.getBaseAllocator(), &MemoryAllocator::GetDefault()); + auto b = PoolAllocator::Debug(MemoryAllocator::GetDefault()); + EXPECT_EQ( + &b.getInternalAllocator().getBaseAllocator(), + &MemoryAllocator::GetDefault()); - EXPECT_EQ(b.getNumAllocatedMemoryBlocks(), 0); + EXPECT_EQ(b.getInternalAllocator().getNumAllocatedMemoryBlocks(), 0); -#ifndef NDEBUG EXPECT_TRUE(a.isEmpty()); EXPECT_TRUE(b.isEmpty()); -#endif } //============================================================================== TEST(PoolAllocatorTest, Allocate) { - auto a = PoolAllocator(); - EXPECT_EQ(a.allocate(0), nullptr); - -#ifndef NDEBUG + auto a = PoolAllocator::Debug(); EXPECT_TRUE(a.isEmpty()); -#endif + + // Cannot allocate 0 bytes + EXPECT_EQ(a.allocate(0), nullptr); // Allocate small memory auto ptr1 = a.allocate(1); EXPECT_NE(ptr1, nullptr); -#ifndef NDEBUG - EXPECT_TRUE(a.isAllocated(ptr1, 1)); - EXPECT_FALSE(a.isAllocated(0, 1)); // incorrect address - EXPECT_FALSE(a.isAllocated(ptr1, 1 * 2)); // incorrect size -#endif - EXPECT_EQ(a.getNumAllocatedMemoryBlocks(), 1); + EXPECT_TRUE(a.hasAllocated(ptr1, 1)); + EXPECT_FALSE(a.hasAllocated(0, 1)); // incorrect address + EXPECT_FALSE(a.hasAllocated(ptr1, 1 * 2)); // incorrect size + EXPECT_EQ(a.getInternalAllocator().getNumAllocatedMemoryBlocks(), 1); // Allocate the same size, which doesn't increase the number of memory block auto ptr2 = a.allocate(1); EXPECT_NE(ptr2, nullptr); - EXPECT_EQ(a.getNumAllocatedMemoryBlocks(), 1); + EXPECT_EQ(a.getInternalAllocator().getNumAllocatedMemoryBlocks(), 1); // Allocate different size auto ptr3 = a.allocate(64); EXPECT_NE(ptr3, nullptr); - EXPECT_EQ(a.getNumAllocatedMemoryBlocks(), 2); + EXPECT_EQ(a.getInternalAllocator().getNumAllocatedMemoryBlocks(), 2); // Allocate memory of the max size (= 1024) auto ptr4 = a.allocate(1024); EXPECT_NE(ptr4, nullptr); -#ifndef NDEBUG - EXPECT_TRUE(a.isAllocated(ptr4, 1024)); - EXPECT_FALSE(a.isAllocated(0, 1024)); - EXPECT_FALSE(a.isAllocated(ptr4, 1024 * 2)); -#endif - EXPECT_EQ(a.getNumAllocatedMemoryBlocks(), 3); + EXPECT_TRUE(a.hasAllocated(ptr4, 1024)); + EXPECT_FALSE(a.hasAllocated(0, 1024)); + EXPECT_FALSE(a.hasAllocated(ptr4, 1024 * 2)); + EXPECT_EQ(a.getInternalAllocator().getNumAllocatedMemoryBlocks(), 3); // Allocate oversized memory (> 1024) auto ptr5 = a.allocate(2048); EXPECT_NE(ptr5, nullptr); -#ifndef NDEBUG - EXPECT_TRUE(a.isAllocated(ptr5, 2048)); - EXPECT_FALSE(a.isAllocated(0, 2048)); - EXPECT_FALSE(a.isAllocated(ptr5, 2048 * 2)); -#endif - EXPECT_EQ(a.getNumAllocatedMemoryBlocks(), 3); - -#ifndef NDEBUG + EXPECT_TRUE(a.hasAllocated(ptr5, 2048)); + EXPECT_FALSE(a.hasAllocated(0, 2048)); + EXPECT_FALSE(a.hasAllocated(ptr5, 2048 * 2)); + EXPECT_EQ(a.getInternalAllocator().getNumAllocatedMemoryBlocks(), 3); + EXPECT_FALSE(a.isEmpty()); -#endif a.deallocate(ptr1, 1); a.deallocate(ptr2, 1); @@ -115,7 +108,5 @@ TEST(PoolAllocatorTest, Allocate) a.deallocate(ptr4, 1024); a.deallocate(ptr5, 2048); -#ifndef NDEBUG EXPECT_TRUE(a.isEmpty()); -#endif } diff --git a/unittests/unit/math/test_StlAllocator.cpp b/unittests/unit/common/test_StlAllocator.cpp similarity index 86% rename from unittests/unit/math/test_StlAllocator.cpp rename to unittests/unit/common/test_StlAllocator.cpp index 952a90e0499da..10c0d55de8d75 100644 --- a/unittests/unit/math/test_StlAllocator.cpp +++ b/unittests/unit/common/test_StlAllocator.cpp @@ -50,15 +50,3 @@ TEST(StlAllocatorTest, Basics) a.deallocate(o2, 1); a.print(); } - -//============================================================================== -TEST(StlAllocatorTest, StdVector) -{ - std::vector> vec; - EXPECT_EQ(vec.capacity(), 0); - vec.reserve(1); - EXPECT_EQ(vec.capacity(), 1); - vec.reserve(2); - EXPECT_EQ(vec.capacity(), 2); - vec.get_allocator().print(); -}