mirror of
https://github.com/matrix-construct/construct
synced 2024-09-30 04:38:52 +02:00
ircd::js: Stub runtime/context callback framework.
This commit is contained in:
parent
c1fd6391c6
commit
01e6e732cc
4 changed files with 288 additions and 79 deletions
|
@ -26,90 +26,86 @@ namespace ircd {
|
|||
namespace js {
|
||||
|
||||
struct context
|
||||
:custom_ptr<JSContext>
|
||||
:private custom_ptr<JSContext>
|
||||
{
|
||||
struct privdata
|
||||
struct opts
|
||||
{
|
||||
virtual ~privdata() noexcept = 0;
|
||||
};
|
||||
size_t stack_chunk_size = 8_KiB;
|
||||
bool dtor_gc = true;
|
||||
}
|
||||
opts; // We keep a copy of the given opts here.
|
||||
|
||||
operator JSContext *() const { return get(); }
|
||||
operator JSContext &() const { return custom_ptr<JSContext>::operator*(); }
|
||||
bool operator!() const { return !custom_ptr<JSContext>::operator bool(); }
|
||||
auto &runtime() const { return our(JS_GetRuntime(get())); }
|
||||
auto &runtime() { return our(JS_GetRuntime(get())); }
|
||||
auto ptr() const { return get(); }
|
||||
auto ptr() { return get(); }
|
||||
|
||||
void reset() { custom_ptr<JSContext>::reset(nullptr); }
|
||||
|
||||
template<class... args> context(JSRuntime *const &, args&&...);
|
||||
context(JSRuntime *const &, const struct opts &);
|
||||
context() = default;
|
||||
context(context &&) noexcept;
|
||||
context(const context &) = delete;
|
||||
context &operator=(context &&) noexcept;
|
||||
context &operator=(const context &) = delete;
|
||||
};
|
||||
|
||||
// A default JSContext instance is provided residing near the main runtime as a convenience
|
||||
// for misc/utility/system purposes if necessary.
|
||||
extern context mc;
|
||||
|
||||
// Get to our `struct context` from any upstream JSContext
|
||||
const context &our(const JSContext *const &);
|
||||
context &our(JSContext *const &);
|
||||
|
||||
// Get/Set your privdata managed by this object, casting to your expected type.
|
||||
template<size_t at, class T = context::privdata> const T *priv(const JSContext &);
|
||||
template<size_t at, class T = context::privdata> T *priv(JSContext &);
|
||||
template<size_t at, class T> void priv(JSContext &, T *);
|
||||
template<class T = privdata> const T *priv(const context &);
|
||||
template<class T = privdata> T *priv(context &);
|
||||
void priv(context &, privdata *const &);
|
||||
|
||||
auto version(const context &c) { return version(JS_GetVersion(c)); }
|
||||
auto running(const context &c) { return JS_IsRunning(c); }
|
||||
auto uncaught_exception(const context &c) { return JS_IsExceptionPending(c); }
|
||||
auto rethrow_exception(context &c) { return JS_ReportPendingException(c); }
|
||||
auto interrupted(const context &c) { return JS_CheckForInterrupt(c); }
|
||||
void out_of_memory(context &c) { JS_ReportOutOfMemory(c); }
|
||||
void allocation_overflow(context &c) { JS_ReportAllocationOverflow(c); }
|
||||
void run_gc(context &c) { JS_MaybeGC(c); }
|
||||
|
||||
|
||||
template<class... args>
|
||||
context::context(JSRuntime *const &runtime,
|
||||
args&&... a)
|
||||
:custom_ptr<JSContext>
|
||||
{
|
||||
JS_NewContext(runtime, std::forward<args>(a)...),
|
||||
[](JSContext *const ctx)
|
||||
{
|
||||
if(likely(ctx))
|
||||
{
|
||||
delete priv<0>(*ctx);
|
||||
delete priv<1>(*ctx);
|
||||
JS_DestroyContext(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void
|
||||
priv(context &c,
|
||||
privdata *const &ptr)
|
||||
{
|
||||
delete priv(c); // Free any existing object to overwrite/null
|
||||
JS_SetSecondContextPrivate(c, ptr);
|
||||
}
|
||||
|
||||
template<size_t at,
|
||||
class T>
|
||||
void
|
||||
priv(JSContext &c,
|
||||
T *const &ptr)
|
||||
{
|
||||
delete priv<at>(c);
|
||||
switch(at)
|
||||
{
|
||||
default:
|
||||
case 0: JS_SetContextPrivate(&c, ptr); break;
|
||||
case 1: JS_SetSecondContextPrivate(&c, ptr); break;
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t at,
|
||||
class T>
|
||||
template<class T>
|
||||
T *
|
||||
priv(JSContext &c)
|
||||
priv(context &c)
|
||||
{
|
||||
switch(at)
|
||||
{
|
||||
default:
|
||||
case 0: return dynamic_cast<T *>(static_cast<context::privdata *>(JS_GetContextPrivate(&c)));
|
||||
case 1: return dynamic_cast<T *>(static_cast<context::privdata *>(JS_GetSecondContextPrivate(&c)));
|
||||
}
|
||||
return dynamic_cast<T *>(static_cast<privdata *>(JS_GetSecondContextPrivate(c)));
|
||||
}
|
||||
|
||||
template<size_t at,
|
||||
class T>
|
||||
template<class T>
|
||||
const T *
|
||||
priv(const JSContext &c)
|
||||
priv(const context &c)
|
||||
{
|
||||
switch(at)
|
||||
{
|
||||
default:
|
||||
case 0: return dynamic_cast<const T *>(static_cast<const context::privdata *>(JS_GetContextPrivate(&c)));
|
||||
case 1: return dynamic_cast<const T *>(static_cast<const context::privdata *>(JS_GetSecondContextPrivate(&c)));
|
||||
}
|
||||
return dynamic_cast<const T *>(static_cast<const privdata *>(JS_GetSecondContextPrivate(c)));
|
||||
}
|
||||
|
||||
inline context &
|
||||
our(JSContext *const &c)
|
||||
{
|
||||
return *static_cast<context *>(JS_GetContextPrivate(c));
|
||||
}
|
||||
|
||||
inline const context &
|
||||
our(const JSContext *const &c)
|
||||
{
|
||||
return *static_cast<const context *>(JS_GetContextPrivate(const_cast<JSContext *>(c)));
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
|
|
@ -41,6 +41,16 @@
|
|||
namespace ircd {
|
||||
namespace js {
|
||||
|
||||
// Extend this class to store your data with any priv(),
|
||||
// i.e priv(runtime, privdata*) or priv(context, privdata*) etc
|
||||
struct privdata
|
||||
{
|
||||
virtual ~privdata() noexcept = 0; // Your object is managed by the host
|
||||
};
|
||||
|
||||
const char *version(const JSVersion &v) { return JS_VersionToString(v); }
|
||||
JSVersion version(const char *const &v) { return JS_StringToVersion(v); }
|
||||
|
||||
} // namespace js
|
||||
} // namespace ircd
|
||||
|
||||
|
|
|
@ -29,31 +29,79 @@ class runtime
|
|||
:custom_ptr<JSRuntime>
|
||||
{
|
||||
static void handle_error(JSContext *, const char *msg, JSErrorReport *);
|
||||
static void handle_out_of_memory(JSContext *, void *);
|
||||
static void handle_large_allocation_failure(void *);
|
||||
static void handle_gc(JSRuntime *, JSGCStatus, void *);
|
||||
static void handle_finalize(JSFreeOp *, JSFinalizeStatus, bool is_compartment, void *);
|
||||
static void handle_destroy_compartment(JSFreeOp *, JSCompartment *);
|
||||
static void handle_iterate_compartments(JSRuntime *, void *, JSCompartment *);
|
||||
static bool handle_context(JSContext *, uint op, void *);
|
||||
static bool handle_interrupt(JSContext *);
|
||||
|
||||
public:
|
||||
struct opts
|
||||
{
|
||||
size_t maxbytes = 64_MiB;
|
||||
size_t code_stack_max = 0;
|
||||
size_t trusted_stack_max = 0;
|
||||
size_t untrusted_stack_max = 0;
|
||||
};
|
||||
|
||||
struct opts opts; // We keep a copy of the given opts here
|
||||
|
||||
operator JSRuntime *() const { return get(); }
|
||||
operator JSRuntime &() const { return custom_ptr<JSRuntime>::operator*(); }
|
||||
bool operator!() const { return !custom_ptr<JSRuntime>::operator bool(); }
|
||||
auto ptr() const { return get(); }
|
||||
auto ptr() { return get(); }
|
||||
|
||||
void reset() { custom_ptr<JSRuntime>::reset(nullptr); }
|
||||
|
||||
template<class... args> runtime(args&&...);
|
||||
runtime(const struct opts &);
|
||||
runtime() = default;
|
||||
runtime(runtime &&) noexcept;
|
||||
runtime(const runtime &) = delete;
|
||||
runtime &operator=(runtime &&) noexcept;
|
||||
runtime &operator=(const runtime &) = delete;
|
||||
|
||||
friend void interrupt(runtime &);
|
||||
};
|
||||
|
||||
// Main JSRuntime instance. This should be passable in any argument requiring a
|
||||
// JSRuntime pointer. It is only valid while the js::init object is held by ircd::main().
|
||||
extern runtime main;
|
||||
|
||||
template<class... args>
|
||||
runtime::runtime(args&&... a)
|
||||
:custom_ptr<JSRuntime>
|
||||
// Get to our `struct runtime` from any upstream JSRuntime
|
||||
const runtime &our(const JSRuntime *const &);
|
||||
runtime &our(JSRuntime *const &);
|
||||
|
||||
void interrupt(runtime &r);
|
||||
void run_gc(runtime &r);
|
||||
|
||||
|
||||
inline void
|
||||
run_gc(runtime &r)
|
||||
{
|
||||
JS_NewRuntime(std::forward<args>(a)...),
|
||||
JS_DestroyRuntime
|
||||
JS_GC(r);
|
||||
}
|
||||
|
||||
inline void
|
||||
interrupt(runtime &r)
|
||||
{
|
||||
JS_SetErrorReporter(get(), handle_error);
|
||||
JS_SetInterruptCallback(r, runtime::handle_interrupt);
|
||||
JS_RequestInterruptCallback(r);
|
||||
}
|
||||
|
||||
inline runtime &
|
||||
our(JSRuntime *const &c)
|
||||
{
|
||||
return *static_cast<runtime *>(JS_GetRuntimePrivate(c));
|
||||
}
|
||||
|
||||
inline const runtime &
|
||||
our(const JSRuntime *const &c)
|
||||
{
|
||||
return *static_cast<const runtime *>(JS_GetRuntimePrivate(const_cast<JSRuntime *>(c)));
|
||||
}
|
||||
|
||||
|
||||
} // namespace js
|
||||
} // namespace ircd
|
||||
|
|
177
ircd/js.cc
177
ircd/js.cc
|
@ -58,23 +58,25 @@ ircd::js::init::init()
|
|||
if(!JS_Init())
|
||||
throw error("JS_Init(): failure");
|
||||
|
||||
const size_t main_maxbytes(64_MiB); //TODO: Configuration
|
||||
const size_t mc_stackchunk(8_KiB); //TODO: Configuration
|
||||
struct runtime::opts runtime_opts;
|
||||
struct context::opts context_opts;
|
||||
log.info("Initializing the main JS Runtime (main_maxbytes: %zu)",
|
||||
runtime_opts.maxbytes);
|
||||
|
||||
log.info("Initializing the main JS Runtime (main_maxbytes: %zu, mc_stackchunk: %zu)",
|
||||
main_maxbytes,
|
||||
mc_stackchunk);
|
||||
|
||||
main = runtime(main_maxbytes);
|
||||
mc = context(main, mc_stackchunk);
|
||||
main = runtime(runtime_opts);
|
||||
mc = context(main, context_opts);
|
||||
log.info("Initialized main JS Runtime and context (version: '%s')",
|
||||
version(mc));
|
||||
}
|
||||
|
||||
ircd::js::init::~init()
|
||||
noexcept
|
||||
{
|
||||
log.info("Terminating the main JS Runtime");
|
||||
mc.reset();
|
||||
main.reset();
|
||||
|
||||
// Assign empty objects to free and reset
|
||||
mc = context{};
|
||||
main = runtime{};
|
||||
|
||||
log.info("Terminating the JS engine");
|
||||
JS_ShutDown();
|
||||
|
@ -103,15 +105,168 @@ ircd::js::version(const ver &type)
|
|||
// ircd/js/context.h
|
||||
//
|
||||
|
||||
ircd::js::context::context(JSRuntime *const &runtime,
|
||||
const struct opts &opts)
|
||||
:custom_ptr<JSContext>
|
||||
{
|
||||
JS_NewContext(runtime, opts.stack_chunk_size),
|
||||
[opts](JSContext *const ctx) //TODO: old gcc/clang can't copy from opts.dtor_gc
|
||||
noexcept
|
||||
{
|
||||
if(!ctx)
|
||||
return;
|
||||
|
||||
// Free the user's privdata managed object
|
||||
delete static_cast<const privdata *>(JS_GetSecondContextPrivate(ctx));
|
||||
|
||||
if(opts.dtor_gc)
|
||||
JS_DestroyContext(ctx);
|
||||
else
|
||||
JS_DestroyContextNoGC(ctx);
|
||||
}
|
||||
}
|
||||
,opts{opts}
|
||||
{
|
||||
JS_SetContextPrivate(get(), this);
|
||||
}
|
||||
|
||||
ircd::js::context::context(context &&other)
|
||||
noexcept
|
||||
:custom_ptr<JSContext>{std::move(other)}
|
||||
,opts{std::move(other.opts)}
|
||||
{
|
||||
// Branch not taken for null/defaulted instance of JSContext smart ptr
|
||||
if(!!*this)
|
||||
JS_SetContextPrivate(get(), this);
|
||||
}
|
||||
|
||||
ircd::js::context &
|
||||
ircd::js::context::operator=(context &&other)
|
||||
noexcept
|
||||
{
|
||||
static_cast<custom_ptr<JSContext> &>(*this) = std::move(other);
|
||||
|
||||
opts = std::move(other.opts);
|
||||
|
||||
// Branch not taken for null/defaulted instance of JSContext smart ptr
|
||||
if(!!*this)
|
||||
JS_SetContextPrivate(get(), this);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ircd/js/runtime.h
|
||||
//
|
||||
|
||||
ircd::js::runtime::runtime(const struct opts &opts)
|
||||
:custom_ptr<JSRuntime>
|
||||
{
|
||||
JS_NewRuntime(opts.maxbytes),
|
||||
JS_DestroyRuntime
|
||||
}
|
||||
,opts(opts)
|
||||
{
|
||||
JS_SetRuntimePrivate(get(), this);
|
||||
|
||||
JS_SetErrorReporter(get(), handle_error);
|
||||
JS::SetOutOfMemoryCallback(get(), handle_out_of_memory, nullptr);
|
||||
JS::SetLargeAllocationFailureCallback(get(), handle_large_allocation_failure, nullptr);
|
||||
JS_SetGCCallback(get(), handle_gc, nullptr);
|
||||
JS_AddFinalizeCallback(get(), handle_finalize, nullptr);
|
||||
JS_SetDestroyCompartmentCallback(get(), handle_destroy_compartment);
|
||||
JS_SetContextCallback(get(), handle_context, nullptr);
|
||||
|
||||
JS_SetNativeStackQuota(get(), opts.code_stack_max, opts.trusted_stack_max, opts.untrusted_stack_max);
|
||||
}
|
||||
|
||||
ircd::js::runtime::runtime(runtime &&other)
|
||||
noexcept
|
||||
:custom_ptr<JSRuntime>{std::move(other)}
|
||||
,opts(std::move(other.opts))
|
||||
{
|
||||
// Branch not taken for null/defaulted instance of JSRuntime smart ptr
|
||||
if(!!*this)
|
||||
JS_SetRuntimePrivate(get(), this);
|
||||
}
|
||||
|
||||
ircd::js::runtime &
|
||||
ircd::js::runtime::operator=(runtime &&other)
|
||||
noexcept
|
||||
{
|
||||
static_cast<custom_ptr<JSRuntime> &>(*this) = std::move(other);
|
||||
|
||||
opts = std::move(other.opts);
|
||||
|
||||
// Branch not taken for null/defaulted instance of JSRuntime smart ptr
|
||||
if(!!*this)
|
||||
JS_SetRuntimePrivate(get(), this);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::js::runtime::handle_interrupt(JSContext *const ctx)
|
||||
{
|
||||
auto &runtime(our(ctx).runtime());
|
||||
JS_SetInterruptCallback(runtime, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::js::runtime::handle_context(JSContext *const c,
|
||||
const uint op,
|
||||
void *const priv)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ircd::js::runtime::handle_iterate_compartments(JSRuntime *const rt,
|
||||
void *const priv,
|
||||
JSCompartment *const compartment)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ircd::js::runtime::handle_destroy_compartment(JSFreeOp *const fop,
|
||||
JSCompartment *const compartment)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ircd::js::runtime::handle_finalize(JSFreeOp *const fop,
|
||||
const JSFinalizeStatus status,
|
||||
const bool is_compartment,
|
||||
void *const priv)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ircd::js::runtime::handle_gc(JSRuntime *const rt,
|
||||
const JSGCStatus status,
|
||||
void *const priv)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ircd::js::runtime::handle_large_allocation_failure(void *const priv)
|
||||
{
|
||||
log.error("Large allocation failure");
|
||||
}
|
||||
|
||||
void
|
||||
ircd::js::runtime::handle_out_of_memory(JSContext *const ctx,
|
||||
void *const priv)
|
||||
{
|
||||
log.error("JSContext(%p): out of memory", (const void *)ctx);
|
||||
}
|
||||
|
||||
void
|
||||
ircd::js::runtime::handle_error(JSContext *const ctx,
|
||||
const char *const msg,
|
||||
JSErrorReport *const report)
|
||||
{
|
||||
log.error("JSContext(%p): %s", ctx, msg);
|
||||
log.error("JSContext(%p): %s", (const void *)ctx, msg);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue