blob: 0a805020f289e31ff53c301eed97168ecd4e7c1b [file] [log] [blame]
// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
//
// 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.
#ifndef yarn_pool_hpp
#define yarn_pool_hpp
#include "ConditionVariable.hpp"
#include <atomic>
#include <mutex>
namespace yarn {
// PoolPolicy controls whether pool items are constructed and destructed each
// time they are borrowed from and returned to a pool, or whether they persist
// constructed for the lifetime of the pool.
enum class PoolPolicy
{
// Call the Pool items constructor on borrow(), and destruct the item
// when the item is returned.
Reconstruct,
// Construct and destruct all items once for the lifetime of the Pool.
// Items will keep their state between loans.
Preserve,
};
////////////////////////////////////////////////////////////////////////////////
// Pool<T>
////////////////////////////////////////////////////////////////////////////////
// Pool is the abstract base class for BoundedPool<> and UnboundedPool<>.
template <typename T>
class Pool
{
protected:
struct Item;
class Storage;
public:
// A Loan is returned by the pool's borrow() function.
// Loans track the number of references to the loaned item, and return the
// item to the pool when the final Loan reference is dropped.
class Loan
{
public:
inline Loan() = default;
inline Loan(Item*, const std::shared_ptr<Storage>&);
inline Loan(const Loan&);
inline Loan(Loan&&);
inline ~Loan();
inline Loan& operator = (const Loan&);
inline Loan& operator = (Loan&&);
inline T& operator * ();
inline T* operator -> () const;
inline T* get() const;
void reset();
private:
Item *item = nullptr;
std::shared_ptr<Storage> storage;
};
protected:
Pool() = default;
// The shared storage between the pool and all loans.
class Storage
{
public:
virtual ~Storage() = default;
virtual void return_(Item*) = 0;
};
// The backing data of a single item in the pool.
struct Item
{
// get() returns a pointer to the item's data.
inline T* get();
// construct() calls the constructor on the item's data.
inline void construct();
// destruct() calls the destructor on the item's data.
inline void destruct();
using Data = typename std::aligned_storage<sizeof(T), alignof(T)>::type;
Data data;
std::atomic<int> refcount = {0};
Item *next = nullptr; // pointer to the next free item in the pool.
};
};
// Loan<T> is an alias to Pool<T>::Loan.
template <typename T>
using Loan = typename Pool<T>::Loan;
////////////////////////////////////////////////////////////////////////////////
// Pool<T>::Item
////////////////////////////////////////////////////////////////////////////////
template <typename T>
T* Pool<T>::Item::get()
{
return reinterpret_cast<T*>(&data);
}
template <typename T>
void Pool<T>::Item::construct()
{
new (&data) T();
}
template <typename T>
void Pool<T>::Item::destruct()
{
get()->~T();
}
////////////////////////////////////////////////////////////////////////////////
// Pool<T>::Loan
////////////////////////////////////////////////////////////////////////////////
template <typename T>
Pool<T>::Loan::Loan(Item* item, const std::shared_ptr<Storage>& storage) : item(item), storage(storage)
{
item->refcount++;
}
template <typename T>
Pool<T>::Loan::Loan(const Loan& other) : item(other.item), storage(other.storage)
{
if (item != nullptr)
{
item->refcount++;
}
}
template <typename T>
Pool<T>::Loan::Loan(Loan&& other) : item(other.item), storage(other.storage)
{
other.item = nullptr;
other.storage = nullptr;
}
template <typename T>
Pool<T>::Loan::~Loan()
{
reset();
}
template <typename T>
void Pool<T>::Loan::reset()
{
if (item != nullptr)
{
auto refs = --item->refcount;
YARN_ASSERT(refs >= 0, "reset() called on zero-ref pool item");
if (refs == 0)
{
storage->return_(item);
}
item = nullptr;
storage = nullptr;
}
}
template <typename T>
typename Pool<T>::Loan& Pool<T>::Loan::operator = (const Pool<T>::Loan& rhs)
{
reset();
if (rhs.item != nullptr)
{
item = rhs.item;
storage = rhs.storage;
rhs.item->refcount++;
}
return *this;
}
template <typename T>
typename Pool<T>::Loan& Pool<T>::Loan::operator = (Pool<T>::Loan&& rhs)
{
reset();
std::swap(item, rhs.item);
std::swap(storage, rhs.storage);
return *this;
}
template <typename T>
T& Pool<T>::Loan::operator * () { return *item->get(); }
template <typename T>
T* Pool<T>::Loan::operator -> () const { return item->get(); }
template <typename T>
T* Pool<T>::Loan::get() const { return item->get(); }
////////////////////////////////////////////////////////////////////////////////
// BoundedPool<T, N, POLICY>
////////////////////////////////////////////////////////////////////////////////
// BoundedPool<T, N, POLICY> is a pool of items of type T, with a maximum
// capacity of N items.
// BoundedPool<> is initially populated with N default-constructed items.
// POLICY controls whether pool items are constructed and destructed each
// time they are borrowed from and returned to the pool.
template <typename T, int N, PoolPolicy POLICY = PoolPolicy::Reconstruct>
class BoundedPool : public Pool<T>
{
public:
using Item = typename Pool<T>::Item;
using Loan = typename Pool<T>::Loan;
// borrow() borrows a single item from the pool, blocking until an item is
// returned if the pool is empty.
inline Loan borrow() const;
// borrow() borrows count items from the pool, blocking until there are at
// least count items in the pool. The function f() is called with each
// borrowed item.
// F must be a function with the signature: void(T&&)
template <typename F>
inline void borrow(size_t count, const F& f) const;
// tryBorrow() attempts to borrow a single item from the pool without
// blocking.
// The boolean of the returned pair is true on success, or false if the pool
// is empty.
inline std::pair<Loan, bool> tryBorrow() const;
private:
class Storage : public Pool<T>::Storage
{
public:
inline Storage();
inline ~Storage();
inline void return_(Item*) override;
std::mutex mutex;
ConditionVariable returned;
Item items[N];
Item *free = nullptr;
};
std::shared_ptr<Storage> storage = std::make_shared<Storage>();
};
template <typename T, int N, PoolPolicy POLICY>
BoundedPool<T, N, POLICY>::Storage::Storage()
{
for (int i = 0; i < N; i++)
{
if (POLICY == PoolPolicy::Preserve)
{
items[i].construct();
}
items[i].next = this->free;
this->free = &items[i];
}
}
template <typename T, int N, PoolPolicy POLICY>
BoundedPool<T, N, POLICY>::Storage::~Storage()
{
if (POLICY == PoolPolicy::Preserve)
{
for (int i = 0; i < N; i++)
{
items[i].destruct();
}
}
}
template <typename T, int N, PoolPolicy POLICY>
typename BoundedPool<T, N, POLICY>::Loan BoundedPool<T, N, POLICY>::borrow() const
{
Loan out;
borrow(1, [&](Loan&& loan) { out = std::move(loan); });
return out;
}
template <typename T, int N, PoolPolicy POLICY>
template <typename F>
void BoundedPool<T, N, POLICY>::borrow(size_t n, const F& f) const
{
std::unique_lock<std::mutex> lock(storage->mutex);
for (size_t i = 0; i < n; i++)
{
storage->returned.wait(lock, [&] { return storage->free != nullptr; });
auto item = storage->free;
storage->free = storage->free->next;
if (POLICY == PoolPolicy::Reconstruct)
{
item->construct();
}
f(std::move(Loan(item, storage)));
}
}
template <typename T, int N, PoolPolicy POLICY>
std::pair<typename BoundedPool<T, N, POLICY>::Loan, bool> BoundedPool<T, N, POLICY>::tryBorrow() const
{
std::unique_lock<std::mutex> lock(storage->mutex);
if (storage->free == nullptr)
{
return std::make_pair(Loan(), false);
}
auto item = storage->free;
storage->free = storage->free->next;
item->pool = this;
lock.unlock();
if (POLICY == PoolPolicy::Reconstruct)
{
item->construct();
}
return std::make_pair(Loan(item, storage), true);
}
template <typename T, int N, PoolPolicy POLICY>
void BoundedPool<T, N, POLICY>::Storage::return_(Item* item)
{
if (POLICY == PoolPolicy::Reconstruct)
{
item->destruct();
}
std::unique_lock<std::mutex> lock(mutex);
item->next = free;
free = item;
lock.unlock();
returned.notify_one();
}
////////////////////////////////////////////////////////////////////////////////
// UnboundedPool
////////////////////////////////////////////////////////////////////////////////
// UnboundedPool<T, POLICY> is a pool of items of type T.
// UnboundedPool<> will automatically allocate more items if the pool becomes
// empty.
// POLICY controls whether pool items are constructed and destructed each
// time they are borrowed from and returned to the pool.
template <typename T, PoolPolicy POLICY = PoolPolicy::Reconstruct>
class UnboundedPool : public Pool<T>
{
public:
using Item = typename Pool<T>::Item;
using Loan = typename Pool<T>::Loan;
// borrow() borrows a single item from the pool, automatically allocating
// more items if the pool is empty.
// This function does not block.
inline Loan borrow() const;
// borrow() borrows count items from the pool, calling the function f() with
// each borrowed item.
// F must be a function with the signature: void(T&&)
// This function does not block.
template <typename F>
inline void borrow(size_t n, const F& f) const;
private:
class Storage : public Pool<T>::Storage
{
public:
inline ~Storage();
inline void return_(Item*) override;
std::mutex mutex;
std::vector<Item*> items;
Item *free = nullptr;
};
std::shared_ptr<Storage> storage = std::make_shared<Storage>();
};
template <typename T, PoolPolicy POLICY>
UnboundedPool<T, POLICY>::Storage::~Storage()
{
for (auto item : items)
{
if (POLICY == PoolPolicy::Preserve)
{
item->destruct();
}
delete item;
}
}
template <typename T, PoolPolicy POLICY>
Loan<T> UnboundedPool<T, POLICY>::borrow() const
{
Loan out;
borrow(1, [&] (Loan&& loan) { out = std::move(loan); });
return out;
}
template <typename T, PoolPolicy POLICY>
template <typename F>
inline void UnboundedPool<T, POLICY>::borrow(size_t n, const F& f) const
{
std::unique_lock<std::mutex> lock(storage->mutex);
for (size_t i = 0; i < n; i++)
{
if (storage->free == nullptr)
{
auto count = std::max<size_t>(storage->items.size(), 32);
for (size_t i = 0; i < count; i++)
{
auto item = new Item();
if (POLICY == PoolPolicy::Preserve)
{
item->construct();
}
storage->items.push_back(item);
item->next = storage->free;
storage->free = item;
}
}
auto item = storage->free;
storage->free = storage->free->next;
if (POLICY == PoolPolicy::Reconstruct)
{
item->construct();
}
f(std::move(Loan(item, storage)));
}
}
template <typename T, PoolPolicy POLICY>
void UnboundedPool<T, POLICY>::Storage::return_(Item* item)
{
if (POLICY == PoolPolicy::Reconstruct)
{
item->destruct();
}
std::unique_lock<std::mutex> lock(mutex);
item->next = free;
free = item;
lock.unlock();
}
} // namespace yarn
#endif // yarn_pool_hpp