151c0b2f7Stbbdev /*
22110128eSsarathnandu Copyright (c) 2005-2023 Intel Corporation
351c0b2f7Stbbdev
451c0b2f7Stbbdev Licensed under the Apache License, Version 2.0 (the "License");
551c0b2f7Stbbdev you may not use this file except in compliance with the License.
651c0b2f7Stbbdev You may obtain a copy of the License at
751c0b2f7Stbbdev
851c0b2f7Stbbdev http://www.apache.org/licenses/LICENSE-2.0
951c0b2f7Stbbdev
1051c0b2f7Stbbdev Unless required by applicable law or agreed to in writing, software
1151c0b2f7Stbbdev distributed under the License is distributed on an "AS IS" BASIS,
1251c0b2f7Stbbdev WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1351c0b2f7Stbbdev See the License for the specific language governing permissions and
1451c0b2f7Stbbdev limitations under the License.
1551c0b2f7Stbbdev */
1651c0b2f7Stbbdev
1751c0b2f7Stbbdev #include <string.h> /* for memset */
1851c0b2f7Stbbdev #include <errno.h>
1951c0b2f7Stbbdev #include "tbbmalloc_internal.h"
2051c0b2f7Stbbdev
2151c0b2f7Stbbdev namespace rml {
2251c0b2f7Stbbdev namespace internal {
2351c0b2f7Stbbdev
2451c0b2f7Stbbdev /*********** Code to acquire memory from the OS or other executive ****************/
2551c0b2f7Stbbdev
2651c0b2f7Stbbdev /*
2751c0b2f7Stbbdev syscall/malloc can set non-zero errno in case of failure,
2851c0b2f7Stbbdev but later allocator might be able to find memory to fulfill the request.
2951c0b2f7Stbbdev And we do not want changing of errno by successful scalable_malloc call.
3051c0b2f7Stbbdev To support this, restore old errno in (get|free)RawMemory, and set errno
3151c0b2f7Stbbdev in frontend just before returning to user code.
3251c0b2f7Stbbdev Please note: every syscall/libc call used inside scalable_malloc that
3351c0b2f7Stbbdev sets errno must be protected this way, not just memory allocation per se.
3451c0b2f7Stbbdev */
3551c0b2f7Stbbdev
3651c0b2f7Stbbdev #if USE_DEFAULT_MEMORY_MAPPING
3751c0b2f7Stbbdev #include "MapMemory.h"
3851c0b2f7Stbbdev #else
3951c0b2f7Stbbdev /* assume MapMemory and UnmapMemory are customized */
4051c0b2f7Stbbdev #endif
4151c0b2f7Stbbdev
getRawMemory(size_t size,PageType pageType)4251c0b2f7Stbbdev void* getRawMemory (size_t size, PageType pageType) {
4351c0b2f7Stbbdev return MapMemory(size, pageType);
4451c0b2f7Stbbdev }
4551c0b2f7Stbbdev
freeRawMemory(void * object,size_t size)4651c0b2f7Stbbdev int freeRawMemory (void *object, size_t size) {
4751c0b2f7Stbbdev return UnmapMemory(object, size);
4851c0b2f7Stbbdev }
4951c0b2f7Stbbdev
5051c0b2f7Stbbdev #if CHECK_ALLOCATION_RANGE
5151c0b2f7Stbbdev
registerAlloc(uintptr_t left,uintptr_t right)5251c0b2f7Stbbdev void Backend::UsedAddressRange::registerAlloc(uintptr_t left, uintptr_t right)
5351c0b2f7Stbbdev {
5451c0b2f7Stbbdev MallocMutex::scoped_lock lock(mutex);
55478de5b1Stbbdev if (left < leftBound.load(std::memory_order_relaxed))
56478de5b1Stbbdev leftBound.store(left, std::memory_order_relaxed);
57478de5b1Stbbdev if (right > rightBound.load(std::memory_order_relaxed))
58478de5b1Stbbdev rightBound.store(right, std::memory_order_relaxed);
59478de5b1Stbbdev MALLOC_ASSERT(leftBound.load(std::memory_order_relaxed), ASSERT_TEXT);
60478de5b1Stbbdev MALLOC_ASSERT(leftBound.load(std::memory_order_relaxed) < rightBound.load(std::memory_order_relaxed), ASSERT_TEXT);
61478de5b1Stbbdev MALLOC_ASSERT(leftBound.load(std::memory_order_relaxed) <= left && right <= rightBound.load(std::memory_order_relaxed), ASSERT_TEXT);
6251c0b2f7Stbbdev }
6351c0b2f7Stbbdev
registerFree(uintptr_t left,uintptr_t right)6451c0b2f7Stbbdev void Backend::UsedAddressRange::registerFree(uintptr_t left, uintptr_t right)
6551c0b2f7Stbbdev {
6651c0b2f7Stbbdev MallocMutex::scoped_lock lock(mutex);
67478de5b1Stbbdev if (leftBound.load(std::memory_order_relaxed) == left) {
68478de5b1Stbbdev if (rightBound.load(std::memory_order_relaxed) == right) {
69478de5b1Stbbdev leftBound.store(ADDRESS_UPPER_BOUND, std::memory_order_relaxed);
70478de5b1Stbbdev rightBound.store(0, std::memory_order_relaxed);
7151c0b2f7Stbbdev } else
72478de5b1Stbbdev leftBound.store(right, std::memory_order_relaxed);
73478de5b1Stbbdev } else if (rightBound.load(std::memory_order_relaxed) == right)
74478de5b1Stbbdev rightBound.store(left, std::memory_order_relaxed);
75478de5b1Stbbdev MALLOC_ASSERT((!rightBound.load(std::memory_order_relaxed) && leftBound.load(std::memory_order_relaxed) == ADDRESS_UPPER_BOUND)
76478de5b1Stbbdev || leftBound.load(std::memory_order_relaxed) < rightBound.load(std::memory_order_relaxed), ASSERT_TEXT);
7751c0b2f7Stbbdev }
7851c0b2f7Stbbdev #endif // CHECK_ALLOCATION_RANGE
7951c0b2f7Stbbdev
8051c0b2f7Stbbdev // Initialized in frontend inside defaultMemPool
8151c0b2f7Stbbdev extern HugePagesStatus hugePages;
8251c0b2f7Stbbdev
allocRawMem(size_t & size)8351c0b2f7Stbbdev void *Backend::allocRawMem(size_t &size)
8451c0b2f7Stbbdev {
8557f524caSIlya Isaev void *res = nullptr;
8651c0b2f7Stbbdev size_t allocSize = 0;
8751c0b2f7Stbbdev
8851c0b2f7Stbbdev if (extMemPool->userPool()) {
8951c0b2f7Stbbdev if (extMemPool->fixedPool && bootsrapMemDone == bootsrapMemStatus.load(std::memory_order_acquire))
9057f524caSIlya Isaev return nullptr;
9151c0b2f7Stbbdev MALLOC_ASSERT(bootsrapMemStatus != bootsrapMemNotDone,
9251c0b2f7Stbbdev "Backend::allocRawMem() called prematurely?");
9351c0b2f7Stbbdev // TODO: support for raw mem not aligned at sizeof(uintptr_t)
9451c0b2f7Stbbdev // memory from fixed pool is asked once and only once
9551c0b2f7Stbbdev allocSize = alignUpGeneric(size, extMemPool->granularity);
9651c0b2f7Stbbdev res = (*extMemPool->rawAlloc)(extMemPool->poolId, allocSize);
9751c0b2f7Stbbdev } else {
9851c0b2f7Stbbdev // Align allocation on page size
9951c0b2f7Stbbdev size_t pageSize = hugePages.isEnabled ? hugePages.getGranularity() : extMemPool->granularity;
10051c0b2f7Stbbdev MALLOC_ASSERT(pageSize, "Page size cannot be zero.");
10151c0b2f7Stbbdev allocSize = alignUpGeneric(size, pageSize);
10251c0b2f7Stbbdev
10351c0b2f7Stbbdev // If user requested huge pages and they are available, try to use preallocated ones firstly.
10451c0b2f7Stbbdev // If there are none, lets check transparent huge pages support and use them instead.
10551c0b2f7Stbbdev if (hugePages.isEnabled) {
10651c0b2f7Stbbdev if (hugePages.isHPAvailable) {
10751c0b2f7Stbbdev res = getRawMemory(allocSize, PREALLOCATED_HUGE_PAGE);
10851c0b2f7Stbbdev }
10951c0b2f7Stbbdev if (!res && hugePages.isTHPAvailable) {
11051c0b2f7Stbbdev res = getRawMemory(allocSize, TRANSPARENT_HUGE_PAGE);
11151c0b2f7Stbbdev }
11251c0b2f7Stbbdev }
11351c0b2f7Stbbdev
11451c0b2f7Stbbdev if (!res) {
11551c0b2f7Stbbdev res = getRawMemory(allocSize, REGULAR);
11651c0b2f7Stbbdev }
11751c0b2f7Stbbdev }
11851c0b2f7Stbbdev
11951c0b2f7Stbbdev if (res) {
12051c0b2f7Stbbdev MALLOC_ASSERT(allocSize > 0, "Invalid size of an allocated region.");
12151c0b2f7Stbbdev size = allocSize;
12251c0b2f7Stbbdev if (!extMemPool->userPool())
12351c0b2f7Stbbdev usedAddrRange.registerAlloc((uintptr_t)res, (uintptr_t)res+size);
12451c0b2f7Stbbdev #if MALLOC_DEBUG
12551c0b2f7Stbbdev volatile size_t curTotalSize = totalMemSize; // to read global value once
12651c0b2f7Stbbdev MALLOC_ASSERT(curTotalSize+size > curTotalSize, "Overflow allocation size.");
12751c0b2f7Stbbdev #endif
12851c0b2f7Stbbdev totalMemSize.fetch_add(size);
12951c0b2f7Stbbdev }
13051c0b2f7Stbbdev
13151c0b2f7Stbbdev return res;
13251c0b2f7Stbbdev }
13351c0b2f7Stbbdev
freeRawMem(void * object,size_t size)13451c0b2f7Stbbdev bool Backend::freeRawMem(void *object, size_t size)
13551c0b2f7Stbbdev {
13651c0b2f7Stbbdev bool fail;
13751c0b2f7Stbbdev #if MALLOC_DEBUG
13851c0b2f7Stbbdev volatile size_t curTotalSize = totalMemSize; // to read global value once
13951c0b2f7Stbbdev MALLOC_ASSERT(curTotalSize-size < curTotalSize, "Negative allocation size.");
14051c0b2f7Stbbdev #endif
14151c0b2f7Stbbdev totalMemSize.fetch_sub(size);
14251c0b2f7Stbbdev if (extMemPool->userPool()) {
14351c0b2f7Stbbdev MALLOC_ASSERT(!extMemPool->fixedPool, "No free for fixed-size pools.");
14451c0b2f7Stbbdev fail = (*extMemPool->rawFree)(extMemPool->poolId, object, size);
14551c0b2f7Stbbdev } else {
14651c0b2f7Stbbdev usedAddrRange.registerFree((uintptr_t)object, (uintptr_t)object + size);
14751c0b2f7Stbbdev fail = freeRawMemory(object, size);
14851c0b2f7Stbbdev }
14951c0b2f7Stbbdev // TODO: use result in all freeRawMem() callers
15051c0b2f7Stbbdev return !fail;
15151c0b2f7Stbbdev }
15251c0b2f7Stbbdev
15351c0b2f7Stbbdev /********* End memory acquisition code ********************************/
15451c0b2f7Stbbdev
15551c0b2f7Stbbdev // Protected object size. After successful locking returns size of locked block,
15651c0b2f7Stbbdev // and releasing requires setting block size.
15751c0b2f7Stbbdev class GuardedSize : tbb::detail::no_copy {
15851c0b2f7Stbbdev std::atomic<uintptr_t> value;
15951c0b2f7Stbbdev public:
16051c0b2f7Stbbdev enum State {
16151c0b2f7Stbbdev LOCKED,
16251c0b2f7Stbbdev COAL_BLOCK, // block is coalescing now
16351c0b2f7Stbbdev MAX_LOCKED_VAL = COAL_BLOCK,
16451c0b2f7Stbbdev LAST_REGION_BLOCK, // used to mark last block in region
16551c0b2f7Stbbdev // values after this are "normal" block sizes
16651c0b2f7Stbbdev MAX_SPEC_VAL = LAST_REGION_BLOCK
16751c0b2f7Stbbdev };
16851c0b2f7Stbbdev
initLocked()16951c0b2f7Stbbdev void initLocked() { value.store(LOCKED, std::memory_order_release); } // TBB_REVAMP_TODO: was relaxed
makeCoalscing()17051c0b2f7Stbbdev void makeCoalscing() {
17151c0b2f7Stbbdev MALLOC_ASSERT(value.load(std::memory_order_relaxed) == LOCKED, ASSERT_TEXT);
17251c0b2f7Stbbdev value.store(COAL_BLOCK, std::memory_order_release); // TBB_REVAMP_TODO: was relaxed
17351c0b2f7Stbbdev }
tryLock(State state)17451c0b2f7Stbbdev size_t tryLock(State state) {
17551c0b2f7Stbbdev MALLOC_ASSERT(state <= MAX_LOCKED_VAL, ASSERT_TEXT);
17651c0b2f7Stbbdev size_t sz = value.load(std::memory_order_acquire);
17751c0b2f7Stbbdev for (;;) {
17851c0b2f7Stbbdev if (sz <= MAX_LOCKED_VAL) {
17951c0b2f7Stbbdev break;
18051c0b2f7Stbbdev }
18151c0b2f7Stbbdev if (value.compare_exchange_strong(sz, state)) {
18251c0b2f7Stbbdev break;
18351c0b2f7Stbbdev }
18451c0b2f7Stbbdev }
18551c0b2f7Stbbdev return sz;
18651c0b2f7Stbbdev }
unlock(size_t size)18751c0b2f7Stbbdev void unlock(size_t size) {
18851c0b2f7Stbbdev MALLOC_ASSERT(value.load(std::memory_order_relaxed) <= MAX_LOCKED_VAL, "The lock is not locked");
18951c0b2f7Stbbdev MALLOC_ASSERT(size > MAX_LOCKED_VAL, ASSERT_TEXT);
19051c0b2f7Stbbdev value.store(size, std::memory_order_release);
19151c0b2f7Stbbdev }
isLastRegionBlock() const19251c0b2f7Stbbdev bool isLastRegionBlock() const { return value.load(std::memory_order_relaxed) == LAST_REGION_BLOCK; }
19351c0b2f7Stbbdev friend void Backend::IndexedBins::verify();
19451c0b2f7Stbbdev };
19551c0b2f7Stbbdev
19651c0b2f7Stbbdev struct MemRegion {
19751c0b2f7Stbbdev MemRegion *next, // keep all regions in any pool to release all them on
19851c0b2f7Stbbdev *prev; // pool destroying, 2-linked list to release individual
19951c0b2f7Stbbdev // regions.
20051c0b2f7Stbbdev size_t allocSz, // got from pool callback
20151c0b2f7Stbbdev blockSz; // initial and maximal inner block size
20251c0b2f7Stbbdev MemRegionType type;
20351c0b2f7Stbbdev };
20451c0b2f7Stbbdev
20551c0b2f7Stbbdev // this data must be unmodified while block is in use, so separate it
20651c0b2f7Stbbdev class BlockMutexes {
20751c0b2f7Stbbdev protected:
20851c0b2f7Stbbdev GuardedSize myL, // lock for me
20951c0b2f7Stbbdev leftL; // lock for left neighbor
21051c0b2f7Stbbdev };
21151c0b2f7Stbbdev
21251c0b2f7Stbbdev class FreeBlock : BlockMutexes {
21351c0b2f7Stbbdev public:
21451c0b2f7Stbbdev static const size_t minBlockSize;
21551c0b2f7Stbbdev friend void Backend::IndexedBins::verify();
21651c0b2f7Stbbdev
21751c0b2f7Stbbdev FreeBlock *prev, // in 2-linked list related to bin
21851c0b2f7Stbbdev *next,
21951c0b2f7Stbbdev *nextToFree; // used to form a queue during coalescing
22051c0b2f7Stbbdev // valid only when block is in processing, i.e. one is not free and not
22151c0b2f7Stbbdev size_t sizeTmp; // used outside of backend
22251c0b2f7Stbbdev int myBin; // bin that is owner of the block
22351c0b2f7Stbbdev bool slabAligned;
22451c0b2f7Stbbdev bool blockInBin; // this block in myBin already
22551c0b2f7Stbbdev
rightNeig(size_t sz) const22651c0b2f7Stbbdev FreeBlock *rightNeig(size_t sz) const {
22751c0b2f7Stbbdev MALLOC_ASSERT(sz, ASSERT_TEXT);
22851c0b2f7Stbbdev return (FreeBlock*)((uintptr_t)this+sz);
22951c0b2f7Stbbdev }
leftNeig(size_t sz) const23051c0b2f7Stbbdev FreeBlock *leftNeig(size_t sz) const {
23151c0b2f7Stbbdev MALLOC_ASSERT(sz, ASSERT_TEXT);
23251c0b2f7Stbbdev return (FreeBlock*)((uintptr_t)this - sz);
23351c0b2f7Stbbdev }
23451c0b2f7Stbbdev
initHeader()23551c0b2f7Stbbdev void initHeader() { myL.initLocked(); leftL.initLocked(); }
setMeFree(size_t size)23651c0b2f7Stbbdev void setMeFree(size_t size) { myL.unlock(size); }
trySetMeUsed(GuardedSize::State s)23751c0b2f7Stbbdev size_t trySetMeUsed(GuardedSize::State s) { return myL.tryLock(s); }
isLastRegionBlock() const23851c0b2f7Stbbdev bool isLastRegionBlock() const { return myL.isLastRegionBlock(); }
23951c0b2f7Stbbdev
setLeftFree(size_t sz)24051c0b2f7Stbbdev void setLeftFree(size_t sz) { leftL.unlock(sz); }
trySetLeftUsed(GuardedSize::State s)24151c0b2f7Stbbdev size_t trySetLeftUsed(GuardedSize::State s) { return leftL.tryLock(s); }
24251c0b2f7Stbbdev
tryLockBlock()24351c0b2f7Stbbdev size_t tryLockBlock() {
24451c0b2f7Stbbdev size_t rSz, sz = trySetMeUsed(GuardedSize::LOCKED);
24551c0b2f7Stbbdev
24651c0b2f7Stbbdev if (sz <= GuardedSize::MAX_LOCKED_VAL)
24751c0b2f7Stbbdev return false;
24851c0b2f7Stbbdev rSz = rightNeig(sz)->trySetLeftUsed(GuardedSize::LOCKED);
24951c0b2f7Stbbdev if (rSz <= GuardedSize::MAX_LOCKED_VAL) {
25051c0b2f7Stbbdev setMeFree(sz);
25151c0b2f7Stbbdev return false;
25251c0b2f7Stbbdev }
25351c0b2f7Stbbdev MALLOC_ASSERT(rSz == sz, ASSERT_TEXT);
25451c0b2f7Stbbdev return sz;
25551c0b2f7Stbbdev }
markCoalescing(size_t blockSz)25651c0b2f7Stbbdev void markCoalescing(size_t blockSz) {
25751c0b2f7Stbbdev myL.makeCoalscing();
25851c0b2f7Stbbdev rightNeig(blockSz)->leftL.makeCoalscing();
25951c0b2f7Stbbdev sizeTmp = blockSz;
26057f524caSIlya Isaev nextToFree = nullptr;
26151c0b2f7Stbbdev }
markUsed()26251c0b2f7Stbbdev void markUsed() {
26351c0b2f7Stbbdev myL.initLocked();
26451c0b2f7Stbbdev rightNeig(sizeTmp)->leftL.initLocked();
26557f524caSIlya Isaev nextToFree = nullptr;
26651c0b2f7Stbbdev }
markBlocks(FreeBlock * fBlock,int num,size_t size)26751c0b2f7Stbbdev static void markBlocks(FreeBlock *fBlock, int num, size_t size) {
26851c0b2f7Stbbdev for (int i=1; i<num; i++) {
26951c0b2f7Stbbdev fBlock = (FreeBlock*)((uintptr_t)fBlock + size);
27051c0b2f7Stbbdev fBlock->initHeader();
27151c0b2f7Stbbdev }
27251c0b2f7Stbbdev }
27351c0b2f7Stbbdev };
27451c0b2f7Stbbdev
27551c0b2f7Stbbdev // Last block in any region. Its "size" field is GuardedSize::LAST_REGION_BLOCK,
27651c0b2f7Stbbdev // This kind of blocks used to find region header
27751c0b2f7Stbbdev // and have a possibility to return region back to OS
27851c0b2f7Stbbdev struct LastFreeBlock : public FreeBlock {
27951c0b2f7Stbbdev MemRegion *memRegion;
28051c0b2f7Stbbdev };
28151c0b2f7Stbbdev
28251c0b2f7Stbbdev const size_t FreeBlock::minBlockSize = sizeof(FreeBlock);
28351c0b2f7Stbbdev
waitTillBlockReleased(intptr_t startModifiedCnt)28451c0b2f7Stbbdev inline bool BackendSync::waitTillBlockReleased(intptr_t startModifiedCnt)
28551c0b2f7Stbbdev {
28651c0b2f7Stbbdev AtomicBackoff backoff;
28751c0b2f7Stbbdev #if __TBB_MALLOC_BACKEND_STAT
28851c0b2f7Stbbdev class ITT_Guard {
28951c0b2f7Stbbdev void *ptr;
29051c0b2f7Stbbdev public:
29151c0b2f7Stbbdev ITT_Guard(void *p) : ptr(p) {
29251c0b2f7Stbbdev MALLOC_ITT_SYNC_PREPARE(ptr);
29351c0b2f7Stbbdev }
29451c0b2f7Stbbdev ~ITT_Guard() {
29551c0b2f7Stbbdev MALLOC_ITT_SYNC_ACQUIRED(ptr);
29651c0b2f7Stbbdev }
29751c0b2f7Stbbdev };
29851c0b2f7Stbbdev ITT_Guard ittGuard(&inFlyBlocks);
29951c0b2f7Stbbdev #endif
30025c399f4SŁukasz Plewa intptr_t myBinsInFlyBlocks = inFlyBlocks.load(std::memory_order_acquire);
30125c399f4SŁukasz Plewa intptr_t myCoalescQInFlyBlocks = backend->blocksInCoalescing();
30225c399f4SŁukasz Plewa while (true) {
30357f524caSIlya Isaev MALLOC_ASSERT(myBinsInFlyBlocks>=0 && myCoalescQInFlyBlocks>=0, nullptr);
30425c399f4SŁukasz Plewa
30525c399f4SŁukasz Plewa intptr_t currBinsInFlyBlocks = inFlyBlocks.load(std::memory_order_acquire);
30625c399f4SŁukasz Plewa intptr_t currCoalescQInFlyBlocks = backend->blocksInCoalescing();
30751c0b2f7Stbbdev WhiteboxTestingYield();
30851c0b2f7Stbbdev // Stop waiting iff:
30951c0b2f7Stbbdev
31051c0b2f7Stbbdev // 1) blocks were removed from processing, not added
31151c0b2f7Stbbdev if (myBinsInFlyBlocks > currBinsInFlyBlocks
31251c0b2f7Stbbdev // 2) released during delayed coalescing queue
31351c0b2f7Stbbdev || myCoalescQInFlyBlocks > currCoalescQInFlyBlocks)
31451c0b2f7Stbbdev break;
31551c0b2f7Stbbdev // 3) if there are blocks in coalescing, and no progress in its processing,
31651c0b2f7Stbbdev // try to scan coalescing queue and stop waiting, if changes were made
31751c0b2f7Stbbdev // (if there are no changes and in-fly blocks exist, we continue
31851c0b2f7Stbbdev // waiting to not increase load on coalescQ)
31951c0b2f7Stbbdev if (currCoalescQInFlyBlocks > 0 && backend->scanCoalescQ(/*forceCoalescQDrop=*/false))
32051c0b2f7Stbbdev break;
32151c0b2f7Stbbdev // 4) when there are no blocks
32232d5ec1fSŁukasz Plewa if (!currBinsInFlyBlocks && !currCoalescQInFlyBlocks) {
32351c0b2f7Stbbdev // re-scan make sense only if bins were modified since scanned
32432d5ec1fSŁukasz Plewa auto pool = backend->extMemPool;
32532d5ec1fSŁukasz Plewa if (pool->hardCachesCleanupInProgress.load(std::memory_order_acquire) ||
32632d5ec1fSŁukasz Plewa pool->softCachesCleanupInProgress.load(std::memory_order_acquire)) {
32732d5ec1fSŁukasz Plewa backoff.pause();
32832d5ec1fSŁukasz Plewa continue;
32932d5ec1fSŁukasz Plewa }
33032d5ec1fSŁukasz Plewa
33151c0b2f7Stbbdev return startModifiedCnt != getNumOfMods();
33232d5ec1fSŁukasz Plewa }
33351c0b2f7Stbbdev myBinsInFlyBlocks = currBinsInFlyBlocks;
33451c0b2f7Stbbdev myCoalescQInFlyBlocks = currCoalescQInFlyBlocks;
33525c399f4SŁukasz Plewa backoff.pause();
33651c0b2f7Stbbdev }
33751c0b2f7Stbbdev return true;
33851c0b2f7Stbbdev }
33951c0b2f7Stbbdev
putBlock(FreeBlock * fBlock)34051c0b2f7Stbbdev void CoalRequestQ::putBlock(FreeBlock *fBlock)
34151c0b2f7Stbbdev {
34251c0b2f7Stbbdev MALLOC_ASSERT(fBlock->sizeTmp >= FreeBlock::minBlockSize, ASSERT_TEXT);
34351c0b2f7Stbbdev fBlock->markUsed();
34451c0b2f7Stbbdev // the block is in the queue, do not forget that it's here
34551c0b2f7Stbbdev inFlyBlocks++;
34651c0b2f7Stbbdev
34751c0b2f7Stbbdev FreeBlock *myBlToFree = blocksToFree.load(std::memory_order_acquire);
34851c0b2f7Stbbdev for (;;) {
34951c0b2f7Stbbdev fBlock->nextToFree = myBlToFree;
35051c0b2f7Stbbdev if (blocksToFree.compare_exchange_strong(myBlToFree, fBlock)) {
35151c0b2f7Stbbdev return;
35251c0b2f7Stbbdev }
35351c0b2f7Stbbdev }
35451c0b2f7Stbbdev }
35551c0b2f7Stbbdev
getAll()35651c0b2f7Stbbdev FreeBlock *CoalRequestQ::getAll()
35751c0b2f7Stbbdev {
35851c0b2f7Stbbdev for (;;) {
35951c0b2f7Stbbdev FreeBlock *myBlToFree = blocksToFree.load(std::memory_order_acquire);
36051c0b2f7Stbbdev
36151c0b2f7Stbbdev if (!myBlToFree) {
36257f524caSIlya Isaev return nullptr;
36351c0b2f7Stbbdev } else {
36457f524caSIlya Isaev if (blocksToFree.compare_exchange_strong(myBlToFree, nullptr)) {
36551c0b2f7Stbbdev return myBlToFree;
36651c0b2f7Stbbdev } else {
36751c0b2f7Stbbdev continue;
36851c0b2f7Stbbdev }
36951c0b2f7Stbbdev }
37051c0b2f7Stbbdev }
37151c0b2f7Stbbdev }
37251c0b2f7Stbbdev
blockWasProcessed()37351c0b2f7Stbbdev inline void CoalRequestQ::blockWasProcessed()
37451c0b2f7Stbbdev {
37551c0b2f7Stbbdev bkndSync->binsModified();
37651c0b2f7Stbbdev int prev = inFlyBlocks.fetch_sub(1);
3772110128eSsarathnandu tbb::detail::suppress_unused_warning(prev);
37851c0b2f7Stbbdev MALLOC_ASSERT(prev > 0, ASSERT_TEXT);
37951c0b2f7Stbbdev }
38051c0b2f7Stbbdev
38151c0b2f7Stbbdev // Try to get a block from a bin.
38251c0b2f7Stbbdev // If the remaining free space would stay in the same bin,
38351c0b2f7Stbbdev // split the block without removing it.
38451c0b2f7Stbbdev // If the free space should go to other bin(s), remove the block.
38551c0b2f7Stbbdev // alignedBin is true, if all blocks in the bin have slab-aligned right side.
getFromBin(int binIdx,BackendSync * sync,size_t size,bool needAlignedRes,bool alignedBin,bool wait,int * binLocked)38651c0b2f7Stbbdev FreeBlock *Backend::IndexedBins::getFromBin(int binIdx, BackendSync *sync, size_t size,
38751c0b2f7Stbbdev bool needAlignedRes, bool alignedBin, bool wait, int *binLocked)
38851c0b2f7Stbbdev {
38951c0b2f7Stbbdev Bin *b = &freeBins[binIdx];
39051c0b2f7Stbbdev try_next:
39157f524caSIlya Isaev FreeBlock *fBlock = nullptr;
392478de5b1Stbbdev if (!b->empty()) {
393fc184738SKonstantin Boyarinov bool locked = false;
39451c0b2f7Stbbdev MallocMutex::scoped_lock scopedLock(b->tLock, wait, &locked);
39551c0b2f7Stbbdev
39651c0b2f7Stbbdev if (!locked) {
39751c0b2f7Stbbdev if (binLocked) (*binLocked)++;
39857f524caSIlya Isaev return nullptr;
39951c0b2f7Stbbdev }
40051c0b2f7Stbbdev
401478de5b1Stbbdev for (FreeBlock *curr = b->head.load(std::memory_order_relaxed); curr; curr = curr->next) {
40251c0b2f7Stbbdev size_t szBlock = curr->tryLockBlock();
40351c0b2f7Stbbdev if (!szBlock) {
40451c0b2f7Stbbdev // block is locked, re-do bin lock, as there is no place to spin
40551c0b2f7Stbbdev // while block coalescing
40651c0b2f7Stbbdev goto try_next;
40751c0b2f7Stbbdev }
40851c0b2f7Stbbdev
40951c0b2f7Stbbdev // GENERAL CASE
41051c0b2f7Stbbdev if (alignedBin || !needAlignedRes) {
41151c0b2f7Stbbdev size_t splitSz = szBlock - size;
41251c0b2f7Stbbdev // If we got a block as split result, it must have a room for control structures.
41351c0b2f7Stbbdev if (szBlock >= size && (splitSz >= FreeBlock::minBlockSize || !splitSz))
41451c0b2f7Stbbdev fBlock = curr;
41551c0b2f7Stbbdev } else {
41651c0b2f7Stbbdev // SPECIAL CASE, to get aligned block from unaligned bin we have to cut the middle of a block
41751c0b2f7Stbbdev // and return remaining left and right part. Possible only in fixed pool scenario, assert for this
41851c0b2f7Stbbdev // is set inside splitBlock() function.
41951c0b2f7Stbbdev
42051c0b2f7Stbbdev void *newB = alignUp(curr, slabSize);
42151c0b2f7Stbbdev uintptr_t rightNew = (uintptr_t)newB + size;
42251c0b2f7Stbbdev uintptr_t rightCurr = (uintptr_t)curr + szBlock;
42351c0b2f7Stbbdev // Check if the block size is sufficient,
42451c0b2f7Stbbdev // and also left and right split results are either big enough or non-existent
42551c0b2f7Stbbdev if (rightNew <= rightCurr
42651c0b2f7Stbbdev && (newB == curr || ((uintptr_t)newB - (uintptr_t)curr) >= FreeBlock::minBlockSize)
42751c0b2f7Stbbdev && (rightNew == rightCurr || (rightCurr - rightNew) >= FreeBlock::minBlockSize))
42851c0b2f7Stbbdev fBlock = curr;
42951c0b2f7Stbbdev }
43051c0b2f7Stbbdev
43151c0b2f7Stbbdev if (fBlock) {
43251c0b2f7Stbbdev // consume must be called before result of removing from a bin is visible externally.
43351c0b2f7Stbbdev sync->blockConsumed();
43451c0b2f7Stbbdev // TODO: think about cases when block stays in the same bin
43551c0b2f7Stbbdev b->removeBlock(fBlock);
43651c0b2f7Stbbdev if (freeBins[binIdx].empty())
43751c0b2f7Stbbdev bitMask.set(binIdx, false);
43851c0b2f7Stbbdev fBlock->sizeTmp = szBlock;
43951c0b2f7Stbbdev break;
44051c0b2f7Stbbdev } else { // block size is not valid, search for next block in the bin
44151c0b2f7Stbbdev curr->setMeFree(szBlock);
44251c0b2f7Stbbdev curr->rightNeig(szBlock)->setLeftFree(szBlock);
44351c0b2f7Stbbdev }
44451c0b2f7Stbbdev }
44551c0b2f7Stbbdev }
44651c0b2f7Stbbdev return fBlock;
44751c0b2f7Stbbdev }
44851c0b2f7Stbbdev
tryReleaseRegions(int binIdx,Backend * backend)44951c0b2f7Stbbdev bool Backend::IndexedBins::tryReleaseRegions(int binIdx, Backend *backend)
45051c0b2f7Stbbdev {
45151c0b2f7Stbbdev Bin *b = &freeBins[binIdx];
45257f524caSIlya Isaev FreeBlock *fBlockList = nullptr;
45351c0b2f7Stbbdev
45451c0b2f7Stbbdev // got all blocks from the bin and re-do coalesce on them
45551c0b2f7Stbbdev // to release single-block regions
45651c0b2f7Stbbdev try_next:
457478de5b1Stbbdev if (!b->empty()) {
45851c0b2f7Stbbdev MallocMutex::scoped_lock binLock(b->tLock);
459478de5b1Stbbdev for (FreeBlock *curr = b->head.load(std::memory_order_relaxed); curr; ) {
46051c0b2f7Stbbdev size_t szBlock = curr->tryLockBlock();
46151c0b2f7Stbbdev if (!szBlock)
46251c0b2f7Stbbdev goto try_next;
46351c0b2f7Stbbdev
46451c0b2f7Stbbdev FreeBlock *next = curr->next;
46551c0b2f7Stbbdev
46651c0b2f7Stbbdev b->removeBlock(curr);
46751c0b2f7Stbbdev curr->sizeTmp = szBlock;
46851c0b2f7Stbbdev curr->nextToFree = fBlockList;
46951c0b2f7Stbbdev fBlockList = curr;
47051c0b2f7Stbbdev curr = next;
47151c0b2f7Stbbdev }
47251c0b2f7Stbbdev }
47351c0b2f7Stbbdev return backend->coalescAndPutList(fBlockList, /*forceCoalescQDrop=*/true,
47451c0b2f7Stbbdev /*reportBlocksProcessed=*/false);
47551c0b2f7Stbbdev }
47651c0b2f7Stbbdev
removeBlock(FreeBlock * fBlock)47751c0b2f7Stbbdev void Backend::Bin::removeBlock(FreeBlock *fBlock)
47851c0b2f7Stbbdev {
479478de5b1Stbbdev MALLOC_ASSERT(fBlock->next||fBlock->prev||fBlock== head.load(std::memory_order_relaxed),
48051c0b2f7Stbbdev "Detected that a block is not in the bin.");
481478de5b1Stbbdev if (head.load(std::memory_order_relaxed) == fBlock)
482478de5b1Stbbdev head.store(fBlock->next, std::memory_order_relaxed);
48351c0b2f7Stbbdev if (tail == fBlock)
48451c0b2f7Stbbdev tail = fBlock->prev;
48551c0b2f7Stbbdev if (fBlock->prev)
48651c0b2f7Stbbdev fBlock->prev->next = fBlock->next;
48751c0b2f7Stbbdev if (fBlock->next)
48851c0b2f7Stbbdev fBlock->next->prev = fBlock->prev;
48951c0b2f7Stbbdev }
49051c0b2f7Stbbdev
addBlock(int binIdx,FreeBlock * fBlock,size_t,bool addToTail)49151c0b2f7Stbbdev void Backend::IndexedBins::addBlock(int binIdx, FreeBlock *fBlock, size_t /* blockSz */, bool addToTail)
49251c0b2f7Stbbdev {
49351c0b2f7Stbbdev Bin *b = &freeBins[binIdx];
49451c0b2f7Stbbdev fBlock->myBin = binIdx;
49557f524caSIlya Isaev fBlock->next = fBlock->prev = nullptr;
49651c0b2f7Stbbdev {
49751c0b2f7Stbbdev MallocMutex::scoped_lock scopedLock(b->tLock);
49851c0b2f7Stbbdev if (addToTail) {
49951c0b2f7Stbbdev fBlock->prev = b->tail;
50051c0b2f7Stbbdev b->tail = fBlock;
50151c0b2f7Stbbdev if (fBlock->prev)
50251c0b2f7Stbbdev fBlock->prev->next = fBlock;
503478de5b1Stbbdev if (!b->head.load(std::memory_order_relaxed))
504478de5b1Stbbdev b->head.store(fBlock, std::memory_order_relaxed);
50551c0b2f7Stbbdev } else {
506478de5b1Stbbdev fBlock->next = b->head.load(std::memory_order_relaxed);
507478de5b1Stbbdev b->head.store(fBlock, std::memory_order_relaxed);
50851c0b2f7Stbbdev if (fBlock->next)
50951c0b2f7Stbbdev fBlock->next->prev = fBlock;
51051c0b2f7Stbbdev if (!b->tail)
51151c0b2f7Stbbdev b->tail = fBlock;
51251c0b2f7Stbbdev }
51351c0b2f7Stbbdev }
51451c0b2f7Stbbdev bitMask.set(binIdx, true);
51551c0b2f7Stbbdev }
51651c0b2f7Stbbdev
tryAddBlock(int binIdx,FreeBlock * fBlock,bool addToTail)51751c0b2f7Stbbdev bool Backend::IndexedBins::tryAddBlock(int binIdx, FreeBlock *fBlock, bool addToTail)
51851c0b2f7Stbbdev {
519fc184738SKonstantin Boyarinov bool locked = false;
52051c0b2f7Stbbdev Bin *b = &freeBins[binIdx];
52151c0b2f7Stbbdev fBlock->myBin = binIdx;
52251c0b2f7Stbbdev if (addToTail) {
52357f524caSIlya Isaev fBlock->next = nullptr;
52451c0b2f7Stbbdev {
52551c0b2f7Stbbdev MallocMutex::scoped_lock scopedLock(b->tLock, /*wait=*/false, &locked);
52651c0b2f7Stbbdev if (!locked)
52751c0b2f7Stbbdev return false;
52851c0b2f7Stbbdev fBlock->prev = b->tail;
52951c0b2f7Stbbdev b->tail = fBlock;
53051c0b2f7Stbbdev if (fBlock->prev)
53151c0b2f7Stbbdev fBlock->prev->next = fBlock;
532478de5b1Stbbdev if (!b->head.load(std::memory_order_relaxed))
533478de5b1Stbbdev b->head.store(fBlock, std::memory_order_relaxed);
53451c0b2f7Stbbdev }
53551c0b2f7Stbbdev } else {
53657f524caSIlya Isaev fBlock->prev = nullptr;
53751c0b2f7Stbbdev {
53851c0b2f7Stbbdev MallocMutex::scoped_lock scopedLock(b->tLock, /*wait=*/false, &locked);
53951c0b2f7Stbbdev if (!locked)
54051c0b2f7Stbbdev return false;
541478de5b1Stbbdev fBlock->next = b->head.load(std::memory_order_relaxed);
542478de5b1Stbbdev b->head.store(fBlock, std::memory_order_relaxed);
54351c0b2f7Stbbdev if (fBlock->next)
54451c0b2f7Stbbdev fBlock->next->prev = fBlock;
54551c0b2f7Stbbdev if (!b->tail)
54651c0b2f7Stbbdev b->tail = fBlock;
54751c0b2f7Stbbdev }
54851c0b2f7Stbbdev }
54951c0b2f7Stbbdev bitMask.set(binIdx, true);
55051c0b2f7Stbbdev return true;
55151c0b2f7Stbbdev }
55251c0b2f7Stbbdev
reset()55351c0b2f7Stbbdev void Backend::IndexedBins::reset()
55451c0b2f7Stbbdev {
55551c0b2f7Stbbdev for (unsigned i=0; i<Backend::freeBinsNum; i++)
55651c0b2f7Stbbdev freeBins[i].reset();
55751c0b2f7Stbbdev bitMask.reset();
55851c0b2f7Stbbdev }
55951c0b2f7Stbbdev
lockRemoveBlock(int binIdx,FreeBlock * fBlock)56051c0b2f7Stbbdev void Backend::IndexedBins::lockRemoveBlock(int binIdx, FreeBlock *fBlock)
56151c0b2f7Stbbdev {
56251c0b2f7Stbbdev MallocMutex::scoped_lock scopedLock(freeBins[binIdx].tLock);
56351c0b2f7Stbbdev freeBins[binIdx].removeBlock(fBlock);
56451c0b2f7Stbbdev if (freeBins[binIdx].empty())
56551c0b2f7Stbbdev bitMask.set(binIdx, false);
56651c0b2f7Stbbdev }
56751c0b2f7Stbbdev
regionsAreReleaseable() const56851c0b2f7Stbbdev bool ExtMemoryPool::regionsAreReleaseable() const
56951c0b2f7Stbbdev {
57051c0b2f7Stbbdev return !keepAllMemory && !delayRegsReleasing;
57151c0b2f7Stbbdev }
57251c0b2f7Stbbdev
splitBlock(FreeBlock * fBlock,int num,size_t size,bool blockIsAligned,bool needAlignedBlock)57351c0b2f7Stbbdev FreeBlock *Backend::splitBlock(FreeBlock *fBlock, int num, size_t size, bool blockIsAligned, bool needAlignedBlock)
57451c0b2f7Stbbdev {
57551c0b2f7Stbbdev const size_t totalSize = num * size;
57651c0b2f7Stbbdev
57751c0b2f7Stbbdev // SPECIAL CASE, for unaligned block we have to cut the middle of a block
57851c0b2f7Stbbdev // and return remaining left and right part. Possible only in a fixed pool scenario.
57951c0b2f7Stbbdev if (needAlignedBlock && !blockIsAligned) {
58051c0b2f7Stbbdev MALLOC_ASSERT(extMemPool->fixedPool,
58151c0b2f7Stbbdev "Aligned block request from unaligned bin possible only in fixed pool scenario.");
58251c0b2f7Stbbdev
58351c0b2f7Stbbdev // Space to use is in the middle
58451c0b2f7Stbbdev FreeBlock *newBlock = alignUp(fBlock, slabSize);
58551c0b2f7Stbbdev FreeBlock *rightPart = (FreeBlock*)((uintptr_t)newBlock + totalSize);
58651c0b2f7Stbbdev uintptr_t fBlockEnd = (uintptr_t)fBlock + fBlock->sizeTmp;
58751c0b2f7Stbbdev
58851c0b2f7Stbbdev // Return free right part
58951c0b2f7Stbbdev if ((uintptr_t)rightPart != fBlockEnd) {
59051c0b2f7Stbbdev rightPart->initHeader(); // to prevent coalescing rightPart with fBlock
59151c0b2f7Stbbdev size_t rightSize = fBlockEnd - (uintptr_t)rightPart;
59251c0b2f7Stbbdev coalescAndPut(rightPart, rightSize, toAlignedBin(rightPart, rightSize));
59351c0b2f7Stbbdev }
59451c0b2f7Stbbdev // And free left part
59551c0b2f7Stbbdev if (newBlock != fBlock) {
59651c0b2f7Stbbdev newBlock->initHeader(); // to prevent coalescing fBlock with newB
59751c0b2f7Stbbdev size_t leftSize = (uintptr_t)newBlock - (uintptr_t)fBlock;
59851c0b2f7Stbbdev coalescAndPut(fBlock, leftSize, toAlignedBin(fBlock, leftSize));
59951c0b2f7Stbbdev }
60051c0b2f7Stbbdev fBlock = newBlock;
60151c0b2f7Stbbdev } else if (size_t splitSize = fBlock->sizeTmp - totalSize) { // need to split the block
60251c0b2f7Stbbdev // GENERAL CASE, cut the left or right part of the block
60357f524caSIlya Isaev FreeBlock *splitBlock = nullptr;
60451c0b2f7Stbbdev if (needAlignedBlock) {
60551c0b2f7Stbbdev // For slab aligned blocks cut the right side of the block
60651c0b2f7Stbbdev // and return it to a requester, original block returns to backend
60751c0b2f7Stbbdev splitBlock = fBlock;
60851c0b2f7Stbbdev fBlock = (FreeBlock*)((uintptr_t)splitBlock + splitSize);
60951c0b2f7Stbbdev fBlock->initHeader();
61051c0b2f7Stbbdev } else {
611*c4a799dfSJhaShweta1 // For large object blocks cut original block and put free right part to backend
61251c0b2f7Stbbdev splitBlock = (FreeBlock*)((uintptr_t)fBlock + totalSize);
61351c0b2f7Stbbdev splitBlock->initHeader();
61451c0b2f7Stbbdev }
61551c0b2f7Stbbdev // Mark free block as it`s parent only when the requested type (needAlignedBlock)
61651c0b2f7Stbbdev // and returned from Bins/OS block (isAligned) are equal (XOR operation used)
61751c0b2f7Stbbdev bool markAligned = (blockIsAligned ^ needAlignedBlock) ? toAlignedBin(splitBlock, splitSize) : blockIsAligned;
61851c0b2f7Stbbdev coalescAndPut(splitBlock, splitSize, markAligned);
61951c0b2f7Stbbdev }
62051c0b2f7Stbbdev MALLOC_ASSERT(!needAlignedBlock || isAligned(fBlock, slabSize), "Expect to get aligned block, if one was requested.");
62151c0b2f7Stbbdev FreeBlock::markBlocks(fBlock, num, size);
62251c0b2f7Stbbdev return fBlock;
62351c0b2f7Stbbdev }
62451c0b2f7Stbbdev
getMaxBinnedSize() const62551c0b2f7Stbbdev size_t Backend::getMaxBinnedSize() const
62651c0b2f7Stbbdev {
62751c0b2f7Stbbdev return hugePages.isEnabled && !inUserPool() ?
62851c0b2f7Stbbdev maxBinned_HugePage : maxBinned_SmallPage;
62951c0b2f7Stbbdev }
63051c0b2f7Stbbdev
operator ()(size_t oldMaxReq,size_t requestSize) const63151c0b2f7Stbbdev inline bool Backend::MaxRequestComparator::operator()(size_t oldMaxReq, size_t requestSize) const
63251c0b2f7Stbbdev {
63351c0b2f7Stbbdev return requestSize > oldMaxReq && requestSize < backend->getMaxBinnedSize();
63451c0b2f7Stbbdev }
63551c0b2f7Stbbdev
63651c0b2f7Stbbdev // last chance to get memory
releaseMemInCaches(intptr_t startModifiedCnt,int * lockedBinsThreshold,int numOfLockedBins)63751c0b2f7Stbbdev FreeBlock *Backend::releaseMemInCaches(intptr_t startModifiedCnt,
63851c0b2f7Stbbdev int *lockedBinsThreshold, int numOfLockedBins)
63951c0b2f7Stbbdev {
64051c0b2f7Stbbdev // something released from caches
64132d5ec1fSŁukasz Plewa if (extMemPool->hardCachesCleanup(false))
64251c0b2f7Stbbdev return (FreeBlock*)VALID_BLOCK_IN_BIN;
64332d5ec1fSŁukasz Plewa
64432d5ec1fSŁukasz Plewa if (bkndSync.waitTillBlockReleased(startModifiedCnt))
64532d5ec1fSŁukasz Plewa return (FreeBlock*)VALID_BLOCK_IN_BIN;
64632d5ec1fSŁukasz Plewa
64751c0b2f7Stbbdev // OS can't give us more memory, but we have some in locked bins
64851c0b2f7Stbbdev if (*lockedBinsThreshold && numOfLockedBins) {
64951c0b2f7Stbbdev *lockedBinsThreshold = 0;
65051c0b2f7Stbbdev return (FreeBlock*)VALID_BLOCK_IN_BIN;
65151c0b2f7Stbbdev }
65257f524caSIlya Isaev return nullptr; // nothing found, give up
65351c0b2f7Stbbdev }
65451c0b2f7Stbbdev
askMemFromOS(size_t blockSize,intptr_t startModifiedCnt,int * lockedBinsThreshold,int numOfLockedBins,bool * splittableRet,bool needSlabRegion)65551c0b2f7Stbbdev FreeBlock *Backend::askMemFromOS(size_t blockSize, intptr_t startModifiedCnt,
65651c0b2f7Stbbdev int *lockedBinsThreshold, int numOfLockedBins,
65751c0b2f7Stbbdev bool *splittableRet, bool needSlabRegion)
65851c0b2f7Stbbdev {
65951c0b2f7Stbbdev FreeBlock *block;
66051c0b2f7Stbbdev // The block sizes can be divided into 3 groups:
66151c0b2f7Stbbdev // 1. "quite small": popular object size, we are in bootstarp or something
66251c0b2f7Stbbdev // like; request several regions.
66351c0b2f7Stbbdev // 2. "quite large": we want to have several such blocks in the region
66451c0b2f7Stbbdev // but not want several pre-allocated regions.
66551c0b2f7Stbbdev // 3. "huge": exact fit, we allocate only one block and do not allow
66651c0b2f7Stbbdev // any other allocations to placed in a region.
66751c0b2f7Stbbdev // Dividing the block sizes in these groups we are trying to balance between
66851c0b2f7Stbbdev // too small regions (that leads to fragmentation) and too large ones (that
66951c0b2f7Stbbdev // leads to excessive address space consumption). If a region is "too
67051c0b2f7Stbbdev // large", allocate only one, to prevent fragmentation. It supposedly
67151c0b2f7Stbbdev // doesn't hurt performance, because the object requested by user is large.
67251c0b2f7Stbbdev // Bounds for the groups are:
67351c0b2f7Stbbdev const size_t maxBinned = getMaxBinnedSize();
67451c0b2f7Stbbdev const size_t quiteSmall = maxBinned / 8;
67551c0b2f7Stbbdev const size_t quiteLarge = maxBinned;
67651c0b2f7Stbbdev
67751c0b2f7Stbbdev if (blockSize >= quiteLarge) {
67851c0b2f7Stbbdev // Do not interact with other threads via semaphores, as for exact fit
67951c0b2f7Stbbdev // we can't share regions with them, memory requesting is individual.
68051c0b2f7Stbbdev block = addNewRegion(blockSize, MEMREG_ONE_BLOCK, /*addToBin=*/false);
68151c0b2f7Stbbdev if (!block)
68251c0b2f7Stbbdev return releaseMemInCaches(startModifiedCnt, lockedBinsThreshold, numOfLockedBins);
68351c0b2f7Stbbdev *splittableRet = false;
68451c0b2f7Stbbdev } else {
68551c0b2f7Stbbdev const size_t regSz_sizeBased = alignUp(4*maxRequestedSize, 1024*1024);
68651c0b2f7Stbbdev // Another thread is modifying backend while we can't get the block.
68751c0b2f7Stbbdev // Wait while it leaves and re-do the scan
68851c0b2f7Stbbdev // before trying other ways to extend the backend.
68951c0b2f7Stbbdev if (bkndSync.waitTillBlockReleased(startModifiedCnt)
69051c0b2f7Stbbdev // semaphore is protecting adding more more memory from OS
69151c0b2f7Stbbdev || memExtendingSema.wait())
69251c0b2f7Stbbdev return (FreeBlock*)VALID_BLOCK_IN_BIN;
69351c0b2f7Stbbdev
69451c0b2f7Stbbdev if (startModifiedCnt != bkndSync.getNumOfMods()) {
69551c0b2f7Stbbdev memExtendingSema.signal();
69651c0b2f7Stbbdev return (FreeBlock*)VALID_BLOCK_IN_BIN;
69751c0b2f7Stbbdev }
69851c0b2f7Stbbdev
69951c0b2f7Stbbdev if (blockSize < quiteSmall) {
70051c0b2f7Stbbdev // For this size of blocks, add NUM_OF_REG "advance" regions in bin,
70151c0b2f7Stbbdev // and return one as a result.
70251c0b2f7Stbbdev // TODO: add to bin first, because other threads can use them right away.
70351c0b2f7Stbbdev // This must be done carefully, because blocks in bins can be released
70451c0b2f7Stbbdev // in releaseCachesToLimit().
70551c0b2f7Stbbdev const unsigned NUM_OF_REG = 3;
70651c0b2f7Stbbdev MemRegionType regType = needSlabRegion ? MEMREG_SLAB_BLOCKS : MEMREG_LARGE_BLOCKS;
70751c0b2f7Stbbdev block = addNewRegion(regSz_sizeBased, regType, /*addToBin=*/false);
70851c0b2f7Stbbdev if (block)
70951c0b2f7Stbbdev for (unsigned idx=0; idx<NUM_OF_REG; idx++)
71051c0b2f7Stbbdev if (! addNewRegion(regSz_sizeBased, regType, /*addToBin=*/true))
71151c0b2f7Stbbdev break;
71251c0b2f7Stbbdev } else {
71351c0b2f7Stbbdev block = addNewRegion(regSz_sizeBased, MEMREG_LARGE_BLOCKS, /*addToBin=*/false);
71451c0b2f7Stbbdev }
71551c0b2f7Stbbdev memExtendingSema.signal();
71651c0b2f7Stbbdev
71751c0b2f7Stbbdev // no regions found, try to clean cache
71851c0b2f7Stbbdev if (!block || block == (FreeBlock*)VALID_BLOCK_IN_BIN)
71951c0b2f7Stbbdev return releaseMemInCaches(startModifiedCnt, lockedBinsThreshold, numOfLockedBins);
72051c0b2f7Stbbdev // Since a region can hold more than one block it can be split.
72151c0b2f7Stbbdev *splittableRet = true;
72251c0b2f7Stbbdev }
72351c0b2f7Stbbdev // after asking memory from OS, release caches if we above the memory limits
72451c0b2f7Stbbdev releaseCachesToLimit();
72551c0b2f7Stbbdev
72651c0b2f7Stbbdev return block;
72751c0b2f7Stbbdev }
72851c0b2f7Stbbdev
releaseCachesToLimit()72951c0b2f7Stbbdev void Backend::releaseCachesToLimit()
73051c0b2f7Stbbdev {
73151c0b2f7Stbbdev if (!memSoftLimit.load(std::memory_order_relaxed)
73251c0b2f7Stbbdev || totalMemSize.load(std::memory_order_relaxed) <= memSoftLimit.load(std::memory_order_relaxed)) {
73351c0b2f7Stbbdev return;
73451c0b2f7Stbbdev }
73551c0b2f7Stbbdev size_t locTotalMemSize, locMemSoftLimit;
73651c0b2f7Stbbdev
73751c0b2f7Stbbdev scanCoalescQ(/*forceCoalescQDrop=*/false);
73851c0b2f7Stbbdev if (extMemPool->softCachesCleanup() &&
73951c0b2f7Stbbdev (locTotalMemSize = totalMemSize.load(std::memory_order_acquire)) <=
74051c0b2f7Stbbdev (locMemSoftLimit = memSoftLimit.load(std::memory_order_acquire)))
74151c0b2f7Stbbdev return;
74251c0b2f7Stbbdev // clean global large-object cache, if this is not enough, clean local caches
74351c0b2f7Stbbdev // do this in several tries, because backend fragmentation can prevent
74451c0b2f7Stbbdev // region from releasing
74551c0b2f7Stbbdev for (int cleanLocal = 0; cleanLocal<2; cleanLocal++)
74651c0b2f7Stbbdev while (cleanLocal ?
74751c0b2f7Stbbdev extMemPool->allLocalCaches.cleanup(/*cleanOnlyUnused=*/true) :
74851c0b2f7Stbbdev extMemPool->loc.decreasingCleanup())
74951c0b2f7Stbbdev if ((locTotalMemSize = totalMemSize.load(std::memory_order_acquire)) <=
75051c0b2f7Stbbdev (locMemSoftLimit = memSoftLimit.load(std::memory_order_acquire)))
75151c0b2f7Stbbdev return;
75251c0b2f7Stbbdev // last chance to match memSoftLimit
75332d5ec1fSŁukasz Plewa extMemPool->hardCachesCleanup(true);
75451c0b2f7Stbbdev }
75551c0b2f7Stbbdev
getMinNonemptyBin(unsigned startBin) const75651c0b2f7Stbbdev int Backend::IndexedBins::getMinNonemptyBin(unsigned startBin) const
75751c0b2f7Stbbdev {
75851c0b2f7Stbbdev int p = bitMask.getMinTrue(startBin);
75951c0b2f7Stbbdev return p == -1 ? Backend::freeBinsNum : p;
76051c0b2f7Stbbdev }
76151c0b2f7Stbbdev
findBlock(int nativeBin,BackendSync * sync,size_t size,bool needAlignedBlock,bool alignedBin,int * numOfLockedBins)76251c0b2f7Stbbdev FreeBlock *Backend::IndexedBins::findBlock(int nativeBin, BackendSync *sync, size_t size,
76351c0b2f7Stbbdev bool needAlignedBlock, bool alignedBin, int *numOfLockedBins)
76451c0b2f7Stbbdev {
7652110128eSsarathnandu for (int i=getMinNonemptyBin(nativeBin); i<(int)freeBinsNum; i=getMinNonemptyBin(i+1))
76651c0b2f7Stbbdev if (FreeBlock *block = getFromBin(i, sync, size, needAlignedBlock, alignedBin, /*wait=*/false, numOfLockedBins))
76751c0b2f7Stbbdev return block;
76851c0b2f7Stbbdev
76957f524caSIlya Isaev return nullptr;
77051c0b2f7Stbbdev }
77151c0b2f7Stbbdev
requestBootstrapMem()77251c0b2f7Stbbdev void Backend::requestBootstrapMem()
77351c0b2f7Stbbdev {
77451c0b2f7Stbbdev if (bootsrapMemDone == bootsrapMemStatus.load(std::memory_order_acquire))
77551c0b2f7Stbbdev return;
77651c0b2f7Stbbdev MallocMutex::scoped_lock lock( bootsrapMemStatusMutex );
77751c0b2f7Stbbdev if (bootsrapMemDone == bootsrapMemStatus)
77851c0b2f7Stbbdev return;
77951c0b2f7Stbbdev MALLOC_ASSERT(bootsrapMemNotDone == bootsrapMemStatus, ASSERT_TEXT);
78051c0b2f7Stbbdev bootsrapMemStatus = bootsrapMemInitializing;
78151c0b2f7Stbbdev // request some rather big region during bootstrap in advance
78257f524caSIlya Isaev // ok to get nullptr here, as later we re-do a request with more modest size
78351c0b2f7Stbbdev addNewRegion(2*1024*1024, MEMREG_SLAB_BLOCKS, /*addToBin=*/true);
78451c0b2f7Stbbdev bootsrapMemStatus = bootsrapMemDone;
78551c0b2f7Stbbdev }
78651c0b2f7Stbbdev
78751c0b2f7Stbbdev // try to allocate size Byte block in available bins
78851c0b2f7Stbbdev // needAlignedRes is true if result must be slab-aligned
genericGetBlock(int num,size_t size,bool needAlignedBlock)78951c0b2f7Stbbdev FreeBlock *Backend::genericGetBlock(int num, size_t size, bool needAlignedBlock)
79051c0b2f7Stbbdev {
79157f524caSIlya Isaev FreeBlock *block = nullptr;
79251c0b2f7Stbbdev const size_t totalReqSize = num*size;
79351c0b2f7Stbbdev // no splitting after requesting new region, asks exact size
79451c0b2f7Stbbdev const int nativeBin = sizeToBin(totalReqSize);
79551c0b2f7Stbbdev
79651c0b2f7Stbbdev requestBootstrapMem();
79751c0b2f7Stbbdev // If we found 2 or less locked bins, it's time to ask more memory from OS.
79851c0b2f7Stbbdev // But nothing can be asked from fixed pool. And we prefer wait, not ask
79951c0b2f7Stbbdev // for more memory, if block is quite large.
80051c0b2f7Stbbdev int lockedBinsThreshold = extMemPool->fixedPool || size>=maxBinned_SmallPage? 0 : 2;
80151c0b2f7Stbbdev
80251c0b2f7Stbbdev // Find maximal requested size limited by getMaxBinnedSize()
80351c0b2f7Stbbdev AtomicUpdate(maxRequestedSize, totalReqSize, MaxRequestComparator(this));
80451c0b2f7Stbbdev scanCoalescQ(/*forceCoalescQDrop=*/false);
80551c0b2f7Stbbdev
80651c0b2f7Stbbdev bool splittable = true;
80751c0b2f7Stbbdev for (;;) {
80851c0b2f7Stbbdev const intptr_t startModifiedCnt = bkndSync.getNumOfMods();
80951c0b2f7Stbbdev int numOfLockedBins;
81032d5ec1fSŁukasz Plewa intptr_t cleanCnt;
81151c0b2f7Stbbdev do {
81232d5ec1fSŁukasz Plewa cleanCnt = backendCleanCnt.load(std::memory_order_acquire);
81351c0b2f7Stbbdev numOfLockedBins = 0;
81451c0b2f7Stbbdev if (needAlignedBlock) {
81551c0b2f7Stbbdev block = freeSlabAlignedBins.findBlock(nativeBin, &bkndSync, num*size, needAlignedBlock,
81651c0b2f7Stbbdev /*alignedBin=*/true, &numOfLockedBins);
81751c0b2f7Stbbdev if (!block && extMemPool->fixedPool)
81851c0b2f7Stbbdev block = freeLargeBlockBins.findBlock(nativeBin, &bkndSync, num*size, needAlignedBlock,
81951c0b2f7Stbbdev /*alignedBin=*/false, &numOfLockedBins);
82051c0b2f7Stbbdev } else {
82151c0b2f7Stbbdev block = freeLargeBlockBins.findBlock(nativeBin, &bkndSync, num*size, needAlignedBlock,
82251c0b2f7Stbbdev /*alignedBin=*/false, &numOfLockedBins);
82351c0b2f7Stbbdev if (!block && extMemPool->fixedPool)
82451c0b2f7Stbbdev block = freeSlabAlignedBins.findBlock(nativeBin, &bkndSync, num*size, needAlignedBlock,
82551c0b2f7Stbbdev /*alignedBin=*/true, &numOfLockedBins);
82651c0b2f7Stbbdev }
82732d5ec1fSŁukasz Plewa } while (!block && (numOfLockedBins>lockedBinsThreshold || cleanCnt % 2 == 1 ||
82832d5ec1fSŁukasz Plewa cleanCnt != backendCleanCnt.load(std::memory_order_acquire)));
82951c0b2f7Stbbdev
83051c0b2f7Stbbdev if (block)
83151c0b2f7Stbbdev break;
83251c0b2f7Stbbdev
833a96a032fSVladislav Shchapov bool retScanCoalescQ = scanCoalescQ(/*forceCoalescQDrop=*/true);
834a96a032fSVladislav Shchapov bool retSoftCachesCleanup = extMemPool->softCachesCleanup();
835a96a032fSVladislav Shchapov if (!(retScanCoalescQ || retSoftCachesCleanup)) {
83651c0b2f7Stbbdev // bins are not updated,
83751c0b2f7Stbbdev // only remaining possibility is to ask for more memory
83851c0b2f7Stbbdev block = askMemFromOS(totalReqSize, startModifiedCnt, &lockedBinsThreshold,
83951c0b2f7Stbbdev numOfLockedBins, &splittable, needAlignedBlock);
84051c0b2f7Stbbdev if (!block)
84157f524caSIlya Isaev return nullptr;
84251c0b2f7Stbbdev if (block != (FreeBlock*)VALID_BLOCK_IN_BIN) {
84351c0b2f7Stbbdev // size can be increased in askMemFromOS, that's why >=
84451c0b2f7Stbbdev MALLOC_ASSERT(block->sizeTmp >= size, ASSERT_TEXT);
84551c0b2f7Stbbdev break;
84651c0b2f7Stbbdev }
84751c0b2f7Stbbdev // valid block somewhere in bins, let's find it
84857f524caSIlya Isaev block = nullptr;
84951c0b2f7Stbbdev }
85051c0b2f7Stbbdev }
85151c0b2f7Stbbdev MALLOC_ASSERT(block, ASSERT_TEXT);
85251c0b2f7Stbbdev if (splittable) {
85351c0b2f7Stbbdev // At this point we have to be sure that slabAligned attribute describes the right block state
85451c0b2f7Stbbdev block = splitBlock(block, num, size, block->slabAligned, needAlignedBlock);
85551c0b2f7Stbbdev }
85651c0b2f7Stbbdev // matched blockConsumed() from startUseBlock()
85751c0b2f7Stbbdev bkndSync.blockReleased();
85851c0b2f7Stbbdev
85951c0b2f7Stbbdev return block;
86051c0b2f7Stbbdev }
86151c0b2f7Stbbdev
getLargeBlock(size_t size)86251c0b2f7Stbbdev LargeMemoryBlock *Backend::getLargeBlock(size_t size)
86351c0b2f7Stbbdev {
86451c0b2f7Stbbdev LargeMemoryBlock *lmb =
86551c0b2f7Stbbdev (LargeMemoryBlock*)genericGetBlock(1, size, /*needAlignedRes=*/false);
86651c0b2f7Stbbdev if (lmb) {
86751c0b2f7Stbbdev lmb->unalignedSize = size;
86851c0b2f7Stbbdev if (extMemPool->userPool())
86951c0b2f7Stbbdev extMemPool->lmbList.add(lmb);
87051c0b2f7Stbbdev }
87151c0b2f7Stbbdev return lmb;
87251c0b2f7Stbbdev }
87351c0b2f7Stbbdev
getSlabBlock(int num)87451c0b2f7Stbbdev BlockI *Backend::getSlabBlock(int num) {
87551c0b2f7Stbbdev BlockI *b = (BlockI*)genericGetBlock(num, slabSize, /*slabAligned=*/true);
87651c0b2f7Stbbdev MALLOC_ASSERT(isAligned(b, slabSize), ASSERT_TEXT);
87751c0b2f7Stbbdev return b;
87851c0b2f7Stbbdev }
87951c0b2f7Stbbdev
putSlabBlock(BlockI * block)88051c0b2f7Stbbdev void Backend::putSlabBlock(BlockI *block) {
88151c0b2f7Stbbdev genericPutBlock((FreeBlock *)block, slabSize, /*slabAligned=*/true);
88251c0b2f7Stbbdev }
88351c0b2f7Stbbdev
getBackRefSpace(size_t size,bool * rawMemUsed)88451c0b2f7Stbbdev void *Backend::getBackRefSpace(size_t size, bool *rawMemUsed)
88551c0b2f7Stbbdev {
88651c0b2f7Stbbdev // This block is released only at shutdown, so it can prevent
88751c0b2f7Stbbdev // a entire region releasing when it's received from the backend,
88851c0b2f7Stbbdev // so prefer getRawMemory using.
88951c0b2f7Stbbdev if (void *ret = getRawMemory(size, REGULAR)) {
89051c0b2f7Stbbdev *rawMemUsed = true;
89151c0b2f7Stbbdev return ret;
89251c0b2f7Stbbdev }
89351c0b2f7Stbbdev void *ret = genericGetBlock(1, size, /*needAlignedRes=*/false);
89451c0b2f7Stbbdev if (ret) *rawMemUsed = false;
89551c0b2f7Stbbdev return ret;
89651c0b2f7Stbbdev }
89751c0b2f7Stbbdev
putBackRefSpace(void * b,size_t size,bool rawMemUsed)89851c0b2f7Stbbdev void Backend::putBackRefSpace(void *b, size_t size, bool rawMemUsed)
89951c0b2f7Stbbdev {
90051c0b2f7Stbbdev if (rawMemUsed)
90151c0b2f7Stbbdev freeRawMemory(b, size);
90251c0b2f7Stbbdev // ignore not raw mem, as it released on region releasing
90351c0b2f7Stbbdev }
90451c0b2f7Stbbdev
removeBlockFromBin(FreeBlock * fBlock)90551c0b2f7Stbbdev void Backend::removeBlockFromBin(FreeBlock *fBlock)
90651c0b2f7Stbbdev {
90751c0b2f7Stbbdev if (fBlock->myBin != Backend::NO_BIN) {
90851c0b2f7Stbbdev if (fBlock->slabAligned)
90951c0b2f7Stbbdev freeSlabAlignedBins.lockRemoveBlock(fBlock->myBin, fBlock);
91051c0b2f7Stbbdev else
91151c0b2f7Stbbdev freeLargeBlockBins.lockRemoveBlock(fBlock->myBin, fBlock);
91251c0b2f7Stbbdev }
91351c0b2f7Stbbdev }
91451c0b2f7Stbbdev
genericPutBlock(FreeBlock * fBlock,size_t blockSz,bool slabAligned)91551c0b2f7Stbbdev void Backend::genericPutBlock(FreeBlock *fBlock, size_t blockSz, bool slabAligned)
91651c0b2f7Stbbdev {
91751c0b2f7Stbbdev bkndSync.blockConsumed();
91851c0b2f7Stbbdev coalescAndPut(fBlock, blockSz, slabAligned);
91951c0b2f7Stbbdev bkndSync.blockReleased();
92051c0b2f7Stbbdev }
92151c0b2f7Stbbdev
add(LargeMemoryBlock * lmb)92251c0b2f7Stbbdev void AllLargeBlocksList::add(LargeMemoryBlock *lmb)
92351c0b2f7Stbbdev {
92451c0b2f7Stbbdev MallocMutex::scoped_lock scoped_cs(largeObjLock);
92557f524caSIlya Isaev lmb->gPrev = nullptr;
92651c0b2f7Stbbdev lmb->gNext = loHead;
92751c0b2f7Stbbdev if (lmb->gNext)
92851c0b2f7Stbbdev lmb->gNext->gPrev = lmb;
92951c0b2f7Stbbdev loHead = lmb;
93051c0b2f7Stbbdev }
93151c0b2f7Stbbdev
remove(LargeMemoryBlock * lmb)93251c0b2f7Stbbdev void AllLargeBlocksList::remove(LargeMemoryBlock *lmb)
93351c0b2f7Stbbdev {
93451c0b2f7Stbbdev MallocMutex::scoped_lock scoped_cs(largeObjLock);
93551c0b2f7Stbbdev if (loHead == lmb)
93651c0b2f7Stbbdev loHead = lmb->gNext;
93751c0b2f7Stbbdev if (lmb->gNext)
93851c0b2f7Stbbdev lmb->gNext->gPrev = lmb->gPrev;
93951c0b2f7Stbbdev if (lmb->gPrev)
94051c0b2f7Stbbdev lmb->gPrev->gNext = lmb->gNext;
94151c0b2f7Stbbdev }
94251c0b2f7Stbbdev
putLargeBlock(LargeMemoryBlock * lmb)94351c0b2f7Stbbdev void Backend::putLargeBlock(LargeMemoryBlock *lmb)
94451c0b2f7Stbbdev {
94551c0b2f7Stbbdev if (extMemPool->userPool())
94651c0b2f7Stbbdev extMemPool->lmbList.remove(lmb);
94751c0b2f7Stbbdev genericPutBlock((FreeBlock *)lmb, lmb->unalignedSize, false);
94851c0b2f7Stbbdev }
94951c0b2f7Stbbdev
returnLargeObject(LargeMemoryBlock * lmb)95051c0b2f7Stbbdev void Backend::returnLargeObject(LargeMemoryBlock *lmb)
95151c0b2f7Stbbdev {
95251c0b2f7Stbbdev removeBackRef(lmb->backRefIdx);
95351c0b2f7Stbbdev putLargeBlock(lmb);
95451c0b2f7Stbbdev STAT_increment(getThreadId(), ThreadCommonCounters, freeLargeObj);
95551c0b2f7Stbbdev }
95651c0b2f7Stbbdev
95751c0b2f7Stbbdev #if BACKEND_HAS_MREMAP
remap(void * ptr,size_t oldSize,size_t newSize,size_t alignment)95851c0b2f7Stbbdev void *Backend::remap(void *ptr, size_t oldSize, size_t newSize, size_t alignment)
95951c0b2f7Stbbdev {
96051c0b2f7Stbbdev // no remap for user pools and for object too small that living in bins
96151c0b2f7Stbbdev if (inUserPool() || min(oldSize, newSize)<maxBinned_SmallPage
96251c0b2f7Stbbdev // during remap, can't guarantee alignment more strict than current or
96351c0b2f7Stbbdev // more strict than page alignment
96451c0b2f7Stbbdev || !isAligned(ptr, alignment) || alignment>extMemPool->granularity)
96557f524caSIlya Isaev return nullptr;
96651c0b2f7Stbbdev const LargeMemoryBlock* lmbOld = ((LargeObjectHdr *)ptr - 1)->memoryBlock;
96751c0b2f7Stbbdev const size_t oldUnalignedSize = lmbOld->unalignedSize;
96851c0b2f7Stbbdev FreeBlock *oldFBlock = (FreeBlock *)lmbOld;
96951c0b2f7Stbbdev FreeBlock *right = oldFBlock->rightNeig(oldUnalignedSize);
97051c0b2f7Stbbdev // in every region only one block can have LAST_REGION_BLOCK on right,
97151c0b2f7Stbbdev // so don't need no synchronization
97251c0b2f7Stbbdev if (!right->isLastRegionBlock())
97357f524caSIlya Isaev return nullptr;
97451c0b2f7Stbbdev
97551c0b2f7Stbbdev MemRegion *oldRegion = static_cast<LastFreeBlock*>(right)->memRegion;
97651c0b2f7Stbbdev MALLOC_ASSERT( oldRegion < ptr, ASSERT_TEXT );
97751c0b2f7Stbbdev const size_t oldRegionSize = oldRegion->allocSz;
97851c0b2f7Stbbdev if (oldRegion->type != MEMREG_ONE_BLOCK)
97957f524caSIlya Isaev return nullptr; // we are not single in the region
98051c0b2f7Stbbdev const size_t userOffset = (uintptr_t)ptr - (uintptr_t)oldRegion;
98151c0b2f7Stbbdev const size_t alignedSize = LargeObjectCache::alignToBin(newSize + userOffset);
98251c0b2f7Stbbdev const size_t requestSize =
98351c0b2f7Stbbdev alignUp(sizeof(MemRegion) + alignedSize + sizeof(LastFreeBlock), extMemPool->granularity);
98451c0b2f7Stbbdev if (requestSize < alignedSize) // is wrapped around?
98557f524caSIlya Isaev return nullptr;
98651c0b2f7Stbbdev regionList.remove(oldRegion);
98751c0b2f7Stbbdev
9884df48f98SAlex // The deallocation should be registered in address range before mremap to
9894df48f98SAlex // prevent a race condition with allocation on another thread.
9904df48f98SAlex // (OS can reuse the memory and registerAlloc will be missed on another thread)
9914df48f98SAlex usedAddrRange.registerFree((uintptr_t)oldRegion, (uintptr_t)oldRegion + oldRegionSize);
9924df48f98SAlex
99351c0b2f7Stbbdev void *ret = mremap(oldRegion, oldRegion->allocSz, requestSize, MREMAP_MAYMOVE);
99451c0b2f7Stbbdev if (MAP_FAILED == ret) { // can't remap, revert and leave
99551c0b2f7Stbbdev regionList.add(oldRegion);
9964df48f98SAlex usedAddrRange.registerAlloc((uintptr_t)oldRegion, (uintptr_t)oldRegion + oldRegionSize);
99757f524caSIlya Isaev return nullptr;
99851c0b2f7Stbbdev }
99951c0b2f7Stbbdev MemRegion *region = (MemRegion*)ret;
100051c0b2f7Stbbdev MALLOC_ASSERT(region->type == MEMREG_ONE_BLOCK, ASSERT_TEXT);
100151c0b2f7Stbbdev region->allocSz = requestSize;
100251c0b2f7Stbbdev region->blockSz = alignedSize;
100351c0b2f7Stbbdev
100451c0b2f7Stbbdev FreeBlock *fBlock = (FreeBlock *)alignUp((uintptr_t)region + sizeof(MemRegion),
100551c0b2f7Stbbdev largeObjectAlignment);
100651c0b2f7Stbbdev
100751c0b2f7Stbbdev regionList.add(region);
100851c0b2f7Stbbdev startUseBlock(region, fBlock, /*addToBin=*/false);
100951c0b2f7Stbbdev MALLOC_ASSERT(fBlock->sizeTmp == region->blockSz, ASSERT_TEXT);
101051c0b2f7Stbbdev // matched blockConsumed() in startUseBlock().
101151c0b2f7Stbbdev // TODO: get rid of useless pair blockConsumed()/blockReleased()
101251c0b2f7Stbbdev bkndSync.blockReleased();
101351c0b2f7Stbbdev
101451c0b2f7Stbbdev // object must start at same offset from region's start
101551c0b2f7Stbbdev void *object = (void*)((uintptr_t)region + userOffset);
101651c0b2f7Stbbdev MALLOC_ASSERT(isAligned(object, alignment), ASSERT_TEXT);
101751c0b2f7Stbbdev LargeObjectHdr *header = (LargeObjectHdr*)object - 1;
101851c0b2f7Stbbdev setBackRef(header->backRefIdx, header);
101951c0b2f7Stbbdev
102051c0b2f7Stbbdev LargeMemoryBlock *lmb = (LargeMemoryBlock*)fBlock;
102151c0b2f7Stbbdev lmb->unalignedSize = region->blockSz;
102251c0b2f7Stbbdev lmb->objectSize = newSize;
102351c0b2f7Stbbdev lmb->backRefIdx = header->backRefIdx;
102451c0b2f7Stbbdev header->memoryBlock = lmb;
102551c0b2f7Stbbdev MALLOC_ASSERT((uintptr_t)lmb + lmb->unalignedSize >=
102651c0b2f7Stbbdev (uintptr_t)object + lmb->objectSize, "An object must fit to the block.");
102751c0b2f7Stbbdev
102851c0b2f7Stbbdev usedAddrRange.registerAlloc((uintptr_t)region, (uintptr_t)region + requestSize);
102951c0b2f7Stbbdev totalMemSize.fetch_add(region->allocSz - oldRegionSize);
103051c0b2f7Stbbdev
103151c0b2f7Stbbdev return object;
103251c0b2f7Stbbdev }
103351c0b2f7Stbbdev #endif /* BACKEND_HAS_MREMAP */
103451c0b2f7Stbbdev
releaseRegion(MemRegion * memRegion)103551c0b2f7Stbbdev void Backend::releaseRegion(MemRegion *memRegion)
103651c0b2f7Stbbdev {
103751c0b2f7Stbbdev regionList.remove(memRegion);
103851c0b2f7Stbbdev freeRawMem(memRegion, memRegion->allocSz);
103951c0b2f7Stbbdev }
104051c0b2f7Stbbdev
104151c0b2f7Stbbdev // coalesce fBlock with its neighborhood
doCoalesc(FreeBlock * fBlock,MemRegion ** mRegion)104251c0b2f7Stbbdev FreeBlock *Backend::doCoalesc(FreeBlock *fBlock, MemRegion **mRegion)
104351c0b2f7Stbbdev {
104451c0b2f7Stbbdev FreeBlock *resBlock = fBlock;
104551c0b2f7Stbbdev size_t resSize = fBlock->sizeTmp;
104657f524caSIlya Isaev MemRegion *memRegion = nullptr;
104751c0b2f7Stbbdev
104851c0b2f7Stbbdev fBlock->markCoalescing(resSize);
104951c0b2f7Stbbdev resBlock->blockInBin = false;
105051c0b2f7Stbbdev
105151c0b2f7Stbbdev // coalescing with left neighbor
105251c0b2f7Stbbdev size_t leftSz = fBlock->trySetLeftUsed(GuardedSize::COAL_BLOCK);
105351c0b2f7Stbbdev if (leftSz != GuardedSize::LOCKED) {
105451c0b2f7Stbbdev if (leftSz == GuardedSize::COAL_BLOCK) {
105551c0b2f7Stbbdev coalescQ.putBlock(fBlock);
105657f524caSIlya Isaev return nullptr;
105751c0b2f7Stbbdev } else {
105851c0b2f7Stbbdev FreeBlock *left = fBlock->leftNeig(leftSz);
105951c0b2f7Stbbdev size_t lSz = left->trySetMeUsed(GuardedSize::COAL_BLOCK);
106051c0b2f7Stbbdev if (lSz <= GuardedSize::MAX_LOCKED_VAL) {
106151c0b2f7Stbbdev fBlock->setLeftFree(leftSz); // rollback
106251c0b2f7Stbbdev coalescQ.putBlock(fBlock);
106357f524caSIlya Isaev return nullptr;
106451c0b2f7Stbbdev } else {
106551c0b2f7Stbbdev MALLOC_ASSERT(lSz == leftSz, "Invalid header");
106651c0b2f7Stbbdev left->blockInBin = true;
106751c0b2f7Stbbdev resBlock = left;
106851c0b2f7Stbbdev resSize += leftSz;
106951c0b2f7Stbbdev resBlock->sizeTmp = resSize;
107051c0b2f7Stbbdev }
107151c0b2f7Stbbdev }
107251c0b2f7Stbbdev }
107351c0b2f7Stbbdev // coalescing with right neighbor
107451c0b2f7Stbbdev FreeBlock *right = fBlock->rightNeig(fBlock->sizeTmp);
107551c0b2f7Stbbdev size_t rightSz = right->trySetMeUsed(GuardedSize::COAL_BLOCK);
107651c0b2f7Stbbdev if (rightSz != GuardedSize::LOCKED) {
107751c0b2f7Stbbdev // LastFreeBlock is on the right side
107851c0b2f7Stbbdev if (GuardedSize::LAST_REGION_BLOCK == rightSz) {
107951c0b2f7Stbbdev right->setMeFree(GuardedSize::LAST_REGION_BLOCK);
108051c0b2f7Stbbdev memRegion = static_cast<LastFreeBlock*>(right)->memRegion;
108151c0b2f7Stbbdev } else if (GuardedSize::COAL_BLOCK == rightSz) {
108251c0b2f7Stbbdev if (resBlock->blockInBin) {
108351c0b2f7Stbbdev resBlock->blockInBin = false;
108451c0b2f7Stbbdev removeBlockFromBin(resBlock);
108551c0b2f7Stbbdev }
108651c0b2f7Stbbdev coalescQ.putBlock(resBlock);
108757f524caSIlya Isaev return nullptr;
108851c0b2f7Stbbdev } else {
108951c0b2f7Stbbdev size_t rSz = right->rightNeig(rightSz)->
109051c0b2f7Stbbdev trySetLeftUsed(GuardedSize::COAL_BLOCK);
109151c0b2f7Stbbdev if (rSz <= GuardedSize::MAX_LOCKED_VAL) {
109251c0b2f7Stbbdev right->setMeFree(rightSz); // rollback
109351c0b2f7Stbbdev if (resBlock->blockInBin) {
109451c0b2f7Stbbdev resBlock->blockInBin = false;
109551c0b2f7Stbbdev removeBlockFromBin(resBlock);
109651c0b2f7Stbbdev }
109751c0b2f7Stbbdev coalescQ.putBlock(resBlock);
109857f524caSIlya Isaev return nullptr;
109951c0b2f7Stbbdev } else {
110051c0b2f7Stbbdev MALLOC_ASSERT(rSz == rightSz, "Invalid header");
110151c0b2f7Stbbdev removeBlockFromBin(right);
110251c0b2f7Stbbdev resSize += rightSz;
110351c0b2f7Stbbdev
110451c0b2f7Stbbdev // Is LastFreeBlock on the right side of right?
110551c0b2f7Stbbdev FreeBlock *nextRight = right->rightNeig(rightSz);
110651c0b2f7Stbbdev size_t nextRightSz = nextRight->
110751c0b2f7Stbbdev trySetMeUsed(GuardedSize::COAL_BLOCK);
110851c0b2f7Stbbdev if (nextRightSz > GuardedSize::MAX_LOCKED_VAL) {
110951c0b2f7Stbbdev if (nextRightSz == GuardedSize::LAST_REGION_BLOCK)
111051c0b2f7Stbbdev memRegion = static_cast<LastFreeBlock*>(nextRight)->memRegion;
111151c0b2f7Stbbdev
111251c0b2f7Stbbdev nextRight->setMeFree(nextRightSz);
111351c0b2f7Stbbdev }
111451c0b2f7Stbbdev }
111551c0b2f7Stbbdev }
111651c0b2f7Stbbdev }
111751c0b2f7Stbbdev if (memRegion) {
111851c0b2f7Stbbdev MALLOC_ASSERT((uintptr_t)memRegion + memRegion->allocSz >=
111951c0b2f7Stbbdev (uintptr_t)right + sizeof(LastFreeBlock), ASSERT_TEXT);
112051c0b2f7Stbbdev MALLOC_ASSERT((uintptr_t)memRegion < (uintptr_t)resBlock, ASSERT_TEXT);
112151c0b2f7Stbbdev *mRegion = memRegion;
112251c0b2f7Stbbdev } else
112357f524caSIlya Isaev *mRegion = nullptr;
112451c0b2f7Stbbdev resBlock->sizeTmp = resSize;
112551c0b2f7Stbbdev return resBlock;
112651c0b2f7Stbbdev }
112751c0b2f7Stbbdev
coalescAndPutList(FreeBlock * list,bool forceCoalescQDrop,bool reportBlocksProcessed)112851c0b2f7Stbbdev bool Backend::coalescAndPutList(FreeBlock *list, bool forceCoalescQDrop, bool reportBlocksProcessed)
112951c0b2f7Stbbdev {
113051c0b2f7Stbbdev bool regionReleased = false;
113151c0b2f7Stbbdev
113251c0b2f7Stbbdev for (FreeBlock *helper; list;
113351c0b2f7Stbbdev list = helper,
113451c0b2f7Stbbdev // matches block enqueue in CoalRequestQ::putBlock()
113551c0b2f7Stbbdev reportBlocksProcessed? coalescQ.blockWasProcessed() : (void)0) {
113651c0b2f7Stbbdev MemRegion *memRegion;
113751c0b2f7Stbbdev bool addToTail = false;
113851c0b2f7Stbbdev
113951c0b2f7Stbbdev helper = list->nextToFree;
114051c0b2f7Stbbdev FreeBlock *toRet = doCoalesc(list, &memRegion);
114151c0b2f7Stbbdev if (!toRet)
114251c0b2f7Stbbdev continue;
114351c0b2f7Stbbdev
114451c0b2f7Stbbdev if (memRegion && memRegion->blockSz == toRet->sizeTmp
114551c0b2f7Stbbdev && !extMemPool->fixedPool) {
114651c0b2f7Stbbdev if (extMemPool->regionsAreReleaseable()) {
114751c0b2f7Stbbdev // release the region, because there is no used blocks in it
114851c0b2f7Stbbdev if (toRet->blockInBin)
114951c0b2f7Stbbdev removeBlockFromBin(toRet);
115051c0b2f7Stbbdev releaseRegion(memRegion);
115151c0b2f7Stbbdev regionReleased = true;
115251c0b2f7Stbbdev continue;
115351c0b2f7Stbbdev } else // add block from empty region to end of bin,
115451c0b2f7Stbbdev addToTail = true; // preserving for exact fit
115551c0b2f7Stbbdev }
115651c0b2f7Stbbdev size_t currSz = toRet->sizeTmp;
115751c0b2f7Stbbdev int bin = sizeToBin(currSz);
115851c0b2f7Stbbdev bool toAligned = extMemPool->fixedPool ? toAlignedBin(toRet, currSz) : toRet->slabAligned;
115951c0b2f7Stbbdev bool needAddToBin = true;
116051c0b2f7Stbbdev
116151c0b2f7Stbbdev if (toRet->blockInBin) {
116251c0b2f7Stbbdev // Does it stay in same bin?
116351c0b2f7Stbbdev if (toRet->myBin == bin && toRet->slabAligned == toAligned)
116451c0b2f7Stbbdev needAddToBin = false;
116551c0b2f7Stbbdev else {
116651c0b2f7Stbbdev toRet->blockInBin = false;
116751c0b2f7Stbbdev removeBlockFromBin(toRet);
116851c0b2f7Stbbdev }
116951c0b2f7Stbbdev }
117051c0b2f7Stbbdev
117151c0b2f7Stbbdev // Does not stay in same bin, or bin-less; add it
117251c0b2f7Stbbdev if (needAddToBin) {
117357f524caSIlya Isaev toRet->prev = toRet->next = toRet->nextToFree = nullptr;
117451c0b2f7Stbbdev toRet->myBin = NO_BIN;
117551c0b2f7Stbbdev toRet->slabAligned = toAligned;
117651c0b2f7Stbbdev
117751c0b2f7Stbbdev // If the block is too small to fit in any bin, keep it bin-less.
117851c0b2f7Stbbdev // It's not a leak because the block later can be coalesced.
117951c0b2f7Stbbdev if (currSz >= minBinnedSize) {
118051c0b2f7Stbbdev toRet->sizeTmp = currSz;
118151c0b2f7Stbbdev IndexedBins *target = toRet->slabAligned ? &freeSlabAlignedBins : &freeLargeBlockBins;
118251c0b2f7Stbbdev if (forceCoalescQDrop) {
118351c0b2f7Stbbdev target->addBlock(bin, toRet, toRet->sizeTmp, addToTail);
118451c0b2f7Stbbdev } else if (!target->tryAddBlock(bin, toRet, addToTail)) {
118551c0b2f7Stbbdev coalescQ.putBlock(toRet);
118651c0b2f7Stbbdev continue;
118751c0b2f7Stbbdev }
118851c0b2f7Stbbdev }
118951c0b2f7Stbbdev toRet->sizeTmp = 0;
119051c0b2f7Stbbdev }
119151c0b2f7Stbbdev // Free (possibly coalesced) free block.
119251c0b2f7Stbbdev // Adding to bin must be done before this point,
119351c0b2f7Stbbdev // because after a block is free it can be coalesced, and
119451c0b2f7Stbbdev // using its pointer became unsafe.
119551c0b2f7Stbbdev // Remember that coalescing is not done under any global lock.
119651c0b2f7Stbbdev toRet->setMeFree(currSz);
119751c0b2f7Stbbdev toRet->rightNeig(currSz)->setLeftFree(currSz);
119851c0b2f7Stbbdev }
119951c0b2f7Stbbdev return regionReleased;
120051c0b2f7Stbbdev }
120151c0b2f7Stbbdev
120251c0b2f7Stbbdev // Coalesce fBlock and add it back to a bin;
120351c0b2f7Stbbdev // processing delayed coalescing requests.
coalescAndPut(FreeBlock * fBlock,size_t blockSz,bool slabAligned)120451c0b2f7Stbbdev void Backend::coalescAndPut(FreeBlock *fBlock, size_t blockSz, bool slabAligned)
120551c0b2f7Stbbdev {
120651c0b2f7Stbbdev fBlock->sizeTmp = blockSz;
120757f524caSIlya Isaev fBlock->nextToFree = nullptr;
120851c0b2f7Stbbdev fBlock->slabAligned = slabAligned;
120951c0b2f7Stbbdev
121051c0b2f7Stbbdev coalescAndPutList(fBlock, /*forceCoalescQDrop=*/false, /*reportBlocksProcessed=*/false);
121151c0b2f7Stbbdev }
121251c0b2f7Stbbdev
scanCoalescQ(bool forceCoalescQDrop)121351c0b2f7Stbbdev bool Backend::scanCoalescQ(bool forceCoalescQDrop)
121451c0b2f7Stbbdev {
121551c0b2f7Stbbdev FreeBlock *currCoalescList = coalescQ.getAll();
121651c0b2f7Stbbdev
121751c0b2f7Stbbdev if (currCoalescList)
121851c0b2f7Stbbdev // reportBlocksProcessed=true informs that the blocks leave coalescQ,
121951c0b2f7Stbbdev // matches blockConsumed() from CoalRequestQ::putBlock()
122051c0b2f7Stbbdev coalescAndPutList(currCoalescList, forceCoalescQDrop,
122151c0b2f7Stbbdev /*reportBlocksProcessed=*/true);
122251c0b2f7Stbbdev // returns status of coalescQ.getAll(), as an indication of possible changes in backend
122351c0b2f7Stbbdev // TODO: coalescAndPutList() may report is some new free blocks became available or not
122451c0b2f7Stbbdev return currCoalescList;
122551c0b2f7Stbbdev }
122651c0b2f7Stbbdev
findBlockInRegion(MemRegion * region,size_t exactBlockSize)122751c0b2f7Stbbdev FreeBlock *Backend::findBlockInRegion(MemRegion *region, size_t exactBlockSize)
122851c0b2f7Stbbdev {
122951c0b2f7Stbbdev FreeBlock *fBlock;
123051c0b2f7Stbbdev size_t blockSz;
123151c0b2f7Stbbdev uintptr_t fBlockEnd,
123251c0b2f7Stbbdev lastFreeBlock = (uintptr_t)region + region->allocSz - sizeof(LastFreeBlock);
123351c0b2f7Stbbdev
123451c0b2f7Stbbdev static_assert(sizeof(LastFreeBlock) % sizeof(uintptr_t) == 0,
123551c0b2f7Stbbdev "Atomic applied on LastFreeBlock, and we put it at the end of region, that"
123651c0b2f7Stbbdev " is uintptr_t-aligned, so no unaligned atomic operations are possible.");
123751c0b2f7Stbbdev // right bound is slab-aligned, keep LastFreeBlock after it
123851c0b2f7Stbbdev if (region->type == MEMREG_SLAB_BLOCKS) {
123951c0b2f7Stbbdev fBlock = (FreeBlock *)alignUp((uintptr_t)region + sizeof(MemRegion), sizeof(uintptr_t));
124051c0b2f7Stbbdev fBlockEnd = alignDown(lastFreeBlock, slabSize);
124151c0b2f7Stbbdev } else {
124251c0b2f7Stbbdev fBlock = (FreeBlock *)alignUp((uintptr_t)region + sizeof(MemRegion), largeObjectAlignment);
124351c0b2f7Stbbdev fBlockEnd = (uintptr_t)fBlock + exactBlockSize;
124451c0b2f7Stbbdev MALLOC_ASSERT(fBlockEnd <= lastFreeBlock, ASSERT_TEXT);
124551c0b2f7Stbbdev }
124651c0b2f7Stbbdev if (fBlockEnd <= (uintptr_t)fBlock)
124757f524caSIlya Isaev return nullptr; // allocSz is too small
124851c0b2f7Stbbdev blockSz = fBlockEnd - (uintptr_t)fBlock;
124951c0b2f7Stbbdev // TODO: extend getSlabBlock to support degradation, i.e. getting less blocks
125051c0b2f7Stbbdev // then requested, and then relax this check
125151c0b2f7Stbbdev // (now all or nothing is implemented, check according to this)
125251c0b2f7Stbbdev if (blockSz < numOfSlabAllocOnMiss*slabSize)
125357f524caSIlya Isaev return nullptr;
125451c0b2f7Stbbdev
125551c0b2f7Stbbdev region->blockSz = blockSz;
125651c0b2f7Stbbdev return fBlock;
125751c0b2f7Stbbdev }
125851c0b2f7Stbbdev
125951c0b2f7Stbbdev // startUseBlock may add the free block to a bin, the block can be used and
126051c0b2f7Stbbdev // even released after this, so the region must be added to regionList already
startUseBlock(MemRegion * region,FreeBlock * fBlock,bool addToBin)126151c0b2f7Stbbdev void Backend::startUseBlock(MemRegion *region, FreeBlock *fBlock, bool addToBin)
126251c0b2f7Stbbdev {
126351c0b2f7Stbbdev size_t blockSz = region->blockSz;
126451c0b2f7Stbbdev fBlock->initHeader();
126551c0b2f7Stbbdev fBlock->setMeFree(blockSz);
126651c0b2f7Stbbdev
126751c0b2f7Stbbdev LastFreeBlock *lastBl = static_cast<LastFreeBlock*>(fBlock->rightNeig(blockSz));
126851c0b2f7Stbbdev // to not get unaligned atomics during LastFreeBlock access
126957f524caSIlya Isaev MALLOC_ASSERT(isAligned(lastBl, sizeof(uintptr_t)), nullptr);
127051c0b2f7Stbbdev lastBl->initHeader();
127151c0b2f7Stbbdev lastBl->setMeFree(GuardedSize::LAST_REGION_BLOCK);
127251c0b2f7Stbbdev lastBl->setLeftFree(blockSz);
127351c0b2f7Stbbdev lastBl->myBin = NO_BIN;
127451c0b2f7Stbbdev lastBl->memRegion = region;
127551c0b2f7Stbbdev
127651c0b2f7Stbbdev if (addToBin) {
127751c0b2f7Stbbdev unsigned targetBin = sizeToBin(blockSz);
127851c0b2f7Stbbdev // during adding advance regions, register bin for a largest block in region
127951c0b2f7Stbbdev advRegBins.registerBin(targetBin);
128051c0b2f7Stbbdev if (region->type == MEMREG_SLAB_BLOCKS) {
128151c0b2f7Stbbdev fBlock->slabAligned = true;
128251c0b2f7Stbbdev freeSlabAlignedBins.addBlock(targetBin, fBlock, blockSz, /*addToTail=*/false);
128351c0b2f7Stbbdev } else {
128451c0b2f7Stbbdev fBlock->slabAligned = false;
128551c0b2f7Stbbdev freeLargeBlockBins.addBlock(targetBin, fBlock, blockSz, /*addToTail=*/false);
128651c0b2f7Stbbdev }
128751c0b2f7Stbbdev } else {
128851c0b2f7Stbbdev // to match with blockReleased() in genericGetBlock
128951c0b2f7Stbbdev bkndSync.blockConsumed();
129051c0b2f7Stbbdev // Understand our alignment for correct splitBlock operation
129151c0b2f7Stbbdev fBlock->slabAligned = region->type == MEMREG_SLAB_BLOCKS ? true : false;
129251c0b2f7Stbbdev fBlock->sizeTmp = fBlock->tryLockBlock();
129351c0b2f7Stbbdev MALLOC_ASSERT(fBlock->sizeTmp >= FreeBlock::minBlockSize, "Locking must be successful");
129451c0b2f7Stbbdev }
129551c0b2f7Stbbdev }
129651c0b2f7Stbbdev
add(MemRegion * r)129751c0b2f7Stbbdev void MemRegionList::add(MemRegion *r)
129851c0b2f7Stbbdev {
129957f524caSIlya Isaev r->prev = nullptr;
130051c0b2f7Stbbdev MallocMutex::scoped_lock lock(regionListLock);
130151c0b2f7Stbbdev r->next = head;
130251c0b2f7Stbbdev head = r;
130351c0b2f7Stbbdev if (head->next)
130451c0b2f7Stbbdev head->next->prev = head;
130551c0b2f7Stbbdev }
130651c0b2f7Stbbdev
remove(MemRegion * r)130751c0b2f7Stbbdev void MemRegionList::remove(MemRegion *r)
130851c0b2f7Stbbdev {
130951c0b2f7Stbbdev MallocMutex::scoped_lock lock(regionListLock);
131051c0b2f7Stbbdev if (head == r)
131151c0b2f7Stbbdev head = head->next;
131251c0b2f7Stbbdev if (r->next)
131351c0b2f7Stbbdev r->next->prev = r->prev;
131451c0b2f7Stbbdev if (r->prev)
131551c0b2f7Stbbdev r->prev->next = r->next;
131651c0b2f7Stbbdev }
131751c0b2f7Stbbdev
131851c0b2f7Stbbdev #if __TBB_MALLOC_BACKEND_STAT
reportStat(FILE * f)131951c0b2f7Stbbdev int MemRegionList::reportStat(FILE *f)
132051c0b2f7Stbbdev {
132151c0b2f7Stbbdev int regNum = 0;
132251c0b2f7Stbbdev MallocMutex::scoped_lock lock(regionListLock);
132351c0b2f7Stbbdev for (MemRegion *curr = head; curr; curr = curr->next) {
132451c0b2f7Stbbdev fprintf(f, "%p: max block %lu B, ", curr, curr->blockSz);
132551c0b2f7Stbbdev regNum++;
132651c0b2f7Stbbdev }
132751c0b2f7Stbbdev return regNum;
132851c0b2f7Stbbdev }
132951c0b2f7Stbbdev #endif
133051c0b2f7Stbbdev
addNewRegion(size_t size,MemRegionType memRegType,bool addToBin)133151c0b2f7Stbbdev FreeBlock *Backend::addNewRegion(size_t size, MemRegionType memRegType, bool addToBin)
133251c0b2f7Stbbdev {
133351c0b2f7Stbbdev static_assert(sizeof(BlockMutexes) <= sizeof(BlockI), "Header must be not overwritten in used blocks");
133451c0b2f7Stbbdev MALLOC_ASSERT(FreeBlock::minBlockSize > GuardedSize::MAX_SPEC_VAL,
133551c0b2f7Stbbdev "Block length must not conflict with special values of GuardedSize");
133651c0b2f7Stbbdev // If the region is not "for slabs" we should reserve some space for
133751c0b2f7Stbbdev // a region header, the worst case alignment and the last block mark.
133851c0b2f7Stbbdev const size_t requestSize = memRegType == MEMREG_SLAB_BLOCKS ? size :
133951c0b2f7Stbbdev size + sizeof(MemRegion) + largeObjectAlignment
134051c0b2f7Stbbdev + FreeBlock::minBlockSize + sizeof(LastFreeBlock);
134151c0b2f7Stbbdev
134251c0b2f7Stbbdev size_t rawSize = requestSize;
134351c0b2f7Stbbdev MemRegion *region = (MemRegion*)allocRawMem(rawSize);
134451c0b2f7Stbbdev if (!region) {
134551c0b2f7Stbbdev MALLOC_ASSERT(rawSize==requestSize, "getRawMem has not allocated memory but changed the allocated size.");
134657f524caSIlya Isaev return nullptr;
134751c0b2f7Stbbdev }
134851c0b2f7Stbbdev if (rawSize < sizeof(MemRegion)) {
134951c0b2f7Stbbdev if (!extMemPool->fixedPool)
135051c0b2f7Stbbdev freeRawMem(region, rawSize);
135157f524caSIlya Isaev return nullptr;
135251c0b2f7Stbbdev }
135351c0b2f7Stbbdev
135451c0b2f7Stbbdev region->type = memRegType;
135551c0b2f7Stbbdev region->allocSz = rawSize;
135651c0b2f7Stbbdev FreeBlock *fBlock = findBlockInRegion(region, size);
135751c0b2f7Stbbdev if (!fBlock) {
135851c0b2f7Stbbdev if (!extMemPool->fixedPool)
135951c0b2f7Stbbdev freeRawMem(region, rawSize);
136057f524caSIlya Isaev return nullptr;
136151c0b2f7Stbbdev }
136251c0b2f7Stbbdev regionList.add(region);
136351c0b2f7Stbbdev startUseBlock(region, fBlock, addToBin);
136451c0b2f7Stbbdev bkndSync.binsModified();
136551c0b2f7Stbbdev return addToBin? (FreeBlock*)VALID_BLOCK_IN_BIN : fBlock;
136651c0b2f7Stbbdev }
136751c0b2f7Stbbdev
init(ExtMemoryPool * extMemoryPool)136851c0b2f7Stbbdev void Backend::init(ExtMemoryPool *extMemoryPool)
136951c0b2f7Stbbdev {
137051c0b2f7Stbbdev extMemPool = extMemoryPool;
137151c0b2f7Stbbdev usedAddrRange.init();
137251c0b2f7Stbbdev coalescQ.init(&bkndSync);
137351c0b2f7Stbbdev bkndSync.init(this);
137451c0b2f7Stbbdev }
137551c0b2f7Stbbdev
reset()137651c0b2f7Stbbdev void Backend::reset()
137751c0b2f7Stbbdev {
137851c0b2f7Stbbdev MALLOC_ASSERT(extMemPool->userPool(), "Only user pool can be reset.");
137951c0b2f7Stbbdev // no active threads are allowed in backend while reset() called
138051c0b2f7Stbbdev verify();
138151c0b2f7Stbbdev
138251c0b2f7Stbbdev freeLargeBlockBins.reset();
138351c0b2f7Stbbdev freeSlabAlignedBins.reset();
138451c0b2f7Stbbdev advRegBins.reset();
138551c0b2f7Stbbdev
138651c0b2f7Stbbdev for (MemRegion *curr = regionList.head; curr; curr = curr->next) {
138751c0b2f7Stbbdev FreeBlock *fBlock = findBlockInRegion(curr, curr->blockSz);
138851c0b2f7Stbbdev MALLOC_ASSERT(fBlock, "A memory region unexpectedly got smaller");
138951c0b2f7Stbbdev startUseBlock(curr, fBlock, /*addToBin=*/true);
139051c0b2f7Stbbdev }
139151c0b2f7Stbbdev }
139251c0b2f7Stbbdev
destroy()139351c0b2f7Stbbdev bool Backend::destroy()
139451c0b2f7Stbbdev {
139551c0b2f7Stbbdev bool noError = true;
139651c0b2f7Stbbdev // no active threads are allowed in backend while destroy() called
139751c0b2f7Stbbdev verify();
139851c0b2f7Stbbdev if (!inUserPool()) {
139951c0b2f7Stbbdev freeLargeBlockBins.reset();
140051c0b2f7Stbbdev freeSlabAlignedBins.reset();
140151c0b2f7Stbbdev }
140251c0b2f7Stbbdev while (regionList.head) {
140351c0b2f7Stbbdev MemRegion *helper = regionList.head->next;
140451c0b2f7Stbbdev noError &= freeRawMem(regionList.head, regionList.head->allocSz);
140551c0b2f7Stbbdev regionList.head = helper;
140651c0b2f7Stbbdev }
140751c0b2f7Stbbdev return noError;
140851c0b2f7Stbbdev }
140951c0b2f7Stbbdev
clean()141051c0b2f7Stbbdev bool Backend::clean()
141151c0b2f7Stbbdev {
141251c0b2f7Stbbdev scanCoalescQ(/*forceCoalescQDrop=*/false);
141332d5ec1fSŁukasz Plewa // Backend::clean is always called under synchronization so only one thread can
141432d5ec1fSŁukasz Plewa // enter to this method at once.
141532d5ec1fSŁukasz Plewa // backendCleanCnt%2== 1 means that clean operation is in progress
141632d5ec1fSŁukasz Plewa backendCleanCnt.fetch_add(1, std::memory_order_acq_rel);
141751c0b2f7Stbbdev bool res = false;
141851c0b2f7Stbbdev // We can have several blocks occupying a whole region,
141951c0b2f7Stbbdev // because such regions are added in advance (see askMemFromOS() and reset()),
142051c0b2f7Stbbdev // and never used. Release them all.
142151c0b2f7Stbbdev for (int i = advRegBins.getMinUsedBin(0); i != -1; i = advRegBins.getMinUsedBin(i+1)) {
142251c0b2f7Stbbdev if (i == freeSlabAlignedBins.getMinNonemptyBin(i))
142351c0b2f7Stbbdev res |= freeSlabAlignedBins.tryReleaseRegions(i, this);
142451c0b2f7Stbbdev if (i == freeLargeBlockBins.getMinNonemptyBin(i))
142551c0b2f7Stbbdev res |= freeLargeBlockBins.tryReleaseRegions(i, this);
142651c0b2f7Stbbdev }
142732d5ec1fSŁukasz Plewa backendCleanCnt.fetch_add(1, std::memory_order_acq_rel);
142851c0b2f7Stbbdev return res;
142951c0b2f7Stbbdev }
143051c0b2f7Stbbdev
verify()143151c0b2f7Stbbdev void Backend::IndexedBins::verify()
143251c0b2f7Stbbdev {
1433478de5b1Stbbdev #if MALLOC_DEBUG
14342110128eSsarathnandu for (int i=0; i<(int)freeBinsNum; i++) {
1435478de5b1Stbbdev for (FreeBlock *fb = freeBins[i].head.load(std::memory_order_relaxed); fb; fb=fb->next) {
143651c0b2f7Stbbdev uintptr_t mySz = fb->myL.value;
143751c0b2f7Stbbdev MALLOC_ASSERT(mySz>GuardedSize::MAX_SPEC_VAL, ASSERT_TEXT);
143851c0b2f7Stbbdev FreeBlock *right = (FreeBlock*)((uintptr_t)fb + mySz);
143951c0b2f7Stbbdev suppress_unused_warning(right);
144051c0b2f7Stbbdev MALLOC_ASSERT(right->myL.value<=GuardedSize::MAX_SPEC_VAL, ASSERT_TEXT);
144151c0b2f7Stbbdev MALLOC_ASSERT(right->leftL.value==mySz, ASSERT_TEXT);
144251c0b2f7Stbbdev MALLOC_ASSERT(fb->leftL.value<=GuardedSize::MAX_SPEC_VAL, ASSERT_TEXT);
144351c0b2f7Stbbdev }
144451c0b2f7Stbbdev }
1445478de5b1Stbbdev #endif
144651c0b2f7Stbbdev }
144751c0b2f7Stbbdev
144851c0b2f7Stbbdev // For correct operation, it must be called when no other threads
144951c0b2f7Stbbdev // is changing backend.
verify()145051c0b2f7Stbbdev void Backend::verify()
145151c0b2f7Stbbdev {
145251c0b2f7Stbbdev #if MALLOC_DEBUG
145351c0b2f7Stbbdev scanCoalescQ(/*forceCoalescQDrop=*/false);
1454478de5b1Stbbdev #endif // MALLOC_DEBUG
145551c0b2f7Stbbdev
145651c0b2f7Stbbdev freeLargeBlockBins.verify();
145751c0b2f7Stbbdev freeSlabAlignedBins.verify();
145851c0b2f7Stbbdev }
145951c0b2f7Stbbdev
146051c0b2f7Stbbdev #if __TBB_MALLOC_BACKEND_STAT
countFreeBlocks()146151c0b2f7Stbbdev size_t Backend::Bin::countFreeBlocks()
146251c0b2f7Stbbdev {
146351c0b2f7Stbbdev size_t cnt = 0;
146451c0b2f7Stbbdev {
146551c0b2f7Stbbdev MallocMutex::scoped_lock lock(tLock);
146651c0b2f7Stbbdev for (FreeBlock *fb = head; fb; fb = fb->next)
146751c0b2f7Stbbdev cnt++;
146851c0b2f7Stbbdev }
146951c0b2f7Stbbdev return cnt;
147051c0b2f7Stbbdev }
147151c0b2f7Stbbdev
reportFreeBlocks(FILE * f)147251c0b2f7Stbbdev size_t Backend::Bin::reportFreeBlocks(FILE *f)
147351c0b2f7Stbbdev {
147451c0b2f7Stbbdev size_t totalSz = 0;
147551c0b2f7Stbbdev MallocMutex::scoped_lock lock(tLock);
147651c0b2f7Stbbdev for (FreeBlock *fb = head; fb; fb = fb->next) {
147751c0b2f7Stbbdev size_t sz = fb->tryLockBlock();
147851c0b2f7Stbbdev fb->setMeFree(sz);
1479c7865c1bSŁukasz Plewa fb->rightNeig(sz)->setLeftFree(sz);
148051c0b2f7Stbbdev fprintf(f, " [%p;%p]", fb, (void*)((uintptr_t)fb+sz));
148151c0b2f7Stbbdev totalSz += sz;
148251c0b2f7Stbbdev }
148351c0b2f7Stbbdev return totalSz;
148451c0b2f7Stbbdev }
148551c0b2f7Stbbdev
reportStat(FILE * f)148651c0b2f7Stbbdev void Backend::IndexedBins::reportStat(FILE *f)
148751c0b2f7Stbbdev {
148851c0b2f7Stbbdev size_t totalSize = 0;
148951c0b2f7Stbbdev
149051c0b2f7Stbbdev for (int i=0; i<Backend::freeBinsNum; i++)
149151c0b2f7Stbbdev if (size_t cnt = freeBins[i].countFreeBlocks()) {
149251c0b2f7Stbbdev totalSize += freeBins[i].reportFreeBlocks(f);
149351c0b2f7Stbbdev fprintf(f, " %d:%lu, ", i, cnt);
149451c0b2f7Stbbdev }
149551c0b2f7Stbbdev fprintf(f, "\ttotal size %lu KB", totalSize/1024);
149651c0b2f7Stbbdev }
149751c0b2f7Stbbdev
reportStat(FILE * f)149851c0b2f7Stbbdev void Backend::reportStat(FILE *f)
149951c0b2f7Stbbdev {
150051c0b2f7Stbbdev scanCoalescQ(/*forceCoalescQDrop=*/false);
150151c0b2f7Stbbdev
150251c0b2f7Stbbdev fprintf(f, "\n regions:\n");
150351c0b2f7Stbbdev int regNum = regionList.reportStat(f);
150451c0b2f7Stbbdev fprintf(f, "\n%d regions, %lu KB in all regions\n free bins:\nlarge bins: ",
150551c0b2f7Stbbdev regNum, totalMemSize/1024);
150651c0b2f7Stbbdev freeLargeBlockBins.reportStat(f);
150751c0b2f7Stbbdev fprintf(f, "\naligned bins: ");
150851c0b2f7Stbbdev freeSlabAlignedBins.reportStat(f);
150951c0b2f7Stbbdev fprintf(f, "\n");
151051c0b2f7Stbbdev }
151151c0b2f7Stbbdev #endif // __TBB_MALLOC_BACKEND_STAT
151251c0b2f7Stbbdev
151351c0b2f7Stbbdev } } // namespaces
1514