From 5c3ee228db67397ad29f1541b143f7573f338edc Mon Sep 17 00:00:00 2001 From: Gordon Smith Date: Tue, 11 Jul 2023 08:36:25 +0100 Subject: [PATCH] HPCC-29914 Add embedded wasm support Signed-off-by: Gordon Smith --- .github/workflows/build-assets.yml | 12 +- .github/workflows/build-vcpkg.yml | 7 +- CMakeLists.txt | 1 + cmake_modules/plugins.cmake | 1 + plugins/CMakeLists.txt | 1 + plugins/wasmembed/CMakeLists.txt | 48 ++ .../wasmembed/secure-enclave/CMakeLists.txt | 41 ++ plugins/wasmembed/secure-enclave/abi.hpp | 157 ++++++ .../secure-enclave/hpcc-platform.wit | 11 + .../secure-enclave/secure-enclave.cpp | 502 ++++++++++++++++++ .../secure-enclave/secure-enclave.hpp | 28 + plugins/wasmembed/wasm.ecllib | 10 + plugins/wasmembed/wasmembed.cpp | 145 +++++ testing/regress/ecl/key/wasmembed.xml | 51 ++ testing/regress/ecl/wasmembed.ecl | 34 ++ testing/regress/ecl/wasmembed.manifest | 3 + testing/regress/ecl/wasmembed.wasm | Bin 0 -> 53462 bytes vcpkg.json.in | 4 + vcpkg_overlays/wasmtime-c-api/portfile.cmake | 37 ++ vcpkg_overlays/wasmtime-c-api/vcpkg.json | 7 + .../wasmtime-cpp-api/portfile.cmake | 16 + vcpkg_overlays/wasmtime-cpp-api/vcpkg.json | 10 + 22 files changed, 1116 insertions(+), 10 deletions(-) create mode 100644 plugins/wasmembed/CMakeLists.txt create mode 100644 plugins/wasmembed/secure-enclave/CMakeLists.txt create mode 100644 plugins/wasmembed/secure-enclave/abi.hpp create mode 100644 plugins/wasmembed/secure-enclave/hpcc-platform.wit create mode 100644 plugins/wasmembed/secure-enclave/secure-enclave.cpp create mode 100644 plugins/wasmembed/secure-enclave/secure-enclave.hpp create mode 100644 plugins/wasmembed/wasm.ecllib create mode 100644 plugins/wasmembed/wasmembed.cpp create mode 100644 testing/regress/ecl/key/wasmembed.xml create mode 100644 testing/regress/ecl/wasmembed.ecl create mode 100644 testing/regress/ecl/wasmembed.manifest create mode 100644 testing/regress/ecl/wasmembed.wasm create mode 100644 vcpkg_overlays/wasmtime-c-api/portfile.cmake create mode 100644 vcpkg_overlays/wasmtime-c-api/vcpkg.json create mode 100644 vcpkg_overlays/wasmtime-cpp-api/portfile.cmake create mode 100644 vcpkg_overlays/wasmtime-cpp-api/vcpkg.json diff --git a/.github/workflows/build-assets.yml b/.github/workflows/build-assets.yml index 4754b9b7763..29163fe97a7 100644 --- a/.github/workflows/build-assets.yml +++ b/.github/workflows/build-assets.yml @@ -55,7 +55,7 @@ jobs: echo "internal_tag=$(echo $community_tag | sed 's/community/internal/')" >> $GITHUB_OUTPUT community_base_ref=${{ github.event.base_ref || github.ref }} echo "community_branch=$(echo $community_base_ref | cut -d'/' -f3)" >> $GITHUB_OUTPUT - echo "cmake_docker_config=-DCMAKE_BUILD_TYPE=RelWithDebInfo -DVCPKG_FILES_DIR=/hpcc-dev -DCPACK_THREADS=0 -DUSE_OPTIONAL=OFF -DSIGN_MODULES=ON" >> $GITHUB_OUTPUT + echo "cmake_docker_config=-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCPACK_THREADS=0 -DUSE_OPTIONAL=OFF -DSIGN_MODULES=ON" >> $GITHUB_OUTPUT echo 'gpg_import=gpg --batch --import /hpcc-dev/build/private.key' >> $GITHUB_OUTPUT - name: Print vars @@ -155,12 +155,16 @@ jobs: tags: | build-${{ matrix.os }}:latest + - name: Prepare build folder + run: | + mkdir -p ${{ needs.preamble.outputs.folder_build }} + docker run --rm --mount ${{ needs.preamble.outputs.mount_platform }} --mount ${{ needs.preamble.outputs.mount_build }} $docker_label "cp /hpcc-dev/vcpkg_installed /hpcc-dev/build" + - name: CMake Packages if: ${{ !matrix.container && !matrix.ln && !matrix.documentation }} run: | - mkdir -p ${{ needs.preamble.outputs.folder_build }} echo "${{ secrets.SIGNING_SECRET }}" > ${{ needs.preamble.outputs.folder_build }}/private.key - plugins=("CASSANDRAEMBED" "COUCHBASEEMBED" "ECLBLAS" "H3" "JAVAEMBED" "KAFKA" "MEMCACHED" "MONGODBEMBED" "MYSQLEMBED" "NLP" "REDIS" "REMBED" "SQLITE3EMBED" "SQS" "PLATFORM") + plugins=("CASSANDRAEMBED" "COUCHBASEEMBED" "ECLBLAS" "H3" "JAVAEMBED" "KAFKA" "MEMCACHED" "MONGODBEMBED" "MYSQLEMBED" "NLP" "REDIS" "REMBED" "SQLITE3EMBED" "SQS" "WASMEMBED" "PLATFORM") for plugin in "${plugins[@]}"; do sudo rm -f ${{ needs.preamble.outputs.folder_build }}/CMakeCache.txt sudo rm -rf ${{ needs.preamble.outputs.folder_build }}/CMakeFiles @@ -175,7 +179,6 @@ jobs: - name: CMake Containerized Packages if: ${{ matrix.container }} run: | - mkdir -p ${{ needs.preamble.outputs.folder_build }} echo "${{ secrets.SIGNING_SECRET }}" > ${{ needs.preamble.outputs.folder_build }}/private.key sudo rm -f ${{ needs.preamble.outputs.folder_build }}/CMakeCache.txt sudo rm -rf ${{ needs.preamble.outputs.folder_build }}/CMakeFiles @@ -209,7 +212,6 @@ jobs: - name: CMake LN Packages if: ${{ matrix.ln }} run: | - mkdir -p ${{ needs.preamble.outputs.folder_build }} echo "${{ secrets.SIGNING_SECRET }}" > ${{ needs.preamble.outputs.folder_build }}/private.key sudo rm -f ${{ needs.preamble.outputs.folder_build }}/CMakeCache.txt sudo rm -rf ${{ needs.preamble.outputs.folder_build }}/CMakeFiles diff --git a/.github/workflows/build-vcpkg.yml b/.github/workflows/build-vcpkg.yml index 54a14ec2f57..72b00829ba9 100644 --- a/.github/workflows/build-vcpkg.yml +++ b/.github/workflows/build-vcpkg.yml @@ -58,7 +58,7 @@ jobs: echo "internal_tag=$(echo $community_tag | sed 's/community/internal/')" >> $GITHUB_OUTPUT community_base_ref=${{ github.event.base_ref || github.ref }} echo "community_branch=$(echo $community_base_ref | cut -d'/' -f3)" >> $GITHUB_OUTPUT - echo "cmake_docker_config=-DCMAKE_BUILD_TYPE=RelWithDebInfo -DVCPKG_FILES_DIR=/hpcc-dev -DCPACK_THREADS=0 -DUSE_OPTIONAL=OFF" >> $GITHUB_OUTPUT + echo "cmake_docker_config=-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCPACK_THREADS=0 -DUSE_OPTIONAL=OFF" >> $GITHUB_OUTPUT - id: skip_check uses: hpcc-systems/github-actions/changed-modules@main @@ -160,10 +160,9 @@ jobs: - name: CMake Packages if: ${{ !matrix.container && !matrix.ln && contains(matrix.event_name, github.event_name) && needs.preamble.outputs.platform }} run: | - mkdir -p ${{ needs.preamble.outputs.folder_build }} declare -a plugins if [ ${{ needs.preamble.outputs.include_plugins }} == "ON" ]; then - plugins=("CASSANDRAEMBED" "COUCHBASEEMBED" "ECLBLAS" "H3" "JAVAEMBED" "KAFKA" "MEMCACHED" "MONGODBEMBED" "MYSQLEMBED" "NLP" "REDIS" "REMBED" "SQLITE3EMBED" "SQS" "PLATFORM") + plugins=("CASSANDRAEMBED" "COUCHBASEEMBED" "ECLBLAS" "H3" "JAVAEMBED" "KAFKA" "MEMCACHED" "MONGODBEMBED" "MYSQLEMBED" "NLP" "REDIS" "REMBED" "SQLITE3EMBED" "SQS" "WASMEMBED" "PLATFORM") else plugins=("PLATFORM") fi @@ -181,7 +180,6 @@ jobs: - name: CMake Containerized Packages if: ${{ matrix.container && contains(matrix.event_name, github.event_name) && needs.preamble.outputs.platform }} run: | - mkdir -p ${{ needs.preamble.outputs.folder_build }} sudo rm -f ${{ needs.preamble.outputs.folder_build }}/CMakeCache.txt sudo rm -rf ${{ needs.preamble.outputs.folder_build }}/CMakeFiles docker_label=build-${{ matrix.os }}:latest @@ -192,7 +190,6 @@ jobs: - name: CMake LN Packages if: ${{ matrix.ln && contains(matrix.event_name, github.event_name) && needs.preamble.outputs.platform }} run: | - mkdir -p ${{ needs.preamble.outputs.folder_build }} sudo rm -f ${{ needs.preamble.outputs.folder_build }}/CMakeCache.txt sudo rm -rf ${{ needs.preamble.outputs.folder_build }}/CMakeFiles docker_label=build-${{ matrix.os }}:latest diff --git a/CMakeLists.txt b/CMakeLists.txt index e226b94631f..951990812c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,7 @@ if ( PLUGIN ) HPCC_ADD_SUBDIRECTORY (dali/base) HPCC_ADD_SUBDIRECTORY (plugins/Rembed "REMBED") HPCC_ADD_SUBDIRECTORY (plugins/v8embed "V8EMBED") + HPCC_ADD_SUBDIRECTORY (plugins/wasmembed "WASMEMBED") HPCC_ADD_SUBDIRECTORY (plugins/memcached "MEMCACHED") HPCC_ADD_SUBDIRECTORY (plugins/redis "REDIS") HPCC_ADD_SUBDIRECTORY (plugins/javaembed "JAVAEMBED") diff --git a/cmake_modules/plugins.cmake b/cmake_modules/plugins.cmake index 88a5cdbbc1b..5be884d4c03 100644 --- a/cmake_modules/plugins.cmake +++ b/cmake_modules/plugins.cmake @@ -41,6 +41,7 @@ set(PLUGINS_LIST SQLITE3EMBED SQS V8EMBED + WASMEMBED EXAMPLEPLUGIN ) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 98fad1db96f..56b5618d4c4 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -30,6 +30,7 @@ add_subdirectory (proxies) add_subdirectory (sqlite3) add_subdirectory (mysql) add_subdirectory (v8embed) +add_subdirectory (wasmembed) HPCC_ADD_SUBDIRECTORY (py3embed "USE_PYTHON3") HPCC_ADD_SUBDIRECTORY (pyembed "USE_PYTHON2") add_subdirectory (javaembed) diff --git a/plugins/wasmembed/CMakeLists.txt b/plugins/wasmembed/CMakeLists.txt new file mode 100644 index 00000000000..476fdbf18de --- /dev/null +++ b/plugins/wasmembed/CMakeLists.txt @@ -0,0 +1,48 @@ +project(wasmembed) + +if(WASMEMBED) + ADD_PLUGIN(wasmembed) + if(MAKE_WASMEMBED) + + add_subdirectory(secure-enclave) + + include_directories( + ./../../common/thorhelper + ./../../dali/base + ./../../rtl/eclrtl + ./../../rtl/include + ./../../rtl/nbcd + ./../../system/include + ./../../system/jlib + ./../../system/mp + ) + + add_definitions(-D_USRDLL -DWASMEMBED_EXPORTS) + + add_library(wasmembed SHARED + wasmembed.cpp + ) + + target_link_libraries(wasmembed + roxiemem + eclrtl + jlib + secure-enclave + ) + + install( + TARGETS wasmembed + DESTINATION plugins + ) + + else() + message(WARNING "Cannot build wasmembed plugin") + endif() +endif() + +if(PLATFORM OR CLIENTTOOLS_ONLY) + install( + FILES ${CMAKE_CURRENT_SOURCE_DIR}/wasm.ecllib + DESTINATION plugins + COMPONENT Runtime) +endif() diff --git a/plugins/wasmembed/secure-enclave/CMakeLists.txt b/plugins/wasmembed/secure-enclave/CMakeLists.txt new file mode 100644 index 00000000000..b4247b79d9b --- /dev/null +++ b/plugins/wasmembed/secure-enclave/CMakeLists.txt @@ -0,0 +1,41 @@ +project(secure-enclave) + +set(CMAKE_CXX_STANDARD 20) + +find_path(WASMTIME_CPP_API_INCLUDE_DIRS "wasmtime-cpp-api/wasmtime.hh" + PATHS ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET} +) +if (WIN32) + find_library(WASMTIME_LIB NAMES wasmtime.dll + PATHS ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET} + ) +else() + find_library(WASMTIME_LIB NAMES wasmtime + PATHS ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET} + ) +endif() + +include_directories( + ${WASMTIME_CPP_API_INCLUDE_DIRS}/wasmtime-c-api + ${WASMTIME_CPP_API_INCLUDE_DIRS}/wasmtime-cpp-api + ./../../../system/include + ./../../../rtl/eclrtl + ./../../../system/jlib +) + +add_definitions(-D_USRDLL -DSECUREENCLAVE_EXPORTS) + +add_library(secure-enclave SHARED + secure-enclave.cpp +) + +target_link_libraries(secure-enclave PRIVATE + ${WASMTIME_LIB} +) + +install( + TARGETS secure-enclave + DESTINATION plugins + CALC_DEPS +) + diff --git a/plugins/wasmembed/secure-enclave/abi.hpp b/plugins/wasmembed/secure-enclave/abi.hpp new file mode 100644 index 00000000000..c547a9fd29a --- /dev/null +++ b/plugins/wasmembed/secure-enclave/abi.hpp @@ -0,0 +1,157 @@ +/* + See: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md + https://github.com/WebAssembly/component-model/blob/main/design/mvp/canonical-abi/definitions.py +*/ + +#include +#include +#include +#include +#include + +auto UTF16_TAG = 1 << 31; + +int align_to(int ptr, int alignment) +{ + return std::ceil(ptr / alignment) * alignment; +} + +// loading --- + +int load_int(const wasmtime::Span &data, int32_t ptr, int32_t nbytes, bool is_signed = false) +{ + int result = 0; + for (int i = 0; i < nbytes; i++) + { + int b = data[ptr + i]; + if (i == 3 && is_signed && b >= 0x80) + { + b -= 0x100; + } + result += b << (i * 8); + } + return result; +} + +std::string global_encoding = "utf8"; +std::string load_string_from_range(const wasmtime::Span &data, uint32_t ptr, uint32_t tagged_code_units) +{ + std::string encoding = "utf-8"; + uint32_t byte_length = tagged_code_units; + uint32_t alignment = 1; + if (global_encoding.compare("utf8") == 0) + { + alignment = 1; + byte_length = tagged_code_units; + encoding = "utf-8"; + } + else if (global_encoding.compare("utf16") == 0) + { + alignment = 2; + byte_length = 2 * tagged_code_units; + encoding = "utf-16-le"; + } + else if (global_encoding.compare("latin1+utf16") == 0) + { + alignment = 2; + if (tagged_code_units & UTF16_TAG) + { + byte_length = 2 * (tagged_code_units ^ UTF16_TAG); + encoding = "utf-16-le"; + } + else + { + byte_length = tagged_code_units; + encoding = "latin-1"; + } + } + + if (ptr != align_to(ptr, alignment)) + { + throw std::runtime_error("Invalid alignment"); + } + if (ptr + byte_length > data.size()) + { + throw std::runtime_error("Out of bounds"); + } + + std::string s; + s.resize(byte_length); + memcpy(&s[0], &data[ptr], byte_length); + return s; +} + +std::string load_string(const wasmtime::Span &data, uint32_t ptr) +{ + uint32_t begin = load_int(data, ptr, 4); + uint32_t tagged_code_units = load_int(data, ptr + 4, 4); + return load_string_from_range(data, begin, tagged_code_units); +} + +// Storing --- +void store_int(const wasmtime::Span &data, int64_t v, size_t ptr, size_t nbytes, bool _signed = false) +{ + // convert v to little-endian byte array + std::vector bytes(nbytes); + for (size_t i = 0; i < nbytes; i++) + { + bytes[i] = (v >> (i * 8)) & 0xFF; + } + // copy bytes to memory + memcpy(&data[ptr], bytes.data(), nbytes); +} + +// Other --- +std::vector read_wasm_binary_to_buffer(const std::string &filename) +{ + std::ifstream file(filename, std::ios::binary | std::ios::ate); + if (!file) + { + throw std::runtime_error("Failed to open file"); + } + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector buffer(size); + if (!file.read(reinterpret_cast(buffer.data()), size)) + { + throw std::runtime_error("Failed to read file"); + } + + return buffer; +} + +std::string extractContentInDoubleQuotes(const std::string &input) +{ + + int firstQuote = input.find_first_of('"'); + int secondQuote = input.find('"', firstQuote + 1); + if (firstQuote == std::string::npos || secondQuote == std::string::npos) + { + return ""; + } + return input.substr(firstQuote + 1, secondQuote - firstQuote - 1); +} + +std::pair splitQualifiedID(const std::string &qualifiedName) +{ + std::istringstream iss(qualifiedName); + std::vector tokens; + std::string token; + + while (std::getline(iss, token, '.')) + { + tokens.push_back(token); + } + if (tokens.size() != 2) + { + throw std::runtime_error("Invalid import function " + qualifiedName + ", expected format: ."); + } + return std::make_pair(tokens[0], tokens[1]); +} + +std::string createQualifiedID(const std::string &wasmName, const std::string &funcName) +{ + return wasmName + "." + funcName; +} \ No newline at end of file diff --git a/plugins/wasmembed/secure-enclave/hpcc-platform.wit b/plugins/wasmembed/secure-enclave/hpcc-platform.wit new file mode 100644 index 00000000000..036df840a23 --- /dev/null +++ b/plugins/wasmembed/secure-enclave/hpcc-platform.wit @@ -0,0 +1,11 @@ +package hpcc-systems:hpcc-platform + +world wasmembed { + // guests dispose all params as needed + // guests should dispose "results" as needed + import dbglog: func(msg: string) + + // guests dispose all params as needed + // hosts call cabi_post_XXX to dispose "results" as needed + // export myfunc(params: string) -> string +} diff --git a/plugins/wasmembed/secure-enclave/secure-enclave.cpp b/plugins/wasmembed/secure-enclave/secure-enclave.cpp new file mode 100644 index 00000000000..d8dd157f21c --- /dev/null +++ b/plugins/wasmembed/secure-enclave/secure-enclave.cpp @@ -0,0 +1,502 @@ +#include "secure-enclave.hpp" + +#include "abi.hpp" + +#include +#include + +std::shared_ptr embedContextCallbacks; + +#define NENABLE_TRACE + +#ifdef ENABLE_TRACE +#define TRACE(format, ...) embedContextCallbacks->DBGLOG(format, ##__VA_ARGS__) +#else +#define TRACE(format, ...) \ + do \ + { \ + } while (0) +#endif + +class WasmEngine +{ +protected: + wasmtime::Engine engine; + + std::map wasmInstances; + std::map wasmMems; + std::map wasmFuncs; + +public: + wasmtime::Store store; + + WasmEngine() : store(engine) + { + } + + ~WasmEngine() + { + } + + bool hasInstance(const std::string &wasmName) + { + return wasmInstances.find(wasmName) != wasmInstances.end(); + } + + wasmtime::Instance getInstance(const std::string &wasmName) + { + auto instanceItr = wasmInstances.find(wasmName); + if (instanceItr == wasmInstances.end()) + throw std::runtime_error("Wasm instance not found: " + wasmName); + return instanceItr->second; + } + + void registerInstance(const std::string &wasmName, const std::variant> &wasm) + { + TRACE("registerInstance %s", wasmName.c_str()); + auto instanceItr = wasmInstances.find(wasmName); + if (instanceItr == wasmInstances.end()) + { + TRACE("resolveModule %s", wasmName.c_str()); + auto module = std::holds_alternative(wasm) ? wasmtime::Module::compile(engine, std::get(wasm)).unwrap() : wasmtime::Module::compile(engine, std::get>(wasm)).unwrap(); + TRACE("resolveModule2 %s", wasmName.c_str()); + + wasmtime::WasiConfig wasi; + wasi.inherit_argv(); + wasi.inherit_env(); + wasi.inherit_stdin(); + wasi.inherit_stdout(); + wasi.inherit_stderr(); + store.context().set_wasi(std::move(wasi)).unwrap(); + TRACE("resolveModule3 %s", wasmName.c_str()); + + wasmtime::Linker linker(engine); + linker.define_wasi().unwrap(); + TRACE("resolveModule4 %s", wasmName.c_str()); + + auto callback = [this, wasmName](wasmtime::Caller caller, uint32_t msg, uint32_t msg_len) + { + TRACE("callback: %i %i", msg_len, msg); + + auto data = this->getData(wasmName); + auto msg_ptr = (char *)&data[msg]; + std::string str(msg_ptr, msg_len); + embedContextCallbacks->DBGLOG("from wasm: %s", str.c_str()); + }; + auto host_func = linker.func_wrap("$root", "dbglog", callback).unwrap(); + + auto newInstance = linker.instantiate(store, module).unwrap(); + linker.define_instance(store, "linking2", newInstance).unwrap(); + + TRACE("resolveModule5 %s", wasmName.c_str()); + + wasmInstances.insert(std::make_pair(wasmName, newInstance)); + + for (auto exportItem : module.exports()) + { + auto externType = wasmtime::ExternType::from_export(exportItem); + std::string name(exportItem.name()); + if (std::holds_alternative(externType)) + { + TRACE("Exported function: %s", name.c_str()); + auto func = std::get(*newInstance.get(store, name)); + wasmFuncs.insert(std::make_pair(wasmName + "." + name, func)); + } + else if (std::holds_alternative(externType)) + { + TRACE("Exported memory: %s", name.c_str()); + auto memory = std::get(*newInstance.get(store, name)); + wasmMems.insert(std::make_pair(wasmName + "." + name, memory)); + } + else if (std::holds_alternative(externType)) + { + TRACE("Exported table: %s", name.c_str()); + } + else if (std::holds_alternative(externType)) + { + TRACE("Exported global: %s", name.c_str()); + } + else + { + TRACE("Unknown export type"); + } + } + } + } + + bool hasFunc(const std::string &qualifiedID) + { + return wasmFuncs.find(qualifiedID) != wasmFuncs.end(); + } + + wasmtime::Func getFunc(const std::string &qualifiedID) + { + auto found = wasmFuncs.find(qualifiedID); + if (found == wasmFuncs.end()) + throw std::runtime_error("Wasm function not found: " + qualifiedID); + return found->second; + } + + wasmtime::ValType::ListRef getFuncParams(const std::string &qualifiedID) + { + auto func = getFunc(qualifiedID); + wasmtime::FuncType funcType = func.type(store.context()); + return funcType->params(); + } + + wasmtime::ValType::ListRef getFuncResults(const std::string &qualifiedID) + { + auto func = getFunc(qualifiedID); + wasmtime::FuncType funcType = func.type(store.context()); + return funcType->results(); + } + + std::vector call(const std::string &qualifiedID, const std::vector ¶ms) + { + return getFunc(qualifiedID).call(store, params).unwrap(); + } + + std::vector callRealloc(const std::string &wasmName, const std::vector ¶ms) + { + return call(createQualifiedID(wasmName, "cabi_realloc"), params); + } + + wasmtime::Span getData(const std::string &wasmName) + { + auto found = wasmMems.find(createQualifiedID(wasmName, "memory")); + if (found == wasmMems.end()) + throw std::runtime_error("Wasm memory not found: " + wasmName); + return found->second.data(store.context()); + } +}; +std::unique_ptr wasmEngine; + +class SecureFunction : public ISecureEnclave +{ + std::string wasmName; + std::string funcName; + std::string qualifiedID; + + const IThorActivityContext *activityCtx = nullptr; + std::vector args; + std::vector results; + +public: + SecureFunction() + { + TRACE("se:constructor"); + } + + virtual ~SecureFunction() override + { + TRACE("se:destructor"); + + // Garbage Collection --- + // Function results --- + auto gc_func_name = createQualifiedID(wasmName, "cabi_post_" + funcName); + if (wasmEngine->hasFunc(gc_func_name)) + { + auto func = wasmEngine->getFunc(gc_func_name); + for (auto &result : results) + { + func.call(wasmEngine->store, {result}).unwrap(); + } + } + } + + // IEmbedFunctionContext --- + void setActivityContext(const IThorActivityContext *_activityCtx) + { + activityCtx = _activityCtx; + } + + virtual void Link() const + { + } + + virtual bool Release() const + { + return false; + }; + + virtual IInterface *bindParamWriter(IInterface *esdl, const char *esdlservice, const char *esdltype, const char *name) + { + TRACE("paramWriterCommit"); + return NULL; + } + virtual void paramWriterCommit(IInterface *writer) + { + TRACE("paramWriterCommit"); + } + virtual void writeResult(IInterface *esdl, const char *esdlservice, const char *esdltype, IInterface *writer) + { + TRACE("writeResult"); + } + virtual void bindBooleanParam(const char *name, bool val) + { + TRACE("bindBooleanParam %s %i", name, val); + args.push_back(val); + } + virtual void bindDataParam(const char *name, size32_t len, const void *val) + { + TRACE("bindDataParam %s %d", name, len); + } + virtual void bindFloatParam(const char *name, float val) + { + TRACE("bindFloatParam %s %f", name, val); + args.push_back(val); + } + virtual void bindRealParam(const char *name, double val) + { + TRACE("bindRealParam %s %f", name, val); + args.push_back(val); + } + virtual void bindSignedSizeParam(const char *name, int size, __int64 val) + { + TRACE("bindSignedSizeParam %s %i %lld", name, size, val); + if (size <= 4) + args.push_back(static_cast(val)); + else + args.push_back(static_cast(val)); + } + virtual void bindSignedParam(const char *name, __int64 val) + { + TRACE("bindSignedParam %s %lld", name, val); + args.push_back(static_cast(val)); + } + virtual void bindUnsignedSizeParam(const char *name, int size, unsigned __int64 val) + { + TRACE("bindUnsignedSizeParam %s %i %llu", name, size, val); + if (size <= 4) + args.push_back(static_cast(val)); + else + args.push_back(static_cast(val)); + } + virtual void bindUnsignedParam(const char *name, unsigned __int64 val) + { + TRACE("bindUnsignedParam %s %llu", name, val); + args.push_back(static_cast(val)); + } + virtual void bindStringParam(const char *paramName, size32_t len, const char *val) + { + TRACE("bindStringParam %s %d %s", paramName, len, val); + auto memIdxVar = wasmEngine->callRealloc(wasmName, {0, 0, 1, (int32_t)len}); + auto memIdx = memIdxVar[0].i32(); + auto mem = wasmEngine->getData(wasmName); + for (int i = 0; i < len; i++) + { + mem[memIdx + i] = val[i]; + } + args.push_back(memIdx); + args.push_back((int32_t)len); + } + virtual void bindVStringParam(const char *name, const char *val) + { + TRACE("bindVStringParam %s %s", name, val); + auto len = strlen(val); + auto memIdxVar = wasmEngine->callRealloc(wasmName, {0, 0, 1, (int32_t)len}); + auto memIdx = memIdxVar[0].i32(); + auto mem = wasmEngine->getData(wasmName); + for (int i = 0; i < len; i++) + { + mem[memIdx + i] = val[i]; + } + args.push_back(memIdx); + args.push_back((int32_t)len); + } + virtual void bindUTF8Param(const char *name, size32_t chars, const char *val) + { + TRACE("bindUTF8Param %s %d %s", name, chars, val); + auto memIdxVar = wasmEngine->callRealloc(wasmName, {0, 0, 1, (int32_t)chars}); + auto memIdx = memIdxVar[0].i32(); + auto mem = wasmEngine->getData(wasmName); + for (int i = 0; i < chars; i++) + { + mem[memIdx + i] = val[i]; + } + args.push_back(memIdx); + args.push_back((int32_t)chars); + } + virtual void bindUnicodeParam(const char *name, size32_t chars, const UChar *val) + { + TRACE("bindUnicodeParam %s %d %S", name, chars, reinterpret_cast(val)); + auto memIdxVar = wasmEngine->callRealloc(wasmName, {0, 0, 2, (int32_t)chars * 2}); + auto memIdx = memIdxVar[0].i32(); + auto mem = wasmEngine->getData(wasmName); + for (int i = 0; i < chars * 2; i += 2) + { + mem[memIdx + i] = val[i]; + } + args.push_back(memIdx); + args.push_back((int32_t)chars); + } + virtual void bindSetParam(const char *name, int elemType, size32_t elemSize, bool isAll, size32_t totalBytes, const void *setData) + { + TRACE("bindSetParam %s %d %d %d %d %p", name, elemType, elemSize, isAll, totalBytes, setData); + } + virtual void bindRowParam(const char *name, IOutputMetaData &metaVal, const byte *val) override + { + TRACE("bindRowParam %s %p", name, val); + } + virtual void bindDatasetParam(const char *name, IOutputMetaData &metaVal, IRowStream *val) + { + TRACE("bindDatasetParam %s %p", name, val); + } + virtual bool getBooleanResult() + { + TRACE("getBooleanResult"); + return results[0].i32(); + } + virtual void getDataResult(size32_t &__len, void *&__result) + { + TRACE("getDataResult"); + } + virtual double getRealResult() + { + TRACE("getRealResult"); + if (results[0].kind() == wasmtime::ValKind::F64) + return (int32_t)results[0].f64(); + return results[0].f32(); + } + virtual __int64 getSignedResult() + { + TRACE("getSignedResult"); + if (results[0].kind() == wasmtime::ValKind::I64) + return (int32_t)results[0].i64(); + return results[0].i32(); + } + virtual unsigned __int64 getUnsignedResult() + { + TRACE("getUnsignedResult"); + if (results[0].kind() == wasmtime::ValKind::I64) + return (int32_t)results[0].i64(); + return results[0].i32(); + } + virtual void getStringResult(size32_t &__chars, char *&__result) + { + TRACE("getStringResult %zu", results.size()); + auto ptr = results[0].i32(); + auto data = wasmEngine->getData(wasmName); + + uint32_t begin = load_int(data, ptr, 4); + TRACE("begin %u", begin); + uint32_t tagged_code_units = load_int(data, ptr + 4, 4); + TRACE("tagged_code_units %u", tagged_code_units); + std::string s = load_string(data, ptr); + TRACE("load_string %s", s.c_str()); + __chars = s.length(); + __result = reinterpret_cast(embedContextCallbacks->rtlMalloc(__chars)); + s.copy(__result, __chars); + } + virtual void getUTF8Result(size32_t &__chars, char *&__result) + { + TRACE("getUTF8Result"); + } + virtual void getUnicodeResult(size32_t &__chars, UChar *&__result) + { + TRACE("getUnicodeResult"); + } + virtual void getSetResult(bool &__isAllResult, size32_t &__resultBytes, void *&__result, int elemType, size32_t elemSize) + { + TRACE("getSetResult"); + } + virtual IRowStream *getDatasetResult(IEngineRowAllocator *_resultAllocator) + { + TRACE("getDatasetResult"); + return NULL; + } + virtual byte *getRowResult(IEngineRowAllocator *_resultAllocator) + { + TRACE("getRowResult"); + return NULL; + } + virtual size32_t getTransformResult(ARowBuilder &builder) + { + TRACE("getTransformResult"); + return 0; + } + virtual void loadCompiledScript(size32_t chars, const void *_script) override + { + TRACE("loadCompiledScript %p", _script); + } + virtual void enter() override + { + TRACE("enter"); + } + virtual void reenter(ICodeContext *codeCtx) override + { + TRACE("reenter"); + } + virtual void exit() override + { + TRACE("exit"); + } + virtual void compileEmbeddedScript(size32_t lenChars, const char *_utf) override + { + TRACE("compileEmbeddedScript"); + std::string utf(_utf, lenChars); + funcName = extractContentInDoubleQuotes(utf); + wasmName = "embed_" + funcName; + qualifiedID = createQualifiedID(wasmName, funcName); + wasmEngine->registerInstance(wasmName, utf); + } + virtual void importFunction(size32_t lenChars, const char *qualifiedName) override + { + TRACE("importFunction: %s", qualifiedName); + + qualifiedID = std::string(qualifiedName, lenChars); + auto [_wasmName, _funcName] = splitQualifiedID(qualifiedID); + wasmName = _wasmName; + funcName = _funcName; + + if (!wasmEngine->hasInstance(wasmName)) + { + std::string fullPath = embedContextCallbacks->resolveManifestPath((wasmName + ".wasm").c_str()); + auto wasmFile = read_wasm_binary_to_buffer(fullPath); + wasmEngine->registerInstance(wasmName, wasmFile); + } + } + virtual void callFunction() + { + TRACE("callFunction %s", qualifiedID.c_str()); + results = wasmEngine->call(qualifiedID, args); + } +}; + +SECUREENCLAVE_API void init(std::shared_ptr embedContext) +{ + embedContextCallbacks = embedContext; + wasmEngine = std::make_unique(); + TRACE("init"); +} + +SECUREENCLAVE_API void kill() +{ + TRACE("kill"); + wasmEngine.reset(); + embedContextCallbacks.reset(); +} + +SECUREENCLAVE_API std::unique_ptr createISecureEnclave() +{ + return std::make_unique(); +} + +SECUREENCLAVE_API void syntaxCheck(size32_t &__lenResult, char *&__result, const char *funcname, size32_t charsBody, const char *body, const char *argNames, const char *compilerOptions, const char *persistOptions) +{ + std::string errMsg = ""; + try + { + wasmtime::Engine engine; + wasmtime::Store store(engine); + auto module = wasmtime::Module::compile(engine, body); + } + catch (const wasmtime::Error &e) + { + errMsg = e.message(); + } + + __lenResult = errMsg.length(); + __result = reinterpret_cast(embedContextCallbacks->rtlMalloc(__lenResult)); + errMsg.copy(__result, __lenResult); +} diff --git a/plugins/wasmembed/secure-enclave/secure-enclave.hpp b/plugins/wasmembed/secure-enclave/secure-enclave.hpp new file mode 100644 index 00000000000..82e8c800fb2 --- /dev/null +++ b/plugins/wasmembed/secure-enclave/secure-enclave.hpp @@ -0,0 +1,28 @@ +#include "platform.h" +#include "eclrtl.hpp" + +#ifdef SECUREENCLAVE_EXPORTS + #define SECUREENCLAVE_API DECL_EXPORT +#else + #define SECUREENCLAVE_API DECL_IMPORT +#endif + +#include + +interface IWasmEmbedCallback +{ + virtual inline void DBGLOG(char const *format, ...) __attribute__((format(printf, 2, 3))) = 0; + virtual void *rtlMalloc(size32_t size) = 0; + + virtual const char *resolveManifestPath(const char *leafName) = 0; +}; + +interface ISecureEnclave : extends IEmbedFunctionContext +{ + virtual ~ISecureEnclave() = default; +}; + +SECUREENCLAVE_API void init(std::shared_ptr embedContext); +SECUREENCLAVE_API void kill(); +SECUREENCLAVE_API std::unique_ptr createISecureEnclave(); +SECUREENCLAVE_API void syntaxCheck(size32_t &__lenResult, char *&__result, const char *funcname, size32_t charsBody, const char *body, const char *argNames, const char *compilerOptions, const char *persistOptions); diff --git a/plugins/wasmembed/wasm.ecllib b/plugins/wasmembed/wasm.ecllib new file mode 100644 index 00000000000..eb5d8313c02 --- /dev/null +++ b/plugins/wasmembed/wasm.ecllib @@ -0,0 +1,10 @@ +EXPORT Language := SERVICE : plugin('wasmembed') + integer getEmbedContext() : cpp, pure, fold, namespace='wasmLanguageHelper', entrypoint='getEmbedContext', prototype='IEmbedContext* getEmbedContext()'; + string syntaxCheck(const varstring funcname, UTF8 body, const varstring argnames, const varstring compileOptions, const varstring persistOptions) : cpp, pure, fold, namespace='wasmLanguageHelper', entrypoint='syntaxCheck'; +END; +EXPORT getEmbedContext := Language.getEmbedContext; +EXPORT syntaxCheck := Language.syntaxCheck; +EXPORT boolean supportsImport := true; +EXPORT boolean supportsScript := true; +EXPORT boolean prebind := false; +EXPORT boolean singletonEmbedContext := false; \ No newline at end of file diff --git a/plugins/wasmembed/wasmembed.cpp b/plugins/wasmembed/wasmembed.cpp new file mode 100644 index 00000000000..717e4958d95 --- /dev/null +++ b/plugins/wasmembed/wasmembed.cpp @@ -0,0 +1,145 @@ +#include "platform.h" +#include "hqlplugins.hpp" +#include "rtlfield.hpp" +#include "enginecontext.hpp" + +#include "secure-enclave/secure-enclave.hpp" + +static const char *compatibleVersions[] = { + "WASM Embed Helper 1.0.0", + NULL}; + +static const char *version = "WASM Embed Helper 1.0.0"; + +extern "C" DECL_EXPORT bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb) +{ + if (pb->size == sizeof(ECLPluginDefinitionBlockEx)) + { + ECLPluginDefinitionBlockEx *pbx = (ECLPluginDefinitionBlockEx *)pb; + pbx->compatibleVersions = compatibleVersions; + } + else if (pb->size != sizeof(ECLPluginDefinitionBlock)) + return false; + pb->magicVersion = PLUGIN_VERSION; + pb->version = version; + pb->moduleName = "wasm"; + pb->ECL = NULL; + pb->flags = PLUGIN_MULTIPLE_VERSIONS; + pb->description = "WASM Embed Helper"; + return true; +} + +namespace wasmLanguageHelper +{ + class Callbacks : public IWasmEmbedCallback + { + protected: + bool manifestAdded = false; + StringArray manifestModules; + + public: + Callbacks() + { + } + ~Callbacks() + { + } + void manifestPaths(ICodeContext *codeCtx) + { + if (codeCtx && !manifestAdded) + { + manifestAdded = true; + IEngineContext *engine = codeCtx->queryEngineContext(); + if (engine) + { + engine->getManifestFiles("wasm", manifestModules); + } + } + } + + // IWasmEmbedCallback --- + virtual inline void DBGLOG(char const *format, ...) override + { + va_list args; + va_start(args, format); + VALOG(MCdebugInfo, unknownJob, format, args); + va_end(args); + } + + virtual void *rtlMalloc(size32_t size) override + { + return ::rtlMalloc(size); + } + + virtual const char *resolveManifestPath(const char *leafName) override + { + if (leafName && *leafName) + { + ForEachItemIn(idx, manifestModules) + { + const char *path = manifestModules.item(idx); + if (endsWith(path, leafName)) + return path; + } + } + return nullptr; + } + }; + std::shared_ptr callbacks; + + class WasmEmbedContext : public CInterfaceOf + { + std::unique_ptr enclave; + + public: + WasmEmbedContext() + { + enclave = createISecureEnclave(); + } + virtual ~WasmEmbedContext() override + { + } + // IEmbedContext --- + virtual IEmbedFunctionContext *createFunctionContext(unsigned flags, const char *options) override + { + return createFunctionContextEx(nullptr, nullptr, flags, options); + } + virtual IEmbedFunctionContext *createFunctionContextEx(ICodeContext *ctx, const IThorActivityContext *activityContext, unsigned flags, const char *options) override + { + callbacks->manifestPaths(ctx); + return enclave.get(); + } + virtual IEmbedServiceContext *createServiceContext(const char *service, unsigned flags, const char *options) override + { + throwUnexpected(); + return nullptr; + } + }; + + extern DECL_EXPORT IEmbedContext *getEmbedContext() + { + return new WasmEmbedContext(); + } + + extern DECL_EXPORT void syntaxCheck(size32_t &__lenResult, char *&__result, const char *funcname, size32_t charsBody, const char *body, const char *argNames, const char *compilerOptions, const char *persistOptions) + { + StringBuffer result; + // MORE - ::syntaxCheck(__lenResult, __result, funcname, charsBody, body, argNames, compilerOptions, persistOptions); + __lenResult = result.length(); + __result = result.detach(); + } + +} // namespace + +MODULE_INIT(INIT_PRIORITY_STANDARD) +{ + wasmLanguageHelper::callbacks = std::make_shared(); + init(wasmLanguageHelper::callbacks); + return true; +} + +MODULE_EXIT() +{ + kill(); + wasmLanguageHelper::callbacks.reset(); +} \ No newline at end of file diff --git a/testing/regress/ecl/key/wasmembed.xml b/testing/regress/ecl/key/wasmembed.xml new file mode 100644 index 00000000000..5f862a9d026 --- /dev/null +++ b/testing/regress/ecl/key/wasmembed.xml @@ -0,0 +1,51 @@ + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + diff --git a/testing/regress/ecl/wasmembed.ecl b/testing/regress/ecl/wasmembed.ecl new file mode 100644 index 00000000000..143b04598aa --- /dev/null +++ b/testing/regress/ecl/wasmembed.ecl @@ -0,0 +1,34 @@ +import wasm; + +boolean boolTest (boolean a, boolean b) := IMPORT(wasm, 'wasmembed.bool-test'); +real4 float32Test (real4 a, real4 b) := IMPORT(wasm, 'wasmembed.float32-test'); +real8 float64Test (real8 a, real8 b) := IMPORT(wasm, 'wasmembed.float64-test'); +unsigned1 u8Test (unsigned1 a, unsigned1 b) := IMPORT(wasm, 'wasmembed.u8-test'); +unsigned2 u16Test (unsigned2 a, unsigned2 b) := IMPORT(wasm, 'wasmembed.u16-test'); +unsigned4 u32Test (unsigned4 a, unsigned4 b) := IMPORT(wasm, 'wasmembed.u32-test'); +unsigned8 u64Test (unsigned8 a, unsigned8 b) := IMPORT(wasm, 'wasmembed.u64-test'); +integer1 s8Test (integer1 a, integer1 b) := IMPORT(wasm, 'wasmembed.s8-test'); +integer2 s16Test (integer2 a, integer2 b) := IMPORT(wasm, 'wasmembed.s16-test'); +integer4 s32Test (integer4 a, integer4 b) := IMPORT(wasm, 'wasmembed.s32-test'); +integer8 s64Test (integer8 a, integer8 b) := IMPORT(wasm, 'wasmembed.s64-test'); +string stringTest (string a, string b) := IMPORT(wasm, 'wasmembed.string-test'); +string7 string5Test (string5 a, string5 b) := IMPORT(wasm, 'wasmembed.string-test'); +varstring varstringTest (varstring a, varstring b) := IMPORT(wasm, 'wasmembed.string-test'); + +boolTest(false, false) = (false AND false); +boolTest(false, true) = (false AND true); +boolTest(true, false) = (true AND false); +boolTest(true, true) = (true AND true); +float32Test(1.0, 2.0) = (1.0 + 2.0); +float64Test(1.0, 2.0) = (1.0 + 2.0); +u8Test(1, 2) = (1 + 2); +u16Test(1, 2) = (1 + 2); +u32Test(1, 2) = (1 + 2); +u64Test(1, 2) = (1 + 2); +s8Test(1, 2) = (1 + 2); +s16Test(1, 2) = (1 + 2); +s32Test(1, 2) = (1 + 2); +s64Test(1, 2) = (1 + 2); +stringTest('aaa', 'bbbb') = ('aaa' + 'bbbb'); +string5Test('aaa', 'bbbb') = (string7)((string5)'aaa' + (string5)'bbbb'); +varstringTest('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb') = ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'); diff --git a/testing/regress/ecl/wasmembed.manifest b/testing/regress/ecl/wasmembed.manifest new file mode 100644 index 00000000000..8a2d68eb5c2 --- /dev/null +++ b/testing/regress/ecl/wasmembed.manifest @@ -0,0 +1,3 @@ + + + diff --git a/testing/regress/ecl/wasmembed.wasm b/testing/regress/ecl/wasmembed.wasm new file mode 100644 index 0000000000000000000000000000000000000000..bb27b76cb859cda62519d509086d10ed6dffc8b9 GIT binary patch literal 53462 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 literal 0 HcmV?d00001 diff --git a/vcpkg.json.in b/vcpkg.json.in index 5a726abe692..ffb4cc02a02 100644 --- a/vcpkg.json.in +++ b/vcpkg.json.in @@ -170,6 +170,10 @@ "name": "tbb", "platform": "@VCPKG_TBB@" }, + { + "name": "wasmtime-cpp-api", + "platform": "@VCPKG_WASMEMBED@" + }, { "name": "winflexbison", "platform": "windows" diff --git a/vcpkg_overlays/wasmtime-c-api/portfile.cmake b/vcpkg_overlays/wasmtime-c-api/portfile.cmake new file mode 100644 index 00000000000..fa605e69608 --- /dev/null +++ b/vcpkg_overlays/wasmtime-c-api/portfile.cmake @@ -0,0 +1,37 @@ +if (WIN32) + vcpkg_download_distfile(ARCHIVE + URLS "https://github.com/bytecodealliance/wasmtime/releases/download/v${VERSION}/wasmtime-v${VERSION}-x86_64-windows-c-api.zip" + FILENAME "wasmtime-v${VERSION}-x86_64-windows-c-api.zip" + SHA512 419a11498f3853764e9285650ef12bbf597445f1e97980ab2b7634a28ad7c81c78502320be23ca4f25302d3408161a9a712ad4a088eb57052cfa21da885e369f + ) +elseif (APPLE) + vcpkg_download_distfile(ARCHIVE + URLS "https://github.com/bytecodealliance/wasmtime/releases/download/v${VERSION}/wasmtime-v${VERSION}-x86_64-macos-c-api.tar.xz" + FILENAME "wasmtime-v${VERSION}-x86_64-macos-c-api.tar.xz" + SHA512 aed34569c3063b44b5a7acda53b274851d2ef8597b7819221d1a550ee2c408b51315bba95ffd9f495feceab3c3825db9ef53c432bdf565cfacfe31c76328f795 + ) +elseif (LINUX) + vcpkg_download_distfile(ARCHIVE + URLS "https://github.com/bytecodealliance/wasmtime/releases/download/v${VERSION}/wasmtime-v${VERSION}-x86_64-linux-c-api.tar.xz" + FILENAME "wasmtime-v${VERSION}-x86_64-linux-c-api.tar.xz" + SHA512 ad590bceeb6b20520275f96a8cdb737792502f581d4ae3c152f9a6c2602090e5d6aa80f27c77b707e31169d12f6daf176cb7260d16af31b4a83afa7ed6991ded + ) +endif() + +vcpkg_extract_source_archive_ex( + OUT_SOURCE_PATH SOURCE_PATH + ARCHIVE ${ARCHIVE} +) + +file(COPY ${SOURCE_PATH}/include/. DESTINATION ${CURRENT_PACKAGES_DIR}/include/wasmtime-c-api) +if (WIN32) + file(COPY ${SOURCE_PATH}/lib/. DESTINATION ${CURRENT_PACKAGES_DIR}/debug/bin) + file(COPY ${SOURCE_PATH}/lib/. DESTINATION ${CURRENT_PACKAGES_DIR}/bin) +else () + file(COPY ${SOURCE_PATH}/lib/. DESTINATION ${CURRENT_PACKAGES_DIR}/debug/lib) + file(COPY ${SOURCE_PATH}/lib/. DESTINATION ${CURRENT_PACKAGES_DIR}/lib) +endif () + +# Handle copyright +file(INSTALL ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/wasmtime-c-api RENAME copyright) + diff --git a/vcpkg_overlays/wasmtime-c-api/vcpkg.json b/vcpkg_overlays/wasmtime-c-api/vcpkg.json new file mode 100644 index 00000000000..d3b68533a0a --- /dev/null +++ b/vcpkg_overlays/wasmtime-c-api/vcpkg.json @@ -0,0 +1,7 @@ +{ + "name": "wasmtime-c-api", + "version-string": "10.0.1", + "description": "Wasmtime is a standalone runtime for WebAssembly, using Cranelift to JIT-compile from WebAssembly to native code.", + "homepage": "https://wasmtime.dev/", + "license": "Apache-2.0" +} \ No newline at end of file diff --git a/vcpkg_overlays/wasmtime-cpp-api/portfile.cmake b/vcpkg_overlays/wasmtime-cpp-api/portfile.cmake new file mode 100644 index 00000000000..fd39c6bb1ce --- /dev/null +++ b/vcpkg_overlays/wasmtime-cpp-api/portfile.cmake @@ -0,0 +1,16 @@ +vcpkg_download_distfile(ARCHIVE + URLS "https://github.com/bytecodealliance/wasmtime-cpp/archive/refs/tags/v${VERSION}.tar.gz" + FILENAME "v${VERSION}.tar.gz" + SHA512 6440472084198572b2f00f455e100c9cc0f8a6c76f5f6278432756335f4a340e1af347d6a88ad2e06e0d22a5b84f240a210a9ecfcab1699c1b0fa21cedb8574d +) + +vcpkg_extract_source_archive_ex( + OUT_SOURCE_PATH SOURCE_PATH + ARCHIVE ${ARCHIVE} +) + +file(COPY ${SOURCE_PATH}/include/. DESTINATION ${CURRENT_PACKAGES_DIR}/include/wasmtime-cpp-api) + +# Handle copyright +file(INSTALL ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/wasmtime-cpp-api RENAME copyright) + diff --git a/vcpkg_overlays/wasmtime-cpp-api/vcpkg.json b/vcpkg_overlays/wasmtime-cpp-api/vcpkg.json new file mode 100644 index 00000000000..2838e70c667 --- /dev/null +++ b/vcpkg_overlays/wasmtime-cpp-api/vcpkg.json @@ -0,0 +1,10 @@ +{ + "name": "wasmtime-cpp-api", + "version-string": "9.0.0", + "description": "Wasmtime is a standalone runtime for WebAssembly, using Cranelift to JIT-compile from WebAssembly to native code.", + "homepage": "https://wasmtime.dev/", + "license": "Apache-2.0", + "dependencies": [ + "wasmtime-c-api" + ] +} \ No newline at end of file