From 2d1597aba5f98dd0da70eee437a9ebe5cc0966ab Mon Sep 17 00:00:00 2001 From: Gordon Smith Date: Wed, 11 Oct 2023 12:02:42 +0100 Subject: [PATCH] HPCC-29914 Add embedded wasm support Signed-off-by: Gordon Smith --- dockerfiles/wasm32-wasi/Dockerfile | 41 +++++ plugins/wasmembed/CMakeLists.txt | 3 +- plugins/wasmembed/abi.cpp | 128 +++++++-------- plugins/wasmembed/secure-enclave.cpp | 147 ++++++++---------- plugins/wasmembed/util.cpp | 46 ++---- testing/regress/ecl/wasmembed/CMakeLists.txt | 33 ++++ testing/regress/ecl/wasmembed/build.sh | 22 +++ .../ecl/wasmembed/hpcc-scalar-test.wit | 28 ++++ testing/regress/ecl/wasmembed/main.cpp | 70 +++++++++ 9 files changed, 347 insertions(+), 171 deletions(-) create mode 100644 dockerfiles/wasm32-wasi/Dockerfile create mode 100644 testing/regress/ecl/wasmembed/CMakeLists.txt create mode 100755 testing/regress/ecl/wasmembed/build.sh create mode 100644 testing/regress/ecl/wasmembed/hpcc-scalar-test.wit create mode 100644 testing/regress/ecl/wasmembed/main.cpp diff --git a/dockerfiles/wasm32-wasi/Dockerfile b/dockerfiles/wasm32-wasi/Dockerfile new file mode 100644 index 00000000000..5746ac1eb34 --- /dev/null +++ b/dockerfiles/wasm32-wasi/Dockerfile @@ -0,0 +1,41 @@ +FROM ubuntu:22.04 + +RUN apt-get update && \ + apt-get install -y \ + autoconf \ + autogen \ + automake \ + clang \ + cmake \ + curl \ + libtool \ + lld \ + llvm \ + make \ + ninja-build \ + wget + +RUN curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh -s -- -y + +SHELL ["/bin/bash", "--login", "-c"] + +WORKDIR /hpcc-dev + +ARG WIT_VERSION=0.9.0 +RUN cargo install wasm-tools && \ + cargo install --git https://github.com/bytecodealliance/wit-bindgen --tag wit-bindgen-cli-${WIT_VERSION} wit-bindgen-cli && \ + curl https://wasmtime.dev/install.sh -sSf | bash + +# List of current vertsion can be found in https://github.com/bytecodealliance/wit-bindgen/releases --- +ARG WASI_VERSION=20 +ARG WASI_MINOR_VERSION=0 +ARG WASI_VERSION_FULL=${WASI_VERSION}.${WASI_MINOR_VERSION} +RUN wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_VERSION}/wasi-sdk-${WASI_VERSION_FULL}-linux.tar.gz +RUN tar xvf wasi-sdk-${WASI_VERSION_FULL}-linux.tar.gz && rm wasi-sdk-${WASI_VERSION_FULL}-linux.tar.gz +RUN mv wasi-sdk-${WASI_VERSION_FULL} wasi-sdk + +WORKDIR /hpcc-dev/wasmembed + +ENTRYPOINT ["/bin/bash", "--login", "-c"] + +CMD ["bash"] diff --git a/plugins/wasmembed/CMakeLists.txt b/plugins/wasmembed/CMakeLists.txt index 9d83a3ee337..d8918058220 100644 --- a/plugins/wasmembed/CMakeLists.txt +++ b/plugins/wasmembed/CMakeLists.txt @@ -1,11 +1,10 @@ project(wasmembed) -set(CMAKE_CXX_STANDARD 20) - if(WASMEMBED) ADD_PLUGIN(wasmembed) if(MAKE_WASMEMBED) + set(CMAKE_CXX_STANDARD 20) find_path(WASMTIME_CPP_API_INCLUDE_DIRS "wasmtime-cpp-api/wasmtime.hh" PATHS ${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET} ) diff --git a/plugins/wasmembed/abi.cpp b/plugins/wasmembed/abi.cpp index 95b395a7fd4..ed81154ca52 100644 --- a/plugins/wasmembed/abi.cpp +++ b/plugins/wasmembed/abi.cpp @@ -12,7 +12,7 @@ #include #include -auto UTF16_TAG = 1 << 31; +auto UTF16_TAG = 1U << 31; // /* canonical despecialize (python) ------------------------------------------------------------- @@ -90,7 +90,12 @@ def align_to(ptr, alignment): uint32_t align_to(uint32_t ptr, uint32_t alignment) { - return std::ceil(ptr / alignment) * alignment; + return (ptr + alignment - 1) & ~(alignment - 1); +} + +bool isAligned(uint32_t ptr, uint32_t alignment) +{ + return (ptr & (alignment - 1)) == 0; } // loading --- @@ -120,18 +125,19 @@ def load_int(cx, ptr, nbytes, signed = False): template T load_int(const wasmtime::Span &data, int32_t ptr) { - T retVal = 0; - auto nbytes = sizeof(retVal); - for (int i = 0; i < nbytes; ++i) - { - uint8_t b = data[ptr + i]; - retVal += b << (i * 8); - } - if (std::is_signed::value) - { - retVal += (data[ptr + nbytes - 1] & 0x80) ? -1 << (8 * nbytes) : 0; - } - return retVal; + T retVal = 0; + auto nbytes = sizeof(retVal); + for (int i = 0; i < nbytes; ++i) + { + std::make_unsigned_t b = data[ptr + i]; + retVal += b << (i * 8); + } + if (std::is_signed::value && data[ptr + nbytes - 1] & 0x80) + { + std::make_unsigned_t b = -1; + retVal += b << (nbytes * 8); + } + return retVal; } /* canonical load_string_from_range (python) ------------------------------------------------------------- @@ -171,47 +177,47 @@ std::string global_encoding = "utf8"; std::pair 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) + 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) { - alignment = 1; - byte_length = tagged_code_units; - encoding = "utf-8"; + byte_length = 2 * (tagged_code_units ^ UTF16_TAG); + encoding = "utf-16-le"; } - else if (global_encoding.compare("utf16") == 0) + else { - 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"; - } + byte_length = tagged_code_units; + encoding = "latin-1"; } + } - if (ptr != align_to(ptr, alignment)) - { - throw makeStringException(3, "Invalid alignment"); - } + if (!isAligned(ptr, alignment)) + { + throw makeStringException(3, "Invalid alignment"); + } - if (ptr + byte_length > data.size()) - { - throw makeStringException(1, "Out of bounds"); - } + if (ptr + byte_length > data.size()) + { + throw makeStringException(1, "Out of bounds"); + } - return std::make_pair(ptr, byte_length); + return std::make_pair(ptr, byte_length); } /* canonical load_string (python) ------------------------------------------------------------- @@ -224,9 +230,9 @@ def load_string(cx, ptr): */ std::pair load_string(const wasmtime::Span &data, uint32_t ptr) { - uint32_t begin = load_int(data, ptr); - uint32_t tagged_code_units = load_int(data, ptr + 4); - return load_string_from_range(data, begin, tagged_code_units); + uint32_t begin = load_int(data, ptr); + uint32_t tagged_code_units = load_int(data, ptr + 4); + return load_string_from_range(data, begin, tagged_code_units); } /* canonical load_list_from_range (python) ------------------------------------------------------------- @@ -244,16 +250,16 @@ def load_list_from_range(cx, ptr, length, elem_type): template std::vector load_list_from_range(const wasmtime::Span &data, uint32_t ptr, uint32_t length) { - if (ptr != align_to(ptr, alignment(T{}))) - throw makeStringException(2, "Pointer is not aligned"); - if (ptr + length * sizeof(T) > data.size()) - throw makeStringException(1, "Out of bounds access"); - std::vector a; - for (uint32_t i = 0; i < length; i++) - { - a.push_back(load(data, ptr + i * sizeof(T))); - } - return a; + if (!isAligned(ptr, alignment(T{}))) + throw makeStringException(2, "Pointer is not aligned"); + if (ptr + length * sizeof(T) > data.size()) + throw makeStringException(1, "Out of bounds access"); + std::vector a; + for (uint32_t i = 0; i < length; i++) + { + a.push_back(load(data, ptr + i * sizeof(T))); + } + return a; } /* canonical load_list (python) ------------------------------------------------------------- diff --git a/plugins/wasmembed/secure-enclave.cpp b/plugins/wasmembed/secure-enclave.cpp index e68c2e54e3f..61ebd873b6f 100644 --- a/plugins/wasmembed/secure-enclave.cpp +++ b/plugins/wasmembed/secure-enclave.cpp @@ -17,9 +17,9 @@ #include #include -// #define ENABLE_TRACE +#define ENABLE_TRACE #ifdef ENABLE_TRACE -#define TRACE(format, ...) DBGLOG(format, ##__VA_ARGS__) +#define TRACE(format, ...) DBGLOG(format __VA_OPT__(, ) __VA_ARGS__) #else #define TRACE(format, ...) \ do \ @@ -40,61 +40,48 @@ class ThreadSafeMap void clear() { - TRACE("WASM SE ThreadSafeMap clear"); std::unique_lock lock(mutex); map.clear(); - TRACE("WASM SE ThreadSafeMap clear2"); } void insertIfMissing(const K &key, std::function &valueCallback) { - TRACE("WASM SE ThreadSafeMap insertIfMissing"); std::unique_lock lock(mutex); if (map.find(key) == map.end()) map.insert(std::make_pair(key, valueCallback())); - TRACE("WASM SE ThreadSafeMap insertIfMissing2"); } void erase(const K &key) { - TRACE("WASM SE ThreadSafeMap erase"); std::unique_lock lock(mutex); map.erase(key); - TRACE("WASM SE ThreadSafeMap erase2"); } bool find(const K &key, std::optional &value) const { - TRACE("WASM SE ThreadSafeMap find"); std::shared_lock lock(mutex); auto it = map.find(key); if (it != map.end()) { value = it->second; - TRACE("WASM SE ThreadSafeMap find2"); return true; } - TRACE("WASM SE ThreadSafeMap find3"); return false; } bool has(const K &key) const { - TRACE("WASM SE ThreadSafeMap has"); std::shared_lock lock(mutex); - TRACE("WASM SE ThreadSafeMap has2"); return map.find(key) != map.end(); } - void for_each(std::function func) const + void forEach(std::function func) const { - TRACE("WASM SE ThreadSafeMap for_each"); std::shared_lock lock(mutex); for (auto it = map.begin(); it != map.end(); ++it) { func(it->first, it->second); } - TRACE("WASM SE ThreadSafeMap for_each2"); } }; @@ -116,68 +103,70 @@ class WasmEngine // Do not call this function directly... wasmtime::Instance createInstance(const std::string &wasmName, const std::variant> &wasm) { - TRACE("WASM SE 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("WASM SE 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("WASM SE resolveModule3 %s", wasmName.c_str()); - - wasmtime::Linker linker(engine); - linker.define_wasi().unwrap(); - TRACE("WASM SE resolveModule4 %s", wasmName.c_str()); - - auto callback = [this, wasmName](wasmtime::Caller caller, uint32_t msg, uint32_t msg_len) + TRACE("WASM SE createInstance %s", wasmName.c_str()); + try { - TRACE("WASM SE callback: %i %i", msg_len, msg); + auto module = std::holds_alternative(wasm) ? wasmtime::Module::compile(engine, std::get(wasm)).unwrap() : wasmtime::Module::compile(engine, std::get>(wasm)).unwrap(); - auto data = this->getData(wasmName); - auto msg_ptr = (char *)&data[msg]; - std::string str(msg_ptr, msg_len); - DBGLOG("from wasm: %s", str.c_str()); - }; - auto host_func = linker.func_wrap("$root", "dbglog", callback).unwrap(); + 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(); - auto newInstance = linker.instantiate(store, module).unwrap(); - linker.define_instance(store, "linking2", newInstance).unwrap(); + wasmtime::Linker linker(engine); + linker.define_wasi().unwrap(); - for (auto exportItem : module.exports()) - { - auto externType = wasmtime::ExternType::from_export(exportItem); - std::string name(exportItem.name()); - if (std::holds_alternative(externType)) - { - TRACE("WASM SE 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("WASM SE 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("WASM SE Exported table: %s", name.c_str()); - } - else if (std::holds_alternative(externType)) + auto callback = [this, wasmName](wasmtime::Caller caller, uint32_t msg, uint32_t msg_len) { - TRACE("WASM SE Exported global: %s", name.c_str()); - } - else + auto data = this->getData(wasmName); + auto msg_ptr = (char *)&data[msg]; + std::string str(msg_ptr, msg_len); + 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(); + + for (auto exportItem : module.exports()) { - TRACE("WASM SE Unknown export type"); + auto externType = wasmtime::ExternType::from_export(exportItem); + std::string name(exportItem.name()); + if (std::holds_alternative(externType)) + { + TRACE("WASM SE 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("WASM SE 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("WASM SE Exported table: %s", name.c_str()); + } + else if (std::holds_alternative(externType)) + { + TRACE("WASM SE Exported global: %s", name.c_str()); + } + else + { + TRACE("WASM SE Unknown export type"); + } } - } - return newInstance; + return newInstance; + } + catch (const wasmtime::Error &e) + { + throw makeStringExceptionV(0, "WASM SE createInstance: %s", e.message().c_str()); + } } public: @@ -228,7 +217,6 @@ class WasmEngine auto found = wasmFuncs.find(qualifiedID); if (found == wasmFuncs.end()) throw makeStringExceptionV(2, "Wasm function not found: %s", qualifiedID.c_str()); - TRACE("WASM SE getFunc2"); return found->second; } @@ -252,9 +240,15 @@ class WasmEngine { TRACE("WASM SE call"); auto func = getFunc(qualifiedID); - auto retVal = func.call(store, params).unwrap(); - TRACE("WASM SE call 2"); - return retVal; + try + { + auto retVal = func.call(store, params).unwrap(); + return retVal; + } + catch (const wasmtime::Trap &e) + { + throw makeStringExceptionV(0, "WASM SE call: %s", e.message().c_str()); + } } std::vector callRealloc(const std::string &wasmName, const std::vector ¶ms) @@ -293,10 +287,8 @@ class SecureFunction : public CInterfaceOf manifestModules.appendArray(_manifestModules); if (!wasmEngine) { - TRACE("WASM SE se:constructor2"); wasmEngine = std::make_unique(); } - TRACE("WASM SE se:constructor3"); } virtual ~SecureFunction() @@ -306,7 +298,6 @@ class SecureFunction : public CInterfaceOf // Garbage Collection --- // Function results --- auto gc_func_name = createQualifiedID(wasmName, "cabi_post_" + funcName); - TRACE("WASM SE se:destructor %s", gc_func_name.c_str()); if (wasmEngine->hasFunc(gc_func_name)) { for (auto &result : wasmResults) @@ -551,7 +542,7 @@ class SecureFunction : public CInterfaceOf uint32_t strPtr; uint32_t bytes; std::tie(strPtr, bytes) = load_string(data, ptr); - rtlStrToStrX(chars, result, bytes, reinterpret_cast(&data[strPtr])); + rtlUtf8ToStrX(chars, result, bytes, reinterpret_cast(&data[strPtr])); } virtual void getUTF8Result(size32_t &chars, char *&result) { diff --git a/plugins/wasmembed/util.cpp b/plugins/wasmembed/util.cpp index c130f924dc0..6a6f0f2c5bf 100644 --- a/plugins/wasmembed/util.cpp +++ b/plugins/wasmembed/util.cpp @@ -1,27 +1,21 @@ #include "util.hpp" #include +#include "jfile.hpp" #include #include std::vector readWasmBinaryToBuffer(const std::string &filename) { - std::ifstream file(filename, std::ios::binary | std::ios::ate); - if (!file) - { - throw makeStringException(0, "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 makeStringException(1, "Failed to read file"); - } - - return buffer; + Owned file = createIFile(filename.c_str()); + Owned fileIO = file->open(IFOread); + if (!fileIO) + throw makeStringExceptionV(0, "Failed to open %s", filename.c_str()); + + MemoryBuffer mb; + size32_t count = read(fileIO, 0, (size32_t)-1, mb); + uint8_t *ptr = (uint8_t *)mb.detach(); + return std::vector(ptr, ptr + count); } std::string extractContentInDoubleQuotes(const std::string &input) @@ -32,7 +26,7 @@ std::string extractContentInDoubleQuotes(const std::string &input) return ""; std::size_t secondQuote = input.find('"', firstQuote + 1); - if (firstQuote == secondQuote == std::string::npos) + if (secondQuote == std::string::npos) return ""; return input.substr(firstQuote + 1, secondQuote - firstQuote - 1); @@ -40,19 +34,11 @@ std::string extractContentInDoubleQuotes(const std::string &input) 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 makeStringExceptionV(3, "Invalid import function %s, expected format: .", qualifiedName.c_str()); - } - return std::make_pair(tokens[0], tokens[1]); + std::size_t firstDot = qualifiedName.find_first_of('.'); + if (firstDot == std::string::npos || firstDot == 0 || firstDot == qualifiedName.size() - 1) + throw makeStringExceptionV(3, "Invalid import function '%s', expected format: .", qualifiedName.c_str()); + + return std::make_pair(qualifiedName.substr(0, firstDot), qualifiedName.substr(firstDot + 1)); } std::string createQualifiedID(const std::string &wasmName, const std::string &funcName) diff --git a/testing/regress/ecl/wasmembed/CMakeLists.txt b/testing/regress/ecl/wasmembed/CMakeLists.txt new file mode 100644 index 00000000000..822963e1126 --- /dev/null +++ b/testing/regress/ecl/wasmembed/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.22) + +project(wasmembed) + +set(WASM_PATH "${CMAKE_CURRENT_BINARY_DIR}/bin/${PROJECT_NAME}.wasm") + +add_custom_command( + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/hpcc-scalar-test.wit + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/hpcc_scalar_test.c ${CMAKE_CURRENT_BINARY_DIR}/hpcc_scalar_test.h ${CMAKE_CURRENT_BINARY_DIR}/hpcc_scalar_test_component_type.o + COMMAND wit-bindgen c --out-dir ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/hpcc-scalar-test.wit +) +add_custom_target(wit-generate ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/hpcc_scalar_test.c) + +set(CMAKE_EXECUTABLE_SUFFIX ".wasm") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -nostartfiles -fno-exceptions --sysroot=/${WASI_SDK_PREFIX}/share/wasi-sysroot -Wl,--no-entry") + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} +) + +add_executable(wasmembed + main.cpp + ${CMAKE_CURRENT_BINARY_DIR}/hpcc_scalar_test.c + ${CMAKE_CURRENT_BINARY_DIR}/hpcc_scalar_test.h +) + +target_link_libraries(wasmembed + ${CMAKE_CURRENT_BINARY_DIR}/hpcc_scalar_test_component_type.o +) + +install(TARGETS wasmembed + RUNTIME DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/.. +) diff --git a/testing/regress/ecl/wasmembed/build.sh b/testing/regress/ecl/wasmembed/build.sh new file mode 100755 index 00000000000..9517c3fd8d0 --- /dev/null +++ b/testing/regress/ecl/wasmembed/build.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )" +ROOT_DIR="$SCRIPT_DIR/../../../../" + +echo "SCRIPT_DIR: $SCRIPT_DIR" +echo "ROOT_DIR: $ROOT_DIR" + +docker build --progress plain -f "$ROOT_DIR/dockerfiles/wasm32-wasi/Dockerfile" \ + -t wasm32-wasi:latest \ + "$SCRIPT_DIR/." + +CMAKE_OPTIONS="-G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=/hpcc-dev/wasi-sdk/share/cmake/wasi-sdk.cmake -DWASI_SDK_PREFIX=/hpcc-dev/wasi-sdk" + +docker run --rm \ + --mount source="$SCRIPT_DIR",target=/hpcc-dev/wasmembed,type=bind,consistency=cached \ + wasm32-wasi:latest \ + "rm -rf ./build && \ + cmake -S . -B ${ROOT_DIR}/build-wasmembed ${CMAKE_OPTIONS} && \ + cmake --build ${ROOT_DIR}/build-wasmembed --target install" + +echo "docker run -it --mount source=\"$SCRIPT_DIR\",target=/hpcc-dev/wasmembed,type=bind,consistency=cached wasm32-wasi:latest bash" diff --git a/testing/regress/ecl/wasmembed/hpcc-scalar-test.wit b/testing/regress/ecl/wasmembed/hpcc-scalar-test.wit new file mode 100644 index 00000000000..3b632d46ea1 --- /dev/null +++ b/testing/regress/ecl/wasmembed/hpcc-scalar-test.wit @@ -0,0 +1,28 @@ +package hpcc-systems:hpcc-platform + +world hpcc-scalar-test { +/* imports --- + + guests dispose all params as needed + guests should dispose "results" as needed +*/ + import dbglog: func(msg: string) + +/* exports --- + + guests dispose all params as needed + hosts call cabi_post_XXX to dispose "results" as needed +*/ + export bool-test: func(a: bool, b: bool) -> bool + export float32-test: func(a: float32, b: float32) -> float32 + export float64-test: func(a: float64, b: float64) -> float64 + export u8-test: func(a: u8, b: u8) -> u8 + export u16-test: func(a: u16, b: u16) -> u16 + export u32-test: func(a: u32, b: u32) -> u32 + export u64-test: func(a: u64, b: u64) -> u64 + export s8-test: func(a: s8, b: s8) -> s8 + export s16-test: func(a: s16, b: s16) -> s16 + export s32-test: func(a: s32, b: s32) -> s32 + export s64-test: func(a: s64, b: s64) -> s64 + export utf8-string-test: func(a: string, b: string) -> string +} diff --git a/testing/regress/ecl/wasmembed/main.cpp b/testing/regress/ecl/wasmembed/main.cpp new file mode 100644 index 00000000000..c0d7420e5f1 --- /dev/null +++ b/testing/regress/ecl/wasmembed/main.cpp @@ -0,0 +1,70 @@ +#include "hpcc_scalar_test.h" + +#include + +void dbglog(const std::string str) +{ + hpcc_scalar_test_string_t msg; + hpcc_scalar_test_string_set(&msg, str.c_str()); + hpcc_scalar_test_dbglog(&msg); +} + +bool hpcc_scalar_test_bool_test(bool a, bool b) +{ + return a && b; +} +float hpcc_scalar_test_float32_test(float a, float b) +{ + return a + b; +} +double hpcc_scalar_test_float64_test(double a, double b) +{ + return a + b; +} +uint8_t hpcc_scalar_test_u8_test(uint8_t a, uint8_t b) +{ + return a + b; +} +uint16_t hpcc_scalar_test_u16_test(uint16_t a, uint16_t b) +{ + return a + b; +} +uint32_t hpcc_scalar_test_u32_test(uint32_t a, uint32_t b) +{ + return a + b; +} +uint64_t hpcc_scalar_test_u64_test(uint64_t a, uint64_t b) +{ + return a + b; +} +int8_t hpcc_scalar_test_s8_test(int8_t a, int8_t b) +{ + return a + b; +} +int16_t hpcc_scalar_test_s16_test(int16_t a, int16_t b) +{ + return a + b; +} +int32_t hpcc_scalar_test_s32_test(int32_t a, int32_t b) +{ + return a + b; +} +int64_t hpcc_scalar_test_s64_test(int64_t a, int64_t b) +{ + return a + b; +} +uint32_t hpcc_scalar_test_char_test(uint32_t a, uint32_t b) +{ + return a + b; +} +static uint32_t tally = 0; +void hpcc_scalar_test_utf8_string_test(hpcc_scalar_test_string_t *a, hpcc_scalar_test_string_t *b, hpcc_scalar_test_string_t *ret) +{ + std::string s1(a->ptr, a->len); + hpcc_scalar_test_string_free(a); + std::string s2(b->ptr, b->len); + hpcc_scalar_test_string_free(b); + std::string r = s1 + s2; + dbglog(std::to_string(++tally) + ": " + r); + hpcc_scalar_test_string_dup(ret, r.c_str()); +}