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:
parent
d0be115181
commit
16c4b8cbe5
9 changed files with 187 additions and 4 deletions
|
@ -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
9
src/dynafunc.asm
Normal 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
|
13
src/main.cpp
13
src/main.cpp
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
95
src/wren/dynafunc_maker.cpp
Normal file
95
src/wren/dynafunc_maker.cpp
Normal 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
|
24
src/wren/dynafunc_maker.hpp
Normal file
24
src/wren/dynafunc_maker.hpp
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue