537 wiersze
12 KiB
C++
537 wiersze
12 KiB
C++
//! Implementation of GPU device memory management
|
|
|
|
#include "top.hpp"
|
|
#include "thread/thread.hpp"
|
|
#include "thread/monitor.hpp"
|
|
#include "device/device.hpp"
|
|
#include "device/gpu/gpuheap.hpp"
|
|
#include "device/gpu/gpudevice.hpp"
|
|
|
|
#include <string>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
|
|
//! Turn this on to enable sanity checks before and after every heap operation.
|
|
#if DEBUG
|
|
#define EXTRA_HEAP_CHECKS 1
|
|
#endif // DEBUG
|
|
|
|
namespace gpu {
|
|
|
|
// The GPU heap. Very simple implementation for now.
|
|
Heap::Heap(
|
|
Device& device)
|
|
: resource_(NULL)
|
|
, freeList_(NULL)
|
|
, busyList_(NULL)
|
|
, freeSize_(0)
|
|
, device_(device)
|
|
, granularity_(Heap::MinGranularity)
|
|
, lock_("GPU heap lock", true)
|
|
, virtualMode_(false)
|
|
, baseAddress_(0)
|
|
{
|
|
}
|
|
|
|
size_t
|
|
Heap::granularityB() const
|
|
{
|
|
return granularity_ * Heap::ElementSize;
|
|
}
|
|
|
|
bool
|
|
Heap::create(size_t totalSize, bool remoteAlloc)
|
|
{
|
|
Resource::MemoryType memType;
|
|
size_t maxHeight = device_.info().image2DMaxHeight_;
|
|
size_t sizeInElements;
|
|
size_t npages;
|
|
|
|
freeSize_ = totalSize;
|
|
|
|
sizeInElements = (totalSize + Heap::ElementSize - 1) / Heap::ElementSize;
|
|
|
|
// Calculate best granularity given the size and device characteristics
|
|
npages = amd::alignUp(sizeInElements, granularity_) / granularity_;
|
|
|
|
// Create a new GPU resource
|
|
resource_ = new Resource(device_, sizeInElements, Heap::ElementType);
|
|
|
|
if (resource_ == NULL) {
|
|
return false;
|
|
}
|
|
|
|
memType = (remoteAlloc) ? Resource::RemoteUSWC : Resource::Local;
|
|
|
|
if (!resource_->create(memType, NULL, true)) {
|
|
return false;
|
|
}
|
|
|
|
// Set up initial free list
|
|
freeList_ = new HeapBlock(this, npages * granularityB(), 0, NULL, NULL);
|
|
if (freeList_ == NULL) {
|
|
return false;
|
|
}
|
|
|
|
guarantee(isSane());
|
|
return true;
|
|
}
|
|
|
|
Heap::~Heap()
|
|
{
|
|
amd::ScopedLock k(lock_);
|
|
|
|
guarantee(isSane());
|
|
|
|
// Release all heap blocks
|
|
HeapBlock *walk, *next;
|
|
walk = busyList_;
|
|
while (walk) {
|
|
next = walk->next_;
|
|
walk->free();
|
|
walk = next;
|
|
}
|
|
|
|
walk = freeList_;
|
|
while (walk) {
|
|
next = walk->next_;
|
|
delete walk;
|
|
walk = next;
|
|
}
|
|
|
|
// Release resource
|
|
delete resource_;
|
|
}
|
|
|
|
HeapBlock*
|
|
Heap::alloc(size_t size)
|
|
{
|
|
amd::ScopedLock k(lock_);
|
|
HeapBlock* walk = freeList_;
|
|
HeapBlock* best = NULL;
|
|
|
|
guarantee(isSane());
|
|
|
|
// Round size
|
|
size = amd::alignUp(size, granularityB());
|
|
|
|
// Walk the free list looking for a suitable block (currently best-fit)
|
|
//! @todo:dgladdin: experiment with switching back to first-fit
|
|
|
|
while (walk) {
|
|
if ((walk->size_ > size) &&
|
|
(best == NULL || walk->size_ < best->size_)) {
|
|
best = walk;
|
|
}
|
|
else if (walk->size_ == size) {
|
|
// No need to split, just move to busy list
|
|
detachBlock(&freeList_, walk);
|
|
walk->inUse_ = true;
|
|
insertBlock(&busyList_, walk);
|
|
guarantee(isSane());
|
|
freeSize_ -= size;
|
|
return walk;
|
|
}
|
|
walk = walk->next_;
|
|
}
|
|
|
|
if (best != NULL) {
|
|
// Got one, but need to split it. Keep first part in free list,
|
|
// put second part into busy list.
|
|
HeapBlock *newblock = splitBlock(best, size);
|
|
newblock->inUse_ = true;
|
|
insertBlock(&busyList_, newblock);
|
|
guarantee(isSane());
|
|
freeSize_ -= size;
|
|
return newblock;
|
|
}
|
|
|
|
// No free block available
|
|
guarantee(isSane());
|
|
return NULL;
|
|
}
|
|
|
|
bool
|
|
Heap::copyTo(Heap* heap)
|
|
{
|
|
HeapBlock *walk;
|
|
|
|
walk = busyList_;
|
|
while (walk) {
|
|
if (walk->getMemory() != NULL) {
|
|
HeapBlock* hb = heap->alloc(walk->size_);
|
|
if (hb == NULL) {
|
|
return false;
|
|
}
|
|
hb->setMemory(walk->getMemory());
|
|
|
|
walk->destroyViewsMemory();
|
|
if (!walk->getMemory()->reallocate(hb, &(heap->resource()))) {
|
|
return false;
|
|
}
|
|
|
|
if (!walk->reallocateViews(hb,
|
|
static_cast<size_t>(hb->offset_ - walk->offset_))) {
|
|
return false;
|
|
}
|
|
}
|
|
walk = walk->next_;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Heap::free(HeapBlock* blk)
|
|
{
|
|
amd::ScopedLock k(lock_);
|
|
guarantee(isSane());
|
|
detachBlock(&busyList_, blk);
|
|
blk->inUse_ = false;
|
|
freeSize_ += blk->size_;
|
|
mergeBlock(&freeList_, blk);
|
|
guarantee(isSane());
|
|
}
|
|
|
|
void
|
|
Heap::detachBlock(HeapBlock** list, HeapBlock* blk)
|
|
{
|
|
// Sanity checks
|
|
guarantee(isSane());
|
|
|
|
if (*list == blk) {
|
|
*list = blk->next_;
|
|
}
|
|
|
|
if (blk->prev_) {
|
|
blk->prev_->next_ = blk->next_;
|
|
}
|
|
if (blk->next_) {
|
|
blk->next_->prev_ = blk->prev_;
|
|
}
|
|
// no heap sanity check as blk is now floating
|
|
}
|
|
|
|
void
|
|
Heap::insertBlock(HeapBlock** head, HeapBlock* blk)
|
|
{
|
|
if (NULL == *head) {
|
|
*head = blk;
|
|
blk->prev_ = NULL;
|
|
blk->next_ = NULL;
|
|
guarantee(isSane());
|
|
return;
|
|
}
|
|
|
|
// Find the place to insert it at
|
|
HeapBlock* walk = *head;
|
|
while (walk->next_ && walk->next_->offset_ < blk->offset_) {
|
|
walk = walk->next_;
|
|
}
|
|
|
|
// Insert it
|
|
if (walk == *head) {
|
|
if (walk->offset_ >= blk->offset_) {
|
|
*head = blk;
|
|
blk->prev_ = NULL;
|
|
blk->next_ = walk;
|
|
walk->prev_ = *head;
|
|
guarantee(isSane());
|
|
return;
|
|
}
|
|
}
|
|
|
|
blk->next_ = walk->next_;
|
|
blk->prev_ = walk;
|
|
if (walk->next_) {
|
|
walk->next_->prev_ = blk;
|
|
}
|
|
walk->next_ = blk;
|
|
guarantee(isSane());
|
|
}
|
|
|
|
HeapBlock*
|
|
Heap::splitBlock(HeapBlock* blk, size_t tailsize)
|
|
{
|
|
// Sanity checks
|
|
|
|
guarantee(isSane());
|
|
guarantee(blk->size_ > tailsize && "block too small to split as requested");
|
|
guarantee(!blk->inUse_ && "can't split in-use block");
|
|
|
|
// Create a new block
|
|
|
|
HeapBlock* nb = new HeapBlock(blk->owner_, tailsize,
|
|
blk->offset_ + blk->size_ - tailsize);
|
|
|
|
// Resize the old block
|
|
|
|
blk->size_ = blk->size_ - tailsize;
|
|
return nb; // no heap sanity check here as the new block hasn't been plugged in yet
|
|
}
|
|
|
|
//! Join two blocks, transferring the size of the second into the first and deleting
|
|
//! the second. Utility fn for mergeBlock()
|
|
|
|
static void
|
|
join2Blocks(HeapBlock* first, HeapBlock* second)
|
|
{
|
|
// Sanity checks
|
|
|
|
guarantee(first->size_ > 0 && "first block invalid");
|
|
guarantee(!first->inUse_ && "can't join an in-use block");
|
|
guarantee(second->size_ > 0 && "second block invalid");
|
|
guarantee(first->offset_ + first->size_ == second->offset_);
|
|
|
|
// Do the join
|
|
first->size_ = first->size_ + second->size_;
|
|
first->next_ = second->next_;
|
|
if (second->next_) {
|
|
second->next_->prev_ = first;
|
|
}
|
|
delete second;
|
|
}
|
|
|
|
//! Insert a block into a list, merging it with adjacent blocks if possible. Must be called
|
|
//! under a lock, cannot be used on in-use blocks or blocks with an associated resource alias.
|
|
|
|
void
|
|
Heap::mergeBlock(HeapBlock** head, HeapBlock* blk)
|
|
{
|
|
insertBlock(head, blk);
|
|
|
|
// Merge with successor if possible
|
|
if ((blk->next_ != NULL) &&
|
|
(blk->offset_ + blk->size_ == blk->next_->offset_)) {
|
|
join2Blocks(blk, blk->next_);
|
|
}
|
|
|
|
// Merge with predecessor if possible
|
|
if ((blk->prev_ != NULL) &&
|
|
(blk->prev_->offset_ + blk->prev_->size_ == blk->offset_)) {
|
|
join2Blocks(blk->prev_, blk);
|
|
}
|
|
|
|
guarantee(isSane());
|
|
}
|
|
|
|
//! Sanity check for both types of block (helper function for Heap::isSane())
|
|
|
|
static bool
|
|
isBlockSane(HeapBlock* b)
|
|
{
|
|
return (b->owner_ != NULL
|
|
&& (b->next_ == NULL || b->next_->prev_ == b)
|
|
&& (b->prev_ == NULL || b->prev_->next_ == b));
|
|
}
|
|
|
|
//! Sanity check for an individual free block (helper function for Heap::isSane())
|
|
static bool
|
|
isFreeBlockSane(HeapBlock* b)
|
|
{
|
|
if (isBlockSane(b) && !b->inUse_) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//! Sanity check for an individual busy block (helper function for Heap::isSane())
|
|
static bool
|
|
isBusyBlockSane(HeapBlock* b)
|
|
{
|
|
if (isBlockSane(b) && b->inUse_) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//! Sanity check for the heap.
|
|
|
|
bool
|
|
Heap::isSane() const
|
|
{
|
|
// If we got this far, everything is (probably) OK
|
|
#if EXTRA_HEAP_CHECKS
|
|
HeapBlock* walkFree = freeList_; // Free list position
|
|
HeapBlock* walkBusy = busyList_; // Busy list position
|
|
size_t offset = 0; // Current offset
|
|
|
|
// We can have zero lists if Heap allocation fails
|
|
if (walkFree == NULL && walkBusy == NULL) {
|
|
return true;
|
|
}
|
|
|
|
// Walk both lists in parallel
|
|
while (walkFree != NULL || walkBusy != NULL) {
|
|
if (walkFree != NULL && walkFree->offset_ == offset) {
|
|
if (!isFreeBlockSane(walkFree)) {
|
|
return false;
|
|
}
|
|
offset += walkFree->size_;
|
|
walkFree = walkFree->next_;
|
|
}
|
|
else if (walkBusy != NULL && walkBusy->offset_ == offset) {
|
|
if (!isBusyBlockSane(walkBusy)) {
|
|
return false;
|
|
}
|
|
offset += walkBusy->size_;
|
|
walkBusy = walkBusy->next_;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endif // EXTRA_HEAP_CHECKS
|
|
return true;
|
|
}
|
|
|
|
void
|
|
HeapBlock::destroyViewsMemory()
|
|
{
|
|
if ((parent_ != NULL) && (0 == views_.size())) {
|
|
memory_->free();
|
|
}
|
|
else if (views_.size() != 0) {
|
|
std::list<HeapBlock*>::const_iterator it;
|
|
for (it = views_.begin(); it != views_.end(); ++it) {
|
|
(*it)->destroyViewsMemory();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
HeapBlock::reallocateViews(HeapBlock* parent, size_t shift)
|
|
{
|
|
if (views_.size() != 0) {
|
|
std::list<HeapBlock*>::const_iterator it;
|
|
|
|
// Loop through all views and reallocate them
|
|
for (it = views_.begin(); it != views_.end(); ++it) {
|
|
// Get the view HeapBlock
|
|
HeapBlock* hb = (*it);
|
|
|
|
// Readjust the offset
|
|
hb->offset_ += shift;
|
|
// Add to the list if we have a new parent
|
|
if (parent != this) {
|
|
parent->addView(hb);
|
|
}
|
|
|
|
// Reallocate memory
|
|
hb->memory_->reallocate(hb, parent->getMemory());
|
|
|
|
// Process a view on view if available
|
|
if (!hb->reallocateViews(hb, shift)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Destroy old list
|
|
if (parent != this) {
|
|
views_.clear();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//! Destructor. Frees the block if in use and does some final sanity checks.
|
|
HeapBlock::~HeapBlock()
|
|
{
|
|
if (NULL != owner_) {
|
|
if (inUse_) {
|
|
owner_->free(this);
|
|
}
|
|
}
|
|
else {
|
|
// View destruction
|
|
if (parent_ != NULL) {
|
|
assert(((parent_->getMemory() != NULL) && (parent_->getMemory()->owner() != NULL)));
|
|
amd::ScopedLock lock(parent_->getMemory()->owner()->lockMemoryOps());
|
|
parent_->removeView(this);
|
|
}
|
|
}
|
|
guarantee(size_ > 0 && "destructor called for zero-size heap block (destructor called twice?)");
|
|
size_ = 0; // Mark as invalid
|
|
|
|
if (views_.size() != 0) {
|
|
LogError("Can't destroy a resource if we still have views!");
|
|
}
|
|
}
|
|
|
|
void
|
|
HeapBlock::free()
|
|
{
|
|
if (NULL != owner_) {
|
|
owner_->free(this);
|
|
}
|
|
else {
|
|
// It's a view. Destroy the object
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
VirtualHeap::VirtualHeap(
|
|
Device& device)
|
|
: Heap(device)
|
|
{
|
|
virtualMode_ = true;
|
|
}
|
|
|
|
bool
|
|
VirtualHeap::create(
|
|
size_t totalSize,
|
|
bool remoteAlloc)
|
|
{
|
|
// Create a new GPU resource
|
|
resource_ = new Resource(device_, 0, Heap::ElementType);
|
|
if (resource_ == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (!resource_->create(Resource::Heap)) {
|
|
return false;
|
|
}
|
|
|
|
if (!device_.settings().hsail_) {
|
|
baseAddress_ = resource_->gslResource()->getSurfaceAddress();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
VirtualHeap::~VirtualHeap()
|
|
{
|
|
}
|
|
|
|
HeapBlock*
|
|
VirtualHeap::alloc(size_t size)
|
|
{
|
|
assert(false && "Dead branch!");
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
VirtualHeap::free(HeapBlock* blk)
|
|
{
|
|
assert(false && "Dead branch!");
|
|
}
|
|
|
|
bool
|
|
VirtualHeap::copyTo(Heap* heap)
|
|
{
|
|
assert(false && "Dead branch!");
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
VirtualHeap::isSane(void) const
|
|
{
|
|
assert(false && "Dead branch!");
|
|
return true;
|
|
}
|
|
|
|
} // namespace gpu
|