From e32d655d80e7b22524a6aff3d637ccf1fed186d0 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sat, 14 May 2022 11:48:27 +0200 Subject: [PATCH] Initial work on WebAssembly script provider --- CMakeLists.txt | 32 ++- sanitizer_ignores | 1 + src/Battling/Library/ScriptResolver.hpp | 1 + src/Library/Moves/MoveLibrary.hpp | 10 + .../WASM/InterfaceMethods/CoreMethods.cpp | 86 ++++++++ .../WASM/InterfaceMethods/CoreMethods.hpp | 14 ++ .../Library/LibraryMethods.cpp | 39 ++++ .../Library/LibraryMethods.hpp | 13 ++ .../WASM/InterfaceMethods/WasmHelperFile.hpp | 197 ++++++++++++++++++ src/ScriptResolving/WASM/WasmExternRef.hpp | 13 ++ .../WASM/WebAssemblyBattleScript.cpp | 40 ++++ .../WASM/WebAssemblyBattleScript.hpp | 35 ++++ .../WASM/WebAssemblyFunctionCall.hpp | 107 ++++++++++ .../WASM/WebAssemblyScriptCapabilities.hpp | 63 ++++++ .../WASM/WebAssemblyScriptResolver.cpp | 175 ++++++++++++++++ .../WASM/WebAssemblyScriptResolver.hpp | 92 ++++++++ tests/LibraryTests/SpeciesLibraryTests.cpp | 5 + .../BaseScriptClassTests.cpp | 2 +- .../{ => Angelscript}/ItemUseScriptTests.cpp | 2 +- .../{ => Angelscript}/MetadataTests.cpp | 6 +- .../{ => Angelscript}/ScriptOwnerTest.cpp | 2 +- .../{ => Angelscript}/ScriptResolverTests.cpp | 2 +- .../ScriptTypeTests/Battle/BattleTests.cpp | 2 +- .../ScriptTypeTests/Battle/PokemonTests.cpp | 2 +- .../ScriptTypeTests/Library/FormesTests.cpp | 2 +- .../ScriptTypeTests/Library/ItemDataTests.cpp | 2 +- .../ScriptTypeTests/Library/MoveTests.cpp | 2 +- .../ScriptTypeTests/Library/SpeciesTests.cpp | 2 +- .../Library/StaticLibraryTests.cpp | 2 +- .../ScriptTests/WASM/ScriptResolverTests.cpp | 63 ++++++ tests/ScriptTests/WASM/gen7_scripts_rs.wasm | Bin 0 -> 28705 bytes 31 files changed, 995 insertions(+), 19 deletions(-) create mode 100644 sanitizer_ignores create mode 100644 src/ScriptResolving/WASM/InterfaceMethods/CoreMethods.cpp create mode 100644 src/ScriptResolving/WASM/InterfaceMethods/CoreMethods.hpp create mode 100644 src/ScriptResolving/WASM/InterfaceMethods/Library/LibraryMethods.cpp create mode 100644 src/ScriptResolving/WASM/InterfaceMethods/Library/LibraryMethods.hpp create mode 100644 src/ScriptResolving/WASM/InterfaceMethods/WasmHelperFile.hpp create mode 100644 src/ScriptResolving/WASM/WasmExternRef.hpp create mode 100644 src/ScriptResolving/WASM/WebAssemblyBattleScript.cpp create mode 100644 src/ScriptResolving/WASM/WebAssemblyBattleScript.hpp create mode 100644 src/ScriptResolving/WASM/WebAssemblyFunctionCall.hpp create mode 100644 src/ScriptResolving/WASM/WebAssemblyScriptCapabilities.hpp create mode 100644 src/ScriptResolving/WASM/WebAssemblyScriptResolver.cpp create mode 100644 src/ScriptResolving/WASM/WebAssemblyScriptResolver.hpp rename tests/ScriptTests/{ => Angelscript}/BaseScriptClassTests.cpp (99%) rename tests/ScriptTests/{ => Angelscript}/ItemUseScriptTests.cpp (98%) rename tests/ScriptTests/{ => Angelscript}/MetadataTests.cpp (94%) rename tests/ScriptTests/{ => Angelscript}/ScriptOwnerTest.cpp (98%) rename tests/ScriptTests/{ => Angelscript}/ScriptResolverTests.cpp (99%) rename tests/ScriptTests/{ => Angelscript}/ScriptTypeTests/Battle/BattleTests.cpp (99%) rename tests/ScriptTests/{ => Angelscript}/ScriptTypeTests/Battle/PokemonTests.cpp (99%) rename tests/ScriptTests/{ => Angelscript}/ScriptTypeTests/Library/FormesTests.cpp (99%) rename tests/ScriptTests/{ => Angelscript}/ScriptTypeTests/Library/ItemDataTests.cpp (99%) rename tests/ScriptTests/{ => Angelscript}/ScriptTypeTests/Library/MoveTests.cpp (99%) rename tests/ScriptTests/{ => Angelscript}/ScriptTypeTests/Library/SpeciesTests.cpp (99%) rename tests/ScriptTests/{ => Angelscript}/ScriptTypeTests/Library/StaticLibraryTests.cpp (99%) create mode 100644 tests/ScriptTests/WASM/ScriptResolverTests.cpp create mode 100755 tests/ScriptTests/WASM/gen7_scripts_rs.wasm diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c3bd16..6c84d85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,10 +73,15 @@ if (SCRIPT_PROVIDER STREQUAL "angelscript") WORKING_DIRECTORY ${Angelscript_SOURCE_DIR}/angelscript/projects/cmake) endif () - include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) include_directories(${Angelscript_SOURCE_DIR}/angelscript/include) include_directories(${Angelscript_SOURCE_DIR}/add_on) +elseif (SCRIPT_PROVIDER STREQUAL "wasm") + message(STATUS "Installing wasmer") + execute_process(COMMAND bash -c "curl https://get.wasmer.io -sSfL | WASMER_INSTALL_LOG=0 sh") + include_directories($ENV{WASMER_DIR}/include) endif () +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + if (WINDOWS) SET(CMAKE_SYSTEM_NAME Windows) @@ -160,13 +165,19 @@ if (SCRIPT_PROVIDER STREQUAL "angelscript") set(_TESTLINKS ${_TESTLINKS} AngelscriptDebugger) endif () ADD_DEFINITIONS(-D AS_USE_ACCESSORS=1) +elseif(SCRIPT_PROVIDER STREQUAL "wasm") + SET(FILE_SOURCE ${FILE_SOURCE} + "src/ScriptResolving/WASM/*.cpp" + "src/ScriptResolving/WASM/*.hpp") + # FIXME: Add C Interface endif () file(GLOB_RECURSE CORE_SRC_FILES ${FILE_SOURCE}) add_library(pkmnLib ${LIBTYPE} ${CORE_SRC_FILES}) # Enable all warnings, and make them error when occurring. target_compile_options(pkmnLib PRIVATE -Wall -Wextra -Werror -pedantic-errors) -target_link_directories(pkmnLib PUBLIC ${Angelscript_BINARY_DIR}) + + # If interprocedural optimization is available, apply it check_ipo_supported(RESULT IPO_SUPPORTED) @@ -180,12 +191,21 @@ SET(_TESTLINKS ${_TESTLINKS} pkmnLib CreatureLib Arbutils) if (SCRIPT_PROVIDER STREQUAL "angelscript") message(STATUS "Using Angelscript as script provider.") + target_link_directories(pkmnLib PUBLIC ${Angelscript_BINARY_DIR}) + ADD_DEFINITIONS(-D ANGELSCRIPT=1) SET(_LINKS ${_LINKS} angelscript) SET(_TESTLINKS ${_TESTLINKS} angelscript) if (ANGELSCRIPT_DEBUGGER) ADD_DEFINITIONS(-D ANGELSCRIPT_DEBUGGER=1) endif () +elseif (SCRIPT_PROVIDER STREQUAL "wasm") + message(STATUS "Using WebAssembly as script provider.") + target_link_directories(pkmnLib PUBLIC $ENV{WASMER_DIR}/lib) + message($ENV{WASMER_DIR}/lib) + ADD_DEFINITIONS(-D WASM=1) + SET(_LINKS ${_LINKS} -Wl,-Bstatic wasmer -Wl,-Bdynamic) + SET(_TESTLINKS ${_TESTLINKS} -Wl,-Bstatic wasmer -Wl,-Bdynamic) endif () # If we are building for Windows we need to set some specific variables. @@ -245,6 +265,8 @@ if (PKMNLIB_TESTS) endif () endif () -if (ANGELSCRIPT_DEBUGGER) - include_directories(extern/AngelscriptDebuggerServer/extern/asio-1.18.2/include) -endif () +if (SCRIPT_PROVIDER STREQUAL "angelscript") + if (ANGELSCRIPT_DEBUGGER) + include_directories(extern/AngelscriptDebuggerServer/extern/asio-1.18.2/include) + endif () +endif() \ No newline at end of file diff --git a/sanitizer_ignores b/sanitizer_ignores new file mode 100644 index 0000000..3a73069 --- /dev/null +++ b/sanitizer_ignores @@ -0,0 +1 @@ +src:*/std_function.h \ No newline at end of file diff --git a/src/Battling/Library/ScriptResolver.hpp b/src/Battling/Library/ScriptResolver.hpp index 9a966dd..696bfbc 100644 --- a/src/Battling/Library/ScriptResolver.hpp +++ b/src/Battling/Library/ScriptResolver.hpp @@ -1,6 +1,7 @@ #ifndef PKMNLIB_SCRIPTRESOLVER_HPP #define PKMNLIB_SCRIPTRESOLVER_HPP +#include #include "../EvolutionScript.hpp" namespace PkmnLib::Battling { diff --git a/src/Library/Moves/MoveLibrary.hpp b/src/Library/Moves/MoveLibrary.hpp index 0343f40..4e4a770 100644 --- a/src/Library/Moves/MoveLibrary.hpp +++ b/src/Library/Moves/MoveLibrary.hpp @@ -18,9 +18,19 @@ namespace PkmnLib::Library { return {}; return res.value().ForceAs(); } + inline std::optional> TryGet(u32 hash) const { + auto res = CreatureLib::Library::AttackLibrary::TryGet(hash); + if (!res.has_value()) + return {}; + return res.value().ForceAs(); + } + inline ArbUt::BorrowedPtr Get(const ArbUt::BasicStringView& name) const { return CreatureLib::Library::AttackLibrary::Get(name).As(); } + inline ArbUt::BorrowedPtr Get(u32 hash) const { + return CreatureLib::Library::AttackLibrary::Get(hash).As(); + } }; } diff --git a/src/ScriptResolving/WASM/InterfaceMethods/CoreMethods.cpp b/src/ScriptResolving/WASM/InterfaceMethods/CoreMethods.cpp new file mode 100644 index 0000000..edef7e6 --- /dev/null +++ b/src/ScriptResolving/WASM/InterfaceMethods/CoreMethods.cpp @@ -0,0 +1,86 @@ +#include "CoreMethods.hpp" +#include +#include "../WebAssemblyScriptResolver.hpp" +#include "WasmHelperFile.hpp" +#include "wasm.h" + +wasm_func_t* CreateErrorFunc(wasm_store_t* store, WebAssemblyScriptResolver* resolver) { + // This is probably the most horrific function we need to expose. As we do not want the scripting library to be + // responsible for string formatting for size reasons, we pass a lot of data separately. This includes several + // strings. + return WasmHelpers::CreateFunc( + store, resolver, [](void* env, const wasm_val_vec_t* args, wasm_val_vec_t*) -> wasm_trap_t* { + auto msg = args->data[0].of.i32; + auto msg_len = args->data[1].of.i32; + auto file = args->data[2].of.i32; + auto file_len = args->data[3].of.i32; + auto line = args->data[4].of.i32; + auto position = args->data[5].of.i32; + + auto* resolver = (WebAssemblyScriptResolver*)env; + auto* msgPointer = wasm_memory_data(resolver->GetMemory()) + msg; + auto* filePointer = wasm_memory_data(resolver->GetMemory()) + file; + + auto msgString = std::string_view(msgPointer, msg_len); + auto fileString = std::string_view(filePointer, file_len); + + std::stringstream fullMessage; + fullMessage << "WASM Error with message: " << msgString << std::endl + << "in file: " << fileString << ". Line: " << line << ":" << position; + + wasm_message_t message; + wasm_name_new_from_string_nt(&message, fullMessage.str().c_str()); + wasm_trap_t* trap = wasm_trap_new(resolver->GetStore(), &message); + wasm_name_delete(&message); + return trap; + }); +} + +wasm_func_t* CreatePrintFunc(wasm_store_t* store, WebAssemblyScriptResolver* resolver) { + return WasmHelpers::CreateFunc( + store, resolver, [](void* env, const wasm_val_vec_t* args, wasm_val_vec_t*) -> wasm_trap_t* { + auto msg = args->data[0].of.i32; + auto msg_len = args->data[1].of.i32; + auto resolver = (WebAssemblyScriptResolver*)env; + auto* msgPointer = wasm_memory_data(resolver->GetMemory()) + msg; + auto msgString = std::string_view(msgPointer, msg_len); + std::cout << msgString << std::endl; + return nullptr; + }); + ; +} + +wasm_func_t* ConstString_GetHash(wasm_store_t* store, WebAssemblyScriptResolver* resolver) { + return WasmHelpers::CreateFunc2( + store, resolver, + std::function( + [](WebAssemblyScriptResolver*, const ArbUt::StringView* sv) -> u32 { return sv->GetHash(); })); +} + +wasm_func_t* ConstString_GetStr(wasm_store_t* store, WebAssemblyScriptResolver* resolver) { + return WasmHelpers::CreateFunc( + store, resolver, [](void* env, const wasm_val_vec_t* args, wasm_val_vec_t* returns) -> wasm_trap_t* { + auto resolver = (WebAssemblyScriptResolver*)env; + auto& constString = *(ArbUt::StringView*)args->data[0].of.i64; + + // To allow webassembly to access the C String, we need to allocate it inside it's memory. + // Length + 1 to make room for the '\0' + auto stringPointer = resolver->AllocateMemory(constString.Length() + 1, std::alignment_of()); + + // After we have the pointer to the memory allocated for the string, copy the string with specified length + // to it, then suffix it with the null byte to end it. + strncpy(reinterpret_cast(stringPointer.first), constString.c_str(), constString.Length()); + stringPointer.first[constString.Length()] = '\0'; + + returns->data[0] = WASM_I32_VAL(stringPointer.second); + return nullptr; + }); +} + +void WebAssemblyCoreMethods::Register(wasm_store_t* store, ArbUt::Dictionary& externs, + WebAssemblyScriptResolver* resolver) { + externs.Insert("_error", CreateErrorFunc(store, resolver)); + externs.Insert("_print", CreatePrintFunc(store, resolver)); + externs.Insert("arbutils_const_string_get_hash", ConstString_GetHash(store, resolver)); + externs.Insert("arbutils_const_string_get_str", ConstString_GetStr(store, resolver)); +} diff --git a/src/ScriptResolving/WASM/InterfaceMethods/CoreMethods.hpp b/src/ScriptResolving/WASM/InterfaceMethods/CoreMethods.hpp new file mode 100644 index 0000000..0bf9bf6 --- /dev/null +++ b/src/ScriptResolving/WASM/InterfaceMethods/CoreMethods.hpp @@ -0,0 +1,14 @@ +#ifndef PKMNLIB_COREMETHODS_HPP +#define PKMNLIB_COREMETHODS_HPP + +#include +#include + +class WebAssemblyScriptResolver; +class WebAssemblyCoreMethods { +public: + static void Register(wasm_store_t* store, ArbUt::Dictionary& externs, + WebAssemblyScriptResolver* resolver); +}; + +#endif // PKMNLIB_COREMETHODS_HPP diff --git a/src/ScriptResolving/WASM/InterfaceMethods/Library/LibraryMethods.cpp b/src/ScriptResolving/WASM/InterfaceMethods/Library/LibraryMethods.cpp new file mode 100644 index 0000000..58c7cec --- /dev/null +++ b/src/ScriptResolving/WASM/InterfaceMethods/Library/LibraryMethods.cpp @@ -0,0 +1,39 @@ +#include "LibraryMethods.hpp" +#include +#include "../../../../Battling/Library/BattleLibrary.hpp" +#include "../../WebAssemblyScriptResolver.hpp" +#include "../WasmHelperFile.hpp" +#include "wasm.h" + +wasm_func_t* MoveLibrary_GetMoveByHash(wasm_store_t* store) { + wasm_functype_t* type = + wasm_functype_new_2_1(wasm_valtype_new_i64(), wasm_valtype_new_i32(), wasm_valtype_new_i64()); + auto* f = wasm_func_new(store, type, [](const wasm_val_vec_t* args, wasm_val_vec_t* returns) -> wasm_trap_t* { + auto moveLibrary = (PkmnLib::Library::MoveLibrary*)args->data[0].of.i64; + auto hash = (u32)args->data[1].of.i32; + auto opt = moveLibrary->TryGet(hash); + if (!opt.has_value()) { + returns->data[0] = WASM_I64_VAL(0); + } else{ + returns->data[0] = WASM_I64_VAL(reinterpret_cast(moveLibrary->Get(hash).GetRaw())); + } + return nullptr; + }); + wasm_functype_delete(type); + return f; +} + +void LibraryMethods::Register(wasm_store_t* store, ArbUt::Dictionary& externs, + WebAssemblyScriptResolver* resolver) { + REGISTER_GETTER("battling_battle_library_get_data_library", PkmnLib::Battling::BattleLibrary, GetStaticLib, store, + resolver); + REGISTER_GETTER("library_data_library_get_move_library", PkmnLib::Library::PokemonLibrary, GetMoveLibrary, store, + resolver); + externs.Insert("library_move_library_get_move_by_hash", MoveLibrary_GetMoveByHash(store)); + + REGISTER_GETTER("library_move_data_get_base_power", CreatureLib::Library::AttackData, GetBasePower, store, + resolver); + + REGISTER_GETTER("library_move_data_get_name", CreatureLib::Library::AttackData, GetName, store, + resolver); +} diff --git a/src/ScriptResolving/WASM/InterfaceMethods/Library/LibraryMethods.hpp b/src/ScriptResolving/WASM/InterfaceMethods/Library/LibraryMethods.hpp new file mode 100644 index 0000000..6220258 --- /dev/null +++ b/src/ScriptResolving/WASM/InterfaceMethods/Library/LibraryMethods.hpp @@ -0,0 +1,13 @@ +#ifndef PKMNLIB_LIBRARYMETHODS_H +#define PKMNLIB_LIBRARYMETHODS_H +#include +#include + +class WebAssemblyScriptResolver; +class LibraryMethods { +public: + static void Register(wasm_store_t* store, ArbUt::Dictionary& externs, + WebAssemblyScriptResolver* resolver); +}; + +#endif // PKMNLIB_LIBRARYMETHODS_H diff --git a/src/ScriptResolving/WASM/InterfaceMethods/WasmHelperFile.hpp b/src/ScriptResolving/WASM/InterfaceMethods/WasmHelperFile.hpp new file mode 100644 index 0000000..d3f7653 --- /dev/null +++ b/src/ScriptResolving/WASM/InterfaceMethods/WasmHelperFile.hpp @@ -0,0 +1,197 @@ +#ifndef PKMNLIB_HELPERFILE_H +#define PKMNLIB_HELPERFILE_H +#include +#include +#include +#include +#include +#include "../WebAssemblyScriptResolver.hpp" +#include "wasm.h" + +template class Ref> struct is_specialization : std::false_type {}; +template