blob: d9c06f1fcd56c59a5a26f0dd51229944eb3360dd [file] [log] [blame]
// Copyright 2018 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.
#pragma once
#include <assert.h>
#include <stddef.h>
#include <cstddef>
#include <memory>
namespace overnet {
template <class F, size_t kStorage = sizeof(void*)>
class OnceFn;
namespace once_fn_detail {
// Use a (manually constructed) vtable to encode what to do when a callback is
// made (or not)
// This has been observed to generate more efficient code than writing out a
// C++ style vtable
template <class R, class... Arg>
struct VTable {
// Returns the size of the stored value
size_t (*move)(void* dst, void* src, size_t dst_size);
R (*call)(void* env, Arg&&... arg);
void (*not_called)(void* env);
};
template <class F, class R, class... Arg>
class SmallFunctor {
public:
static const VTable<R, Arg...> vtable;
static void InitEnv(void* env, F&& f) { new (env) F(std::forward<F>(f)); }
private:
static size_t Move(void* dst, void* src, size_t dst_size) {
assert(dst_size >= sizeof(F));
F* f = static_cast<F*>(src);
new (dst) F(std::move(*f));
f->~F();
return sizeof(F);
}
static R Call(void* env, Arg&&... arg) {
F* f = static_cast<F*>(env);
R r = (*f)(std::forward<Arg>(arg)...);
f->~F();
return r;
}
static void NotCalled(void* env) {
F* f = static_cast<F*>(env);
f->~F();
}
};
template <class F, class... Arg>
class SmallFunctor<F, void, Arg...> {
public:
static const VTable<void, Arg...> vtable;
static void InitEnv(void* env, F&& f) { new (env) F(std::forward<F>(f)); }
private:
static size_t Move(void* dst, void* src, size_t dst_size) {
assert(dst_size >= sizeof(F));
F* f = static_cast<F*>(src);
new (dst) F(std::move(*f));
f->~F();
return sizeof(F);
}
static void Call(void* env, Arg&&... arg) {
F* f = static_cast<F*>(env);
(*f)(std::forward<Arg>(arg)...);
f->~F();
}
static void NotCalled(void* env) {
F* f = static_cast<F*>(env);
f->~F();
}
};
template <class F, class R, class... Arg>
const VTable<R, Arg...> SmallFunctor<F, R, Arg...>::vtable = {Move, Call,
NotCalled};
template <class F, class... Arg>
const VTable<void, Arg...> SmallFunctor<F, void, Arg...>::vtable = {Move, Call,
NotCalled};
template <class R, class... Arg>
struct NullVTable {
static size_t Move(void*, void*, size_t) { return 0; }
static void NotCalled(void*) {}
static R Call(void*, Arg&&...) { abort(); }
static const VTable<R, Arg...> vtable;
};
template <class R, class... Arg>
const VTable<R, Arg...> NullVTable<R, Arg...>::vtable = {Move, Call, NotCalled};
} // namespace once_fn_detail
// Function that is called at most once
template <class R, class... Arg, size_t kStorage>
class OnceFn<R(Arg...), kStorage> {
public:
OnceFn() : vtable_(&once_fn_detail::NullVTable<R, Arg...>::vtable) {}
~OnceFn() { vtable_->not_called(&env_); }
template <class F,
typename = typename std::enable_if<sizeof(F) <= kStorage>::type>
OnceFn(F&& f) {
vtable_ = &once_fn_detail::SmallFunctor<F, R, Arg...>::vtable;
once_fn_detail::SmallFunctor<F, R, Arg...>::InitEnv(&env_,
std::forward<F>(f));
}
OnceFn(const OnceFn&) = delete;
OnceFn& operator=(const OnceFn&) = delete;
OnceFn(OnceFn&& other) {
vtable_ = other.vtable_;
other.vtable_ = &once_fn_detail::NullVTable<R, Arg...>::vtable;
vtable_->move(&env_, &other.env_, kStorage);
}
OnceFn& operator=(OnceFn&& other) {
vtable_->not_called(&env_);
vtable_ = other.vtable_;
other.vtable_ = &once_fn_detail::NullVTable<R, Arg...>::vtable;
vtable_->move(&env_, &other.env_, kStorage);
return *this;
}
R operator()(Arg... arg) {
const auto* const vtable = vtable_;
vtable_ = &once_fn_detail::NullVTable<R, Arg...>::vtable;
return vtable->call(&env_, std::forward<Arg>(arg)...);
}
bool empty() const {
return vtable_ == &once_fn_detail::NullVTable<R, Arg...>::vtable;
}
// A mutator is a functor of the form:
// mutator(fn, args...) -> result
// where fn(args...) -> result is the current function
// The addition of a mutator occurs in place, and abort()s if env_ is not
// sufficiently large for the new combined functor
template <class Mutator>
void AddMutator(Mutator mutator);
private:
const once_fn_detail::VTable<R, Arg...>* vtable_;
typename std::aligned_storage<kStorage>::type env_;
};
namespace once_fn_detail {
template <size_t kStorage, class F, class R, class... Arg>
struct MutatedEnv {
static const VTable<R, Arg...> vtable;
struct alignas(alignof(std::max_align_t)) Header {
const VTable<R, Arg...>* wrapped_vtable;
size_t wrapped_size;
F fn;
};
Header hdr;
typename std::aligned_storage<kStorage - sizeof(Header)>::type storage;
MutatedEnv(const VTable<R, Arg...>* vtable, size_t size, F fn)
: hdr{vtable, size, std::move(fn)} {}
static size_t Move(void* dst, void* src, size_t dst_size) {
MutatedEnv* s = static_cast<MutatedEnv*>(src);
MutatedEnv* d = static_cast<MutatedEnv*>(dst);
new (&d->hdr) Header(std::move(s->hdr));
s->hdr.~Header();
return sizeof(Header) + s->hdr.wrapped_vtable->move(
&d->storage, &s->storage, sizeof(s->storage));
}
static R Call(void* env, Arg&&... arg) {
MutatedEnv* e = static_cast<MutatedEnv*>(env);
auto vt = e->hdr.wrapped_vtable;
e->hdr.wrapped_vtable = &NullVTable<R, Arg...>::vtable;
auto r = e->hdr.fn(
[e, vt](Arg&&... args) mutable {
auto v = vt;
vt = &NullVTable<R, Arg...>::vtable;
return v->call(&e->storage, std::forward<Arg>(args)...);
},
std::forward<Arg>(arg)...);
e->hdr.~Header();
return r;
}
static void NotCalled(void* env) {
MutatedEnv* e = static_cast<MutatedEnv*>(env);
e->hdr.wrapped_vtable->not_called(&e->storage);
e->hdr.~Header();
}
};
template <size_t kStorage, class F, class R, class... Arg>
const VTable<R, Arg...> MutatedEnv<kStorage, F, R, Arg...>::vtable = {
Move, Call, NotCalled};
template <size_t kStorage, class F, class... Arg>
struct MutatedEnv<kStorage, F, void, Arg...> {
static const VTable<void, Arg...> vtable;
struct alignas(alignof(std::max_align_t)) Header {
const VTable<void, Arg...>* wrapped_vtable;
size_t wrapped_size;
F fn;
};
Header hdr;
typename std::aligned_storage<kStorage - sizeof(Header)>::type storage;
MutatedEnv(const VTable<void, Arg...>* vtable, size_t size, F fn)
: hdr{vtable, size, std::move(fn)} {}
static size_t Move(void* dst, void* src, size_t dst_size) {
MutatedEnv* s = static_cast<MutatedEnv*>(src);
MutatedEnv* d = static_cast<MutatedEnv*>(dst);
new (&d->hdr) Header(std::move(s->hdr));
s->hdr.~Header();
return sizeof(Header) + s->hdr.wrapped_vtable->move(
&d->storage, &s->storage, sizeof(s->storage));
}
static void Call(void* env, Arg&&... arg) {
MutatedEnv* e = static_cast<MutatedEnv*>(env);
auto vt = e->hdr.wrapped_vtable;
e->hdr.wrapped_vtable = &NullVTable<void, Arg...>::vtable;
e->hdr.fn(
[e, vt](Arg&&... args) mutable {
auto v = vt;
vt = &NullVTable<void, Arg...>::vtable;
v->call(&e->storage, std::forward<Arg>(args)...);
},
std::forward<Arg>(arg)...);
e->hdr.~Header();
}
static void NotCalled(void* env) {
MutatedEnv* e = static_cast<MutatedEnv*>(env);
e->hdr.wrapped_vtable->not_called(&e->storage);
e->hdr.~Header();
}
};
template <size_t kStorage, class F, class... Arg>
const VTable<void, Arg...> MutatedEnv<kStorage, F, void, Arg...>::vtable = {
Move, Call, NotCalled};
} // namespace once_fn_detail
template <class R, class... Args, size_t kStorage>
template <class Mutator>
void OnceFn<R(Args...), kStorage>::AddMutator(Mutator mutator) {
typename std::aligned_storage<kStorage>::type temp;
size_t size = vtable_->move(&temp, &env_, kStorage);
using MEnv = once_fn_detail::MutatedEnv<kStorage, Mutator, R, Args...>;
static_assert(sizeof(MEnv) <= kStorage, "Math wrong for MEnv storage");
MEnv* p = new (&env_) MEnv(vtable_, size, std::move(mutator));
vtable_->move(&p->storage, &temp, sizeof(p->storage));
vtable_ = &MEnv::vtable;
}
} // namespace overnet