blob: d3faef700dda39054612ed4e6e283d074bb4c552 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LIB_FIDL_LLCPP_FIDL_ALLOCATOR_H_
#define LIB_FIDL_LLCPP_FIDL_ALLOCATOR_H_
#include <lib/fidl/llcpp/traits.h>
#include <zircon/assert.h>
#include <functional>
#include <type_traits>
namespace fidl {
// Class common to all the FidlAllocator classes. This one is independent of the initial buffer
// size.
// All the implementation is done here. The only specialization available is FidlAllocator which
// defines the initial buffer size.
// The allocator owns all the data which are allocated. That means that the allocated data can be
// used by pure views.
// The allocated data are freed when the allocator is freed.
// The methods of the allocator are not directly called by the user. Instead ObjectView, StringView
// and VectorView use them.
// The allocation is done within the initial buffer. When the initial buffer is full (or, at least,
// the next allocation doesn't fit in the remaining space), the allocator allocates extra buffers
// on the heap. If one allocation is bigger than the capacity of a standard extra buffer, a
// tailored buffer is allocated which only contains the allocation.
//
// Allocations are put one after the other in the buffers. When a buffer can't fit the next
// allocation, the remaining space is lost an another buffer is allocated on the heap.
// Each allocation respects FIDL_ALIGNMENT.
// For allocations which don't need a destructor, we only allocate the requested size within the
// buffer.
// For allocations with a non trivial destructor, we also allocate some space for a
// |struct Destructor| which is stored before the requested data.
class AnyAllocator {
private:
AnyAllocator(uint8_t* next_data_available, size_t available_size)
: next_data_available_(next_data_available), available_size_(available_size) {}
~AnyAllocator();
// Struct used to store the data needed to deallocate an allocation (to call the destructor).
struct Destructor {
Destructor(Destructor* next, size_t count, void (*destructor)(uint8_t*, size_t))
: next(next), count(count), destructor(destructor) {}
Destructor* const next;
const size_t count;
void (*const destructor)(uint8_t*, size_t);
};
// Struct used to have more allocation buffers on the heap (when the initial buffer is full).
struct ExtraBlock {
public:
// In most cases, the size in big enough to only need an extra allocation. It's also small
// enough to not use too much heap memory.
// The actual allocated size for the ExtraBlock struct will be 16Kb.
static constexpr size_t kExtraSize = 16 * 1024 - FIDL_ALIGN(sizeof(ExtraBlock*));
explicit ExtraBlock(ExtraBlock* next_block) : next_block_(next_block) {}
ExtraBlock* next_block() const { return next_block_; }
uint8_t* data() { return data_; }
private:
// Next block to deallocate (block allocated before this one).
ExtraBlock* next_block_;
// The usable data.
alignas(FIDL_ALIGNMENT) uint8_t data_[kExtraSize];
};
// Deallocate anything allocated by the allocator. Any data previously allocated must not be
// accessed anymore.
void Clean();
// Deallocate anything allocated by the allocator. After this call, the allocator is in the
// extact same state it was after the construction. Any data previously allocated must not be
// accessed anymore.
void Reset(uint8_t* next_data_available, size_t available_size) {
Clean();
next_data_available_ = next_data_available;
available_size_ = available_size;
}
// Allocates and default constructs an instance of T. Used by fidl::ObjectView.
template <typename T, typename... Args>
T* Allocate(Args&&... args) {
return new (Allocate(sizeof(T), 1,
std::is_trivially_destructible<T>::value ? nullptr : ObjectDestructor<T>))
T(std::forward<Args>(args)...);
}
// Allocates and default constructs a vector of T. Used by fidl::VectorView and StringView.
// All the |count| vector elements are constructed.
template <typename T>
T* AllocateVector(size_t count) {
return new (Allocate(sizeof(T), count,
std::is_trivially_destructible<T>::value ? nullptr : VectorDestructor<T>))
typename std::remove_const<T>::type[count];
}
// Method which can deallocate an instance of T.
template <typename T>
static void ObjectDestructor(uint8_t* data, size_t count) {
T* object = reinterpret_cast<T*>(data);
object->~T();
}
// Method which can deallocate a vector of T.
template <typename T>
static void VectorDestructor(uint8_t* data, size_t count) {
T* object = reinterpret_cast<T*>(data);
T* end = object + count;
while (object < end) {
object->~T();
++object;
}
}
// The actual allocation.
// |Allocate| allocates the requested elements and eventually records the destructor to call
// during the allocator destruction if |destructor_function| is not null.
// The allocated data is not initialized (it will be initialized by the caller).
uint8_t* Allocate(size_t item_size, size_t count,
void (*destructor_function)(uint8_t* data, size_t count));
// Pointer to the next available data.
uint8_t* next_data_available_;
// Size of the data available at next_data_available_.
size_t available_size_;
// Linked list of the destructors to call starting with the last allocation.
Destructor* last_destructor_ = nullptr;
// Linked list of the extra blocks used for the allocation.
ExtraBlock* last_extra_block_ = nullptr;
template <typename T>
friend class ObjectView;
template <typename T>
friend class VectorView;
template <size_t>
friend class FidlAllocator;
};
// Class which allows the allocation of data for the views (ObjectView, StringView, VectorView).
template <size_t initial_capacity = 512>
class FidlAllocator : public AnyAllocator {
public:
// Can't move because destructor pointers can point within initial_buffer_.
FidlAllocator(FidlAllocator&& to_move) = delete;
// Copying an allocator doesn't make sense.
FidlAllocator(FidlAllocator& to_copy) = delete;
FidlAllocator() : AnyAllocator(initial_buffer_, initial_capacity) {}
// Deallocate anything allocated by the allocator. After this call, the allocator is in the
// exact same state it was after the construction. Any data previously allocated must not be
// accessed anymore.
void Reset() { AnyAllocator::Reset(initial_buffer_, initial_capacity); }
private:
alignas(FIDL_ALIGNMENT) uint8_t initial_buffer_[initial_capacity];
};
} // namespace fidl
#endif // LIB_FIDL_LLCPP_FIDL_ALLOCATOR_H_