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/vm.cpp',
|
||||||
'src/wren/configuration.cpp',
|
'src/wren/configuration.cpp',
|
||||||
'src/wren/def_configuration.cpp',
|
'src/wren/def_configuration.cpp',
|
||||||
|
'src/wren/dynafunc_maker.cpp',
|
||||||
dependencies: [wren_dep],
|
dependencies: [wren_dep],
|
||||||
install: true,
|
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>
|
#include <thread>
|
||||||
|
|
||||||
namespace {
|
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
|
} //unnamed namespace
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
std::cout << "hello world\n";
|
std::cout << "hello world\n";
|
||||||
wren::DefConfiguration config;
|
wren::DefConfiguration config;
|
||||||
wren::VM vm(&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));
|
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace wren {
|
namespace wren {
|
||||||
|
void test (VM* vm) {
|
||||||
|
std::cout << "VM " << vm << " invoked foreign method!\n";
|
||||||
|
}
|
||||||
|
|
||||||
void DefConfiguration::write_fn (VM*, const char* text) {
|
void DefConfiguration::write_fn (VM*, const char* text) {
|
||||||
std::cout << 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) {
|
void DefConfiguration::error_fn (VM*, ErrorType type, const char* module, int line, const char* message) {
|
||||||
std::cerr << "Wren error: " << message << " in " << module << ':' << line << '\n';
|
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
|
} //namespace wren
|
||||||
|
|
|
@ -6,9 +6,12 @@
|
||||||
namespace wren {
|
namespace wren {
|
||||||
class VM;
|
class VM;
|
||||||
|
|
||||||
|
typedef void(*foreign_method_t)(VM*);
|
||||||
|
|
||||||
class DefConfiguration : public Configuration {
|
class DefConfiguration : public Configuration {
|
||||||
public:
|
public:
|
||||||
static void write_fn (VM*, const char* text);
|
static void write_fn (VM*, const char* text);
|
||||||
static void error_fn (VM*, ErrorType, const char* module, int line, const char* msg);
|
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
|
} //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 "vm.hpp"
|
||||||
#include "configuration.hpp"
|
#include "configuration.hpp"
|
||||||
|
#include "dynafunc_maker.hpp"
|
||||||
#include <wren.hpp>
|
#include <wren.hpp>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
@ -43,13 +44,26 @@ namespace wren {
|
||||||
assert(cb->load_module_fn and cb->config_obj and cb->owner);
|
assert(cb->load_module_fn and cb->config_obj and cb->owner);
|
||||||
return cb->load_module_fn(*cb->config_obj, cb->owner, name);
|
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
|
} //unnamed namespace
|
||||||
|
|
||||||
struct VM::LocalData {
|
struct VM::LocalData {
|
||||||
explicit LocalData (const detail::Callbacks& cb) :
|
explicit LocalData (const detail::Callbacks& cb) :
|
||||||
callbacks(cb),
|
callbacks(cb),
|
||||||
wvm(nullptr)
|
wvm(nullptr)
|
||||||
{}
|
{
|
||||||
|
callbacks.dynafunc = &dynafunc;
|
||||||
|
}
|
||||||
~LocalData() noexcept {
|
~LocalData() noexcept {
|
||||||
if (wvm)
|
if (wvm)
|
||||||
wrenFreeVM(wvm);
|
wrenFreeVM(wvm);
|
||||||
|
@ -59,6 +73,7 @@ namespace wren {
|
||||||
}
|
}
|
||||||
|
|
||||||
detail::Callbacks callbacks;
|
detail::Callbacks callbacks;
|
||||||
|
DynafuncMaker dynafunc;
|
||||||
WrenVM* wvm;
|
WrenVM* wvm;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -88,15 +103,20 @@ namespace wren {
|
||||||
if (cb.load_module_fn)
|
if (cb.load_module_fn)
|
||||||
wconf.loadModuleFn = &load_module_fn;
|
wconf.loadModuleFn = &load_module_fn;
|
||||||
|
|
||||||
|
if (cb.foreign_method_fn)
|
||||||
|
wconf.bindForeignMethodFn = &foreign_method_fn;
|
||||||
|
|
||||||
m_local->wvm = wrenNewVM(&wconf);
|
m_local->wvm = wrenNewVM(&wconf);
|
||||||
if (not m_local->wvm)
|
if (not m_local->wvm)
|
||||||
throw std::runtime_error("Failed to initialize Wren VM");
|
throw std::runtime_error("Failed to initialize Wren VM");
|
||||||
}
|
}
|
||||||
|
|
||||||
VM::VM (VM&& other) = default;
|
|
||||||
VM::~VM() noexcept = default;
|
VM::~VM() noexcept = default;
|
||||||
|
|
||||||
|
DynafuncMaker* VM::dynafunc_maker() {
|
||||||
|
return &m_local->dynafunc;
|
||||||
|
}
|
||||||
|
|
||||||
void VM::interpret (const char* module_name, const char* script) {
|
void VM::interpret (const char* module_name, const char* script) {
|
||||||
using std::string;
|
using std::string;
|
||||||
using std::runtime_error;
|
using std::runtime_error;
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
namespace wren {
|
namespace wren {
|
||||||
class Configuration;
|
class Configuration;
|
||||||
class VM;
|
class VM;
|
||||||
|
class DynafuncMaker;
|
||||||
|
|
||||||
|
typedef void(*foreign_method_t)(VM*);
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
struct Callbacks {
|
struct Callbacks {
|
||||||
|
@ -16,9 +19,11 @@ namespace wren {
|
||||||
void* (*reallocate_fn)(void*, std::size_t) {nullptr};
|
void* (*reallocate_fn)(void*, std::size_t) {nullptr};
|
||||||
const char* (*resolve_module_fn)(Configuration&, VM*, const char*, const char*) {nullptr};
|
const char* (*resolve_module_fn)(Configuration&, VM*, const char*, const char*) {nullptr};
|
||||||
char* (*load_module_fn)(Configuration&, VM*, 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};
|
Configuration* config_obj {nullptr};
|
||||||
VM* owner {nullptr};
|
VM* owner {nullptr};
|
||||||
|
DynafuncMaker* dynafunc {nullptr};
|
||||||
};
|
};
|
||||||
} //namespace detail
|
} //namespace detail
|
||||||
|
|
||||||
|
@ -27,7 +32,7 @@ namespace wren {
|
||||||
template <typename T>
|
template <typename T>
|
||||||
VM (T* conf);
|
VM (T* conf);
|
||||||
VM (const VM&) = delete;
|
VM (const VM&) = delete;
|
||||||
VM (VM&& other);
|
VM (VM&& other) = default;
|
||||||
~VM() noexcept;
|
~VM() noexcept;
|
||||||
|
|
||||||
void interpret (const char* module_name, const char* script);
|
void interpret (const char* module_name, const char* script);
|
||||||
|
@ -37,6 +42,7 @@ namespace wren {
|
||||||
struct LocalData;
|
struct LocalData;
|
||||||
|
|
||||||
VM (Configuration* conf, const detail::Callbacks&);
|
VM (Configuration* conf, const detail::Callbacks&);
|
||||||
|
DynafuncMaker* dynafunc_maker();
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
detail::Callbacks to_callbacks (T& conf);
|
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(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(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(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 F> struct AnyFunctionWrap;
|
||||||
template <typename T, typename R, typename... Args>
|
template <typename T, typename R, typename... Args>
|
||||||
|
@ -75,6 +82,7 @@ namespace wren {
|
||||||
detail::Callbacks ret;
|
detail::Callbacks ret;
|
||||||
ret.config_obj = &conf;
|
ret.config_obj = &conf;
|
||||||
ret.owner = this;
|
ret.owner = this;
|
||||||
|
ret.dynafunc = nullptr;
|
||||||
|
|
||||||
if constexpr (detail::method_write::exists<T>::value)
|
if constexpr (detail::method_write::exists<T>::value)
|
||||||
ret.write_fn = &detail::AnyFunctionWrap<T, decltype(&T::write_fn)>::template call<&T::write_fn>;
|
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)
|
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>;
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue