Files
vortex/runtime/common/malloc.h
Blaise Tine c1e168fdbe Vortex 2.0 changes:
+ Microarchitecture optimizations
+ 64-bit support
+ Xilinx FPGA support
+ LLVM-16 support
+ Refactoring and quality control fixes

minor update

minor update

minor update

minor update

minor update

minor update

cleanup

cleanup

cache bindings and memory perf refactory

minor update

minor update

hw unit tests fixes

minor update

minor update

minor update

minor update

minor update

minor udpate

minor update

minor update

minor update

minor update

minor update

minor update

minor update

minor updates

minor updates

minor update

minor update

minor update

minor update

minor update

minor update

minor updates

minor updates

minor updates

minor updates

minor update

minor update
2023-11-10 02:47:05 -08:00

455 lines
14 KiB
C++

// Copyright © 2019-2023
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <cstdint>
#include <assert.h>
#include <stdio.h>
namespace vortex {
class MemoryAllocator {
public:
MemoryAllocator(
uint64_t baseAddress,
uint64_t capacity,
uint32_t pageAlign,
uint32_t blockAlign)
: baseAddress_(baseAddress)
, capacity_(capacity)
, pageAlign_(pageAlign)
, blockAlign_(blockAlign)
, pages_(nullptr)
, nextAddress_(0)
, allocated_(0)
{}
~MemoryAllocator() {
// Free allocated pages
page_t* currPage = pages_;
while (currPage) {
auto nextPage = currPage->next;
this->DeletePage(currPage);
currPage = nextPage;
}
}
uint32_t baseAddress() const {
return baseAddress_;
}
uint32_t capacity() const {
return capacity_;
}
uint64_t free() const {
return (capacity_ - allocated_);
}
uint64_t allocated() const {
return allocated_;
}
int allocate(uint64_t size, uint64_t* addr) {
if (size == 0 || addr == nullptr) {
printf("error: invalid argurments\n");
return -1;
}
// Align allocation size
size = AlignSize(size, blockAlign_);
// Walk thru all pages to find a free block
block_t* freeBlock = nullptr;
auto currPage = pages_;
while (currPage) {
auto currBlock = currPage->freeSList;
if (currBlock) {
// The free S-list is already sorted with the largest block first
// Quick check if the head block has enough space.
if (currBlock->size >= size) {
// Find the smallest matching block in the S-list
while (currBlock->nextFreeS
&& (currBlock->nextFreeS->size >= size)) {
currBlock = currBlock->nextFreeS;
}
// Return the free block
freeBlock = currBlock;
break;
}
}
currPage = currPage->next;
}
if (nullptr == freeBlock) {
// Allocate a new page for this request
currPage = this->NewPage(size);
if (nullptr == currPage) {
printf("error: out of memory\n");
return -1;
}
freeBlock = currPage->freeSList;
}
// Remove the block from the free lists
assert(freeBlock->size >= size);
currPage->RemoveFreeMList(freeBlock);
currPage->RemoveFreeSList(freeBlock);
// If the free block we have found is larger than what we are looking for,
// we may be able to split our free block in two.
uint64_t extraBytes = freeBlock->size - size;
if (extraBytes >= blockAlign_) {
// Reduce the free block size to the requested value
freeBlock->size = size;
// Allocate a new block to contain the extra buffer
auto nextAddr = freeBlock->addr + size;
auto newBlock = new block_t(nextAddr, extraBytes);
// Add the new block to the free lists
currPage->InsertFreeMList(newBlock);
currPage->InsertFreeSList(newBlock);
}
// Insert the free block into the used list
currPage->InsertUsedList(freeBlock);
// Return the free block address
*addr = baseAddress_ + freeBlock->addr;
// Update allocated size
allocated_ += size;
return 0;
}
int release(uint64_t addr) {
// Walk all pages to find the pointer
uint64_t local_addr = addr - baseAddress_;
block_t* usedBlock = nullptr;
auto currPage = pages_;
while (currPage) {
if (local_addr >= currPage->addr
&& local_addr < (currPage->addr + currPage->size)) {
auto currBlock = currPage->usedList;
while (currBlock) {
if (currBlock->addr == local_addr) {
usedBlock = currBlock;
break;
}
currBlock = currBlock->nextUsed;
}
break;
}
currPage = currPage->next;
}
// found the corresponding block?
if (nullptr == usedBlock) {
printf("error: invalid address to release: 0x%lx\n", addr);
return -1;
}
auto size = usedBlock->size;
// Remove the block from the used list
currPage->RemoveUsedList(usedBlock);
// Insert the block into the free M-list.
currPage->InsertFreeMList(usedBlock);
// Check if we can merge adjacent free blocks from the left.
if (usedBlock->prevFreeM) {
// Calculate the previous address
auto prevAddr = usedBlock->prevFreeM->addr + usedBlock->prevFreeM->size;
if (usedBlock->addr == prevAddr) {
auto prevBlock = usedBlock->prevFreeM;
// Merge the blocks to the left
prevBlock->size += usedBlock->size;
prevBlock->nextFreeM = usedBlock->nextFreeM;
if (prevBlock->nextFreeM) {
prevBlock->nextFreeM->prevFreeM = prevBlock;
}
// Detach previous block from the free S-list since size increased
currPage->RemoveFreeSList(prevBlock);
// reset usedBlock
delete usedBlock;
usedBlock = prevBlock;
}
}
// Check if we can merge adjacent free blocks from the right.
if (usedBlock->nextFreeM) {
// Calculate the next allocation start address
auto nextAddr = usedBlock->addr + usedBlock->size;
if (usedBlock->nextFreeM->addr == nextAddr) {
auto nextBlock = usedBlock->nextFreeM;
// Merge the blocks to the right
usedBlock->size += nextBlock->size;
usedBlock->nextFreeM = nextBlock->nextFreeM;
if (usedBlock->nextFreeM) {
usedBlock->nextFreeM->prevFreeM = usedBlock;
}
// Delete next block
currPage->RemoveFreeSList(nextBlock);
delete nextBlock;
}
}
// Insert the block into the free S-list.
currPage->InsertFreeSList(usedBlock);
// Check if we can free empty pages
if (nullptr == currPage->usedList) {
// Try to delete the page
while (currPage && this->DeletePage(currPage)) {
currPage = this->FindNextEmptyPage();
}
}
// update allocated size
allocated_ -= size;
return 0;
}
private:
struct block_t {
block_t* nextFreeS;
block_t* prevFreeS;
block_t* nextFreeM;
block_t* prevFreeM;
block_t* nextUsed;
block_t* prevUsed;
uint64_t addr;
uint64_t size;
block_t(uint64_t addr, uint64_t size)
: nextFreeS(nullptr)
, prevFreeS(nullptr)
, nextFreeM(nullptr)
, prevFreeM(nullptr)
, nextUsed(nullptr)
, prevUsed(nullptr)
, addr(addr)
, size(size)
{}
};
struct page_t {
page_t* next;
// List of used blocks
block_t* usedList;
// List with blocks sorted by descreasing sizes
// Used for block lookup during memory allocation.
block_t* freeSList;
// List with blocks sorted by increasing memory addresses
// Used for block merging during memory release.
block_t* freeMList;
uint64_t addr;
uint64_t size;
page_t(uint64_t addr, uint64_t size) :
next(nullptr),
usedList(nullptr),
addr(addr),
size(size) {
freeSList = freeMList = new block_t(addr, size);
}
void InsertUsedList(block_t* block) {
block->nextUsed = usedList;
if (usedList) {
usedList->prevUsed = block;
}
usedList = block;
}
void RemoveUsedList(block_t* block) {
if (block->prevUsed) {
block->prevUsed->nextUsed = block->nextUsed;
} else {
usedList = block->nextUsed;
}
if (block->nextUsed) {
block->nextUsed->prevUsed = block->prevUsed;
}
block->nextUsed = nullptr;
block->prevUsed = nullptr;
}
void InsertFreeMList(block_t* block) {
block_t* currBlock = freeMList;
block_t* prevBlock = nullptr;
while (currBlock && (currBlock->addr < block->addr)) {
prevBlock = currBlock;
currBlock = currBlock->nextFreeM;
}
block->nextFreeM = currBlock;
block->prevFreeM = prevBlock;
if (prevBlock) {
prevBlock->nextFreeM = block;
} else {
freeMList = block;
}
if (currBlock) {
currBlock->prevFreeM = block;
}
}
void RemoveFreeMList(block_t* block) {
if (block->prevFreeM) {
block->prevFreeM->nextFreeM = block->nextFreeM;
} else {
freeMList = block->nextFreeM;
}
if (block->nextFreeM) {
block->nextFreeM->prevFreeM = block->prevFreeM;
}
block->nextFreeM = nullptr;
block->prevFreeM = nullptr;
}
void InsertFreeSList(block_t* block) {
block_t* currBlock = this->freeSList;
block_t* prevBlock = nullptr;
while (currBlock && (currBlock->size > block->size)) {
prevBlock = currBlock;
currBlock = currBlock->nextFreeS;
}
block->nextFreeS = currBlock;
block->prevFreeS = prevBlock;
if (prevBlock) {
prevBlock->nextFreeS = block;
} else {
this->freeSList = block;
}
if (currBlock) {
currBlock->prevFreeS = block;
}
}
void RemoveFreeSList(block_t* block) {
if (block->prevFreeS) {
block->prevFreeS->nextFreeS = block->nextFreeS;
} else {
freeSList = block->nextFreeS;
}
if (block->nextFreeS) {
block->nextFreeS->prevFreeS = block->prevFreeS;
}
block->nextFreeS = nullptr;
block->prevFreeS = nullptr;
}
};
page_t* NewPage(uint64_t size) {
// Increase buffer size to include the page and first block size
// also add padding to ensure page alignment
size = AlignSize(size, pageAlign_);
// Allocate page memory
auto addr = nextAddress_;
nextAddress_ += size;
// Overflow check
if (nextAddress_ > capacity_)
return nullptr;
// Allocate object
auto newPage = new page_t(addr, size);
// Insert the new page into the list
newPage->next = pages_;
pages_ = newPage;
return newPage;
}
bool DeletePage(page_t* page) {
// The page should be empty
assert(nullptr == page->usedList);
assert(page->freeMList && (nullptr == page->freeMList->nextFreeM));
// Only delete top-level pages
auto nextAddr = page->addr + page->size;
if (nextAddr != nextAddress_)
return false;
// Remove the page from the list
page_t* prevPage = nullptr;
auto currPage = pages_;
while (currPage) {
if (currPage == page) {
if (prevPage) {
prevPage->next = currPage->next;
} else {
pages_ = currPage->next;
}
break;
}
prevPage = currPage;
currPage = currPage->next;
}
// Update next allocation address
nextAddress_ = page->addr;
// free object
delete page->freeMList;
delete page;
return true;
}
page_t* FindNextEmptyPage() {
auto currPage = pages_;
while (currPage) {
if (nullptr == currPage->usedList)
return currPage;
currPage = currPage->next;
}
return nullptr;
}
static uint64_t AlignSize(uint64_t size, uint64_t alignment) {
assert(0 == (alignment & (alignment - 1)));
return (size + alignment - 1) & ~(alignment - 1);
}
uint64_t baseAddress_;
uint64_t capacity_;
uint32_t pageAlign_;
uint32_t blockAlign_;
page_t* pages_;
uint64_t nextAddress_;
uint64_t allocated_;
};
} // namespace vortex