This commit breaks the ARM64 version, I will fix it next. Lots going on here. DynafuncMaker got updated to store strings to back the ModuleAndName objects that get hardcoded in the assembly glue function. ModuleAndName is not a typedef to a tuple anymore, because I discovered that tuples suck. They get always pushed on the stack when passed as parameter, instead the new implementation gets passed into 2 registers being it a standard layout type. dhandy::bt::string got updated so it can be used as a literal value for non-type template parameters, which allowed for a really easy to use `wren::MN<>` helper. Code now fully requires c++20.
407 lines
12 KiB
C++
407 lines
12 KiB
C++
/* Copyright 2020-2022, Michele Santullo
|
|
* This file is part of wrenpp.
|
|
*
|
|
* Wrenpp is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Wrenpp is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with wrenpp. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "wrenpp/vm.hpp"
|
|
#include "wrenpp/configuration.hpp"
|
|
#include "wrenpp/callback_manager.hpp"
|
|
#include "wrenpp/class_manager.hpp"
|
|
#include "dynafunc_maker.hpp"
|
|
#include <wren.hpp>
|
|
#include <cassert>
|
|
#include <stdexcept>
|
|
|
|
namespace wren {
|
|
namespace {
|
|
[[gnu::const]]
|
|
ErrorType to_error_type (WrenErrorType wet) {
|
|
switch (wet) {
|
|
case WREN_ERROR_COMPILE: return ErrorType::Compile;
|
|
case WREN_ERROR_RUNTIME: return ErrorType::Runtime;
|
|
case WREN_ERROR_STACK_TRACE: return ErrorType::StackTrace;
|
|
default: return ErrorType::Runtime;
|
|
};
|
|
}
|
|
|
|
void load_module_complete_fn (WrenVM* wvm, const char* name, struct WrenLoadModuleResult result);
|
|
|
|
void write_fn (WrenVM* wvm, const char* text) {
|
|
auto cb = static_cast<detail::Callbacks*>(wrenGetUserData(wvm));
|
|
assert(cb);
|
|
assert(cb->write_fn and cb->config_obj and cb->owner);
|
|
cb->write_fn(*cb->config_obj, cb->owner, text);
|
|
}
|
|
|
|
void error_fn (WrenVM* wvm, WrenErrorType type, const char* module, int line, const char* message) {
|
|
auto cb = static_cast<detail::Callbacks*>(wrenGetUserData(wvm));
|
|
assert(cb);
|
|
assert(cb->error_fn and cb->config_obj and cb->owner);
|
|
const char* const sane_module = (module ? module : "");
|
|
cb->error_fn(*cb->config_obj, cb->owner, to_error_type(type), sane_module, line, message);
|
|
}
|
|
|
|
const char* resolve_module_fn (WrenVM* wvm, const char* importer, const char* name) {
|
|
auto cb = static_cast<detail::Callbacks*>(wrenGetUserData(wvm));
|
|
assert(cb);
|
|
assert(cb->resolve_module_fn and cb->config_obj and cb->owner);
|
|
return cb->resolve_module_fn(*cb->config_obj, cb->owner, importer, name);
|
|
}
|
|
|
|
void* reallocate_fn (void* memory, size_t size, void* user_data) {
|
|
auto cb = static_cast<detail::Callbacks*>(user_data);
|
|
assert(cb);
|
|
assert(cb->reallocate_fn);
|
|
return cb->reallocate_fn(memory, size);
|
|
}
|
|
|
|
WrenLoadModuleResult load_module_fn (WrenVM* wvm, const char* name) {
|
|
const auto user_data = wrenGetUserData(wvm);
|
|
auto cb = static_cast<detail::Callbacks*>(user_data);
|
|
assert(cb);
|
|
assert(cb->load_module_fn and cb->config_obj and cb->owner);
|
|
|
|
char* const source = cb->load_module_fn(*cb->config_obj, cb->owner, name);
|
|
return ::WrenLoadModuleResult{
|
|
.source = source,
|
|
.onComplete = (cb->load_module_complete_fn ? &load_module_complete_fn : nullptr),
|
|
.userData = nullptr
|
|
};
|
|
}
|
|
|
|
WrenForeignMethodFn foreign_method_fn (WrenVM* wvm, const char* module, const char* class_name, bool is_static, const char* signature) {
|
|
auto cb = static_cast<detail::Callbacks*>(wrenGetUserData(wvm));
|
|
assert(cb);
|
|
assert(cb->foreign_method_fn and cb->config_obj and cb->owner);
|
|
foreign_method_t func = cb->foreign_method_fn(*cb->config_obj, cb->owner, module, class_name, is_static, signature);
|
|
if (func) {
|
|
DynafuncMaker* const dfm = cb->dynafunc;
|
|
assert(dfm);
|
|
//dfm->make creates a function that takes one WrenVM* parameter
|
|
//which it disregards; then it calls func passing cb->owner to
|
|
//it and a ModuleAndName object populated with the values given
|
|
//here. This should rarely change, but please double check if
|
|
//this is important to you as this comment might become
|
|
//outdated.
|
|
auto retval = reinterpret_cast<WrenForeignMethodFn>(dfm->make(
|
|
cb->owner,
|
|
func,
|
|
module,
|
|
class_name
|
|
));
|
|
return retval;
|
|
}
|
|
else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void load_module_complete_fn (WrenVM* wvm, const char* name, struct WrenLoadModuleResult result) {
|
|
auto cb = static_cast<detail::Callbacks*>(wrenGetUserData(wvm));
|
|
assert(cb);
|
|
assert(cb->load_module_complete_fn and cb->config_obj and cb->owner);
|
|
|
|
//Documentation says result is the same object we returned from
|
|
//load_module_fn. It should then be safe to const_cast source,
|
|
//since it wasn't const when we got it there
|
|
char* const non_const_buff = const_cast<char*>(result.source);
|
|
(*cb->load_module_complete_fn)(*cb->config_obj, cb->owner, name, non_const_buff);
|
|
}
|
|
|
|
WrenForeignClassMethods foreign_class_fn (WrenVM* wvm, const char* module, const char* class_name) {
|
|
auto cb = static_cast<detail::Callbacks*>(wrenGetUserData(wvm));
|
|
assert(cb);
|
|
assert(cb->foreign_class_fn and cb->config_obj and cb->owner);
|
|
foreign_class_t funcs = cb->foreign_class_fn(*cb->config_obj, cb->owner, module, class_name);
|
|
WrenForeignClassMethods retval;
|
|
if (funcs.allocate) {
|
|
DynafuncMaker* const dfm = cb->dynafunc;
|
|
assert(dfm);
|
|
retval.allocate = reinterpret_cast<WrenForeignMethodFn>(dfm->make(
|
|
cb->owner,
|
|
funcs.allocate,
|
|
module,
|
|
class_name
|
|
));
|
|
}
|
|
else {
|
|
retval.allocate = nullptr;
|
|
}
|
|
retval.finalize = funcs.finalize;
|
|
return retval;
|
|
}
|
|
|
|
void throw_if_err (WrenInterpretResult res, const char* module_name) {
|
|
using std::runtime_error;
|
|
using std::string;
|
|
|
|
switch (res) {
|
|
case WREN_RESULT_COMPILE_ERROR:
|
|
throw runtime_error(string("Compilation of ") + module_name + " has failed");
|
|
case WREN_RESULT_RUNTIME_ERROR:
|
|
throw runtime_error(string("A runtime error occurred in ") + module_name);
|
|
|
|
case WREN_RESULT_SUCCESS:
|
|
break;
|
|
}
|
|
}
|
|
} //unnamed namespace
|
|
|
|
struct VM::LocalData {
|
|
explicit LocalData (const detail::Callbacks& cb) :
|
|
callbacks(cb),
|
|
wvm(nullptr)
|
|
{
|
|
callbacks.dynafunc = &dynafunc;
|
|
}
|
|
~LocalData() noexcept {
|
|
if (wvm)
|
|
wrenFreeVM(wvm);
|
|
#if !defined(NDEBUG)
|
|
wvm = nullptr;
|
|
#endif
|
|
}
|
|
|
|
CallbackManager callback_manager;
|
|
ClassManager class_manager;
|
|
detail::Callbacks callbacks;
|
|
DynafuncMaker dynafunc;
|
|
WrenVM* wvm;
|
|
void* user_data;
|
|
std::uint32_t user_data_type;
|
|
};
|
|
|
|
VM::VM (Configuration* conf, const detail::Callbacks& cb, void* user_data, std::uint32_t user_data_type) :
|
|
m_local(std::make_unique<LocalData>(cb))
|
|
{
|
|
this->reset(conf);
|
|
m_local->user_data = user_data;
|
|
m_local->user_data_type = user_data_type;
|
|
}
|
|
|
|
VM::~VM() noexcept = default;
|
|
|
|
DynafuncMaker* VM::dynafunc_maker() {
|
|
return &m_local->dynafunc;
|
|
}
|
|
|
|
std::uint32_t VM::user_data_type() const {
|
|
return m_local->user_data_type;
|
|
}
|
|
|
|
void* VM::void_user_data() {
|
|
return m_local->user_data;
|
|
}
|
|
|
|
void VM::set_user_data (void* user_data, std::uint32_t user_data_type) {
|
|
m_local->user_data = user_data;
|
|
m_local->user_data_type = user_data_type;
|
|
}
|
|
|
|
void VM::interpret (const char* module_name, const char* script) {
|
|
using std::string;
|
|
using std::runtime_error;
|
|
|
|
const WrenInterpretResult res = wrenInterpret(m_local->wvm, module_name, script);
|
|
throw_if_err(res, module_name);
|
|
}
|
|
|
|
void VM::call (const Handle& method) {
|
|
const WrenInterpretResult res = wrenCall(m_local->wvm, method);
|
|
throw_if_err(res, "[wrenCall()]");
|
|
}
|
|
|
|
void VM::release_handle (Handle& handle) noexcept {
|
|
if (handle)
|
|
wrenReleaseHandle(m_local->wvm, handle);
|
|
}
|
|
|
|
void VM::set_slot_string (int slot_num, const char* value) {
|
|
wrenSetSlotString(m_local->wvm, slot_num, value);
|
|
}
|
|
|
|
void VM::set_slot_double (int slot_num, double value) {
|
|
wrenSetSlotDouble(m_local->wvm, slot_num, value);
|
|
}
|
|
|
|
void VM::set_slot_bool (int slot_num, bool value) {
|
|
wrenSetSlotBool(m_local->wvm, slot_num, value);
|
|
}
|
|
|
|
void VM::set_slot_null (int slot_num) {
|
|
wrenSetSlotNull(m_local->wvm, slot_num);
|
|
}
|
|
|
|
void VM::set_slot_bytes (int slot_num, const char* bytes, std::size_t length) {
|
|
wrenSetSlotBytes(m_local->wvm, slot_num, bytes, length);
|
|
}
|
|
|
|
void* VM::set_slot_new_foreign (int slot_num, int class_slot, std::size_t size) {
|
|
return wrenSetSlotNewForeign(m_local->wvm, slot_num, class_slot, size);
|
|
}
|
|
|
|
bool VM::slot_bool (int slot_num) {
|
|
assert(SlotType::Bool == slot_type(slot_num));
|
|
return wrenGetSlotBool(m_local->wvm, slot_num);
|
|
}
|
|
|
|
const char* VM::slot_string (int slot_num) {
|
|
assert(SlotType::String == slot_type(slot_num));
|
|
return wrenGetSlotString(m_local->wvm, slot_num);
|
|
}
|
|
|
|
double VM::slot_double (int slot_num) {
|
|
assert(SlotType::Num == slot_type(slot_num));
|
|
return wrenGetSlotDouble(m_local->wvm, slot_num);
|
|
}
|
|
|
|
std::pair<const char*, int> VM::slot_bytes (int slot_num) {
|
|
assert(SlotType::String == slot_type(slot_num));
|
|
int length;
|
|
const char* const data = wrenGetSlotBytes(m_local->wvm, slot_num, &length);
|
|
return {data, length};
|
|
}
|
|
|
|
void* VM::slot_foreign (int slot_num) {
|
|
assert(SlotType::Foreign == slot_type(slot_num));
|
|
return wrenGetSlotForeign(m_local->wvm, slot_num);
|
|
}
|
|
|
|
void VM::set_user_data (std::nullptr_t) {
|
|
m_local->user_data = nullptr;
|
|
m_local->user_data_type = detail::type_id<std::nullptr_t>();
|
|
}
|
|
|
|
bool VM::has_user_data() const noexcept {
|
|
return nullptr != m_local->user_data;
|
|
}
|
|
|
|
bool VM::has_module(const char* module) const noexcept {
|
|
return wrenHasModule(m_local->wvm, module);
|
|
}
|
|
|
|
bool VM::has_variable(const char* module, const char* name) const noexcept {
|
|
return wrenHasVariable(m_local->wvm, module, name);
|
|
}
|
|
|
|
void VM::abort_fiber(int slot_num) {
|
|
wrenAbortFiber(m_local->wvm, slot_num);
|
|
}
|
|
|
|
std::size_t VM::dynafunc_byte_size() const {
|
|
return m_local->dynafunc.total_memory();
|
|
}
|
|
|
|
void VM::reset (Configuration* conf) {
|
|
WrenConfiguration wconf;
|
|
wrenInitConfiguration(&wconf);
|
|
wconf.userData = static_cast<void*>(&m_local->callbacks);
|
|
|
|
wconf.initialHeapSize = conf->initial_heap_size();
|
|
wconf.minHeapSize = conf->min_heap_size();
|
|
wconf.heapGrowthPercent = conf->heap_growth_percent();
|
|
|
|
auto& cb = m_local->callbacks;
|
|
if (cb.write_fn)
|
|
wconf.writeFn = &write_fn;
|
|
|
|
if (cb.error_fn)
|
|
wconf.errorFn = &error_fn;
|
|
|
|
if (cb.reallocate_fn)
|
|
wconf.reallocateFn = &reallocate_fn;
|
|
|
|
if (cb.resolve_module_fn)
|
|
wconf.resolveModuleFn = &resolve_module_fn;
|
|
|
|
if (cb.load_module_fn)
|
|
wconf.loadModuleFn = &load_module_fn;
|
|
|
|
if (cb.foreign_method_fn)
|
|
wconf.bindForeignMethodFn = &foreign_method_fn;
|
|
|
|
if (cb.foreign_class_fn)
|
|
wconf.bindForeignClassFn = &foreign_class_fn;
|
|
|
|
if (m_local->wvm)
|
|
wrenFreeVM(m_local->wvm);
|
|
m_local->wvm = wrenNewVM(&wconf);
|
|
if (not m_local->wvm)
|
|
throw std::runtime_error("Failed to initialize Wren VM");
|
|
|
|
m_local->dynafunc.clear();
|
|
}
|
|
|
|
CallbackManager& VM::callback_manager() {
|
|
return m_local->callback_manager;
|
|
}
|
|
|
|
ClassManager& VM::class_manager() {
|
|
return m_local->class_manager;
|
|
}
|
|
|
|
void VM::ensure_slots (int num_slots) {
|
|
wrenEnsureSlots(m_local->wvm, num_slots);
|
|
}
|
|
|
|
int VM::slot_count() {
|
|
return wrenGetSlotCount(m_local->wvm);
|
|
}
|
|
|
|
void VM::variable(const char* module, const char* name, int slot) noexcept {
|
|
assert(slot_count() >= 1);
|
|
wrenGetVariable(m_local->wvm, module, name, slot);
|
|
}
|
|
|
|
void VM::variable_or_throw(const char* module, const char* name, int slot) {
|
|
using std::string;
|
|
using std::runtime_error;
|
|
|
|
if (not has_module(module))
|
|
throw runtime_error(string{"Module "} + module + " is unknown");
|
|
if (not has_variable(module, name))
|
|
throw runtime_error(string{"Variable "} + module + "::" + name + " not found");
|
|
|
|
variable(module, name, slot);
|
|
}
|
|
|
|
void VM::set_slot_handle (const Handle& handle, int slot) {
|
|
wrenSetSlotHandle(m_local->wvm, slot, handle);
|
|
}
|
|
|
|
SlotType VM::slot_type(int slot_num) {
|
|
const WrenType tp = wrenGetSlotType(m_local->wvm, slot_num);
|
|
switch (tp) {
|
|
case WREN_TYPE_NUM: return SlotType::Num;
|
|
case WREN_TYPE_BOOL: return SlotType::Bool;
|
|
case WREN_TYPE_LIST: return SlotType::List;
|
|
case WREN_TYPE_NULL: return SlotType::Null;
|
|
case WREN_TYPE_STRING: return SlotType::String;
|
|
case WREN_TYPE_FOREIGN: return SlotType::Foreign;
|
|
case WREN_TYPE_UNKNOWN: return SlotType::Unknown;
|
|
case WREN_TYPE_MAP: return SlotType::Map;
|
|
};
|
|
assert(false);
|
|
return SlotType::Unknown;
|
|
}
|
|
|
|
Handle VM::slot_handle(int slot_num) {
|
|
return {this, wrenGetSlotHandle(m_local->wvm, slot_num)};
|
|
}
|
|
|
|
Handle VM::make_call_handle(const char* signature) {
|
|
return {this, wrenMakeCallHandle(m_local->wvm, signature)};
|
|
}
|
|
} //namespace wren
|