#ifndef COURGETTE_MEMORY_ALLOCATOR_H_
#define COURGETTE_MEMORY_ALLOCATOR_H_
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "base/files/file.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/process/memory.h"
#include "build/build_config.h"
#ifndef NDEBUG
template<class T>
class CheckReturnValue {
public:
CheckReturnValue(T value) : value_(value), checked_(false) {
}
CheckReturnValue(const CheckReturnValue& other)
: value_(other.value_), checked_(other.checked_) {
other.checked_ = true;
}
CheckReturnValue& operator=(const CheckReturnValue& other) {
if (this != &other) {
DCHECK(checked_);
value_ = other.value_;
checked_ = other.checked_;
other.checked_ = true;
}
return *this;
}
~CheckReturnValue() {
DCHECK(checked_);
}
operator const T&() const {
checked_ = true;
return value_;
}
private:
T value_;
mutable bool checked_;
};
typedef CheckReturnValue<bool> CheckBool;
#else
typedef bool CheckBool;
#endif
namespace courgette {
template <class T, class... ArgTypes>
T* UncheckedNew(ArgTypes... args) {
void* ram = nullptr;
return base::UncheckedMalloc(sizeof(T), &ram) ? new (ram) T(args...)
: nullptr;
}
template <class T>
void UncheckedDelete(T* object) {
if (object) {
object->T::~T();
free(object);
}
}
template <class T>
struct UncheckedDeleter {
inline void operator()(T* ptr) const { UncheckedDelete(ptr); }
};
#if BUILDFLAG(IS_WIN)
class FileMapping {
public:
FileMapping();
~FileMapping();
bool Create(HANDLE file, size_t size);
void Close();
bool valid() const;
void* view() const;
protected:
bool InitializeView(size_t size);
HANDLE mapping_;
raw_ptr<void> view_;
};
class TempMapping {
public:
TempMapping();
~TempMapping();
bool Initialize(size_t size);
void* memory() const;
bool valid() const;
static TempMapping* GetMappingFromPtr(void* mem);
protected:
base::File file_;
FileMapping mapping_;
};
template<class T>
class MemoryAllocator {
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type& reference;
typedef const value_type* const_pointer;
typedef const value_type& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
enum AllocationType : uint8_t {
HEAP_ALLOCATION = 0xF0,
FILE_ALLOCATION = 0x0F,
};
static const size_t kMaxHeapAllocationSize = 1024 * 1024 * 5;
template<class OtherT>
struct rebind {
typedef MemoryAllocator<OtherT> other;
};
MemoryAllocator() {}
MemoryAllocator(const MemoryAllocator<T>& other) {
}
template <class OtherT>
MemoryAllocator(const MemoryAllocator<OtherT>& other) {
}
~MemoryAllocator() {
}
void deallocate(pointer ptr, size_type size) {
uint8_t* mem = reinterpret_cast<uint8_t*>(ptr);
mem -= sizeof(T);
if (mem[0] == HEAP_ALLOCATION)
free(mem);
else if (mem[0] == FILE_ALLOCATION)
UncheckedDelete(TempMapping::GetMappingFromPtr(mem));
else
LOG(FATAL);
}
pointer allocate(size_type count) {
count++;
if (count > max_size())
return nullptr;
size_type bytes = count * sizeof(T);
uint8_t* mem = nullptr;
if (count < kMaxHeapAllocationSize &&
base::UncheckedMalloc(bytes, reinterpret_cast<void**>(&mem))) {
mem[0] = static_cast<uint8_t>(HEAP_ALLOCATION);
} else {
TempMapping* mapping = UncheckedNew<TempMapping>();
if (mapping) {
if (mapping->Initialize(bytes)) {
mem = reinterpret_cast<uint8_t*>(mapping->memory());
mem[0] = static_cast<uint8_t>(FILE_ALLOCATION);
} else {
UncheckedDelete(mapping);
}
}
}
if (!mem && base::UncheckedMalloc(bytes, reinterpret_cast<void**>(&mem))) {
mem[0] = static_cast<uint8_t>(HEAP_ALLOCATION);
}
return mem ? reinterpret_cast<pointer>(mem + sizeof(T)) : nullptr;
}
pointer allocate(size_type count, const void* hint) {
return allocate(count);
}
void construct(pointer ptr, const T& value) {
::new(ptr) T(value);
}
void destroy(pointer ptr) {
ptr->~T();
}
size_type max_size() const {
size_type count = static_cast<size_type>(-1) / sizeof(T);
return (0 < count ? count : 1);
}
};
#else
template<class T>
class MemoryAllocator {
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type& reference;
typedef const value_type* const_pointer;
typedef const value_type& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
template<class OtherT>
struct rebind {
typedef MemoryAllocator<OtherT> other;
};
MemoryAllocator() {
}
explicit MemoryAllocator(const MemoryAllocator<T>& other) {
}
template<class OtherT>
explicit MemoryAllocator(const MemoryAllocator<OtherT>& other) {
}
~MemoryAllocator() {
}
void deallocate(pointer ptr, size_type size) { free(ptr); }
pointer allocate(size_type count) {
if (count > max_size())
return nullptr;
pointer result = nullptr;
return base::UncheckedMalloc(count * sizeof(T),
reinterpret_cast<void**>(&result))
? result
: nullptr;
}
pointer allocate(size_type count, const void* hint) {
return allocate(count);
}
void construct(pointer ptr, const T& value) {
::new(ptr) T(value);
}
void destroy(pointer ptr) {
ptr->~T();
}
size_type max_size() const {
size_type count = static_cast<size_type>(-1) / sizeof(T);
return (0 < count ? count : 1);
}
};
#endif
template<typename T, class Allocator = MemoryAllocator<T> >
class NoThrowBuffer {
public:
typedef T value_type;
static const size_t kAllocationFailure = 0xffffffff;
static const size_t kStartSize = sizeof(T) > 0x100 ? 1 : 0x100 / sizeof(T);
NoThrowBuffer() : buffer_(nullptr), size_(0), alloc_size_(0) {}
~NoThrowBuffer() {
clear();
}
void clear() {
if (buffer_) {
alloc_.deallocate(buffer_, alloc_size_);
buffer_ = nullptr;
size_ = 0;
alloc_size_ = 0;
}
}
bool empty() const {
return size_ == 0;
}
[[nodiscard]] CheckBool reserve(size_t size) {
if (failed())
return false;
if (size <= alloc_size_)
return true;
if (size < kStartSize)
size = kStartSize;
T* new_buffer = alloc_.allocate(size);
if (!new_buffer) {
clear();
alloc_size_ = kAllocationFailure;
} else {
if (buffer_) {
memcpy(new_buffer, buffer_, size_ * sizeof(T));
alloc_.deallocate(buffer_, alloc_size_);
}
buffer_ = new_buffer;
alloc_size_ = size;
}
return !failed();
}
[[nodiscard]] CheckBool append(const T* data, size_t size) {
if (failed())
return false;
if (size > alloc_.max_size() - size_)
return false;
if (!size)
return true;
DCHECK(data + size <= buffer_ || data >= buffer_ + alloc_size_);
if ((alloc_size_ - size_) < size) {
const size_t max_size = alloc_.max_size();
size_t new_size = alloc_size_ ? alloc_size_ : kStartSize;
while (new_size < size_ + size) {
if (new_size < max_size - new_size) {
new_size *= 2;
} else {
new_size = max_size;
}
}
if (!reserve(new_size))
return false;
}
memcpy(buffer_ + size_, data, size * sizeof(T));
size_ += size;
return true;
}
[[nodiscard]] CheckBool resize(size_t size, const T& init_value) {
if (size > size_) {
if (!reserve(size))
return false;
for (size_t i = size_; i < size; ++i)
buffer_[i] = init_value;
} else if (size < size_) {
}
size_ = size;
return true;
}
[[nodiscard]] CheckBool push_back(const T& item) { return append(&item, 1); }
const T& back() const {
return buffer_[size_ - 1];
}
T& back() {
return buffer_[size_ - 1];
}
const T* begin() const {
if (!size_)
return nullptr;
return buffer_;
}
T* begin() {
if (!size_)
return nullptr;
return buffer_;
}
const T* end() const {
if (!size_)
return nullptr;
return buffer_ + size_;
}
T* end() {
if (!size_)
return nullptr;
return buffer_ + size_;
}
const T& operator[](size_t index) const {
DCHECK(index < size_);
return buffer_[index];
}
T& operator[](size_t index) {
DCHECK(index < size_);
return buffer_[index];
}
size_t size() const {
return size_;
}
size_t capacity() const {
return alloc_size_;
}
T* data() const {
return buffer_;
}
bool failed() const {
return alloc_size_ == kAllocationFailure;
}
protected:
raw_ptr<T, DanglingUntriaged | AllowPtrArithmetic> buffer_;
size_t size_;
size_t alloc_size_;
Allocator alloc_;
};
}
#endif