/* 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 . */ #include "wrenpp/vm.hpp" #include "wrenpp/configuration.hpp" #include "wrenpp/callback_manager.hpp" #include "wrenpp/class_manager.hpp" #include "dynafunc_maker.hpp" #include #include #include 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(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(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(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(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(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(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(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(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(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(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(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(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 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(); } 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(&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