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