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.
This commit is contained in:
King_DuckZ 2020-04-26 17:19:02 +02:00
parent d0be115181
commit 16c4b8cbe5
9 changed files with 187 additions and 4 deletions

View file

@ -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,
)

9
src/dynafunc.asm Normal file
View file

@ -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

View file

@ -5,13 +5,24 @@
#include <thread>
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;

View file

@ -2,6 +2,10 @@
#include <iostream>
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

View file

@ -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

View file

@ -0,0 +1,95 @@
#if !defined(_GNU_SOURCE)
//for sys/mman.h
# define _GNU_SOURCE
#endif
#include "dynafunc_maker.hpp"
#include <algorithm>
#include <cassert>
#include <unistd.h> //for sysconf()
#include <stdexcept>
#include <string>
#include <cstdlib> //for aligned_alloc()
#include <sys/mman.h> //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 <typename T, unsigned int S>
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<unsigned char*>(&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<unsigned char*>(m_pages.back()) + m_used_bytes;
m_used_bytes += g_dynafunc_len;
return ret;
}
} //namespace wren

View file

@ -0,0 +1,24 @@
#pragma once
#include <vector>
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<void*> m_pages;
std::size_t m_page_size;
std::size_t m_used_bytes;
};
} //namespace wren

View file

@ -1,5 +1,6 @@
#include "vm.hpp"
#include "configuration.hpp"
#include "dynafunc_maker.hpp"
#include <wren.hpp>
#include <cassert>
#include <stdexcept>
@ -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<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);
DynafuncMaker* const dfm = cb->dynafunc;
assert(dfm);
auto retval = reinterpret_cast<WrenForeignMethodFn>(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;

View file

@ -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 <typename T>
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 <typename T>
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 <typename T, typename F> struct AnyFunctionWrap;
template <typename T, typename R, typename... Args>
@ -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<T>::value)
ret.write_fn = &detail::AnyFunctionWrap<T, decltype(&T::write_fn)>::template call<&T::write_fn>;
@ -92,6 +100,9 @@ namespace wren {
if constexpr (detail::method_load_module::exists<T>::value)
ret.load_module_fn = &detail::AnyFunctionWrap<T, decltype(&T::load_module_fn)>::template call<&T::load_module_fn>;
if constexpr (detail::method_foreign_method::exists<T>::value)
ret.foreign_method_fn = &detail::AnyFunctionWrap<T, decltype(&T::foreign_method_fn)>::template call<&T::foreign_method_fn>;
return ret;
}