Merge "Ensure we check for null when freeing from the memory pool" into devel/master
[platform/core/uifw/dali-core.git] / dali / internal / common / fixed-size-memory-pool.cpp
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/internal/common/fixed-size-memory-pool.h>
20
21 // INTERNAL HEADERS
22 #include <dali/devel-api/threading/mutex.h>
23 #include <dali/public-api/common/dali-common.h>
24
25 namespace Dali
26 {
27
28 namespace Internal
29 {
30
31 /**
32  * @brief Private implementation class
33  */
34 struct FixedSizeMemoryPool::Impl
35 {
36   /**
37    * @brief Struct to represent a block of memory from which allocations can be made.
38    *
39    * The block forms a linked list.
40    */
41   struct Block
42   {
43     void* blockMemory;      ///< The allocated memory from which allocations can be made
44     Block* nextBlock;       ///< The next block in the linked list
45 #ifdef DEBUG_ENABLED
46     SizeType mBlockSize; ///< Size of the block in bytes
47 #endif
48     /**
49      * @brief Construct a new block with given size
50      *
51      * @param size The size of the memory block to allocate in bytes. Must be non-zero.
52      */
53     Block( SizeType size )
54     : nextBlock( nullptr )
55 #ifdef DEBUG_ENABLED
56       ,mBlockSize( size )
57 #endif
58     {
59       blockMemory = ::operator new( size );
60       DALI_ASSERT_ALWAYS( blockMemory && "Out of memory" );
61     }
62
63     /**
64      * @brief Destructor
65      */
66     ~Block()
67     {
68       ::operator delete( blockMemory );
69     }
70
71   private:
72     // Undefined
73     Block( const Block& block );
74
75     // Undefined
76     Block& operator=( const Block& block );
77   };
78
79   /**
80    * @brief Constructor
81    */
82   Impl( SizeType fixedSize, SizeType initialCapacity, SizeType maximumBlockCapacity )
83   :  mMutex(),
84      mFixedSize( fixedSize ),
85      mMemoryBlocks( initialCapacity * mFixedSize ),
86      mMaximumBlockCapacity( maximumBlockCapacity ),
87      mCurrentBlock( &mMemoryBlocks ),
88      mCurrentBlockCapacity( initialCapacity ),
89      mCurrentBlockSize( 0 ),
90      mDeletedObjects( nullptr )
91   {
92     // We need enough room to store the deleted list in the data
93     DALI_ASSERT_DEBUG( mFixedSize >= sizeof( void* ) );
94   }
95
96   /**
97    * @brief Destructor
98    */
99   ~Impl()
100   {
101     // Clean up memory block linked list (mMemoryBlocks will be auto-destroyed by its destructor)
102     Block* block = mMemoryBlocks.nextBlock;
103     while( block )
104     {
105       Block* nextBlock = block->nextBlock;
106       delete block;
107       block = nextBlock;
108     }
109   }
110
111   /**
112    * @brief Allocate a new block for allocating memory from
113    */
114   void AllocateNewBlock()
115   {
116     // Double capacity for the new block
117     SizeType size = mCurrentBlockCapacity * 2;
118     if( size > mMaximumBlockCapacity || size < mCurrentBlockCapacity )    // Check for overflow of size type
119     {
120       size = mMaximumBlockCapacity;
121     }
122
123     mCurrentBlockCapacity = size;
124
125     // Allocate
126     Block* block = new Block( mCurrentBlockCapacity * mFixedSize );
127     mCurrentBlock->nextBlock = block;       // Add to end of linked list
128     mCurrentBlock = block;
129
130     mCurrentBlockSize = 0;
131   }
132 #ifdef DEBUG_ENABLED
133
134   /**
135    * @brief check the memory being free'd exists inside the memory pool
136    * @param[in] memory address of object to remove
137    */
138   void CheckMemoryIsInsidePool( const void* const memory )
139   {
140     bool inRange = false;
141     const Block* block = &mMemoryBlocks;
142
143     while( block )
144     {
145       const void* const endOfBlock = reinterpret_cast<char *>( block->blockMemory )+ block->mBlockSize;
146
147       if( ( memory >= block->blockMemory ) && ( memory < (endOfBlock) ) )
148       {
149         inRange = true;
150         break;
151       }
152       block = block->nextBlock;
153     }
154     DALI_ASSERT_DEBUG( inRange && "Freeing memory that does not exist in memory pool" );
155   }
156 #endif
157
158   Mutex mMutex;                       ///< Mutex for thread-safe allocation and deallocation
159
160   SizeType mFixedSize;                ///< The size of each allocation in bytes
161
162   Block mMemoryBlocks;                ///< Linked list of allocated memory blocks
163   SizeType mMaximumBlockCapacity;     ///< The maximum allowed capacity of allocations in a new memory block
164
165   Block* mCurrentBlock;               ///< Pointer to the active block
166   SizeType mCurrentBlockCapacity;     ///< The maximum number of allocations that can be allocated for the current block
167   SizeType mCurrentBlockSize;         ///< The number of allocations allocated to the current block
168
169   void* mDeletedObjects;              ///< Pointer to the head of the list of deleted objects. The addresses are stored in the allocated memory blocks.
170 };
171
172 FixedSizeMemoryPool::FixedSizeMemoryPool( SizeType fixedSize, SizeType initialCapacity, SizeType maximumBlockCapacity )
173 {
174   mImpl = new Impl( fixedSize, initialCapacity, maximumBlockCapacity );
175 }
176
177 FixedSizeMemoryPool::~FixedSizeMemoryPool()
178 {
179   delete mImpl;
180 }
181
182 void* FixedSizeMemoryPool::Allocate()
183 {
184   // First, recycle deleted objects
185   if( mImpl->mDeletedObjects )
186   {
187     void* recycled = mImpl->mDeletedObjects;
188     mImpl->mDeletedObjects = *( reinterpret_cast< void** >( mImpl->mDeletedObjects ) );  // Pop head off front of deleted objects list
189     return recycled;
190   }
191
192   // Check if current block is full
193   if( mImpl->mCurrentBlockSize >= mImpl->mCurrentBlockCapacity )
194   {
195     mImpl->AllocateNewBlock();
196   }
197
198   // Placement new the object in block memory
199   uint8_t* objectAddress = static_cast< uint8_t* >( mImpl->mCurrentBlock->blockMemory );
200   objectAddress += mImpl->mCurrentBlockSize * mImpl->mFixedSize;
201   mImpl->mCurrentBlockSize++;
202
203   return objectAddress;
204 }
205
206 void FixedSizeMemoryPool::Free( void* memory )
207 {
208   if( memory )
209   {
210 #ifdef DEBUG_ENABLED
211     mImpl->CheckMemoryIsInsidePool( memory );
212 #endif
213
214     // Add memory to head of deleted objects list. Store next address in the same memory space as the old object.
215     *( reinterpret_cast< void** >( memory ) ) = mImpl->mDeletedObjects;
216     mImpl->mDeletedObjects = memory;
217   }
218 }
219
220 void* FixedSizeMemoryPool::AllocateThreadSafe()
221 {
222   Mutex::ScopedLock lock( mImpl->mMutex );
223   return Allocate();
224 }
225
226 void FixedSizeMemoryPool::FreeThreadSafe( void* memory )
227 {
228   if( memory )
229   {
230     Mutex::ScopedLock lock( mImpl->mMutex );
231     Free( memory );
232   }
233 }
234
235
236 } // namespace Internal
237
238 } // namespace Dali