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 <string.h> /* for memset */ |
18 | #include <errno.h> |
19 | #include "tbbmalloc_internal.h" |
20 | |
21 | namespace rml { |
22 | namespace internal { |
23 | |
24 | /*********** Code to acquire memory from the OS or other executive ****************/ |
25 | |
26 | /* |
27 | syscall/malloc can set non-zero errno in case of failure, |
28 | but later allocator might be able to find memory to fulfill the request. |
29 | And we do not want changing of errno by successful scalable_malloc call. |
30 | To support this, restore old errno in (get|free)RawMemory, and set errno |
31 | in frontend just before returning to user code. |
32 | Please note: every syscall/libc call used inside scalable_malloc that |
33 | sets errno must be protected this way, not just memory allocation per se. |
34 | */ |
35 | |
36 | #if USE_DEFAULT_MEMORY_MAPPING |
37 | #include "MapMemory.h" |
38 | #else |
39 | /* assume MapMemory and UnmapMemory are customized */ |
40 | #endif |
41 | |
42 | void* getRawMemory (size_t size, PageType pageType) { |
43 | return MapMemory(size, pageType); |
44 | } |
45 | |
46 | int freeRawMemory (void *object, size_t size) { |
47 | return UnmapMemory(object, size); |
48 | } |
49 | |
50 | #if CHECK_ALLOCATION_RANGE |
51 | |
52 | void Backend::UsedAddressRange::registerAlloc(uintptr_t left, uintptr_t right) |
53 | { |
54 | MallocMutex::scoped_lock lock(mutex); |
55 | if (left < leftBound) |
56 | leftBound = left; |
57 | if (right > rightBound) |
58 | rightBound = right; |
59 | MALLOC_ASSERT(leftBound, ASSERT_TEXT); |
60 | MALLOC_ASSERT(leftBound < rightBound, ASSERT_TEXT); |
61 | MALLOC_ASSERT(leftBound <= left && right <= rightBound, ASSERT_TEXT); |
62 | } |
63 | |
64 | void Backend::UsedAddressRange::registerFree(uintptr_t left, uintptr_t right) |
65 | { |
66 | MallocMutex::scoped_lock lock(mutex); |
67 | if (leftBound == left) { |
68 | if (rightBound == right) { |
69 | leftBound = ADDRESS_UPPER_BOUND; |
70 | rightBound = 0; |
71 | } else |
72 | leftBound = right; |
73 | } else if (rightBound == right) |
74 | rightBound = left; |
75 | MALLOC_ASSERT((!rightBound && leftBound == ADDRESS_UPPER_BOUND) |
76 | || leftBound < rightBound, ASSERT_TEXT); |
77 | } |
78 | #endif // CHECK_ALLOCATION_RANGE |
79 | |
80 | // Initialized in frontend inside defaultMemPool |
81 | extern HugePagesStatus hugePages; |
82 | |
83 | void *Backend::allocRawMem(size_t &size) |
84 | { |
85 | void *res = NULL; |
86 | size_t allocSize = 0; |
87 | |
88 | if (extMemPool->userPool()) { |
89 | if (extMemPool->fixedPool && bootsrapMemDone == FencedLoad(bootsrapMemStatus)) |
90 | return NULL; |
91 | MALLOC_ASSERT(bootsrapMemStatus != bootsrapMemNotDone, |
92 | "Backend::allocRawMem() called prematurely?" ); |
93 | // TODO: support for raw mem not aligned at sizeof(uintptr_t) |
94 | // memory from fixed pool is asked once and only once |
95 | allocSize = alignUpGeneric(size, extMemPool->granularity); |
96 | res = (*extMemPool->rawAlloc)(extMemPool->poolId, allocSize); |
97 | } else { |
98 | // Align allocation on page size |
99 | size_t pageSize = hugePages.isEnabled ? hugePages.getGranularity() : extMemPool->granularity; |
100 | MALLOC_ASSERT(pageSize, "Page size cannot be zero." ); |
101 | allocSize = alignUpGeneric(size, pageSize); |
102 | |
103 | // If user requested huge pages and they are available, try to use preallocated ones firstly. |
104 | // If there are none, lets check transparent huge pages support and use them instead. |
105 | if (hugePages.isEnabled) { |
106 | if (hugePages.isHPAvailable) { |
107 | res = getRawMemory(allocSize, PREALLOCATED_HUGE_PAGE); |
108 | } |
109 | if (!res && hugePages.isTHPAvailable) { |
110 | res = getRawMemory(allocSize, TRANSPARENT_HUGE_PAGE); |
111 | } |
112 | } |
113 | |
114 | if (!res) { |
115 | res = getRawMemory(allocSize, REGULAR); |
116 | } |
117 | } |
118 | |
119 | if (res) { |
120 | MALLOC_ASSERT(allocSize > 0, "Invalid size of an allocated region." ); |
121 | size = allocSize; |
122 | if (!extMemPool->userPool()) |
123 | usedAddrRange.registerAlloc((uintptr_t)res, (uintptr_t)res+size); |
124 | #if MALLOC_DEBUG |
125 | volatile size_t curTotalSize = totalMemSize; // to read global value once |
126 | MALLOC_ASSERT(curTotalSize+size > curTotalSize, "Overflow allocation size." ); |
127 | #endif |
128 | AtomicAdd((intptr_t&)totalMemSize, size); |
129 | } |
130 | |
131 | return res; |
132 | } |
133 | |
134 | bool Backend::freeRawMem(void *object, size_t size) |
135 | { |
136 | bool fail; |
137 | #if MALLOC_DEBUG |
138 | volatile size_t curTotalSize = totalMemSize; // to read global value once |
139 | MALLOC_ASSERT(curTotalSize-size < curTotalSize, "Negative allocation size." ); |
140 | #endif |
141 | AtomicAdd((intptr_t&)totalMemSize, -size); |
142 | if (extMemPool->userPool()) { |
143 | MALLOC_ASSERT(!extMemPool->fixedPool, "No free for fixed-size pools." ); |
144 | fail = (*extMemPool->rawFree)(extMemPool->poolId, object, size); |
145 | } else { |
146 | usedAddrRange.registerFree((uintptr_t)object, (uintptr_t)object + size); |
147 | fail = freeRawMemory(object, size); |
148 | } |
149 | // TODO: use result in all freeRawMem() callers |
150 | return !fail; |
151 | } |
152 | |
153 | /********* End memory acquisition code ********************************/ |
154 | |
155 | // Protected object size. After successful locking returns size of locked block, |
156 | // and releasing requires setting block size. |
157 | class GuardedSize : tbb::internal::no_copy { |
158 | uintptr_t value; |
159 | public: |
160 | enum State { |
161 | LOCKED, |
162 | COAL_BLOCK, // block is coalescing now |
163 | MAX_LOCKED_VAL = COAL_BLOCK, |
164 | LAST_REGION_BLOCK, // used to mark last block in region |
165 | // values after this are "normal" block sizes |
166 | MAX_SPEC_VAL = LAST_REGION_BLOCK |
167 | }; |
168 | |
169 | void initLocked() { value = LOCKED; } |
170 | void makeCoalscing() { |
171 | MALLOC_ASSERT(value == LOCKED, ASSERT_TEXT); |
172 | value = COAL_BLOCK; |
173 | } |
174 | size_t tryLock(State state) { |
175 | size_t szVal, sz; |
176 | MALLOC_ASSERT(state <= MAX_LOCKED_VAL, ASSERT_TEXT); |
177 | for (;;) { |
178 | sz = FencedLoad((intptr_t&)value); |
179 | if (sz <= MAX_LOCKED_VAL) |
180 | break; |
181 | szVal = AtomicCompareExchange((intptr_t&)value, state, sz); |
182 | |
183 | if (szVal==sz) |
184 | break; |
185 | } |
186 | return sz; |
187 | } |
188 | void unlock(size_t size) { |
189 | MALLOC_ASSERT(value <= MAX_LOCKED_VAL, "The lock is not locked" ); |
190 | MALLOC_ASSERT(size > MAX_LOCKED_VAL, ASSERT_TEXT); |
191 | FencedStore((intptr_t&)value, size); |
192 | } |
193 | bool isLastRegionBlock() const { return value==LAST_REGION_BLOCK; } |
194 | friend void Backend::IndexedBins::verify(); |
195 | }; |
196 | |
197 | struct MemRegion { |
198 | MemRegion *next, // keep all regions in any pool to release all them on |
199 | *prev; // pool destroying, 2-linked list to release individual |
200 | // regions. |
201 | size_t allocSz, // got from pool callback |
202 | blockSz; // initial and maximal inner block size |
203 | MemRegionType type; |
204 | }; |
205 | |
206 | // this data must be unmodified while block is in use, so separate it |
207 | class BlockMutexes { |
208 | protected: |
209 | GuardedSize myL, // lock for me |
210 | leftL; // lock for left neighbor |
211 | }; |
212 | |
213 | class FreeBlock : BlockMutexes { |
214 | public: |
215 | static const size_t minBlockSize; |
216 | friend void Backend::IndexedBins::verify(); |
217 | |
218 | FreeBlock *prev, // in 2-linked list related to bin |
219 | *next, |
220 | *nextToFree; // used to form a queue during coalescing |
221 | // valid only when block is in processing, i.e. one is not free and not |
222 | size_t sizeTmp; // used outside of backend |
223 | int myBin; // bin that is owner of the block |
224 | bool slabAligned; |
225 | bool blockInBin; // this block in myBin already |
226 | |
227 | FreeBlock *rightNeig(size_t sz) const { |
228 | MALLOC_ASSERT(sz, ASSERT_TEXT); |
229 | return (FreeBlock*)((uintptr_t)this+sz); |
230 | } |
231 | FreeBlock *leftNeig(size_t sz) const { |
232 | MALLOC_ASSERT(sz, ASSERT_TEXT); |
233 | return (FreeBlock*)((uintptr_t)this - sz); |
234 | } |
235 | |
236 | void () { myL.initLocked(); leftL.initLocked(); } |
237 | void setMeFree(size_t size) { myL.unlock(size); } |
238 | size_t trySetMeUsed(GuardedSize::State s) { return myL.tryLock(s); } |
239 | bool isLastRegionBlock() const { return myL.isLastRegionBlock(); } |
240 | |
241 | void setLeftFree(size_t sz) { leftL.unlock(sz); } |
242 | size_t trySetLeftUsed(GuardedSize::State s) { return leftL.tryLock(s); } |
243 | |
244 | size_t tryLockBlock() { |
245 | size_t rSz, sz = trySetMeUsed(GuardedSize::LOCKED); |
246 | |
247 | if (sz <= GuardedSize::MAX_LOCKED_VAL) |
248 | return false; |
249 | rSz = rightNeig(sz)->trySetLeftUsed(GuardedSize::LOCKED); |
250 | if (rSz <= GuardedSize::MAX_LOCKED_VAL) { |
251 | setMeFree(sz); |
252 | return false; |
253 | } |
254 | MALLOC_ASSERT(rSz == sz, ASSERT_TEXT); |
255 | return sz; |
256 | } |
257 | void markCoalescing(size_t blockSz) { |
258 | myL.makeCoalscing(); |
259 | rightNeig(blockSz)->leftL.makeCoalscing(); |
260 | sizeTmp = blockSz; |
261 | nextToFree = NULL; |
262 | } |
263 | void markUsed() { |
264 | myL.initLocked(); |
265 | rightNeig(sizeTmp)->leftL.initLocked(); |
266 | nextToFree = NULL; |
267 | } |
268 | static void markBlocks(FreeBlock *fBlock, int num, size_t size) { |
269 | for (int i=1; i<num; i++) { |
270 | fBlock = (FreeBlock*)((uintptr_t)fBlock + size); |
271 | fBlock->initHeader(); |
272 | } |
273 | } |
274 | }; |
275 | |
276 | // Last block in any region. Its "size" field is GuardedSize::LAST_REGION_BLOCK, |
277 | // This kind of blocks used to find region header |
278 | // and have a possibility to return region back to OS |
279 | struct LastFreeBlock : public FreeBlock { |
280 | MemRegion *memRegion; |
281 | }; |
282 | |
283 | const size_t FreeBlock::minBlockSize = sizeof(FreeBlock); |
284 | |
285 | inline bool BackendSync::waitTillBlockReleased(intptr_t startModifiedCnt) |
286 | { |
287 | AtomicBackoff backoff; |
288 | #if __TBB_MALLOC_BACKEND_STAT |
289 | class ITT_Guard { |
290 | void *ptr; |
291 | public: |
292 | ITT_Guard(void *p) : ptr(p) { |
293 | MALLOC_ITT_SYNC_PREPARE(ptr); |
294 | } |
295 | ~ITT_Guard() { |
296 | MALLOC_ITT_SYNC_ACQUIRED(ptr); |
297 | } |
298 | }; |
299 | ITT_Guard ittGuard(&inFlyBlocks); |
300 | #endif |
301 | for (intptr_t myBinsInFlyBlocks = FencedLoad(inFlyBlocks), |
302 | myCoalescQInFlyBlocks = backend->blocksInCoalescing(); ; |
303 | backoff.pause()) { |
304 | MALLOC_ASSERT(myBinsInFlyBlocks>=0 && myCoalescQInFlyBlocks>=0, NULL); |
305 | intptr_t currBinsInFlyBlocks = FencedLoad(inFlyBlocks), |
306 | currCoalescQInFlyBlocks = backend->blocksInCoalescing(); |
307 | WhiteboxTestingYield(); |
308 | // Stop waiting iff: |
309 | |
310 | // 1) blocks were removed from processing, not added |
311 | if (myBinsInFlyBlocks > currBinsInFlyBlocks |
312 | // 2) released during delayed coalescing queue |
313 | || myCoalescQInFlyBlocks > currCoalescQInFlyBlocks) |
314 | break; |
315 | // 3) if there are blocks in coalescing, and no progress in its processing, |
316 | // try to scan coalescing queue and stop waiting, if changes were made |
317 | // (if there are no changes and in-fly blocks exist, we continue |
318 | // waiting to not increase load on coalescQ) |
319 | if (currCoalescQInFlyBlocks > 0 && backend->scanCoalescQ(/*forceCoalescQDrop=*/false)) |
320 | break; |
321 | // 4) when there are no blocks |
322 | if (!currBinsInFlyBlocks && !currCoalescQInFlyBlocks) |
323 | // re-scan make sense only if bins were modified since scanned |
324 | return startModifiedCnt != getNumOfMods(); |
325 | myBinsInFlyBlocks = currBinsInFlyBlocks; |
326 | myCoalescQInFlyBlocks = currCoalescQInFlyBlocks; |
327 | } |
328 | return true; |
329 | } |
330 | |
331 | void CoalRequestQ::putBlock(FreeBlock *fBlock) |
332 | { |
333 | MALLOC_ASSERT(fBlock->sizeTmp >= FreeBlock::minBlockSize, ASSERT_TEXT); |
334 | fBlock->markUsed(); |
335 | // the block is in the queue, do not forget that it's here |
336 | AtomicIncrement(inFlyBlocks); |
337 | |
338 | for (;;) { |
339 | FreeBlock *myBlToFree = (FreeBlock*)FencedLoad((intptr_t&)blocksToFree); |
340 | |
341 | fBlock->nextToFree = myBlToFree; |
342 | if (myBlToFree == |
343 | (FreeBlock*)AtomicCompareExchange((intptr_t&)blocksToFree, |
344 | (intptr_t)fBlock, |
345 | (intptr_t)myBlToFree)) |
346 | return; |
347 | } |
348 | } |
349 | |
350 | FreeBlock *CoalRequestQ::getAll() |
351 | { |
352 | for (;;) { |
353 | FreeBlock *myBlToFree = (FreeBlock*)FencedLoad((intptr_t&)blocksToFree); |
354 | |
355 | if (!myBlToFree) |
356 | return NULL; |
357 | else { |
358 | if (myBlToFree == |
359 | (FreeBlock*)AtomicCompareExchange((intptr_t&)blocksToFree, |
360 | 0, (intptr_t)myBlToFree)) |
361 | return myBlToFree; |
362 | else |
363 | continue; |
364 | } |
365 | } |
366 | } |
367 | |
368 | inline void CoalRequestQ::blockWasProcessed() |
369 | { |
370 | bkndSync->binsModified(); |
371 | int prev = AtomicAdd(inFlyBlocks, -1); |
372 | MALLOC_ASSERT(prev > 0, ASSERT_TEXT); |
373 | } |
374 | |
375 | // Try to get a block from a bin. |
376 | // If the remaining free space would stay in the same bin, |
377 | // split the block without removing it. |
378 | // If the free space should go to other bin(s), remove the block. |
379 | // alignedBin is true, if all blocks in the bin have slab-aligned right side. |
380 | FreeBlock *Backend::IndexedBins::getFromBin(int binIdx, BackendSync *sync, size_t size, |
381 | bool needAlignedRes, bool alignedBin, bool wait, int *binLocked) |
382 | { |
383 | Bin *b = &freeBins[binIdx]; |
384 | try_next: |
385 | FreeBlock *fBlock = NULL; |
386 | if (b->head) { |
387 | bool locked; |
388 | MallocMutex::scoped_lock scopedLock(b->tLock, wait, &locked); |
389 | |
390 | if (!locked) { |
391 | if (binLocked) (*binLocked)++; |
392 | return NULL; |
393 | } |
394 | |
395 | for (FreeBlock *curr = b->head; curr; curr = curr->next) { |
396 | size_t szBlock = curr->tryLockBlock(); |
397 | if (!szBlock) { |
398 | // block is locked, re-do bin lock, as there is no place to spin |
399 | // while block coalescing |
400 | goto try_next; |
401 | } |
402 | |
403 | // GENERAL CASE |
404 | if (alignedBin || !needAlignedRes) { |
405 | size_t splitSz = szBlock - size; |
406 | // If we got a block as split result, it must have a room for control structures. |
407 | if (szBlock >= size && (splitSz >= FreeBlock::minBlockSize || !splitSz)) |
408 | fBlock = curr; |
409 | } else { |
410 | // SPECIAL CASE, to get aligned block from unaligned bin we have to cut the middle of a block |
411 | // and return remaining left and right part. Possible only in fixed pool scenario, assert for this |
412 | // is set inside splitBlock() function. |
413 | |
414 | void *newB = alignUp(curr, slabSize); |
415 | uintptr_t rightNew = (uintptr_t)newB + size; |
416 | uintptr_t rightCurr = (uintptr_t)curr + szBlock; |
417 | // Check if the block size is sufficient, |
418 | // and also left and right split results are either big enough or non-existent |
419 | if (rightNew <= rightCurr |
420 | && (newB == curr || ((uintptr_t)newB - (uintptr_t)curr) >= FreeBlock::minBlockSize) |
421 | && (rightNew == rightCurr || (rightCurr - rightNew) >= FreeBlock::minBlockSize)) |
422 | fBlock = curr; |
423 | } |
424 | |
425 | if (fBlock) { |
426 | // consume must be called before result of removing from a bin is visible externally. |
427 | sync->blockConsumed(); |
428 | // TODO: think about cases when block stays in the same bin |
429 | b->removeBlock(fBlock); |
430 | if (freeBins[binIdx].empty()) |
431 | bitMask.set(binIdx, false); |
432 | fBlock->sizeTmp = szBlock; |
433 | break; |
434 | } else { // block size is not valid, search for next block in the bin |
435 | curr->setMeFree(szBlock); |
436 | curr->rightNeig(szBlock)->setLeftFree(szBlock); |
437 | } |
438 | } |
439 | } |
440 | return fBlock; |
441 | } |
442 | |
443 | bool Backend::IndexedBins::tryReleaseRegions(int binIdx, Backend *backend) |
444 | { |
445 | Bin *b = &freeBins[binIdx]; |
446 | FreeBlock *fBlockList = NULL; |
447 | |
448 | // got all blocks from the bin and re-do coalesce on them |
449 | // to release single-block regions |
450 | try_next: |
451 | if (b->head) { |
452 | MallocMutex::scoped_lock binLock(b->tLock); |
453 | for (FreeBlock *curr = b->head; curr; ) { |
454 | size_t szBlock = curr->tryLockBlock(); |
455 | if (!szBlock) |
456 | goto try_next; |
457 | |
458 | FreeBlock *next = curr->next; |
459 | |
460 | b->removeBlock(curr); |
461 | curr->sizeTmp = szBlock; |
462 | curr->nextToFree = fBlockList; |
463 | fBlockList = curr; |
464 | curr = next; |
465 | } |
466 | } |
467 | return backend->coalescAndPutList(fBlockList, /*forceCoalescQDrop=*/true, |
468 | /*reportBlocksProcessed=*/false); |
469 | } |
470 | |
471 | void Backend::Bin::removeBlock(FreeBlock *fBlock) |
472 | { |
473 | MALLOC_ASSERT(fBlock->next||fBlock->prev||fBlock==head, |
474 | "Detected that a block is not in the bin." ); |
475 | if (head == fBlock) |
476 | head = fBlock->next; |
477 | if (tail == fBlock) |
478 | tail = fBlock->prev; |
479 | if (fBlock->prev) |
480 | fBlock->prev->next = fBlock->next; |
481 | if (fBlock->next) |
482 | fBlock->next->prev = fBlock->prev; |
483 | } |
484 | |
485 | void Backend::IndexedBins::addBlock(int binIdx, FreeBlock *fBlock, size_t blockSz, bool addToTail) |
486 | { |
487 | Bin *b = &freeBins[binIdx]; |
488 | fBlock->myBin = binIdx; |
489 | fBlock->next = fBlock->prev = NULL; |
490 | { |
491 | MallocMutex::scoped_lock scopedLock(b->tLock); |
492 | if (addToTail) { |
493 | fBlock->prev = b->tail; |
494 | b->tail = fBlock; |
495 | if (fBlock->prev) |
496 | fBlock->prev->next = fBlock; |
497 | if (!b->head) |
498 | b->head = fBlock; |
499 | } else { |
500 | fBlock->next = b->head; |
501 | b->head = fBlock; |
502 | if (fBlock->next) |
503 | fBlock->next->prev = fBlock; |
504 | if (!b->tail) |
505 | b->tail = fBlock; |
506 | } |
507 | } |
508 | bitMask.set(binIdx, true); |
509 | } |
510 | |
511 | bool Backend::IndexedBins::tryAddBlock(int binIdx, FreeBlock *fBlock, bool addToTail) |
512 | { |
513 | bool locked; |
514 | Bin *b = &freeBins[binIdx]; |
515 | fBlock->myBin = binIdx; |
516 | if (addToTail) { |
517 | fBlock->next = NULL; |
518 | { |
519 | MallocMutex::scoped_lock scopedLock(b->tLock, /*wait=*/false, &locked); |
520 | if (!locked) |
521 | return false; |
522 | fBlock->prev = b->tail; |
523 | b->tail = fBlock; |
524 | if (fBlock->prev) |
525 | fBlock->prev->next = fBlock; |
526 | if (!b->head) |
527 | b->head = fBlock; |
528 | } |
529 | } else { |
530 | fBlock->prev = NULL; |
531 | { |
532 | MallocMutex::scoped_lock scopedLock(b->tLock, /*wait=*/false, &locked); |
533 | if (!locked) |
534 | return false; |
535 | fBlock->next = b->head; |
536 | b->head = fBlock; |
537 | if (fBlock->next) |
538 | fBlock->next->prev = fBlock; |
539 | if (!b->tail) |
540 | b->tail = fBlock; |
541 | } |
542 | } |
543 | bitMask.set(binIdx, true); |
544 | return true; |
545 | } |
546 | |
547 | void Backend::IndexedBins::reset() |
548 | { |
549 | for (int i=0; i<Backend::freeBinsNum; i++) |
550 | freeBins[i].reset(); |
551 | bitMask.reset(); |
552 | } |
553 | |
554 | void Backend::IndexedBins::lockRemoveBlock(int binIdx, FreeBlock *fBlock) |
555 | { |
556 | MallocMutex::scoped_lock scopedLock(freeBins[binIdx].tLock); |
557 | freeBins[binIdx].removeBlock(fBlock); |
558 | if (freeBins[binIdx].empty()) |
559 | bitMask.set(binIdx, false); |
560 | } |
561 | |
562 | bool ExtMemoryPool::regionsAreReleaseable() const |
563 | { |
564 | return !keepAllMemory && !delayRegsReleasing; |
565 | } |
566 | |
567 | FreeBlock *Backend::splitBlock(FreeBlock *fBlock, int num, size_t size, bool blockIsAligned, bool needAlignedBlock) |
568 | { |
569 | const size_t totalSize = num * size; |
570 | |
571 | // SPECIAL CASE, for unaligned block we have to cut the middle of a block |
572 | // and return remaining left and right part. Possible only in a fixed pool scenario. |
573 | if (needAlignedBlock && !blockIsAligned) { |
574 | MALLOC_ASSERT(extMemPool->fixedPool, |
575 | "Aligned block request from unaligned bin possible only in fixed pool scenario." ); |
576 | |
577 | // Space to use is in the middle |
578 | FreeBlock *newBlock = alignUp(fBlock, slabSize); |
579 | FreeBlock *rightPart = (FreeBlock*)((uintptr_t)newBlock + totalSize); |
580 | uintptr_t fBlockEnd = (uintptr_t)fBlock + fBlock->sizeTmp; |
581 | |
582 | // Return free right part |
583 | if ((uintptr_t)rightPart != fBlockEnd) { |
584 | rightPart->initHeader(); // to prevent coalescing rightPart with fBlock |
585 | size_t rightSize = fBlockEnd - (uintptr_t)rightPart; |
586 | coalescAndPut(rightPart, rightSize, toAlignedBin(rightPart, rightSize)); |
587 | } |
588 | // And free left part |
589 | if (newBlock != fBlock) { |
590 | newBlock->initHeader(); // to prevent coalescing fBlock with newB |
591 | size_t leftSize = (uintptr_t)newBlock - (uintptr_t)fBlock; |
592 | coalescAndPut(fBlock, leftSize, toAlignedBin(fBlock, leftSize)); |
593 | } |
594 | fBlock = newBlock; |
595 | } else if (size_t splitSize = fBlock->sizeTmp - totalSize) { // need to split the block |
596 | // GENERAL CASE, cut the left or right part of the block |
597 | FreeBlock *splitBlock = NULL; |
598 | if (needAlignedBlock) { |
599 | // For slab aligned blocks cut the right side of the block |
600 | // and return it to a requester, original block returns to backend |
601 | splitBlock = fBlock; |
602 | fBlock = (FreeBlock*)((uintptr_t)splitBlock + splitSize); |
603 | fBlock->initHeader(); |
604 | } else { |
605 | // For large object blocks cut original block and put free righ part to backend |
606 | splitBlock = (FreeBlock*)((uintptr_t)fBlock + totalSize); |
607 | splitBlock->initHeader(); |
608 | } |
609 | // Mark free block as it`s parent only when the requested type (needAlignedBlock) |
610 | // and returned from Bins/OS block (isAligned) are equal (XOR operation used) |
611 | bool markAligned = (blockIsAligned ^ needAlignedBlock) ? toAlignedBin(splitBlock, splitSize) : blockIsAligned; |
612 | coalescAndPut(splitBlock, splitSize, markAligned); |
613 | } |
614 | MALLOC_ASSERT(!needAlignedBlock || isAligned(fBlock, slabSize), "Expect to get aligned block, if one was requested." ); |
615 | FreeBlock::markBlocks(fBlock, num, size); |
616 | return fBlock; |
617 | } |
618 | |
619 | size_t Backend::getMaxBinnedSize() const |
620 | { |
621 | return hugePages.isEnabled && !inUserPool() ? |
622 | maxBinned_HugePage : maxBinned_SmallPage; |
623 | } |
624 | |
625 | inline bool Backend::MaxRequestComparator::operator()(size_t oldMaxReq, size_t requestSize) const |
626 | { |
627 | return requestSize > oldMaxReq && requestSize < backend->getMaxBinnedSize(); |
628 | } |
629 | |
630 | // last chance to get memory |
631 | FreeBlock *Backend::releaseMemInCaches(intptr_t startModifiedCnt, |
632 | int *lockedBinsThreshold, int numOfLockedBins) |
633 | { |
634 | // something released from caches |
635 | if (extMemPool->hardCachesCleanup() |
636 | // ..or can use blocks that are in processing now |
637 | || bkndSync.waitTillBlockReleased(startModifiedCnt)) |
638 | return (FreeBlock*)VALID_BLOCK_IN_BIN; |
639 | // OS can't give us more memory, but we have some in locked bins |
640 | if (*lockedBinsThreshold && numOfLockedBins) { |
641 | *lockedBinsThreshold = 0; |
642 | return (FreeBlock*)VALID_BLOCK_IN_BIN; |
643 | } |
644 | return NULL; // nothing found, give up |
645 | } |
646 | |
647 | FreeBlock *Backend::askMemFromOS(size_t blockSize, intptr_t startModifiedCnt, |
648 | int *lockedBinsThreshold, int numOfLockedBins, |
649 | bool *splittableRet, bool needSlabRegion) |
650 | { |
651 | FreeBlock *block; |
652 | // The block sizes can be divided into 3 groups: |
653 | // 1. "quite small": popular object size, we are in bootstarp or something |
654 | // like; request several regions. |
655 | // 2. "quite large": we want to have several such blocks in the region |
656 | // but not want several pre-allocated regions. |
657 | // 3. "huge": exact fit, we allocate only one block and do not allow |
658 | // any other allocations to placed in a region. |
659 | // Dividing the block sizes in these groups we are trying to balance between |
660 | // too small regions (that leads to fragmentation) and too large ones (that |
661 | // leads to excessive address space consumption). If a region is "too |
662 | // large", allocate only one, to prevent fragmentation. It supposedly |
663 | // doesn't hurt performance, because the object requested by user is large. |
664 | // Bounds for the groups are: |
665 | const size_t maxBinned = getMaxBinnedSize(); |
666 | const size_t quiteSmall = maxBinned / 8; |
667 | const size_t quiteLarge = maxBinned; |
668 | |
669 | if (blockSize >= quiteLarge) { |
670 | // Do not interact with other threads via semaphores, as for exact fit |
671 | // we can't share regions with them, memory requesting is individual. |
672 | block = addNewRegion(blockSize, MEMREG_ONE_BLOCK, /*addToBin=*/false); |
673 | if (!block) |
674 | return releaseMemInCaches(startModifiedCnt, lockedBinsThreshold, numOfLockedBins); |
675 | *splittableRet = false; |
676 | } else { |
677 | const size_t regSz_sizeBased = alignUp(4*maxRequestedSize, 1024*1024); |
678 | // Another thread is modifying backend while we can't get the block. |
679 | // Wait while it leaves and re-do the scan |
680 | // before trying other ways to extend the backend. |
681 | if (bkndSync.waitTillBlockReleased(startModifiedCnt) |
682 | // semaphore is protecting adding more more memory from OS |
683 | || memExtendingSema.wait()) |
684 | return (FreeBlock*)VALID_BLOCK_IN_BIN; |
685 | |
686 | if (startModifiedCnt != bkndSync.getNumOfMods()) { |
687 | memExtendingSema.signal(); |
688 | return (FreeBlock*)VALID_BLOCK_IN_BIN; |
689 | } |
690 | |
691 | if (blockSize < quiteSmall) { |
692 | // For this size of blocks, add NUM_OF_REG "advance" regions in bin, |
693 | // and return one as a result. |
694 | // TODO: add to bin first, because other threads can use them right away. |
695 | // This must be done carefully, because blocks in bins can be released |
696 | // in releaseCachesToLimit(). |
697 | const unsigned NUM_OF_REG = 3; |
698 | MemRegionType regType = needSlabRegion ? MEMREG_SLAB_BLOCKS : MEMREG_LARGE_BLOCKS; |
699 | block = addNewRegion(regSz_sizeBased, regType, /*addToBin=*/false); |
700 | if (block) |
701 | for (unsigned idx=0; idx<NUM_OF_REG; idx++) |
702 | if (! addNewRegion(regSz_sizeBased, regType, /*addToBin=*/true)) |
703 | break; |
704 | } else { |
705 | block = addNewRegion(regSz_sizeBased, MEMREG_LARGE_BLOCKS, /*addToBin=*/false); |
706 | } |
707 | memExtendingSema.signal(); |
708 | |
709 | // no regions found, try to clean cache |
710 | if (!block || block == (FreeBlock*)VALID_BLOCK_IN_BIN) |
711 | return releaseMemInCaches(startModifiedCnt, lockedBinsThreshold, numOfLockedBins); |
712 | // Since a region can hold more than one block it can be split. |
713 | *splittableRet = true; |
714 | } |
715 | // after asking memory from OS, release caches if we above the memory limits |
716 | releaseCachesToLimit(); |
717 | |
718 | return block; |
719 | } |
720 | |
721 | void Backend::releaseCachesToLimit() |
722 | { |
723 | if (!memSoftLimit || totalMemSize <= memSoftLimit) |
724 | return; |
725 | size_t locTotalMemSize, locMemSoftLimit; |
726 | |
727 | scanCoalescQ(/*forceCoalescQDrop=*/false); |
728 | if (extMemPool->softCachesCleanup() && |
729 | (locTotalMemSize = FencedLoad((intptr_t&)totalMemSize)) <= |
730 | (locMemSoftLimit = FencedLoad((intptr_t&)memSoftLimit))) |
731 | return; |
732 | // clean global large-object cache, if this is not enough, clean local caches |
733 | // do this in several tries, because backend fragmentation can prevent |
734 | // region from releasing |
735 | for (int cleanLocal = 0; cleanLocal<2; cleanLocal++) |
736 | while (cleanLocal ? |
737 | extMemPool->allLocalCaches.cleanup(/*cleanOnlyUnused=*/true) : |
738 | extMemPool->loc.decreasingCleanup()) |
739 | if ((locTotalMemSize = FencedLoad((intptr_t&)totalMemSize)) <= |
740 | (locMemSoftLimit = FencedLoad((intptr_t&)memSoftLimit))) |
741 | return; |
742 | // last chance to match memSoftLimit |
743 | extMemPool->hardCachesCleanup(); |
744 | } |
745 | |
746 | int Backend::IndexedBins::getMinNonemptyBin(unsigned startBin) const |
747 | { |
748 | int p = bitMask.getMinTrue(startBin); |
749 | return p == -1 ? Backend::freeBinsNum : p; |
750 | } |
751 | |
752 | FreeBlock *Backend::IndexedBins::findBlock(int nativeBin, BackendSync *sync, size_t size, |
753 | bool needAlignedBlock, bool alignedBin, int *numOfLockedBins) |
754 | { |
755 | for (int i=getMinNonemptyBin(nativeBin); i<freeBinsNum; i=getMinNonemptyBin(i+1)) |
756 | if (FreeBlock *block = getFromBin(i, sync, size, needAlignedBlock, alignedBin, /*wait=*/false, numOfLockedBins)) |
757 | return block; |
758 | |
759 | return NULL; |
760 | } |
761 | |
762 | void Backend::requestBootstrapMem() |
763 | { |
764 | if (bootsrapMemDone == FencedLoad(bootsrapMemStatus)) |
765 | return; |
766 | MallocMutex::scoped_lock lock( bootsrapMemStatusMutex ); |
767 | if (bootsrapMemDone == bootsrapMemStatus) |
768 | return; |
769 | MALLOC_ASSERT(bootsrapMemNotDone == bootsrapMemStatus, ASSERT_TEXT); |
770 | bootsrapMemStatus = bootsrapMemInitializing; |
771 | // request some rather big region during bootstrap in advance |
772 | // ok to get NULL here, as later we re-do a request with more modest size |
773 | addNewRegion(2*1024*1024, MEMREG_SLAB_BLOCKS, /*addToBin=*/true); |
774 | bootsrapMemStatus = bootsrapMemDone; |
775 | } |
776 | |
777 | // try to allocate size Byte block in available bins |
778 | // needAlignedRes is true if result must be slab-aligned |
779 | FreeBlock *Backend::genericGetBlock(int num, size_t size, bool needAlignedBlock) |
780 | { |
781 | FreeBlock *block = NULL; |
782 | const size_t totalReqSize = num*size; |
783 | // no splitting after requesting new region, asks exact size |
784 | const int nativeBin = sizeToBin(totalReqSize); |
785 | |
786 | requestBootstrapMem(); |
787 | // If we found 2 or less locked bins, it's time to ask more memory from OS. |
788 | // But nothing can be asked from fixed pool. And we prefer wait, not ask |
789 | // for more memory, if block is quite large. |
790 | int lockedBinsThreshold = extMemPool->fixedPool || size>=maxBinned_SmallPage? 0 : 2; |
791 | |
792 | // Find maximal requested size limited by getMaxBinnedSize() |
793 | AtomicUpdate(maxRequestedSize, totalReqSize, MaxRequestComparator(this)); |
794 | scanCoalescQ(/*forceCoalescQDrop=*/false); |
795 | |
796 | bool splittable = true; |
797 | for (;;) { |
798 | const intptr_t startModifiedCnt = bkndSync.getNumOfMods(); |
799 | int numOfLockedBins; |
800 | |
801 | do { |
802 | numOfLockedBins = 0; |
803 | if (needAlignedBlock) { |
804 | block = freeSlabAlignedBins.findBlock(nativeBin, &bkndSync, num*size, needAlignedBlock, |
805 | /*alignedBin=*/true, &numOfLockedBins); |
806 | if (!block && extMemPool->fixedPool) |
807 | block = freeLargeBlockBins.findBlock(nativeBin, &bkndSync, num*size, needAlignedBlock, |
808 | /*alignedBin=*/false, &numOfLockedBins); |
809 | } else { |
810 | block = freeLargeBlockBins.findBlock(nativeBin, &bkndSync, num*size, needAlignedBlock, |
811 | /*alignedBin=*/false, &numOfLockedBins); |
812 | if (!block && extMemPool->fixedPool) |
813 | block = freeSlabAlignedBins.findBlock(nativeBin, &bkndSync, num*size, needAlignedBlock, |
814 | /*alignedBin=*/true, &numOfLockedBins); |
815 | } |
816 | } while (!block && numOfLockedBins>lockedBinsThreshold); |
817 | |
818 | if (block) |
819 | break; |
820 | |
821 | if (!(scanCoalescQ(/*forceCoalescQDrop=*/true) | extMemPool->softCachesCleanup())) { |
822 | // bins are not updated, |
823 | // only remaining possibility is to ask for more memory |
824 | block = askMemFromOS(totalReqSize, startModifiedCnt, &lockedBinsThreshold, |
825 | numOfLockedBins, &splittable, needAlignedBlock); |
826 | if (!block) |
827 | return NULL; |
828 | if (block != (FreeBlock*)VALID_BLOCK_IN_BIN) { |
829 | // size can be increased in askMemFromOS, that's why >= |
830 | MALLOC_ASSERT(block->sizeTmp >= size, ASSERT_TEXT); |
831 | break; |
832 | } |
833 | // valid block somewhere in bins, let's find it |
834 | block = NULL; |
835 | } |
836 | } |
837 | MALLOC_ASSERT(block, ASSERT_TEXT); |
838 | if (splittable) { |
839 | // At this point we have to be sure that slabAligned attribute describes the right block state |
840 | block = splitBlock(block, num, size, block->slabAligned, needAlignedBlock); |
841 | } |
842 | // matched blockConsumed() from startUseBlock() |
843 | bkndSync.blockReleased(); |
844 | |
845 | return block; |
846 | } |
847 | |
848 | LargeMemoryBlock *Backend::getLargeBlock(size_t size) |
849 | { |
850 | LargeMemoryBlock *lmb = |
851 | (LargeMemoryBlock*)genericGetBlock(1, size, /*needAlignedRes=*/false); |
852 | if (lmb) { |
853 | lmb->unalignedSize = size; |
854 | if (extMemPool->userPool()) |
855 | extMemPool->lmbList.add(lmb); |
856 | } |
857 | return lmb; |
858 | } |
859 | |
860 | BlockI *Backend::getSlabBlock(int num) { |
861 | BlockI *b = (BlockI*)genericGetBlock(num, slabSize, /*slabAligned=*/true); |
862 | MALLOC_ASSERT(isAligned(b, slabSize), ASSERT_TEXT); |
863 | return b; |
864 | } |
865 | |
866 | void Backend::putSlabBlock(BlockI *block) { |
867 | genericPutBlock((FreeBlock *)block, slabSize, /*slabAligned=*/true); |
868 | } |
869 | |
870 | void *Backend::getBackRefSpace(size_t size, bool *rawMemUsed) |
871 | { |
872 | // This block is released only at shutdown, so it can prevent |
873 | // a entire region releasing when it's received from the backend, |
874 | // so prefer getRawMemory using. |
875 | if (void *ret = getRawMemory(size, REGULAR)) { |
876 | *rawMemUsed = true; |
877 | return ret; |
878 | } |
879 | void *ret = genericGetBlock(1, size, /*needAlignedRes=*/false); |
880 | if (ret) *rawMemUsed = false; |
881 | return ret; |
882 | } |
883 | |
884 | void Backend::putBackRefSpace(void *b, size_t size, bool rawMemUsed) |
885 | { |
886 | if (rawMemUsed) |
887 | freeRawMemory(b, size); |
888 | // ignore not raw mem, as it released on region releasing |
889 | } |
890 | |
891 | void Backend::removeBlockFromBin(FreeBlock *fBlock) |
892 | { |
893 | if (fBlock->myBin != Backend::NO_BIN) { |
894 | if (fBlock->slabAligned) |
895 | freeSlabAlignedBins.lockRemoveBlock(fBlock->myBin, fBlock); |
896 | else |
897 | freeLargeBlockBins.lockRemoveBlock(fBlock->myBin, fBlock); |
898 | } |
899 | } |
900 | |
901 | void Backend::genericPutBlock(FreeBlock *fBlock, size_t blockSz, bool slabAligned) |
902 | { |
903 | bkndSync.blockConsumed(); |
904 | coalescAndPut(fBlock, blockSz, slabAligned); |
905 | bkndSync.blockReleased(); |
906 | } |
907 | |
908 | void AllLargeBlocksList::add(LargeMemoryBlock *lmb) |
909 | { |
910 | MallocMutex::scoped_lock scoped_cs(largeObjLock); |
911 | lmb->gPrev = NULL; |
912 | lmb->gNext = loHead; |
913 | if (lmb->gNext) |
914 | lmb->gNext->gPrev = lmb; |
915 | loHead = lmb; |
916 | } |
917 | |
918 | void AllLargeBlocksList::remove(LargeMemoryBlock *lmb) |
919 | { |
920 | MallocMutex::scoped_lock scoped_cs(largeObjLock); |
921 | if (loHead == lmb) |
922 | loHead = lmb->gNext; |
923 | if (lmb->gNext) |
924 | lmb->gNext->gPrev = lmb->gPrev; |
925 | if (lmb->gPrev) |
926 | lmb->gPrev->gNext = lmb->gNext; |
927 | } |
928 | |
929 | void Backend::putLargeBlock(LargeMemoryBlock *lmb) |
930 | { |
931 | if (extMemPool->userPool()) |
932 | extMemPool->lmbList.remove(lmb); |
933 | genericPutBlock((FreeBlock *)lmb, lmb->unalignedSize, false); |
934 | } |
935 | |
936 | void Backend::returnLargeObject(LargeMemoryBlock *lmb) |
937 | { |
938 | removeBackRef(lmb->backRefIdx); |
939 | putLargeBlock(lmb); |
940 | STAT_increment(getThreadId(), ThreadCommonCounters, freeLargeObj); |
941 | } |
942 | |
943 | #if BACKEND_HAS_MREMAP |
944 | void *Backend::remap(void *ptr, size_t oldSize, size_t newSize, size_t alignment) |
945 | { |
946 | // no remap for user pools and for object too small that living in bins |
947 | if (inUserPool() || min(oldSize, newSize)<maxBinned_SmallPage |
948 | // during remap, can't guarantee alignment more strict than current or |
949 | // more strict than page alignment |
950 | || !isAligned(ptr, alignment) || alignment>extMemPool->granularity) |
951 | return NULL; |
952 | const LargeMemoryBlock* lmbOld = ((LargeObjectHdr *)ptr - 1)->memoryBlock; |
953 | const size_t oldUnalignedSize = lmbOld->unalignedSize; |
954 | FreeBlock *oldFBlock = (FreeBlock *)lmbOld; |
955 | FreeBlock *right = oldFBlock->rightNeig(oldUnalignedSize); |
956 | // in every region only one block can have LAST_REGION_BLOCK on right, |
957 | // so don't need no synchronization |
958 | if (!right->isLastRegionBlock()) |
959 | return NULL; |
960 | |
961 | MemRegion *oldRegion = static_cast<LastFreeBlock*>(right)->memRegion; |
962 | MALLOC_ASSERT( oldRegion < ptr, ASSERT_TEXT ); |
963 | const size_t oldRegionSize = oldRegion->allocSz; |
964 | if (oldRegion->type != MEMREG_ONE_BLOCK) |
965 | return NULL; // we are not single in the region |
966 | const size_t userOffset = (uintptr_t)ptr - (uintptr_t)oldRegion; |
967 | const size_t alignedSize = LargeObjectCache::alignToBin(newSize + userOffset); |
968 | const size_t requestSize = |
969 | alignUp(sizeof(MemRegion) + alignedSize + sizeof(LastFreeBlock), extMemPool->granularity); |
970 | if (requestSize < alignedSize) // is wrapped around? |
971 | return NULL; |
972 | regionList.remove(oldRegion); |
973 | |
974 | void *ret = mremap(oldRegion, oldRegion->allocSz, requestSize, MREMAP_MAYMOVE); |
975 | if (MAP_FAILED == ret) { // can't remap, revert and leave |
976 | regionList.add(oldRegion); |
977 | return NULL; |
978 | } |
979 | MemRegion *region = (MemRegion*)ret; |
980 | MALLOC_ASSERT(region->type == MEMREG_ONE_BLOCK, ASSERT_TEXT); |
981 | region->allocSz = requestSize; |
982 | region->blockSz = alignedSize; |
983 | |
984 | FreeBlock *fBlock = (FreeBlock *)alignUp((uintptr_t)region + sizeof(MemRegion), |
985 | largeObjectAlignment); |
986 | |
987 | regionList.add(region); |
988 | startUseBlock(region, fBlock, /*addToBin=*/false); |
989 | MALLOC_ASSERT(fBlock->sizeTmp == region->blockSz, ASSERT_TEXT); |
990 | // matched blockConsumed() in startUseBlock(). |
991 | // TODO: get rid of useless pair blockConsumed()/blockReleased() |
992 | bkndSync.blockReleased(); |
993 | |
994 | // object must start at same offset from region's start |
995 | void *object = (void*)((uintptr_t)region + userOffset); |
996 | MALLOC_ASSERT(isAligned(object, alignment), ASSERT_TEXT); |
997 | LargeObjectHdr * = (LargeObjectHdr*)object - 1; |
998 | setBackRef(header->backRefIdx, header); |
999 | |
1000 | LargeMemoryBlock *lmb = (LargeMemoryBlock*)fBlock; |
1001 | lmb->unalignedSize = region->blockSz; |
1002 | lmb->objectSize = newSize; |
1003 | lmb->backRefIdx = header->backRefIdx; |
1004 | header->memoryBlock = lmb; |
1005 | MALLOC_ASSERT((uintptr_t)lmb + lmb->unalignedSize >= |
1006 | (uintptr_t)object + lmb->objectSize, "An object must fit to the block." ); |
1007 | |
1008 | usedAddrRange.registerFree((uintptr_t)oldRegion, (uintptr_t)oldRegion + oldRegionSize); |
1009 | usedAddrRange.registerAlloc((uintptr_t)region, (uintptr_t)region + requestSize); |
1010 | AtomicAdd((intptr_t&)totalMemSize, region->allocSz - oldRegionSize); |
1011 | |
1012 | return object; |
1013 | } |
1014 | #endif /* BACKEND_HAS_MREMAP */ |
1015 | |
1016 | void Backend::releaseRegion(MemRegion *memRegion) |
1017 | { |
1018 | regionList.remove(memRegion); |
1019 | freeRawMem(memRegion, memRegion->allocSz); |
1020 | } |
1021 | |
1022 | // coalesce fBlock with its neighborhood |
1023 | FreeBlock *Backend::doCoalesc(FreeBlock *fBlock, MemRegion **mRegion) |
1024 | { |
1025 | FreeBlock *resBlock = fBlock; |
1026 | size_t resSize = fBlock->sizeTmp; |
1027 | MemRegion *memRegion = NULL; |
1028 | |
1029 | fBlock->markCoalescing(resSize); |
1030 | resBlock->blockInBin = false; |
1031 | |
1032 | // coalescing with left neighbor |
1033 | size_t leftSz = fBlock->trySetLeftUsed(GuardedSize::COAL_BLOCK); |
1034 | if (leftSz != GuardedSize::LOCKED) { |
1035 | if (leftSz == GuardedSize::COAL_BLOCK) { |
1036 | coalescQ.putBlock(fBlock); |
1037 | return NULL; |
1038 | } else { |
1039 | FreeBlock *left = fBlock->leftNeig(leftSz); |
1040 | size_t lSz = left->trySetMeUsed(GuardedSize::COAL_BLOCK); |
1041 | if (lSz <= GuardedSize::MAX_LOCKED_VAL) { |
1042 | fBlock->setLeftFree(leftSz); // rollback |
1043 | coalescQ.putBlock(fBlock); |
1044 | return NULL; |
1045 | } else { |
1046 | MALLOC_ASSERT(lSz == leftSz, "Invalid header" ); |
1047 | left->blockInBin = true; |
1048 | resBlock = left; |
1049 | resSize += leftSz; |
1050 | resBlock->sizeTmp = resSize; |
1051 | } |
1052 | } |
1053 | } |
1054 | // coalescing with right neighbor |
1055 | FreeBlock *right = fBlock->rightNeig(fBlock->sizeTmp); |
1056 | size_t rightSz = right->trySetMeUsed(GuardedSize::COAL_BLOCK); |
1057 | if (rightSz != GuardedSize::LOCKED) { |
1058 | // LastFreeBlock is on the right side |
1059 | if (GuardedSize::LAST_REGION_BLOCK == rightSz) { |
1060 | right->setMeFree(GuardedSize::LAST_REGION_BLOCK); |
1061 | memRegion = static_cast<LastFreeBlock*>(right)->memRegion; |
1062 | } else if (GuardedSize::COAL_BLOCK == rightSz) { |
1063 | if (resBlock->blockInBin) { |
1064 | resBlock->blockInBin = false; |
1065 | removeBlockFromBin(resBlock); |
1066 | } |
1067 | coalescQ.putBlock(resBlock); |
1068 | return NULL; |
1069 | } else { |
1070 | size_t rSz = right->rightNeig(rightSz)-> |
1071 | trySetLeftUsed(GuardedSize::COAL_BLOCK); |
1072 | if (rSz <= GuardedSize::MAX_LOCKED_VAL) { |
1073 | right->setMeFree(rightSz); // rollback |
1074 | if (resBlock->blockInBin) { |
1075 | resBlock->blockInBin = false; |
1076 | removeBlockFromBin(resBlock); |
1077 | } |
1078 | coalescQ.putBlock(resBlock); |
1079 | return NULL; |
1080 | } else { |
1081 | MALLOC_ASSERT(rSz == rightSz, "Invalid header" ); |
1082 | removeBlockFromBin(right); |
1083 | resSize += rightSz; |
1084 | |
1085 | // Is LastFreeBlock on the right side of right? |
1086 | FreeBlock *nextRight = right->rightNeig(rightSz); |
1087 | size_t nextRightSz = nextRight-> |
1088 | trySetMeUsed(GuardedSize::COAL_BLOCK); |
1089 | if (nextRightSz > GuardedSize::MAX_LOCKED_VAL) { |
1090 | if (nextRightSz == GuardedSize::LAST_REGION_BLOCK) |
1091 | memRegion = static_cast<LastFreeBlock*>(nextRight)->memRegion; |
1092 | |
1093 | nextRight->setMeFree(nextRightSz); |
1094 | } |
1095 | } |
1096 | } |
1097 | } |
1098 | if (memRegion) { |
1099 | MALLOC_ASSERT((uintptr_t)memRegion + memRegion->allocSz >= |
1100 | (uintptr_t)right + sizeof(LastFreeBlock), ASSERT_TEXT); |
1101 | MALLOC_ASSERT((uintptr_t)memRegion < (uintptr_t)resBlock, ASSERT_TEXT); |
1102 | *mRegion = memRegion; |
1103 | } else |
1104 | *mRegion = NULL; |
1105 | resBlock->sizeTmp = resSize; |
1106 | return resBlock; |
1107 | } |
1108 | |
1109 | bool Backend::coalescAndPutList(FreeBlock *list, bool forceCoalescQDrop, bool reportBlocksProcessed) |
1110 | { |
1111 | bool regionReleased = false; |
1112 | |
1113 | for (FreeBlock *helper; list; |
1114 | list = helper, |
1115 | // matches block enqueue in CoalRequestQ::putBlock() |
1116 | reportBlocksProcessed? coalescQ.blockWasProcessed() : (void)0) { |
1117 | MemRegion *memRegion; |
1118 | bool addToTail = false; |
1119 | |
1120 | helper = list->nextToFree; |
1121 | FreeBlock *toRet = doCoalesc(list, &memRegion); |
1122 | if (!toRet) |
1123 | continue; |
1124 | |
1125 | if (memRegion && memRegion->blockSz == toRet->sizeTmp |
1126 | && !extMemPool->fixedPool) { |
1127 | if (extMemPool->regionsAreReleaseable()) { |
1128 | // release the region, because there is no used blocks in it |
1129 | if (toRet->blockInBin) |
1130 | removeBlockFromBin(toRet); |
1131 | releaseRegion(memRegion); |
1132 | regionReleased = true; |
1133 | continue; |
1134 | } else // add block from empty region to end of bin, |
1135 | addToTail = true; // preserving for exact fit |
1136 | } |
1137 | size_t currSz = toRet->sizeTmp; |
1138 | int bin = sizeToBin(currSz); |
1139 | bool toAligned = extMemPool->fixedPool ? toAlignedBin(toRet, currSz) : toRet->slabAligned; |
1140 | bool needAddToBin = true; |
1141 | |
1142 | if (toRet->blockInBin) { |
1143 | // Does it stay in same bin? |
1144 | if (toRet->myBin == bin && toRet->slabAligned == toAligned) |
1145 | needAddToBin = false; |
1146 | else { |
1147 | toRet->blockInBin = false; |
1148 | removeBlockFromBin(toRet); |
1149 | } |
1150 | } |
1151 | |
1152 | // Does not stay in same bin, or bin-less; add it |
1153 | if (needAddToBin) { |
1154 | toRet->prev = toRet->next = toRet->nextToFree = NULL; |
1155 | toRet->myBin = NO_BIN; |
1156 | toRet->slabAligned = toAligned; |
1157 | |
1158 | // If the block is too small to fit in any bin, keep it bin-less. |
1159 | // It's not a leak because the block later can be coalesced. |
1160 | if (currSz >= minBinnedSize) { |
1161 | toRet->sizeTmp = currSz; |
1162 | IndexedBins *target = toRet->slabAligned ? &freeSlabAlignedBins : &freeLargeBlockBins; |
1163 | if (forceCoalescQDrop) { |
1164 | target->addBlock(bin, toRet, toRet->sizeTmp, addToTail); |
1165 | } else if (!target->tryAddBlock(bin, toRet, addToTail)) { |
1166 | coalescQ.putBlock(toRet); |
1167 | continue; |
1168 | } |
1169 | } |
1170 | toRet->sizeTmp = 0; |
1171 | } |
1172 | // Free (possibly coalesced) free block. |
1173 | // Adding to bin must be done before this point, |
1174 | // because after a block is free it can be coalesced, and |
1175 | // using its pointer became unsafe. |
1176 | // Remember that coalescing is not done under any global lock. |
1177 | toRet->setMeFree(currSz); |
1178 | toRet->rightNeig(currSz)->setLeftFree(currSz); |
1179 | } |
1180 | return regionReleased; |
1181 | } |
1182 | |
1183 | // Coalesce fBlock and add it back to a bin; |
1184 | // processing delayed coalescing requests. |
1185 | void Backend::coalescAndPut(FreeBlock *fBlock, size_t blockSz, bool slabAligned) |
1186 | { |
1187 | fBlock->sizeTmp = blockSz; |
1188 | fBlock->nextToFree = NULL; |
1189 | fBlock->slabAligned = slabAligned; |
1190 | |
1191 | coalescAndPutList(fBlock, /*forceCoalescQDrop=*/false, /*reportBlocksProcessed=*/false); |
1192 | } |
1193 | |
1194 | bool Backend::scanCoalescQ(bool forceCoalescQDrop) |
1195 | { |
1196 | FreeBlock *currCoalescList = coalescQ.getAll(); |
1197 | |
1198 | if (currCoalescList) |
1199 | // reportBlocksProcessed=true informs that the blocks leave coalescQ, |
1200 | // matches blockConsumed() from CoalRequestQ::putBlock() |
1201 | coalescAndPutList(currCoalescList, forceCoalescQDrop, |
1202 | /*reportBlocksProcessed=*/true); |
1203 | // returns status of coalescQ.getAll(), as an indication of possible changes in backend |
1204 | // TODO: coalescAndPutList() may report is some new free blocks became available or not |
1205 | return currCoalescList; |
1206 | } |
1207 | |
1208 | FreeBlock *Backend::findBlockInRegion(MemRegion *region, size_t exactBlockSize) |
1209 | { |
1210 | FreeBlock *fBlock; |
1211 | size_t blockSz; |
1212 | uintptr_t fBlockEnd, |
1213 | lastFreeBlock = (uintptr_t)region + region->allocSz - sizeof(LastFreeBlock); |
1214 | |
1215 | MALLOC_STATIC_ASSERT(sizeof(LastFreeBlock) % sizeof(uintptr_t) == 0, |
1216 | "Atomic applied on LastFreeBlock, and we put it at the end of region, that" |
1217 | " is uintptr_t-aligned, so no unaligned atomic operations are possible." ); |
1218 | // right bound is slab-aligned, keep LastFreeBlock after it |
1219 | if (region->type == MEMREG_SLAB_BLOCKS) { |
1220 | fBlock = (FreeBlock *)alignUp((uintptr_t)region + sizeof(MemRegion), sizeof(uintptr_t)); |
1221 | fBlockEnd = alignDown(lastFreeBlock, slabSize); |
1222 | } else { |
1223 | fBlock = (FreeBlock *)alignUp((uintptr_t)region + sizeof(MemRegion), largeObjectAlignment); |
1224 | fBlockEnd = (uintptr_t)fBlock + exactBlockSize; |
1225 | MALLOC_ASSERT(fBlockEnd <= lastFreeBlock, ASSERT_TEXT); |
1226 | } |
1227 | if (fBlockEnd <= (uintptr_t)fBlock) |
1228 | return NULL; // allocSz is too small |
1229 | blockSz = fBlockEnd - (uintptr_t)fBlock; |
1230 | // TODO: extend getSlabBlock to support degradation, i.e. getting less blocks |
1231 | // then requested, and then relax this check |
1232 | // (now all or nothing is implemented, check according to this) |
1233 | if (blockSz < numOfSlabAllocOnMiss*slabSize) |
1234 | return NULL; |
1235 | |
1236 | region->blockSz = blockSz; |
1237 | return fBlock; |
1238 | } |
1239 | |
1240 | // startUseBlock may add the free block to a bin, the block can be used and |
1241 | // even released after this, so the region must be added to regionList already |
1242 | void Backend::startUseBlock(MemRegion *region, FreeBlock *fBlock, bool addToBin) |
1243 | { |
1244 | size_t blockSz = region->blockSz; |
1245 | fBlock->initHeader(); |
1246 | fBlock->setMeFree(blockSz); |
1247 | |
1248 | LastFreeBlock *lastBl = static_cast<LastFreeBlock*>(fBlock->rightNeig(blockSz)); |
1249 | // to not get unaligned atomics during LastFreeBlock access |
1250 | MALLOC_ASSERT(isAligned(lastBl, sizeof(uintptr_t)), NULL); |
1251 | lastBl->initHeader(); |
1252 | lastBl->setMeFree(GuardedSize::LAST_REGION_BLOCK); |
1253 | lastBl->setLeftFree(blockSz); |
1254 | lastBl->myBin = NO_BIN; |
1255 | lastBl->memRegion = region; |
1256 | |
1257 | if (addToBin) { |
1258 | unsigned targetBin = sizeToBin(blockSz); |
1259 | // during adding advance regions, register bin for a largest block in region |
1260 | advRegBins.registerBin(targetBin); |
1261 | if (region->type == MEMREG_SLAB_BLOCKS) { |
1262 | fBlock->slabAligned = true; |
1263 | freeSlabAlignedBins.addBlock(targetBin, fBlock, blockSz, /*addToTail=*/false); |
1264 | } else { |
1265 | fBlock->slabAligned = false; |
1266 | freeLargeBlockBins.addBlock(targetBin, fBlock, blockSz, /*addToTail=*/false); |
1267 | } |
1268 | } else { |
1269 | // to match with blockReleased() in genericGetBlock |
1270 | bkndSync.blockConsumed(); |
1271 | // Understand our alignment for correct splitBlock operation |
1272 | fBlock->slabAligned = region->type == MEMREG_SLAB_BLOCKS ? true : false; |
1273 | fBlock->sizeTmp = fBlock->tryLockBlock(); |
1274 | MALLOC_ASSERT(fBlock->sizeTmp >= FreeBlock::minBlockSize, "Locking must be successful" ); |
1275 | } |
1276 | } |
1277 | |
1278 | void MemRegionList::add(MemRegion *r) |
1279 | { |
1280 | r->prev = NULL; |
1281 | MallocMutex::scoped_lock lock(regionListLock); |
1282 | r->next = head; |
1283 | head = r; |
1284 | if (head->next) |
1285 | head->next->prev = head; |
1286 | } |
1287 | |
1288 | void MemRegionList::remove(MemRegion *r) |
1289 | { |
1290 | MallocMutex::scoped_lock lock(regionListLock); |
1291 | if (head == r) |
1292 | head = head->next; |
1293 | if (r->next) |
1294 | r->next->prev = r->prev; |
1295 | if (r->prev) |
1296 | r->prev->next = r->next; |
1297 | } |
1298 | |
1299 | #if __TBB_MALLOC_BACKEND_STAT |
1300 | int MemRegionList::reportStat(FILE *f) |
1301 | { |
1302 | int regNum = 0; |
1303 | MallocMutex::scoped_lock lock(regionListLock); |
1304 | for (MemRegion *curr = head; curr; curr = curr->next) { |
1305 | fprintf(f, "%p: max block %lu B, " , curr, curr->blockSz); |
1306 | regNum++; |
1307 | } |
1308 | return regNum; |
1309 | } |
1310 | #endif |
1311 | |
1312 | FreeBlock *Backend::addNewRegion(size_t size, MemRegionType memRegType, bool addToBin) |
1313 | { |
1314 | MALLOC_STATIC_ASSERT(sizeof(BlockMutexes) <= sizeof(BlockI), |
1315 | "Header must be not overwritten in used blocks" ); |
1316 | MALLOC_ASSERT(FreeBlock::minBlockSize > GuardedSize::MAX_SPEC_VAL, |
1317 | "Block length must not conflict with special values of GuardedSize" ); |
1318 | // If the region is not "for slabs" we should reserve some space for |
1319 | // a region header, the worst case alignment and the last block mark. |
1320 | const size_t requestSize = memRegType == MEMREG_SLAB_BLOCKS ? size : |
1321 | size + sizeof(MemRegion) + largeObjectAlignment |
1322 | + FreeBlock::minBlockSize + sizeof(LastFreeBlock); |
1323 | |
1324 | size_t rawSize = requestSize; |
1325 | MemRegion *region = (MemRegion*)allocRawMem(rawSize); |
1326 | if (!region) { |
1327 | MALLOC_ASSERT(rawSize==requestSize, "getRawMem has not allocated memory but changed the allocated size." ); |
1328 | return NULL; |
1329 | } |
1330 | if (rawSize < sizeof(MemRegion)) { |
1331 | if (!extMemPool->fixedPool) |
1332 | freeRawMem(region, rawSize); |
1333 | return NULL; |
1334 | } |
1335 | |
1336 | region->type = memRegType; |
1337 | region->allocSz = rawSize; |
1338 | FreeBlock *fBlock = findBlockInRegion(region, size); |
1339 | if (!fBlock) { |
1340 | if (!extMemPool->fixedPool) |
1341 | freeRawMem(region, rawSize); |
1342 | return NULL; |
1343 | } |
1344 | regionList.add(region); |
1345 | startUseBlock(region, fBlock, addToBin); |
1346 | bkndSync.binsModified(); |
1347 | return addToBin? (FreeBlock*)VALID_BLOCK_IN_BIN : fBlock; |
1348 | } |
1349 | |
1350 | void Backend::init(ExtMemoryPool *extMemoryPool) |
1351 | { |
1352 | extMemPool = extMemoryPool; |
1353 | usedAddrRange.init(); |
1354 | coalescQ.init(&bkndSync); |
1355 | bkndSync.init(this); |
1356 | } |
1357 | |
1358 | void Backend::reset() |
1359 | { |
1360 | MALLOC_ASSERT(extMemPool->userPool(), "Only user pool can be reset." ); |
1361 | // no active threads are allowed in backend while reset() called |
1362 | verify(); |
1363 | |
1364 | freeLargeBlockBins.reset(); |
1365 | freeSlabAlignedBins.reset(); |
1366 | advRegBins.reset(); |
1367 | |
1368 | for (MemRegion *curr = regionList.head; curr; curr = curr->next) { |
1369 | FreeBlock *fBlock = findBlockInRegion(curr, curr->blockSz); |
1370 | MALLOC_ASSERT(fBlock, "A memory region unexpectedly got smaller" ); |
1371 | startUseBlock(curr, fBlock, /*addToBin=*/true); |
1372 | } |
1373 | } |
1374 | |
1375 | bool Backend::destroy() |
1376 | { |
1377 | bool noError = true; |
1378 | // no active threads are allowed in backend while destroy() called |
1379 | verify(); |
1380 | if (!inUserPool()) { |
1381 | freeLargeBlockBins.reset(); |
1382 | freeSlabAlignedBins.reset(); |
1383 | } |
1384 | while (regionList.head) { |
1385 | MemRegion *helper = regionList.head->next; |
1386 | noError &= freeRawMem(regionList.head, regionList.head->allocSz); |
1387 | regionList.head = helper; |
1388 | } |
1389 | return noError; |
1390 | } |
1391 | |
1392 | bool Backend::clean() |
1393 | { |
1394 | scanCoalescQ(/*forceCoalescQDrop=*/false); |
1395 | |
1396 | bool res = false; |
1397 | // We can have several blocks occupying a whole region, |
1398 | // because such regions are added in advance (see askMemFromOS() and reset()), |
1399 | // and never used. Release them all. |
1400 | for (int i = advRegBins.getMinUsedBin(0); i != -1; i = advRegBins.getMinUsedBin(i+1)) { |
1401 | if (i == freeSlabAlignedBins.getMinNonemptyBin(i)) |
1402 | res |= freeSlabAlignedBins.tryReleaseRegions(i, this); |
1403 | if (i == freeLargeBlockBins.getMinNonemptyBin(i)) |
1404 | res |= freeLargeBlockBins.tryReleaseRegions(i, this); |
1405 | } |
1406 | |
1407 | return res; |
1408 | } |
1409 | |
1410 | void Backend::IndexedBins::verify() |
1411 | { |
1412 | for (int i=0; i<freeBinsNum; i++) { |
1413 | for (FreeBlock *fb = freeBins[i].head; fb; fb=fb->next) { |
1414 | uintptr_t mySz = fb->myL.value; |
1415 | MALLOC_ASSERT(mySz>GuardedSize::MAX_SPEC_VAL, ASSERT_TEXT); |
1416 | FreeBlock *right = (FreeBlock*)((uintptr_t)fb + mySz); |
1417 | suppress_unused_warning(right); |
1418 | MALLOC_ASSERT(right->myL.value<=GuardedSize::MAX_SPEC_VAL, ASSERT_TEXT); |
1419 | MALLOC_ASSERT(right->leftL.value==mySz, ASSERT_TEXT); |
1420 | MALLOC_ASSERT(fb->leftL.value<=GuardedSize::MAX_SPEC_VAL, ASSERT_TEXT); |
1421 | } |
1422 | } |
1423 | } |
1424 | |
1425 | // For correct operation, it must be called when no other threads |
1426 | // is changing backend. |
1427 | void Backend::verify() |
1428 | { |
1429 | #if MALLOC_DEBUG |
1430 | scanCoalescQ(/*forceCoalescQDrop=*/false); |
1431 | |
1432 | freeLargeBlockBins.verify(); |
1433 | freeSlabAlignedBins.verify(); |
1434 | #endif // MALLOC_DEBUG |
1435 | } |
1436 | |
1437 | #if __TBB_MALLOC_BACKEND_STAT |
1438 | size_t Backend::Bin::countFreeBlocks() |
1439 | { |
1440 | size_t cnt = 0; |
1441 | { |
1442 | MallocMutex::scoped_lock lock(tLock); |
1443 | for (FreeBlock *fb = head; fb; fb = fb->next) |
1444 | cnt++; |
1445 | } |
1446 | return cnt; |
1447 | } |
1448 | |
1449 | size_t Backend::Bin::reportFreeBlocks(FILE *f) |
1450 | { |
1451 | size_t totalSz = 0; |
1452 | MallocMutex::scoped_lock lock(tLock); |
1453 | for (FreeBlock *fb = head; fb; fb = fb->next) { |
1454 | size_t sz = fb->tryLockBlock(); |
1455 | fb->setMeFree(sz); |
1456 | fprintf(f, " [%p;%p]" , fb, (void*)((uintptr_t)fb+sz)); |
1457 | totalSz += sz; |
1458 | } |
1459 | return totalSz; |
1460 | } |
1461 | |
1462 | void Backend::IndexedBins::reportStat(FILE *f) |
1463 | { |
1464 | size_t totalSize = 0; |
1465 | |
1466 | for (int i=0; i<Backend::freeBinsNum; i++) |
1467 | if (size_t cnt = freeBins[i].countFreeBlocks()) { |
1468 | totalSize += freeBins[i].reportFreeBlocks(f); |
1469 | fprintf(f, " %d:%lu, " , i, cnt); |
1470 | } |
1471 | fprintf(f, "\ttotal size %lu KB" , totalSize/1024); |
1472 | } |
1473 | |
1474 | void Backend::reportStat(FILE *f) |
1475 | { |
1476 | scanCoalescQ(/*forceCoalescQDrop=*/false); |
1477 | |
1478 | fprintf(f, "\n regions:\n" ); |
1479 | int regNum = regionList.reportStat(f); |
1480 | fprintf(f, "\n%d regions, %lu KB in all regions\n free bins:\nlarge bins: " , |
1481 | regNum, totalMemSize/1024); |
1482 | freeLargeBlockBins.reportStat(f); |
1483 | fprintf(f, "\naligned bins: " ); |
1484 | freeSlabAlignedBins.reportStat(f); |
1485 | fprintf(f, "\n" ); |
1486 | } |
1487 | #endif // __TBB_MALLOC_BACKEND_STAT |
1488 | |
1489 | } } // namespaces |
1490 | |