blob: 339830982688955b9a51fe678dced06ba0bd2231 [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_ALLOCATOR_H_
#define LIB_FIDL_LLCPP_ALLOCATOR_H_
#include <lib/fidl/llcpp/traits.h>
#include <zircon/assert.h>
#include <functional>
#include <type_traits>
#include "tracking_ptr.h"
namespace fidl {
// Allocator is a base class for a family of classes that implement allocation algorithms
// to allocate objects on the heap or stack.
//
// Usage:
// BufferThenHeapAllocator<2048> allocator;
// tracking_ptr<MyObj> obj = allocator.make<MyObj>(arg1, arg2);
// tracking_ptr<int[]> arr = allocator.make<int[]>(10);
//
// Allocator is intended to work with fidl::tracking_ptr, fidl::unowned_ptr_t and fidl::aligned
// and can be used to build LLCPP objects. Example of building tables:
// BufferThenHeapAllocator<2048> allocator;
// MyTable table = MyTable::Builder(allocator.make<MyTable::Frame>())
// .set_some_field(allocator.make<uint64_t>(1234))
// .build();
// In the above example, each out-of-line element is allocated using an Allocator.
//
// Allocator implementations must either:
// - Handle destruction themselves (i.e. call destructors where needed when the allocator goes
// out of scope).
// - Return a allocation_result with heap_allocate set to true. This will result in a
// tracking_ptr being returned that will apply the delete/delete[] operator when the tracking_ptr
// goes out of scope
// TODO(fxbug.dev/42059) Support the equivalent of std::make_unique_for_overwrite. (see helpers.h).
class Allocator {
public:
// make allocates an object of the specified type and initializes it with the given arguments.
// It intended to behave identically to std::make_unique<T> but use the allocator rather than
// the heap.
// TODO(fxbug.dev/42059) Consider making it possible to pack small objects tighter by having dedicated
// blocks. More complication always has more performance impact, though.
template <typename T, typename = std::enable_if_t<!std::is_array<T>::value>, typename... Args>
tracking_ptr<T> make(Args&&... args) {
allocation_result result = allocate(AllocationType::kNonArray, sizeof(fidl::aligned<T>), 1,
destructors<T>::make_destructor());
if (result.data == nullptr) {
if (result.heap_allocate) {
return tracking_ptr<T>(std::make_unique<T>(std::forward<Args>(args)...));
} else {
// Direct use of UnsafeBufferAllocator risks ending up here:
ZX_PANIC("Allocator sub-class allocate() failed and heap_allocate was false\n");
}
}
ZX_DEBUG_ASSERT(!result.heap_allocate);
fidl::aligned<T>* ptr = new (result.data) fidl::aligned<T>(std::forward<Args>(args)...);
return fidl::unowned_ptr_t<fidl::aligned<T>>(ptr);
}
// make allocates an array of the specified type and size.
// It is intended to behave identically to std::make_unique<T[]> but uses the allocator rather
// than the heap.
template <typename T, typename = std::enable_if_t<std::is_array<T>::value>,
typename ArraylessT = std::remove_extent_t<T>>
tracking_ptr<T> make(size_t count) {
allocation_result result = allocate(AllocationType::kArray, sizeof(ArraylessT), count,
destructors<ArraylessT>::make_destructor());
if (result.data == nullptr) {
if (result.heap_allocate) {
return tracking_ptr<ArraylessT[]>(std::make_unique<ArraylessT[]>(count));
} else {
// Direct use of UnsafeBufferAllocator risks ending up here:
ZX_PANIC("Allocator sub-class allocate() failed and heap_allocate was false\n");
}
}
ZX_DEBUG_ASSERT(!result.heap_allocate);
ArraylessT* ptr = new (result.data) ArraylessT[count]();
return fidl::unowned_ptr_t<ArraylessT>(ptr);
}
template <typename Table, typename std::enable_if<::fidl::IsTable<Table>::value, int>::type = 0>
typename Table::Builder make_table_builder() {
return typename Table::Builder(make<typename Table::Frame>());
}
template <typename T,
typename std::enable_if<!std::is_array<T>::value && !fidl::IsVectorView<T>::value,
int>::type = 0>
typename fidl::VectorView<T> make_vec(size_t count) {
return fidl::VectorView<T>(make<T[]>(count), count);
}
template <typename T,
typename std::enable_if<!std::is_array<T>::value && !fidl::IsVectorView<T>::value,
int>::type = 0>
typename fidl::VectorView<T> make_vec(size_t count, size_t capacity) {
ZX_DEBUG_ASSERT(capacity >= count);
return fidl::VectorView<T>(make<T[]>(capacity), count);
}
template <typename T,
typename std::enable_if<!std::is_array<T>::value && !::fidl::IsVectorView<T>::value &&
!::fidl::IsStringView<T>::value,
int>::type = 0>
tracking_ptr<fidl::VectorView<T>> make_vec_ptr(size_t count) {
return fidl::tracking_ptr<fidl::VectorView<T>>(
make<fidl::VectorView<T>>(make<T[]>(count), count));
}
template <typename T,
typename std::enable_if<!std::is_array<T>::value && !::fidl::IsVectorView<T>::value &&
!::fidl::IsStringView<T>::value,
int>::type = 0>
tracking_ptr<fidl::VectorView<T>> make_vec_ptr(size_t count, size_t capacity) {
return fidl::tracking_ptr<fidl::VectorView<T>>(
make<fidl::VectorView<T>>(make<T[]>(capacity), count));
}
template <typename, typename...>
friend class FailoverHeapAllocator;
protected:
Allocator() {}
virtual ~Allocator() {}
// Use a raw function pointer rather than std::function to ensure we don't
// accidentally heap allocate.
typedef void (*destructor)(void* ptr, size_t count);
static constexpr destructor trivial_destructor = nullptr;
enum class AllocationType {
kArray = 1,
kNonArray = 2,
};
struct allocation_result {
void* data;
// If true, the sub-class allocate() is specifying that we want to allocate from the heap (as
// failover, or just because we want heap). Since the heap allocation will end up being
// deleted using delete/delete[], we need to use new/new[] for the new-ing. This must be false
// unless data is nullptr.
bool heap_allocate;
};
virtual allocation_result allocate(AllocationType type, size_t obj_size, size_t count,
destructor dtor) = 0;
private:
template <typename T>
struct destructors {
static destructor make_destructor() {
if (std::is_trivially_destructible<T>::value) {
return trivial_destructor;
}
return nontrivial_destructor;
}
static void nontrivial_destructor(void* ptr, size_t count) {
T* obj = reinterpret_cast<T*>(ptr);
for (; count > 0; count--) {
obj->~T();
obj++;
}
}
};
};
} // namespace fidl
#endif // LIB_FIDL_LLCPP_ALLOCATOR_H_