blob: 735fc4a7de8c701620fb197923e92776745bbb39 [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 F, class A, class R, class... Arg>
class SmallMustCall {
public:
typedef std::tuple<F, A> Rep;
static const VTable<R, Arg...> vtable;
static void InitEnv(void* env, F&& f, A&& a) {
new (env) Rep(std::forward<F>(f), std::forward<A>(a));
}
private:
static size_t Move(void* dst, void* src, size_t dst_size) {
assert(dst_size >= sizeof(Rep));
Rep* rep = static_cast<Rep*>(src);
new (dst) Rep(std::move(*rep));
rep->~Rep();
return sizeof(Rep);
}
static R Call(void* env, Arg&&... arg) {
Rep* rep = static_cast<Rep*>(env);
R r = std::get<0>(*rep)(std::forward<Arg>(arg)...);
rep->~Rep();
return r;
}
static void NotCalled(void* env) {
Rep* rep = static_cast<Rep*>(env);
std::apply(std::get<0>(*rep), std::get<1>(*rep)());
rep->~Rep();
}
};
template <class F, class A, class... Arg>
class SmallMustCall<F, A, void, Arg...> {
public:
typedef std::tuple<F, A> Rep;
static const VTable<void, Arg...> vtable;
static void InitEnv(void* env, F&& f, A&& a) {
new (env) Rep(std::forward<F>(f), std::forward<A>(a));
}
private:
static size_t Move(void* dst, void* src, size_t dst_size) {
assert(dst_size >= sizeof(Rep));
Rep* rep = static_cast<Rep*>(src);
new (dst) Rep(std::move(*rep));
rep->~Rep();
return sizeof(Rep);
}
static void Call(void* env, Arg&&... arg) {
Rep* rep = static_cast<Rep*>(env);
std::get<0> (*rep)(std::forward<Arg>(arg)...);
rep->~Rep();
}
static void NotCalled(void* env) {
Rep* rep = static_cast<Rep*>(env);
std::apply(std::get<0>(*rep), std::get<1>(*rep)());
rep->~Rep();
}
};
template <class F, class A, class R, class... Arg>
const VTable<R, Arg...> SmallMustCall<F, A, R, Arg...>::vtable = {Move, Call,
NotCalled};
template <class F, class A, class... Arg>
const VTable<void, Arg...> SmallMustCall<F, A, 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
// Marker to designate a function that *must* be called
enum MustCall { MUST_CALL };
// 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));
}
template <class F, class A,
typename = typename std::enable_if<sizeof(std::tuple<F, A>) <=
kStorage>::type>
OnceFn(MustCall, F&& f, A&& a) {
vtable_ = &once_fn_detail::SmallMustCall<F, A, R, Arg...>::vtable;
once_fn_detail::SmallMustCall<F, A, R, Arg...>::InitEnv(
&env_, std::forward<F>(f), std::forward<A>(a));
}
template <class F>
OnceFn(MustCall, F&& f)
: OnceFn(MUST_CALL, std::forward<F>(f),
[]() { return std::tuple<Arg...>(); }) {}
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