From 16c4b8cbe5f2a4126471353f3fbcc88c7c743b44 Mon Sep 17 00:00:00 2001 From: King_DuckZ Date: Sun, 26 Apr 2020 17:19:02 +0200 Subject: [PATCH] Allow foreign_method_fn() in the configuration object. Wren expects this function to return a pointer to a function that takes a WrenVM*. This is bad news because that type is wrapped inside wren::VM and client code doesn't know about it. It would be better for client code to return a pointer to a function that takes a VM* instead. In order to do the VM* to WrenVM* translation I make a "handmade" closure, that is I generate a function at runtime that contains the VM* hardcoded into it. The same function also contains the hardcoded address of the callback function client code wanted to use. By doing this I can pass a different function pointer to Wren for each foreign method, and that function knows how to forward the call to the real client callback. Currently only implemented for amd64 gnu/linux. --- meson.build | 1 + src/dynafunc.asm | 9 ++++ src/main.cpp | 13 ++++- src/wren/def_configuration.cpp | 9 ++++ src/wren/def_configuration.hpp | 3 ++ src/wren/dynafunc_maker.cpp | 95 ++++++++++++++++++++++++++++++++++ src/wren/dynafunc_maker.hpp | 24 +++++++++ src/wren/vm.cpp | 24 ++++++++- src/wren/vm.hpp | 13 ++++- 9 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 src/dynafunc.asm create mode 100644 src/wren/dynafunc_maker.cpp create mode 100644 src/wren/dynafunc_maker.hpp diff --git a/meson.build b/meson.build index 5f7d38a..01681df 100644 --- a/meson.build +++ b/meson.build @@ -11,6 +11,7 @@ executable(meson.project_name(), 'src/wren/vm.cpp', 'src/wren/configuration.cpp', 'src/wren/def_configuration.cpp', + 'src/wren/dynafunc_maker.cpp', dependencies: [wren_dep], install: true, ) diff --git a/src/dynafunc.asm b/src/dynafunc.asm new file mode 100644 index 0000000..59ef322 --- /dev/null +++ b/src/dynafunc.asm @@ -0,0 +1,9 @@ +#as --march=generic64 -o dynafunc.o ~/dev/code/cpp/wrentest/src/dynafunc.asm +#objcopy -j .text -O binary dynafunc.o dynafunc.bin +#xxd -i dynafunc.bin + +dynafunc: + movq $0xdeadbeefdeadbeef, %rdi + movq $0xbadc0ffee0ddf00d, %rdx + jmp *%rdx + ret diff --git a/src/main.cpp b/src/main.cpp index 400a1de..c14e3ea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,13 +5,24 @@ #include namespace { + const constexpr char g_script[] = "" +R"(class Math { + construct new() {} + foreign add(a, b) +} +System.print("I am running in a VM!") +var myvar = Math.new() +myvar.add(5, 6) +System.print("Script done") +)"; + } //unnamed namespace int main() { std::cout << "hello world\n"; wren::DefConfiguration config; wren::VM vm(&config); - vm.interpret("my_module", "System.print(\"I am running in a VM!\")"); + vm.interpret("my_module", g_script); std::this_thread::sleep_for(std::chrono::milliseconds(2000)); return 0; diff --git a/src/wren/def_configuration.cpp b/src/wren/def_configuration.cpp index d5af0f8..dddfc27 100644 --- a/src/wren/def_configuration.cpp +++ b/src/wren/def_configuration.cpp @@ -2,6 +2,10 @@ #include namespace wren { + void test (VM* vm) { + std::cout << "VM " << vm << " invoked foreign method!\n"; + } + void DefConfiguration::write_fn (VM*, const char* text) { std::cout << text; } @@ -9,4 +13,9 @@ namespace wren { void DefConfiguration::error_fn (VM*, ErrorType type, const char* module, int line, const char* message) { std::cerr << "Wren error: " << message << " in " << module << ':' << line << '\n'; } + + foreign_method_t DefConfiguration::foreign_method_fn (VM* vm, const char* module, const char* class_name, bool is_static, const char* signature) { + std::cout << "VM " << vm << " requested foreign method " << class_name << "::" << signature << " in module " << module << "\n"; + return &test; + } } //namespace wren diff --git a/src/wren/def_configuration.hpp b/src/wren/def_configuration.hpp index 763b2ba..9d5099c 100644 --- a/src/wren/def_configuration.hpp +++ b/src/wren/def_configuration.hpp @@ -6,9 +6,12 @@ namespace wren { class VM; + typedef void(*foreign_method_t)(VM*); + class DefConfiguration : public Configuration { public: static void write_fn (VM*, const char* text); static void error_fn (VM*, ErrorType, const char* module, int line, const char* msg); + foreign_method_t foreign_method_fn (VM* vm, const char* module, const char* class_name, bool is_static, const char* signature); }; } //namespace wren diff --git a/src/wren/dynafunc_maker.cpp b/src/wren/dynafunc_maker.cpp new file mode 100644 index 0000000..f2040c3 --- /dev/null +++ b/src/wren/dynafunc_maker.cpp @@ -0,0 +1,95 @@ +#if !defined(_GNU_SOURCE) + //for sys/mman.h +# define _GNU_SOURCE +#endif + +#include "dynafunc_maker.hpp" +#include +#include +#include //for sysconf() +#include +#include +#include //for aligned_alloc() +#include //for mprotect() + +namespace wren { + namespace { +#if defined(__amd64__) and defined(__gnu_linux__) + const constexpr unsigned char g_dynafunc[] = { + 0x48, 0xbf, 0xef, 0xbe, 0xad, 0xde, 0xef, 0xbe, 0xad, 0xde, 0x48, 0xba, + 0x0d, 0xf0, 0xdd, 0xe0, 0xfe, 0x0f, 0xdc, 0xba, 0xff, 0xe2, 0xc3 + }; + const constexpr unsigned int g_dynafunc_len = 23; + const constexpr unsigned int g_dynafunc_ptr1_size = 8; + const constexpr unsigned int g_dynafunc_ptr2_size = 8; + const constexpr unsigned char g_dynafunc_ptr1[g_dynafunc_ptr1_size] = {0xef, 0xbe, 0xad, 0xde, 0xef, 0xbe, 0xad, 0xde}; + const constexpr unsigned char g_dynafunc_ptr2[g_dynafunc_ptr2_size] = {0x0d, 0xf0, 0xdd, 0xe0, 0xfe, 0x0f, 0xdc, 0xba}; +#else +# error "Not implemented on this platform" +#endif + + template + void replace_ptr( + unsigned char* beg, + unsigned char* end, + const unsigned char (&search)[S], + T replace + ) { + static_assert(sizeof(T) == S, "Unexpected pointer size"); + const auto subseq = std::search(beg, end, search, search + S); + assert(subseq != end); + const auto ptr = reinterpret_cast(&replace); + std::copy(ptr, ptr + sizeof(T), subseq); + } + + void make_dynafunc_asm(unsigned char* out, unsigned int len, VM* ptr1, foreign_method_t ptr2) { + assert(g_dynafunc_len == len); + std::copy(g_dynafunc, g_dynafunc + g_dynafunc_len, out); + + replace_ptr(out, out + g_dynafunc_len, g_dynafunc_ptr1, ptr1); + replace_ptr(out, out + g_dynafunc_len, g_dynafunc_ptr2, ptr2); + } + } //unnamed namespace + + DynafuncMaker::DynafuncMaker() : + m_page_size(sysconf(_SC_PAGE_SIZE)), + m_used_bytes(m_page_size) + { + if (m_page_size <= 0) + throw std::runtime_error("Invalid page size " + std::to_string(m_page_size)); + if (m_page_size < g_dynafunc_len) + throw std::runtime_error("Page size " + std::to_string(m_page_size) + " is too small to accomodate assembly the whose size is " + std::to_string(g_dynafunc_len)); + assert(m_page_size == m_used_bytes); + } + + DynafuncMaker::~DynafuncMaker() noexcept { + clear(); + } + + void* DynafuncMaker::make (VM* vm, foreign_method_t cb) { + unsigned char* const mem = allocate_chunk(); + make_dynafunc_asm(mem, g_dynafunc_len, vm, cb); + return mem; + } + + void DynafuncMaker::clear() noexcept { + for (auto page : m_pages) { + std::free(page); + } + m_pages.clear(); + } + + unsigned char* DynafuncMaker::allocate_chunk() { + if (m_page_size - m_used_bytes < g_dynafunc_len) { + void* const page = std::aligned_alloc(m_page_size, m_page_size); + m_pages.push_back(page); + mprotect(page, m_page_size, PROT_EXEC | PROT_READ | PROT_WRITE); + m_used_bytes = 0; + } + + assert(not m_pages.empty()); + const auto ret = static_cast(m_pages.back()) + m_used_bytes; + m_used_bytes += g_dynafunc_len; + return ret; + } +} //namespace wren diff --git a/src/wren/dynafunc_maker.hpp b/src/wren/dynafunc_maker.hpp new file mode 100644 index 0000000..aa1a4d5 --- /dev/null +++ b/src/wren/dynafunc_maker.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace wren { + class VM; + typedef void(*foreign_method_t)(VM*); + + class DynafuncMaker { + public: + DynafuncMaker(); + ~DynafuncMaker() noexcept; + + void* make (VM* vm, foreign_method_t cb); + void clear() noexcept; + + private: + unsigned char* allocate_chunk(); + + std::vector m_pages; + std::size_t m_page_size; + std::size_t m_used_bytes; + }; +} //namespace wren diff --git a/src/wren/vm.cpp b/src/wren/vm.cpp index a63d263..b460f92 100644 --- a/src/wren/vm.cpp +++ b/src/wren/vm.cpp @@ -1,5 +1,6 @@ #include "vm.hpp" #include "configuration.hpp" +#include "dynafunc_maker.hpp" #include #include #include @@ -43,13 +44,26 @@ namespace wren { assert(cb->load_module_fn and cb->config_obj and cb->owner); return cb->load_module_fn(*cb->config_obj, cb->owner, name); } + + 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); + DynafuncMaker* const dfm = cb->dynafunc; + assert(dfm); + auto retval = reinterpret_cast(dfm->make(cb->owner, func)); + return retval; + } } //unnamed namespace struct VM::LocalData { explicit LocalData (const detail::Callbacks& cb) : callbacks(cb), wvm(nullptr) - {} + { + callbacks.dynafunc = &dynafunc; + } ~LocalData() noexcept { if (wvm) wrenFreeVM(wvm); @@ -59,6 +73,7 @@ namespace wren { } detail::Callbacks callbacks; + DynafuncMaker dynafunc; WrenVM* wvm; }; @@ -88,15 +103,20 @@ namespace wren { if (cb.load_module_fn) wconf.loadModuleFn = &load_module_fn; + if (cb.foreign_method_fn) + wconf.bindForeignMethodFn = &foreign_method_fn; m_local->wvm = wrenNewVM(&wconf); if (not m_local->wvm) throw std::runtime_error("Failed to initialize Wren VM"); } - VM::VM (VM&& other) = default; VM::~VM() noexcept = default; + DynafuncMaker* VM::dynafunc_maker() { + return &m_local->dynafunc; + } + void VM::interpret (const char* module_name, const char* script) { using std::string; using std::runtime_error; diff --git a/src/wren/vm.hpp b/src/wren/vm.hpp index 80f53ca..d57beab 100644 --- a/src/wren/vm.hpp +++ b/src/wren/vm.hpp @@ -8,6 +8,9 @@ namespace wren { class Configuration; class VM; + class DynafuncMaker; + + typedef void(*foreign_method_t)(VM*); namespace detail { struct Callbacks { @@ -16,9 +19,11 @@ namespace wren { void* (*reallocate_fn)(void*, std::size_t) {nullptr}; const char* (*resolve_module_fn)(Configuration&, VM*, const char*, const char*) {nullptr}; char* (*load_module_fn)(Configuration&, VM*, const char*) {nullptr}; + foreign_method_t (*foreign_method_fn)(Configuration&, VM*, const char*, const char*, bool, const char*); Configuration* config_obj {nullptr}; VM* owner {nullptr}; + DynafuncMaker* dynafunc {nullptr}; }; } //namespace detail @@ -27,7 +32,7 @@ namespace wren { template VM (T* conf); VM (const VM&) = delete; - VM (VM&& other); + VM (VM&& other) = default; ~VM() noexcept; void interpret (const char* module_name, const char* script); @@ -37,6 +42,7 @@ namespace wren { struct LocalData; VM (Configuration* conf, const detail::Callbacks&); + DynafuncMaker* dynafunc_maker(); template detail::Callbacks to_callbacks (T& conf); @@ -50,6 +56,7 @@ namespace wren { define_method_info(reallocate_fn, reallocate, void*, void*, std::size_t); define_method_info(resolve_module_fn, resolve_module, const char*, const char*, const char*); define_method_info(load_module_fn, load_module, char*, VM*, const char*); + define_method_info(foreign_method_fn, foreign_method, foreign_method_t, VM*, const char*, const char*, bool, const char*); template struct AnyFunctionWrap; template @@ -75,6 +82,7 @@ namespace wren { detail::Callbacks ret; ret.config_obj = &conf; ret.owner = this; + ret.dynafunc = nullptr; if constexpr (detail::method_write::exists::value) ret.write_fn = &detail::AnyFunctionWrap::template call<&T::write_fn>; @@ -92,6 +100,9 @@ namespace wren { if constexpr (detail::method_load_module::exists::value) ret.load_module_fn = &detail::AnyFunctionWrap::template call<&T::load_module_fn>; + if constexpr (detail::method_foreign_method::exists::value) + ret.foreign_method_fn = &detail::AnyFunctionWrap::template call<&T::foreign_method_fn>; + return ret; }