diff --git a/examples/calendar/main.cpp b/examples/calendar/main.cpp index 443bcbd..9d39058 100644 --- a/examples/calendar/main.cpp +++ b/examples/calendar/main.cpp @@ -69,7 +69,7 @@ System.print("You have %(cale.appointment_count()) appointment(s)") } } - static void today (wren::VM& vm) { + static void today (wren::VM& vm, wren::ModuleAndName) { vm.set_slot_string(0, today_unique_str().get()); } @@ -116,7 +116,7 @@ System.print("You have %(cale.appointment_count()) appointment(s)") } //unnamed namespace int main() { - typedef wren::ModuleAndName MN; + using wren::MN; MyConf conf; wren::VM vm(&conf, nullptr); @@ -127,7 +127,7 @@ int main() { vm.interpret("main", g_test_script); - Calendar* const cale = std::get<0>(wren::variables(vm, MN{"main", "cale"})); + Calendar* const cale = std::get<0>(wren::variables(vm, MN<"main", "cale">)); cale->print_appointments(); return 0; diff --git a/examples/dieroll/main.cpp b/examples/dieroll/main.cpp index 030f226..fdd6b5c 100644 --- a/examples/dieroll/main.cpp +++ b/examples/dieroll/main.cpp @@ -37,13 +37,13 @@ namespace { std::uniform_int_distribution distrib; }; - void user_input(wren::VM& vm) { + void user_input(wren::VM& vm, wren::ModuleAndName) { std::string retval; std::cin >> retval; set(vm, 0, retval); } - void random_num(wren::VM& vm) { + void random_num(wren::VM& vm, wren::ModuleAndName) { auto* const cust_data = vm.user_data(); set(vm, 0, cust_data->distrib(cust_data->generator)); } @@ -69,7 +69,7 @@ int main() { .add_callback(true, "main", "Game", "random()", &random_num); interpret(vm, "main", load_file("main.wren")); - wren::call(vm, {"main", "the_game"}, "play_game"); + wren::call(vm, wren::MN<"main", "the_game">, "play_game"); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); return 0; diff --git a/examples/greet/main.cpp b/examples/greet/main.cpp index dd6daa3..085e982 100644 --- a/examples/greet/main.cpp +++ b/examples/greet/main.cpp @@ -56,7 +56,7 @@ const char* raw_fetch_env (const char* name) noexcept { return val_ptr; } -void user_get_env (wren::VM& vm) { +void user_get_env (wren::VM& vm, wren::ModuleAndName) { set(vm, 0, raw_fetch_env(wren::get(vm, 1))); } } //unnamed namespace @@ -70,7 +70,7 @@ int main() { vm.interpret("main", g_script); - wren::call(vm, {"main", "the_user"}, "greet"); + wren::call(vm, wren::MN<"main", "the_user">, "greet"); std::cout << "Quitting in 1 sec" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); diff --git a/include/wrenpp/detail/construct_foreign_class.hpp b/include/wrenpp/detail/construct_foreign_class.hpp index 53f65fe..59a6c3c 100644 --- a/include/wrenpp/detail/construct_foreign_class.hpp +++ b/include/wrenpp/detail/construct_foreign_class.hpp @@ -130,7 +130,7 @@ namespace wren { detail::static_assert_all_packs_are_unique(); foreign_class_t ret; - ret.allocate = [](VM& vm) { + ret.allocate = [](VM& vm, ModuleAndName mn) { void* const mem = vm.set_slot_new_foreign(0, 0, sizeof(T)); const auto result = detail::ForeignClassHelper::construct(vm, vm.slot_count() - 1, mem); diff --git a/include/wrenpp/detail/guess_class_name.hpp b/include/wrenpp/detail/guess_class_name.hpp index 53edf8a..087724f 100644 --- a/include/wrenpp/detail/guess_class_name.hpp +++ b/include/wrenpp/detail/guess_class_name.hpp @@ -61,7 +61,7 @@ namespace wren { constexpr auto from = last_whatever + (last_whatever == fname_len ? 0 : 1); constexpr auto len = detail::find_token_end(fname + from, fname_len - from); - constexpr dhandy::bt::string retval{fname + from}; + constexpr dhandy::bt::string retval{fname + from, 0}; return retval; //#elif defined(_MSC_VER) //void __cdecl guess_class_name(void) diff --git a/include/wrenpp/detail/meson.build b/include/wrenpp/detail/meson.build index 37c2629..832864c 100644 --- a/include/wrenpp/detail/meson.build +++ b/include/wrenpp/detail/meson.build @@ -3,6 +3,7 @@ include_files = [ 'error_type.hpp', 'guess_class_name.hpp', 'has_method.hpp', + 'module_and_name.hpp', 'setters_getters.hpp', 'string_bt.hpp', 'StringCRC32.hpp', diff --git a/include/wrenpp/detail/module_and_name.hpp b/include/wrenpp/detail/module_and_name.hpp new file mode 100644 index 0000000..7999b24 --- /dev/null +++ b/include/wrenpp/detail/module_and_name.hpp @@ -0,0 +1,98 @@ +/* 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 . + */ + +#pragma once + +#include "string_bt.hpp" +#include +#include + +namespace wren { + namespace detail { + template + struct ModuleAndNameStaticStorage { + static constexpr const auto value = S; + }; + } //unnamed namespace + + //Non-owning, lightweight class for storing a module nome/class name pair. + //They are stored as consecutive strings within a single buffer with a + //null-char in-between and at the end so that each name is null-terminated + //and can be used with C APIs. + //Keep the size small, ideally within 2 registers size. Altering this might + //require changes to the assembly source files for DynafuncMaker. + //When passed as a parameter by value, objects of ModuleAndName fit into + //2 registers on 64 bit architectures. This is important because objects + //of this class get hardcoded in the dynamic functions that DynafuncMaker + //spits out. + class ModuleAndName { + public: + constexpr ModuleAndName (const char* base, std::uint32_t hash, std::size_t mod_name_len, std::size_t class_name_len); + + constexpr const char* module_name_char() const noexcept { return m_base; } + constexpr const char* class_name_char() const noexcept { return m_base + 1 + m_mod_name_len; } + constexpr std::size_t module_name_len() const noexcept { return m_mod_name_len; } + constexpr std::size_t class_name_len() const noexcept { return m_class_name_len; } + constexpr std::string_view module_name() const noexcept { return {module_name_char(), m_mod_name_len}; } + constexpr std::string_view class_name() const noexcept { return {class_name_char(), m_class_name_len}; } + constexpr const char* buffer_address() const noexcept { return m_base; } + + private: + const char* m_base; + std::uint32_t m_hash; + std::uint16_t m_mod_name_len; + std::uint16_t m_class_name_len; + }; + + union RawAccessModuleAndName { + constexpr RawAccessModuleAndName (ModuleAndName mn) : + module_and_name(mn) + { } + + ModuleAndName module_and_name; + + struct { + void* string; + void* data; + } raw; + + static_assert(sizeof(raw) == sizeof(module_and_name)); + }; + + constexpr ModuleAndName::ModuleAndName (const char* base, std::uint32_t hash, std::size_t mod_name_len, std::size_t class_name_len) : + m_base(base), + m_hash(hash), + m_mod_name_len(mod_name_len), + m_class_name_len(class_name_len) + {} + + template + constexpr ModuleAndName make_module_and_name() { + using dhandy::bt::string; + + constexpr const char* data = detail::ModuleAndNameStaticStorage::value.data(); + constexpr std::uint16_t s1_len = static_cast(S1.size()); + constexpr std::uint16_t s2_len = static_cast(S2.size()); + constexpr std::uint32_t hash = 0; //not implemented yet + + return ModuleAndName{data, hash, s1_len, s2_len}; + } + + //short-hand alias for make_module_and_name + template + constexpr ModuleAndName MN = make_module_and_name(); +} //namespace wren diff --git a/include/wrenpp/detail/string_bt.hpp b/include/wrenpp/detail/string_bt.hpp index 3fe671c..6344d4c 100644 --- a/include/wrenpp/detail/string_bt.hpp +++ b/include/wrenpp/detail/string_bt.hpp @@ -41,7 +41,11 @@ namespace dhandy { friend constexpr bool implem::eq (const string&, const string&); public: using value_type = Ch; - constexpr string ( const value_type* parString ); + constexpr string ( const string& other ) = default; + constexpr string ( const value_type (&parString)[S] ); + + //overload for pointers, int is only for overload disambiguation + constexpr string ( const value_type* parString, int ); constexpr std::size_t size ( void ) const { return S - 1; } template @@ -52,18 +56,26 @@ namespace dhandy { template constexpr bool operator== (const string&) const { return false; } template + requires (std::is_same_v::value_type> && ...) constexpr string ( Args... ); constexpr const value_type (&data_arr() const)[S] { return m_data; } constexpr const value_type* data() const { return m_data; } - private: + //can't have any private members but from below this point please + //consider everything as private + //private: template constexpr string ( std::index_sequence, const value_type* parString ); const value_type m_data[S]; }; + template + constexpr auto string::operator[] (std::size_t parIndex) const -> value_type { + return (parIndex < S ? m_data[parIndex] : throw std::out_of_range("")); + } + namespace implem { template constexpr string concat ( std::index_sequence, const string& parLeft, const string& parRight ) { @@ -85,13 +97,20 @@ namespace dhandy { } template - inline constexpr string::string (const value_type* parString) : + inline constexpr string::string (const value_type (&parString)[S]) : + string(std::make_index_sequence(), parString) + { + } + + template + inline constexpr string::string (const value_type* parString, int) : string(std::make_index_sequence(), parString) { } template template + requires (std::is_same_v::value_type> && ...) inline constexpr string::string (Args... parArgs) : m_data{parArgs...} { @@ -109,16 +128,6 @@ namespace dhandy { return parStream; } - template - constexpr auto string::operator[] (std::size_t parIndex) const -> value_type { - return (parIndex < S ? m_data[parIndex] : throw std::out_of_range("")); - } - - template - constexpr string make_string (const Ch (&parData)[S]) { - return string(parData); - } - template constexpr bool string::operator== (const string& other) const { return implem::eq(*this, other); @@ -127,7 +136,7 @@ namespace dhandy { template template constexpr auto string::substr() const { - return string(m_data + std::min(Start, S - 1)) + make_string(""); + return string(m_data + std::min(Start, S - 1), 0) + string(""); } } //namespace bt } //namespace dhandy diff --git a/include/wrenpp/detail/strings_in_vector.hpp b/include/wrenpp/detail/strings_in_vector.hpp index b88e2d7..7a7a22f 100644 --- a/include/wrenpp/detail/strings_in_vector.hpp +++ b/include/wrenpp/detail/strings_in_vector.hpp @@ -70,6 +70,7 @@ namespace wren { template class StringsInVectorImplem> : detail::StringsInVectorBase... { typedef detail::StringsInVectorWordRange WordRange; + template friend std::vector strings_to_vector(const Strings&...); public: template StringsInVectorImplem (const Strings&... strings); @@ -133,4 +134,10 @@ namespace wren { template using StringsInVector = StringsInVectorImplem())>; + + template + inline std::vector strings_to_vector (const Strings&... strings) { + StringsInVector siv{strings...}; + return siv.m_memory; + } } //namespace wren diff --git a/include/wrenpp/detail/wren_types.hpp b/include/wrenpp/detail/wren_types.hpp index 95c1029..c5cccbc 100644 --- a/include/wrenpp/detail/wren_types.hpp +++ b/include/wrenpp/detail/wren_types.hpp @@ -17,16 +17,15 @@ #pragma once +#include "module_and_name.hpp" #include -#include namespace wren { class VM; - typedef std::tuple ModuleAndName; typedef std::string_view wren_string_t; typedef void(*finalizer_t)(void*); - typedef void(*foreign_method_t)(VM&); + typedef void(*foreign_method_t)(VM&, ModuleAndName); struct foreign_class_t { foreign_method_t allocate; diff --git a/include/wrenpp/vm_fun.hpp b/include/wrenpp/vm_fun.hpp index d35782f..4fe9aab 100644 --- a/include/wrenpp/vm_fun.hpp +++ b/include/wrenpp/vm_fun.hpp @@ -36,16 +36,16 @@ namespace wren { R call (VM& vm, const Handle& object, const Handle& method, const Args&... args); template - R call (VM& vm, const ModuleAndName& object, const Handle& method, const Args&... args); + R call (VM& vm, ModuleAndName object, const Handle& method, const Args&... args); template R call (VM& vm, const Handle& object, const char (&method)[N], const Args&... args); template - R call (VM& vm, const ModuleAndName& object, const char (&method)[N], const Args&... args); + R call (VM& vm, ModuleAndName object, const char (&method)[N], const Args&... args); template - T* make_wren_object(VM& vm, const ModuleAndName& mn, Args&&... args); + T* make_wren_object(VM& vm, ModuleAndName mn, Args&&... args); #if defined(WRENPP_WITH_NAME_GUESSING) template @@ -56,9 +56,9 @@ namespace wren { foreign_method_t make_method_bindable(); void interpret (VM& vm, const std::string& module_name, const std::string& script); - void variable(VM& vm, const ModuleAndName& mod_and_name, int slot); + void variable(VM& vm, ModuleAndName mod_and_name, int slot); void variable(VM& vm, const Handle& handle, int slot); - void variable_ensure_slot(VM& vm, const ModuleAndName& mod_and_name, int slot); + void variable_ensure_slot(VM& vm, ModuleAndName mod_and_name, int slot); void variable_ensure_slot(VM& vm, const Handle& handle, int slot); namespace detail { @@ -97,7 +97,7 @@ namespace wren { public: static foreign_method_t make() { - return [](VM& vm) { + return [](VM& vm, ModuleAndName) { if constexpr (std::is_same_v) { call_implem(std::make_integer_sequence(), vm); } @@ -113,7 +113,7 @@ namespace wren { template struct MakeMethodBindable; template struct MakeMethodBindable { static foreign_method_t make() { - return [](VM& vm) { + return [](VM& vm, ModuleAndName) { T* obj = foreign(vm, 0); (obj->*Method)(vm); }; @@ -139,7 +139,7 @@ namespace wren { } template - inline R call (VM& vm, const ModuleAndName& object, const Handle& method, const Args&... args) { + inline R call (VM& vm, ModuleAndName object, const Handle& method, const Args&... args) { vm.ensure_slots(sizeof...(args) + 1); variable(vm, object, 0); Handle obj_handle = vm.slot_handle(0); @@ -148,11 +148,13 @@ namespace wren { template inline R call (VM& vm, const Handle& object, const char (&method)[N], const Args&... args) { + using dhandy::bt::string; + const constexpr char dummy_name_buff[N] = {0}; - const constexpr auto params = dhandy::bt::string(dummy_name_buff) + - dhandy::bt::make_string("(") + - ((static_cast(args), dhandy::bt::make_string(",_")) + ... + dhandy::bt::make_string("")).template substr<1>() + - dhandy::bt::make_string(")"); + const constexpr auto params = string(dummy_name_buff) + + string("(") + + ((static_cast(args), string(",_")) + ... + string("")).template substr<1>() + + string(")"); ; char cat_buff[params.size() + 1]; std::copy(method, method + N - 1, cat_buff); @@ -161,7 +163,7 @@ namespace wren { } template - inline R call (VM& vm, const ModuleAndName& object, const char (&method)[N], const Args&... args) { + inline R call (VM& vm, ModuleAndName object, const char (&method)[N], const Args&... args) { vm.ensure_slots(sizeof...(args) + 1); variable(vm, object, 0); Handle obj_handle = vm.slot_handle(0); @@ -169,7 +171,7 @@ namespace wren { } template - inline T* make_wren_object(VM& vm, const ModuleAndName& mn, Args&&... args) { + inline T* make_wren_object(VM& vm, ModuleAndName mn, Args&&... args) { variable(vm, mn, 0); void* const mem = vm.set_slot_new_foreign(0, 0, sizeof(T)); T* const obj = new(mem) T{std::forward(args)...}; diff --git a/src/dynafunc_amd64_gnu.S b/src/dynafunc_amd64_gnu.S index 86f16e6..975bf17 100644 --- a/src/dynafunc_amd64_gnu.S +++ b/src/dynafunc_amd64_gnu.S @@ -23,8 +23,15 @@ .global g_dynafunc_end g_dynafunc: - movq $0xdeadbeefdeadbeef, %rdi - movq $0xbadc0ffee0ddf00d, %rdx - jmp *%rdx + //VM pointer, parameter 1 + movabsq $0xdeadbeefdeadbeef, %rdi + //ModuleAndName string part, parameter 2 + movabsq $0x1badb0021badb002, %rsi + //ModuleAndName data part, parameter 3 + movabsq $0xfee1deadfee1dead, %rdx + + //jump address + movabsq $0xbadc0ffee0ddf00d, %rax + jmp *%rax ret g_dynafunc_end: diff --git a/src/dynafunc_maker.cpp b/src/dynafunc_maker.cpp index 3d19bec..5c863bb 100644 --- a/src/dynafunc_maker.cpp +++ b/src/dynafunc_maker.cpp @@ -22,6 +22,7 @@ #include "dynafunc_maker.hpp" #include "pvt_config.h" +#include "wrenpp/detail/strings_in_vector.hpp" #include #include #include //for sysconf() @@ -31,6 +32,8 @@ #include //for mprotect() #include #include +#include +#include namespace wren { namespace { @@ -44,8 +47,15 @@ namespace wren { constexpr unsigned int g_dynafunc_ptr1_size = ASM_PTR_SIZE; constexpr unsigned int g_dynafunc_ptr2_size = ASM_FUNC_PTR_SIZE; + //For inspiration: https://www.liquisearch.com/deadbeef/magic_debug_values + //0xdeadbeefdeadbeef constexpr unsigned char g_dynafunc_ptr1[] = {0xef, 0xbe, 0xad, 0xde, 0xef, 0xbe, 0xad, 0xde}; + //0xbadc0ffee0ddf00d constexpr unsigned char g_dynafunc_ptr2[] = {0x0d, 0xf0, 0xdd, 0xe0, 0xfe, 0x0f, 0xdc, 0xba}; + //0x1badb0021badb002 + constexpr unsigned char g_dynafunc_ptr3[] = {0x02, 0xb0, 0xad, 0x1b, 0x02, 0xb0, 0xad, 0x1b}; + //0xfee1deadfee1dead + constexpr unsigned char g_dynafunc_ptr4[] = {0xad, 0xde, 0xe1, 0xfe, 0xad, 0xde, 0xe1, 0xfe}; static_assert(sizeof(g_dynafunc_ptr1) / sizeof(g_dynafunc_ptr1[0]) >= g_dynafunc_ptr1_size); static_assert(sizeof(g_dynafunc_ptr2) / sizeof(g_dynafunc_ptr2[0]) >= g_dynafunc_ptr2_size); @@ -64,11 +74,52 @@ namespace wren { std::copy(ptr, ptr + sizeof(T), subseq); } - void make_dynafunc_asm(unsigned char* out, VM* ptr1, foreign_method_t ptr2) { + void make_dynafunc_asm( + unsigned char* out, + VM* ptr1, + foreign_method_t ptr2, + RawAccessModuleAndName mn + ) { + static_assert(std::is_standard_layout::value, + "Must be a standard layout to be compliant with the assembly functions"); + static_assert(sizeof(void*) * 2 == sizeof(ModuleAndName), + "Must fit into 2 registers to be compliant with the assembly functions"); + std::copy(g_dynafunc, g_dynafunc_end, out); replace_ptr(out, out + g_dynafunc_len, g_dynafunc_ptr1, ptr1); replace_ptr(out, out + g_dynafunc_len, g_dynafunc_ptr2, ptr2); + replace_ptr(out, out + g_dynafunc_len, g_dynafunc_ptr3, mn.raw.string); + replace_ptr(out, out + g_dynafunc_len, g_dynafunc_ptr4, mn.raw.data); + } + + ModuleAndName store_module_name ( + std::list>& dst, + std::string_view mod_name, + std::string_view cls_name + ) { + typedef std::vector CharVec; + CharVec new_entry = strings_to_vector(mod_name, cls_name); + + const auto it_found = std::lower_bound( + dst.cbegin(), + dst.cend(), + new_entry, + [](const CharVec& a, const CharVec& b) { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); + } + ); + + const CharVec* source_mem; + if (dst.cend() != it_found) { + assert(std::equal(it_found->cbegin(), it_found->cend(), new_entry.cbegin(), new_entry.cend())); + source_mem = &*it_found; + } + else { + source_mem = &*dst.insert(it_found, std::move(new_entry)); + } + + return {source_mem->data(), 0, mod_name.size(), cls_name.size()}; } } //unnamed namespace @@ -87,9 +138,11 @@ namespace wren { clear(); } - void* DynafuncMaker::make (VM* vm, foreign_method_t cb) { + void* DynafuncMaker::make (VM* vm, foreign_method_t cb, const char* mod_name, const char* cls_name) { unsigned char* const mem = allocate_chunk(); - make_dynafunc_asm(mem, vm, cb); + const auto mn = store_module_name(m_string_params, mod_name, cls_name); + make_dynafunc_asm(mem, vm, cb, mn); + return mem; } diff --git a/src/dynafunc_maker.hpp b/src/dynafunc_maker.hpp index e2a852a..93dd75a 100644 --- a/src/dynafunc_maker.hpp +++ b/src/dynafunc_maker.hpp @@ -19,6 +19,7 @@ #include "wrenpp/detail/wren_types.hpp" #include +#include #include namespace wren { @@ -27,7 +28,7 @@ namespace wren { DynafuncMaker(); ~DynafuncMaker() noexcept; - void* make (VM* vm, foreign_method_t cb); + void* make (VM* vm, foreign_method_t cb, const char* mod_name, const char* cls_name); void clear() noexcept; std::size_t total_memory() const; @@ -35,6 +36,7 @@ namespace wren { private: unsigned char* allocate_chunk(); + std::list> m_string_params; std::vector m_pages; std::size_t m_page_size; std::size_t m_used_bytes; diff --git a/src/vm.cpp b/src/vm.cpp index a599d61..9f3d59a 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -89,7 +89,18 @@ namespace wren { if (func) { DynafuncMaker* const dfm = cb->dynafunc; assert(dfm); - auto retval = reinterpret_cast(dfm->make(cb->owner, func)); + //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 { @@ -118,7 +129,12 @@ namespace wren { if (funcs.allocate) { DynafuncMaker* const dfm = cb->dynafunc; assert(dfm); - retval.allocate = reinterpret_cast(dfm->make(cb->owner, funcs.allocate)); + retval.allocate = reinterpret_cast(dfm->make( + cb->owner, + funcs.allocate, + module, + class_name + )); } else { retval.allocate = nullptr; diff --git a/src/vm_fun.cpp b/src/vm_fun.cpp index c201dda..bd76fc2 100644 --- a/src/vm_fun.cpp +++ b/src/vm_fun.cpp @@ -72,15 +72,15 @@ namespace wren { return std::vector{arr.first, arr.first + arr.second}; } - void variable(VM& vm, const ModuleAndName& mod_and_name, int slot) { - vm.variable_or_throw(std::get<0>(mod_and_name), std::get<1>(mod_and_name), slot); + void variable(VM& vm, ModuleAndName mod_and_name, int slot) { + vm.variable_or_throw(mod_and_name.module_name_char(), mod_and_name.class_name_char(), slot); } void variable (VM& vm, const Handle& handle, int slot) { vm.set_slot_handle(handle, slot); } - void variable_ensure_slot(VM& vm, const ModuleAndName& mod_and_name, int slot) { + void variable_ensure_slot(VM& vm, ModuleAndName mod_and_name, int slot) { vm.ensure_slots(1); variable(vm, mod_and_name, slot); }