add cs_callable to replace std::function

now function data can be allocated with our own allocator as
well, plus we get guarantees on small storage; this type is
move-only, and it's also more memory efficient than std::function,
at least the libstdc++ implementation
master
Daniel Kolesa 2021-03-22 00:58:35 +01:00
parent f72a39f656
commit 63251f0eac
4 changed files with 306 additions and 49 deletions

View File

@ -2,12 +2,15 @@
#define LIBCUBESCRIPT_CUBESCRIPT_HH
#include <cstring>
#include <cstddef>
#include <vector>
#include <optional>
#include <functional>
#include <type_traits>
#include <algorithm>
#include <memory>
#include <utility>
#include <span>
#include "cubescript_conf.hh"
@ -42,6 +45,216 @@ struct cs_internal_error: std::runtime_error {
using std::runtime_error::runtime_error;
};
template<typename R, typename ...A>
struct cs_callable {
private:
struct base {
base(base const &);
base &operator=(base const &);
public:
base() {}
virtual ~base() {}
virtual void move_to(base *) = 0;
virtual R operator()(A &&...args) = 0;
};
template<typename F>
struct store: base {
explicit store(F &&f): p_stor{std::move(f)} {}
virtual void move_to(base *p) {
::new (p) store{std::move(p_stor)};
}
virtual R operator()(A &&...args) {
return std::invoke(
*reinterpret_cast<F *>(&p_stor), std::forward<A>(args)...
);
}
private:
F p_stor;
};
using alloc_f = void *(*)(void *, void *, std::size_t, std::size_t);
struct f_alloc {
alloc_f af;
void *ud;
size_t asize;
};
std::aligned_storage_t<sizeof(void *) * 4> p_stor;
base *p_func;
static inline base *as_base(void *p) {
return static_cast<base *>(p);
}
template<typename T>
static inline bool f_not_null(T const &) { return true; }
template<typename T>
static inline bool f_not_null(T *p) { return !!p; }
template<typename CR, typename C>
static inline bool f_not_null(CR C::*p) { return !!p; }
template<typename T>
static inline bool f_not_null(cs_callable<T> const &f) { return !!f; }
bool small_storage() {
return (static_cast<void *>(p_func) == &p_stor);
}
void cleanup() {
if (!p_func) {
return;
}
p_func->~base();
if (!small_storage()) {
auto &ad = *reinterpret_cast<f_alloc *>(&p_stor);
ad.af(ad.ud, p_func, ad.asize, 0);
}
}
public:
cs_callable() noexcept: p_func{nullptr} {}
cs_callable(std::nullptr_t) noexcept: p_func{nullptr} {}
cs_callable(std::nullptr_t, alloc_f, void *) noexcept: p_func{nullptr} {}
cs_callable(cs_callable &&f) noexcept {
if (!f.p_func) {
p_func = nullptr;
} else if (f.small_storage()) {
p_func = as_base(&p_stor);
f.p_func->move_to(p_func);
} else {
p_func = f.p_func;
f.p_func = nullptr;
}
}
template<typename F>
cs_callable(F func, alloc_f af, void *ud) {
if (!f_not_null(func)) {
return;
}
if constexpr (sizeof(store<F>) <= sizeof(p_stor)) {
auto *p = static_cast<void *>(&p_stor);
p_func = ::new (p) store<F>{std::move(func)};
} else {
auto &ad = *reinterpret_cast<f_alloc *>(&p_stor);
ad.af = af;
ad.ud = ud;
ad.asize = sizeof(store<F>);
p_func = static_cast<store<F> *>(
af(ud, nullptr, 0, sizeof(store<F>))
);
try {
new (p_func) store<F>{std::move(func)};
} catch (...) {
af(ud, p_func, sizeof(store<F>), 0);
throw;
}
}
}
cs_callable &operator=(cs_callable const &) = delete;
cs_callable &operator=(cs_callable &&f) noexcept {
cleanup();
if (f.p_func == nullptr) {
p_func = nullptr;
} else if (f.small_storage()) {
p_func = as_base(&p_stor);
f.p_func->move_to(p_func);
} else {
p_func = f.p_func;
f.p_func = nullptr;
}
return *this;
}
cs_callable &operator=(std::nullptr_t) noexcept {
cleanup();
p_func = nullptr;
return *this;
}
template<typename F>
cs_callable &operator=(F &&func) {
cs_callable{std::forward<F>(func)}.swap(*this);
return *this;
}
~cs_callable() {
cleanup();
}
void swap(cs_callable &f) noexcept {
std::aligned_storage_t<sizeof(p_stor)> tmp_stor;
if (small_storage() && f.small_storage()) {
auto *t = as_base(&tmp_stor);
p_func->move_to(t);
p_func->~base();
p_func = nullptr;
f.p_func->move_to(as_base(&p_stor));
f.p_func->~base();
f.p_func = nullptr;
p_func = as_base(&p_stor);
t->move_to(as_base(&f.p_stor));
t->~base();
f.p_func = as_base(&f.p_stor);
} else if (small_storage()) {
/* copy allocator address/size */
memcpy(&tmp_stor, &f.p_stor, sizeof(tmp_stor));
p_func->move_to(as_base(&f.p_stor));
p_func->~base();
p_func = f.p_func;
f.p_func = as_base(&f.p_stor);
memcpy(&p_stor, &tmp_stor, sizeof(tmp_stor));
} else if (f.small_storage()) {
/* copy allocator address/size */
memcpy(&tmp_stor, &p_stor, sizeof(tmp_stor));
f.p_func->move_to(as_base(&p_stor));
f.p_func->~base();
f.p_func = p_func;
p_func = as_base(&p_stor);
memcpy(&f.p_stor, &tmp_stor, sizeof(tmp_stor));
} else {
/* copy allocator address/size */
memcpy(&tmp_stor, &p_stor, sizeof(tmp_stor));
memcpy(&p_stor, &f.p_stor, sizeof(tmp_stor));
memcpy(&f.p_stor, &tmp_stor, sizeof(tmp_stor));
std::swap(p_func, f.p_func);
}
}
explicit operator bool() const noexcept {
return !!p_func;
}
R operator()(A ...args) {
return (*p_func)(std::forward<A>(args)...);
}
};
using cs_alloc_cb = void *(*)(void *, void *, size_t, size_t);
struct cs_state;
struct cs_ident;
struct cs_value;
struct cs_var;
using cs_hook_cb = cs_callable<void, cs_state &>;
using cs_var_cb = cs_callable<void, cs_state &, cs_ident &>;
using cs_vprint_cb = cs_callable<void, cs_state const &, cs_var const &>;
using cs_command_cb = cs_callable<
void, cs_state &, std::span<cs_value>, cs_value &
>;
enum {
CS_IDF_PERSIST = 1 << 0,
CS_IDF_OVERRIDE = 1 << 1,
@ -53,8 +266,6 @@ enum {
};
struct cs_bcode;
struct cs_value;
struct cs_state;
struct cs_shared_state;
struct cs_ident_impl;
@ -355,11 +566,21 @@ struct LIBCUBESCRIPT_EXPORT cs_state {
cs_state new_thread();
cs_hook_cb set_call_hook(cs_hook_cb func);
template<typename F>
cs_hook_cb set_call_hook(F &&f) {
return std::move(set_call_hook(
cs_hook_cb{std::forward<F>(f), callable_alloc, this}
));
}
cs_hook_cb const &get_call_hook() const;
cs_hook_cb &get_call_hook();
cs_vprint_cb set_var_printer(cs_vprint_cb func);
template<typename F>
cs_vprint_cb set_var_printer(F &&f) {
return std::move(set_var_printer(
cs_vprint_cb{std::forward<F>(f), callable_alloc, this}
));
}
cs_vprint_cb const &get_var_printer() const;
void init_libs(int libs = CS_LIB_ALL);
@ -370,22 +591,58 @@ struct LIBCUBESCRIPT_EXPORT cs_state {
cs_ident *new_ident(std::string_view name, int flags = CS_IDF_UNKNOWN);
cs_ident *force_ident(cs_value &v);
template<typename F>
cs_ivar *new_ivar(
std::string_view n, cs_int m, cs_int x, cs_int v,
cs_var_cb f = cs_var_cb(), int flags = 0
);
cs_fvar *new_fvar(
std::string_view n, cs_float m, cs_float x, cs_float v,
cs_var_cb f = cs_var_cb(), int flags = 0
);
cs_svar *new_svar(
std::string_view n, std::string_view v,
cs_var_cb f = cs_var_cb(), int flags = 0
);
std::string_view name, cs_int m, cs_int x, cs_int v,
F &&f, int flags = 0
) {
return new_ivar(
name, m, x, v,
cs_var_cb{std::forward<F>(f), callable_alloc, this}, flags
);
}
cs_ivar *new_ivar(std::string_view name, cs_int m, cs_int x, cs_int v) {
return new_ivar(name, m, x, v, cs_var_cb{}, 0);
}
template<typename F>
cs_fvar *new_fvar(
std::string_view name, cs_float m, cs_float x, cs_float v,
F &&f, int flags = 0
) {
return new_fvar(
name, m, x, v,
cs_var_cb{std::forward<F>(f), callable_alloc, this}, flags
);
}
cs_fvar *new_fvar(
std::string_view name, cs_float m, cs_float x, cs_float v
) {
return new_fvar(name, m, x, v, cs_var_cb{}, 0);
}
template<typename F>
cs_svar *new_svar(
std::string_view name, std::string_view v, F &&f, int flags = 0
) {
return new_svar(
name, v,
cs_var_cb{std::forward<F>(f), callable_alloc, this}, flags
);
}
cs_svar *new_svar(std::string_view name, std::string_view v) {
return new_svar(name, v, cs_var_cb{}, 0);
}
template<typename F>
cs_command *new_command(
std::string_view name, std::string_view args, cs_command_cb func
);
std::string_view name, std::string_view args, F &&f
) {
return new_command(
name, args,
cs_command_cb{std::forward<F>(f), callable_alloc, this}
);
}
cs_ident *get_ident(std::string_view name);
cs_alias *get_alias(std::string_view name);
@ -448,11 +705,36 @@ struct LIBCUBESCRIPT_EXPORT cs_state {
void print_var(cs_var const &v) const;
private:
cs_hook_cb set_call_hook(cs_hook_cb func);
cs_vprint_cb set_var_printer(cs_vprint_cb func);
cs_ivar *new_ivar(
std::string_view n, cs_int m, cs_int x, cs_int v,
cs_var_cb f, int flags
);
cs_fvar *new_fvar(
std::string_view n, cs_float m, cs_float x, cs_float v,
cs_var_cb f, int flags
);
cs_svar *new_svar(
std::string_view n, std::string_view v, cs_var_cb f, int flags
);
cs_command *new_command(
std::string_view name, std::string_view args, cs_command_cb func
);
static void *callable_alloc(
void *data, void *p, std::size_t os, std::size_t ns
) {
return static_cast<cs_state *>(data)->alloc(p, os, ns);
}
LIBCUBESCRIPT_LOCAL cs_state(cs_shared_state *s);
cs_ident *add_ident(cs_ident *id, cs_ident_impl *impl);
LIBCUBESCRIPT_LOCAL void *alloc(void *ptr, size_t olds, size_t news);
void *alloc(void *ptr, size_t olds, size_t news);
cs_gen_state *p_pstate = nullptr;
void *p_errbuf = nullptr;

View File

@ -1,40 +1,13 @@
#ifndef LIBCUBESCRIPT_CUBESCRIPT_CONF_HH
#define LIBCUBESCRIPT_CUBESCRIPT_CONF_HH
#include <functional>
#include <span>
/* do not modify */
namespace cscript {
struct cs_state;
struct cs_ident;
struct cs_value;
struct cs_var;
}
/* configurable section */
namespace cscript {
using cs_int = int;
using cs_float = float;
/* probably don't want to change these, but if you use a custom allocation
* function for your state, keep in mind potential heap allocations in
* these are not handled by it (as std::function has no allocator support)
*
* normally std::function is optimized not to do allocations for small
* objects, so as long as you don't pass a lambda that captures by copy
* or move or something similar, you should be fine - but if you really
* need to make sure, override this with your own type
*/
using cs_var_cb = std::function<void(cs_state &, cs_ident &)>;
using cs_vprint_cb = std::function<void(cs_state const &, cs_var const &)>;
using cs_command_cb = std::function<void(cs_state &, std::span<cs_value>, cs_value &)>;
using cs_hook_cb = std::function<void(cs_state &)>;
using cs_alloc_cb = void *(*)(void *, void *, size_t, size_t);
constexpr auto const CS_INT_FORMAT = "%d";
constexpr auto const CS_FLOAT_FORMAT = "%.7g";
constexpr auto const CS_ROUND_FLOAT_FORMAT = "%.1f";
} /* namespace cscript */
#endif /* LIBCUBESCRIPT_CUBESCRIPT_CONF_HH */
#endif /* LIBCUBESCRIPT_CUBESCRIPT_CONF_HH */

View File

@ -147,7 +147,7 @@ struct cs_shared_state {
allocf{af}, aptr{data},
idents{allocator_type{this}},
identmap{allocator_type{this}},
varprintf{[](auto &, auto &) {}},
varprintf{},
strman{create<cs_strman>(this)}
{}

View File

@ -458,7 +458,7 @@ LIBCUBESCRIPT_EXPORT cs_vprint_cb const &cs_state::get_var_printer() const {
return p_state->varprintf;
}
void *cs_state::alloc(void *ptr, size_t os, size_t ns) {
LIBCUBESCRIPT_EXPORT void *cs_state::alloc(void *ptr, size_t os, size_t ns) {
return p_state->alloc(ptr, os, ns);
}
@ -668,7 +668,9 @@ LIBCUBESCRIPT_EXPORT void cs_state::set_alias(std::string_view name, cs_value v)
}
LIBCUBESCRIPT_EXPORT void cs_state::print_var(cs_var const &v) const {
p_state->varprintf(*this, v);
if (p_state->varprintf) {
p_state->varprintf(*this, v);
}
}
LIBCUBESCRIPT_EXPORT cs_value cs_alias::get_value() const {