1 | /* |
2 | Copyright (c) 2005-2019 Intel Corporation |
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 | #include "tbbmalloc_internal.h" |
18 | #include <new> /* for placement new */ |
19 | |
20 | namespace rml { |
21 | namespace internal { |
22 | |
23 | |
24 | /********* backreferences ***********************/ |
25 | /* Each slab block and each large memory object header contains BackRefIdx |
26 | * that points out in some BackRefBlock which points back to this block or header. |
27 | */ |
28 | struct BackRefBlock : public BlockI { |
29 | BackRefBlock *nextForUse; // the next in the chain of blocks with free items |
30 | FreeObject *bumpPtr; // bump pointer moves from the end to the beginning of the block |
31 | FreeObject *freeList; |
32 | // list of all blocks that were allocated from raw mem (i.e., not from backend) |
33 | BackRefBlock *; |
34 | int allocatedCount; // the number of objects allocated |
35 | BackRefIdx::master_t myNum; // the index in the master |
36 | MallocMutex blockMutex; |
37 | // true if this block has been added to the listForUse chain, |
38 | // modifications protected by masterMutex |
39 | bool addedToForUse; |
40 | |
41 | BackRefBlock(const BackRefBlock *blockToUse, intptr_t num) : |
42 | nextForUse(NULL), bumpPtr((FreeObject*)((uintptr_t)blockToUse + slabSize - sizeof(void*))), |
43 | freeList(NULL), nextRawMemBlock(NULL), allocatedCount(0), myNum(num), |
44 | addedToForUse(false) { |
45 | memset(&blockMutex, 0, sizeof(MallocMutex)); |
46 | |
47 | MALLOC_ASSERT(!(num >> CHAR_BIT*sizeof(BackRefIdx::master_t)), |
48 | "index in BackRefMaster must fit to BackRefIdx::master" ); |
49 | } |
50 | // clean all but header |
51 | void zeroSet() { memset(this+1, 0, BackRefBlock::bytes-sizeof(BackRefBlock)); } |
52 | static const int bytes = slabSize; |
53 | }; |
54 | |
55 | // max number of backreference pointers in slab block |
56 | static const int BR_MAX_CNT = (BackRefBlock::bytes-sizeof(BackRefBlock))/sizeof(void*); |
57 | |
58 | struct BackRefMaster { |
59 | /* On 64-bit systems a slab block can hold up to ~2K back pointers to slab blocks |
60 | * or large objects, so it can address at least 32MB. The master array of 256KB |
61 | * holds 32K pointers to such blocks, addressing ~1 TB. |
62 | * On 32-bit systems there is ~4K back pointers in a slab block, so ~64MB can be addressed. |
63 | * The master array of 8KB holds 2K pointers to leaves, so ~128 GB can addressed. |
64 | */ |
65 | static const size_t bytes = sizeof(uintptr_t)>4? 256*1024 : 8*1024; |
66 | static const int dataSz; |
67 | /* space is reserved for master table and 4 leaves |
68 | taking into account VirtualAlloc allocation granularity */ |
69 | static const int leaves = 4; |
70 | static const size_t masterSize = BackRefMaster::bytes+leaves*BackRefBlock::bytes; |
71 | // The size of memory request for a few more leaf blocks; |
72 | // selected to match VirtualAlloc granularity |
73 | static const size_t blockSpaceSize = 64*1024; |
74 | |
75 | Backend *backend; |
76 | BackRefBlock *active; // if defined, use it for allocations |
77 | BackRefBlock *listForUse; // the chain of data blocks with free items |
78 | BackRefBlock *allRawMemBlocks; |
79 | intptr_t lastUsed; // index of the last used block |
80 | bool rawMemUsed; |
81 | MallocMutex requestNewSpaceMutex; |
82 | BackRefBlock *backRefBl[1]; // the real size of the array is dataSz |
83 | |
84 | BackRefBlock *findFreeBlock(); |
85 | void addToForUseList(BackRefBlock *bl); |
86 | void initEmptyBackRefBlock(BackRefBlock *newBl); |
87 | bool requestNewSpace(); |
88 | }; |
89 | |
90 | const int BackRefMaster::dataSz |
91 | = 1+(BackRefMaster::bytes-sizeof(BackRefMaster))/sizeof(BackRefBlock*); |
92 | |
93 | static MallocMutex masterMutex; |
94 | static BackRefMaster *backRefMaster; |
95 | |
96 | bool initBackRefMaster(Backend *backend) |
97 | { |
98 | bool rawMemUsed; |
99 | BackRefMaster *master = |
100 | (BackRefMaster*)backend->getBackRefSpace(BackRefMaster::masterSize, |
101 | &rawMemUsed); |
102 | if (! master) |
103 | return false; |
104 | master->backend = backend; |
105 | master->listForUse = master->allRawMemBlocks = NULL; |
106 | master->rawMemUsed = rawMemUsed; |
107 | master->lastUsed = -1; |
108 | memset(&master->requestNewSpaceMutex, 0, sizeof(MallocMutex)); |
109 | for (int i=0; i<BackRefMaster::leaves; i++) { |
110 | BackRefBlock *bl = (BackRefBlock*)((uintptr_t)master + BackRefMaster::bytes + i*BackRefBlock::bytes); |
111 | bl->zeroSet(); |
112 | master->initEmptyBackRefBlock(bl); |
113 | if (i) |
114 | master->addToForUseList(bl); |
115 | else // active leaf is not needed in listForUse |
116 | master->active = bl; |
117 | } |
118 | // backRefMaster is read in getBackRef, so publish it in consistent state |
119 | FencedStore((intptr_t&)backRefMaster, (intptr_t)master); |
120 | return true; |
121 | } |
122 | |
123 | void destroyBackRefMaster(Backend *backend) |
124 | { |
125 | if (backRefMaster) { // Is initBackRefMaster() called? |
126 | for (BackRefBlock *curr=backRefMaster->allRawMemBlocks; curr; ) { |
127 | BackRefBlock *next = curr->nextRawMemBlock; |
128 | // allRawMemBlocks list is only for raw mem blocks |
129 | backend->putBackRefSpace(curr, BackRefMaster::blockSpaceSize, |
130 | /*rawMemUsed=*/true); |
131 | curr = next; |
132 | } |
133 | backend->putBackRefSpace(backRefMaster, BackRefMaster::masterSize, |
134 | backRefMaster->rawMemUsed); |
135 | } |
136 | } |
137 | |
138 | void BackRefMaster::addToForUseList(BackRefBlock *bl) |
139 | { |
140 | bl->nextForUse = listForUse; |
141 | listForUse = bl; |
142 | bl->addedToForUse = true; |
143 | } |
144 | |
145 | void BackRefMaster::initEmptyBackRefBlock(BackRefBlock *newBl) |
146 | { |
147 | intptr_t nextLU = lastUsed+1; |
148 | new (newBl) BackRefBlock(newBl, nextLU); |
149 | MALLOC_ASSERT(nextLU < dataSz, NULL); |
150 | backRefBl[nextLU] = newBl; |
151 | // lastUsed is read in getBackRef, and access to backRefBl[lastUsed] |
152 | // is possible only after checking backref against current lastUsed |
153 | FencedStore(lastUsed, nextLU); |
154 | } |
155 | |
156 | bool BackRefMaster::requestNewSpace() |
157 | { |
158 | bool isRawMemUsed; |
159 | MALLOC_STATIC_ASSERT(!(blockSpaceSize % BackRefBlock::bytes), |
160 | "Must request space for whole number of blocks." ); |
161 | |
162 | if (backRefMaster->dataSz <= lastUsed + 1) // no space in master |
163 | return false; |
164 | |
165 | // only one thread at a time may add blocks |
166 | MallocMutex::scoped_lock newSpaceLock(requestNewSpaceMutex); |
167 | |
168 | if (listForUse) // double check that only one block is available |
169 | return true; |
170 | BackRefBlock *newBl = |
171 | (BackRefBlock*)backend->getBackRefSpace(blockSpaceSize, &isRawMemUsed); |
172 | if (!newBl) return false; |
173 | |
174 | // touch a page for the 1st time without taking masterMutex ... |
175 | for (BackRefBlock *bl = newBl; (uintptr_t)bl < (uintptr_t)newBl + blockSpaceSize; |
176 | bl = (BackRefBlock*)((uintptr_t)bl + BackRefBlock::bytes)) |
177 | bl->zeroSet(); |
178 | |
179 | MallocMutex::scoped_lock lock(masterMutex); // ... and share under lock |
180 | |
181 | const size_t numOfUnusedIdxs = backRefMaster->dataSz - lastUsed - 1; |
182 | if (numOfUnusedIdxs <= 0) { // no space in master under lock, roll back |
183 | backend->putBackRefSpace(newBl, blockSpaceSize, isRawMemUsed); |
184 | return false; |
185 | } |
186 | // It's possible that only part of newBl is used, due to lack of indices in master. |
187 | // This is OK as such underutilization is possible only once for backreferneces table. |
188 | int blocksToUse = min(numOfUnusedIdxs, blockSpaceSize / BackRefBlock::bytes); |
189 | |
190 | // use the first block in the batch to maintain the list of "raw" memory |
191 | // to be released at shutdown |
192 | if (isRawMemUsed) { |
193 | newBl->nextRawMemBlock = backRefMaster->allRawMemBlocks; |
194 | backRefMaster->allRawMemBlocks = newBl; |
195 | } |
196 | for (BackRefBlock *bl = newBl; blocksToUse>0; |
197 | bl = (BackRefBlock*)((uintptr_t)bl + BackRefBlock::bytes), blocksToUse--) { |
198 | initEmptyBackRefBlock(bl); |
199 | if (active->allocatedCount == BR_MAX_CNT) |
200 | active = bl; // active leaf is not needed in listForUse |
201 | else |
202 | addToForUseList(bl); |
203 | } |
204 | return true; |
205 | } |
206 | |
207 | BackRefBlock *BackRefMaster::findFreeBlock() |
208 | { |
209 | if (active->allocatedCount < BR_MAX_CNT) |
210 | return active; |
211 | |
212 | if (listForUse) { // use released list |
213 | MallocMutex::scoped_lock lock(masterMutex); |
214 | |
215 | if (active->allocatedCount == BR_MAX_CNT && listForUse) { |
216 | active = listForUse; |
217 | listForUse = listForUse->nextForUse; |
218 | MALLOC_ASSERT(active->addedToForUse, ASSERT_TEXT); |
219 | active->addedToForUse = false; |
220 | } |
221 | } else // allocate new data node |
222 | if (!requestNewSpace()) |
223 | return NULL; |
224 | return active; |
225 | } |
226 | |
227 | void *getBackRef(BackRefIdx backRefIdx) |
228 | { |
229 | // !backRefMaster means no initialization done, so it can't be valid memory |
230 | // see addEmptyBackRefBlock for fences around lastUsed |
231 | if (!FencedLoad((intptr_t&)backRefMaster) |
232 | || backRefIdx.getMaster() > FencedLoad(backRefMaster->lastUsed) |
233 | || backRefIdx.getOffset() >= BR_MAX_CNT) |
234 | return NULL; |
235 | return *(void**)((uintptr_t)backRefMaster->backRefBl[backRefIdx.getMaster()] |
236 | + sizeof(BackRefBlock)+backRefIdx.getOffset()*sizeof(void*)); |
237 | } |
238 | |
239 | void setBackRef(BackRefIdx backRefIdx, void *newPtr) |
240 | { |
241 | MALLOC_ASSERT(backRefIdx.getMaster()<=backRefMaster->lastUsed && backRefIdx.getOffset()<BR_MAX_CNT, |
242 | ASSERT_TEXT); |
243 | *(void**)((uintptr_t)backRefMaster->backRefBl[backRefIdx.getMaster()] |
244 | + sizeof(BackRefBlock) + backRefIdx.getOffset()*sizeof(void*)) = newPtr; |
245 | } |
246 | |
247 | BackRefIdx BackRefIdx::newBackRef(bool largeObj) |
248 | { |
249 | BackRefBlock *blockToUse; |
250 | void **toUse; |
251 | BackRefIdx res; |
252 | bool lastBlockFirstUsed = false; |
253 | |
254 | do { |
255 | MALLOC_ASSERT(backRefMaster, ASSERT_TEXT); |
256 | blockToUse = backRefMaster->findFreeBlock(); |
257 | if (!blockToUse) |
258 | return BackRefIdx(); |
259 | toUse = NULL; |
260 | { // the block is locked to find a reference |
261 | MallocMutex::scoped_lock lock(blockToUse->blockMutex); |
262 | |
263 | if (blockToUse->freeList) { |
264 | toUse = (void**)blockToUse->freeList; |
265 | blockToUse->freeList = blockToUse->freeList->next; |
266 | MALLOC_ASSERT(!blockToUse->freeList || |
267 | ((uintptr_t)blockToUse->freeList>=(uintptr_t)blockToUse |
268 | && (uintptr_t)blockToUse->freeList < |
269 | (uintptr_t)blockToUse + slabSize), ASSERT_TEXT); |
270 | } else if (blockToUse->allocatedCount < BR_MAX_CNT) { |
271 | toUse = (void**)blockToUse->bumpPtr; |
272 | blockToUse->bumpPtr = |
273 | (FreeObject*)((uintptr_t)blockToUse->bumpPtr - sizeof(void*)); |
274 | if (blockToUse->allocatedCount == BR_MAX_CNT-1) { |
275 | MALLOC_ASSERT((uintptr_t)blockToUse->bumpPtr |
276 | < (uintptr_t)blockToUse+sizeof(BackRefBlock), |
277 | ASSERT_TEXT); |
278 | blockToUse->bumpPtr = NULL; |
279 | } |
280 | } |
281 | if (toUse) { |
282 | if (!blockToUse->allocatedCount && !backRefMaster->listForUse) |
283 | lastBlockFirstUsed = true; |
284 | blockToUse->allocatedCount++; |
285 | } |
286 | } // end of lock scope |
287 | } while (!toUse); |
288 | // The first thread that uses the last block requests new space in advance; |
289 | // possible failures are ignored. |
290 | if (lastBlockFirstUsed) |
291 | backRefMaster->requestNewSpace(); |
292 | |
293 | res.master = blockToUse->myNum; |
294 | uintptr_t offset = |
295 | ((uintptr_t)toUse - ((uintptr_t)blockToUse + sizeof(BackRefBlock)))/sizeof(void*); |
296 | // Is offset too big? |
297 | MALLOC_ASSERT(!(offset >> 15), ASSERT_TEXT); |
298 | res.offset = offset; |
299 | if (largeObj) res.largeObj = largeObj; |
300 | |
301 | return res; |
302 | } |
303 | |
304 | void removeBackRef(BackRefIdx backRefIdx) |
305 | { |
306 | MALLOC_ASSERT(!backRefIdx.isInvalid(), ASSERT_TEXT); |
307 | MALLOC_ASSERT(backRefIdx.getMaster()<=backRefMaster->lastUsed |
308 | && backRefIdx.getOffset()<BR_MAX_CNT, ASSERT_TEXT); |
309 | BackRefBlock *currBlock = backRefMaster->backRefBl[backRefIdx.getMaster()]; |
310 | FreeObject *freeObj = (FreeObject*)((uintptr_t)currBlock + sizeof(BackRefBlock) |
311 | + backRefIdx.getOffset()*sizeof(void*)); |
312 | MALLOC_ASSERT(((uintptr_t)freeObj>(uintptr_t)currBlock && |
313 | (uintptr_t)freeObj<(uintptr_t)currBlock + slabSize), ASSERT_TEXT); |
314 | { |
315 | MallocMutex::scoped_lock lock(currBlock->blockMutex); |
316 | |
317 | freeObj->next = currBlock->freeList; |
318 | MALLOC_ASSERT(!freeObj->next || |
319 | ((uintptr_t)freeObj->next > (uintptr_t)currBlock |
320 | && (uintptr_t)freeObj->next < |
321 | (uintptr_t)currBlock + slabSize), ASSERT_TEXT); |
322 | currBlock->freeList = freeObj; |
323 | currBlock->allocatedCount--; |
324 | } |
325 | // TODO: do we need double-check here? |
326 | if (!currBlock->addedToForUse && currBlock!=backRefMaster->active) { |
327 | MallocMutex::scoped_lock lock(masterMutex); |
328 | |
329 | if (!currBlock->addedToForUse && currBlock!=backRefMaster->active) |
330 | backRefMaster->addToForUseList(currBlock); |
331 | } |
332 | } |
333 | |
334 | /********* End of backreferences ***********************/ |
335 | |
336 | } // namespace internal |
337 | } // namespace rml |
338 | |
339 | |