Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 112 additions & 16 deletions runtime/core/freeable_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
#pragma once

#include <cstddef>
#include <cstdint>
#include <variant>

#include <executorch/runtime/core/error.h>
#include <executorch/runtime/core/result.h>
#include <executorch/runtime/platform/assert.h>

namespace executorch {
namespace runtime {
Expand All @@ -20,20 +26,35 @@ class FreeableBuffer final {
public:
// Callback signature for the function that does the freeing.
using FreeFn = void (*)(void* context, void* data, size_t size);
using FreeUInt64Fn =
void (*)(void* context, uint64_t data_uint64, size_t size);

private:
// Forward declare types.
struct PointerData {
const void* data_;
FreeFn free_fn_;
};

struct UInt64Data {
// A pointer value cast to uint64_t.
uint64_t data_;
FreeUInt64Fn free_fn_;
};

public:
/**
* Creates an empty FreeableBuffer with size zero and a null data pointer.
*/
FreeableBuffer()
: free_fn_(nullptr),
: data_(PointerData{nullptr, nullptr}),
free_fn_context_(nullptr),
data_(nullptr),
size_(0) {}

/**
* Creates a FreeableBuffer with an optional free function.
*
* @param[in] data The data of the segment.
* @param[in] data The data of the segment, as a void*.
* @param[in] size The size of the segment data, in bytes.
* @param[in] free_fn Optional function to free the data. Guaranteed to be
* called exactly once before the FreeableBuffer is destroyed. May be
Expand All @@ -47,23 +68,51 @@ class FreeableBuffer final {
size_t size,
FreeFn free_fn,
void* free_fn_context = nullptr)
: free_fn_(free_fn),
: data_(PointerData{data, free_fn}),
free_fn_context_(free_fn_context),
size_(size) {}

/**
* Creates a FreeableBuffer with an optional free function.
*
* NOTE: most users should use the other ctor with FreeFn.
* This variant exists for situations where the FreeableBuffer points to
* memory on a different core whose pointer value is larger than the local
* core's void*.
*
* @param[in] data Pointer to the data of the segment, cast to a uint64_t
* value.
* @param[in] size The size of the segment data, in bytes.
* @param[in] free_fn Optional function to free the data. Guaranteed to be
* called exactly once before the FreeableBuffer is destroyed. May be
* nullptr. NOTE: This function must be thread-safe. If it modifies common
* state, the function must do its own locking.
* @param[in] free_fn_context Opaque pointer to pass as the `context`
* parameter of `free_fn`. May be nullptr.
*/
explicit FreeableBuffer(
const uint64_t data_uint64,
size_t size,
FreeUInt64Fn free_fn,
void* free_fn_context = nullptr)
: data_(UInt64Data{data_uint64, free_fn}),
free_fn_context_(free_fn_context),
data_(data),
size_(size) {}

/**
* Move ctor. Takes the ownership of the data previously owned by `rhs`,
* leaving `rhs` pointing to nullptr.
*/
FreeableBuffer(FreeableBuffer&& rhs) noexcept
: free_fn_(rhs.free_fn_),
: data_(rhs.data_),
free_fn_context_(rhs.free_fn_context_),
data_(rhs.data_),
size_(rhs.size_) {
rhs.free_fn_ = nullptr;
if (std::holds_alternative<PointerData>(rhs.data_)) {
rhs.data_ = PointerData{nullptr, nullptr};
} else {
rhs.data_ = UInt64Data{0, nullptr};
}
rhs.free_fn_context_ = nullptr;
rhs.data_ = nullptr;
rhs.size_ = 0;
}

Expand All @@ -75,11 +124,22 @@ class FreeableBuffer final {
* Frees the data if not already free. Safe to call multiple times.
*/
void Free() {
if (data_ != nullptr) {
if (free_fn_ != nullptr) {
free_fn_(free_fn_context_, const_cast<void*>(data_), size_);
if (std::holds_alternative<PointerData>(data_)) {
PointerData& ptr_data = std::get<PointerData>(data_);
if (ptr_data.data_ != nullptr && ptr_data.free_fn_ != nullptr) {
// Do not need to check for truncation here, as free_fn_ is only set
// using the void* ctor.
ptr_data.free_fn_(
free_fn_context_, const_cast<void*>(ptr_data.data_), size_);
}
data_ = nullptr;
ptr_data.data_ = nullptr;
size_ = 0;
} else {
UInt64Data& int64_data = std::get<UInt64Data>(data_);
if (int64_data.data_ != 0 && int64_data.free_fn_ != nullptr) {
int64_data.free_fn_(free_fn_context_, int64_data.data_, size_);
}
int64_data.data_ = static_cast<uint64_t>(0);
size_ = 0;
}
}
Expand All @@ -95,7 +155,37 @@ class FreeableBuffer final {
* Pointer to the data. Returns nullptr if the data has been freed.
*/
const void* data() const {
return data_;
ET_CHECK_MSG(
std::holds_alternative<PointerData>(data_),
"FreeableBuffer is backed by an uint64_t, please use the data_uint64_type() API.");
return std::get<PointerData>(data_).data_;
}

/**
* Pointer to the data. Returns nullptr if the data has been freed.
* Safe version of data() API that returns an ERror if the data is
* backed by int64_t instead of void*.
*/
Result<const void*> data_safe() const {
ET_CHECK_OR_RETURN_ERROR(
std::holds_alternative<PointerData>(data_),
InvalidType,
"FreeableBuffer is backed by an uint64_t, please use the data_uint64_type() API.");
return std::get<PointerData>(data_).data_;
}

/**
* Data address as a uint64_t. Returns zero if the data has been freed.
* Most users should use data(). data_uint64_type() is only helpful in
* situations where the FreeableBuffer points to memory on a different core
* whose pointer value is larger than the local core's void *.
*/
Result<uint64_t> data_uint64_type() const {
ET_CHECK_OR_RETURN_ERROR(
std::holds_alternative<UInt64Data>(data_),
InvalidType,
"FreeableBuffer is backed by a void*, please use the data() API.");
return std::get<UInt64Data>(data_).data_;
}

private:
Expand All @@ -104,9 +194,15 @@ class FreeableBuffer final {
FreeableBuffer& operator=(FreeableBuffer&& rhs) noexcept = delete;
FreeableBuffer& operator=(const FreeableBuffer& rhs) = delete;

FreeFn free_fn_;
// This stores either a PointerData or a UInt64Data structure. Most users
// should use the PointerData variant and the void* ctor. This creates a
// FreeableBuffer backed by void*, accessed using the void* getter data().
// The UInt64Data variant is only helpful in situations where the
// FreeableBuffer points to memory on a different core whose pointer value
// is larger than the local core's void*.
std::variant<PointerData, UInt64Data> data_;

void* free_fn_context_;
const void* data_;
size_t size_;
};

Expand Down
Loading
Loading