From a6fb8fc4878820be2ab0ce9139d055e9befd0044 Mon Sep 17 00:00:00 2001 From: Adam Laszlo Kulcsar Date: Mon, 16 Oct 2023 14:22:37 +0200 Subject: [PATCH] Implement uvwasi library and improve WASI structure Introduce uvwasi into the build system. Fix build issues with libuv integration. Introduce class WasiFunction to enable WASI to acces Instance resources. Implement further WASI types and fd_write function. Signed-off-by: Adam Laszlo Kulcsar --- .github/workflows/actions.yml | 4 +- .gitmodules | 3 + build/walrus.cmake | 8 +- src/runtime/Function.cpp | 45 ++++++++++ src/runtime/Function.h | 48 +++++++++++ src/runtime/Module.cpp | 7 +- src/runtime/SpecTest.h | 21 +++++ src/shell/Shell.cpp | 20 ++--- src/wasi/Fd.h | 46 +++++++++++ src/wasi/Wasi.cpp | 36 +++++++- src/wasi/Wasi.h | 110 +++++++++++++++++++++++-- third_party/uvwasi | 1 + third_party/wabt/include/wabt/common.h | 5 ++ third_party/wabt/src/binary-reader.cc | 3 +- 14 files changed, 336 insertions(+), 21 deletions(-) create mode 100644 src/wasi/Fd.h create mode 160000 third_party/uvwasi diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 3034d0333..63a0f94e3 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -133,7 +133,7 @@ jobs: install: | apt-get update - apt-get install -y cmake build-essential ninja-build pkg-config python3 clang-12 + apt-get install -y cmake build-essential ninja-build pkg-config python3 clang-12 git #FIXME fix clang version as to 12 ln -s /usr/bin/clang-12 /usr/bin/clang ln -s /usr/bin/clang++-12 /usr/bin/clang++ @@ -163,7 +163,7 @@ jobs: install: | apt-get update - apt-get install -y cmake build-essential ninja-build pkg-config python3 clang-12 + apt-get install -y cmake build-essential ninja-build pkg-config python3 clang-12 git #FIXME fix clang version as to 12 ln -s /usr/bin/clang-12 /usr/bin/clang ln -s /usr/bin/clang++-12 /usr/bin/clang++ diff --git a/.gitmodules b/.gitmodules index f4fda49b2..9bc891090 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = third_party/wasm-c-api url = https://github.com/WebAssembly/wasm-c-api ignore = untracked +[submodule "third_party/uvwasi"] + path = third_party/uvwasi + url = https://github.com/kulcsaradam/uvwasi.git diff --git a/build/walrus.cmake b/build/walrus.cmake index 939734451..555afdcb2 100644 --- a/build/walrus.cmake +++ b/build/walrus.cmake @@ -64,7 +64,13 @@ ENDIF() SET (WABT_DEFINITIONS ${WALRUS_DEFINITIONS}) SET (WITH_EXCEPTIONS TRUE) ADD_SUBDIRECTORY (third_party/wabt) -SET (WALRUS_LIBRARIES ${WALRUS_LIBRARIES} wabt) + +# uvwasi +ADD_SUBDIRECTORY(third_party/uvwasi) +INCLUDE_DIRECTORIES (${WALRUS_INCDIRS} PRIVATE ${WALRUS_THIRD_PARTY_ROOT}/uvwasi/include) +INCLUDE_DIRECTORIES (${WALRUS_INCDIRS} PRIVATE ${CMAKE_BINARY_DIR}/_deps/libuv-src/include) + +SET (WALRUS_LIBRARIES ${WALRUS_LIBRARIES} wabt uvwasi_a) # BUILD INCLUDE_DIRECTORIES (${WALRUS_INCDIRS}) diff --git a/src/runtime/Function.cpp b/src/runtime/Function.cpp index a225f7565..663e00914 100644 --- a/src/runtime/Function.cpp +++ b/src/runtime/Function.cpp @@ -149,4 +149,49 @@ void ImportedFunction::call(ExecutionState& state, Value* argv, Value* result) m_callback(newState, argv, result, m_data); } +WasiFunction* WasiFunction::createWasiFunction(Store* store, + FunctionType* functionType, + WasiFunctionCallback callback, + Instance* instance) +{ + WasiFunction* func = new WasiFunction(functionType, + callback, + instance); + store->appendExtern(func); + return func; +} + +void WasiFunction::interpreterCall(ExecutionState& state, uint8_t* bp, ByteCodeStackOffset* offsets, + uint16_t parameterOffsetCount, uint16_t resultOffsetCount) +{ + const FunctionType* ft = functionType(); + const ValueTypeVector& paramTypeInfo = ft->param(); + const ValueTypeVector& resultTypeInfo = ft->result(); + + ALLOCA(Value, paramVector, sizeof(Value) * paramTypeInfo.size()); + ALLOCA(Value, resultVector, sizeof(Value) * resultTypeInfo.size()); + + size_t offsetIndex = 0; + size_t size = paramTypeInfo.size(); + Value* paramVectorStart = paramVector; + for (size_t i = 0; i < size; i++) { + paramVector[i] = Value(paramTypeInfo[i], bp + offsets[offsetIndex]); + offsetIndex += valueFunctionCopyCount(paramTypeInfo[i]); + } + + call(state, paramVectorStart, resultVector); + + for (size_t i = 0; i < resultTypeInfo.size(); i++) { + resultVector[i].writeToMemory(bp + offsets[offsetIndex]); + offsetIndex += valueFunctionCopyCount(resultTypeInfo[i]); + } +} + +void WasiFunction::call(ExecutionState& state, Value* argv, Value* result) +{ + ExecutionState newState(state, this); + CHECK_STACK_LIMIT(newState); + m_callback(newState, argv, result, this->m_runningInstance); +} + } // namespace Walrus diff --git a/src/runtime/Function.h b/src/runtime/Function.h index c4d8ab4ed..f54e620a5 100644 --- a/src/runtime/Function.h +++ b/src/runtime/Function.h @@ -43,6 +43,7 @@ class FunctionType; class ModuleFunction; class DefinedFunction; class ImportedFunction; +class WasiFunction; class Function : public Extern { public: @@ -71,6 +72,10 @@ class Function : public Extern { { return false; } + virtual bool isWasiFunction() const + { + return false; + } DefinedFunction* asDefinedFunction() { @@ -84,6 +89,12 @@ class Function : public Extern { return reinterpret_cast(this); } + WasiFunction* asWasiFunction() + { + assert(isWasiFunction()); + return reinterpret_cast(this); + } + protected: Function(FunctionType* functionType) : m_functionType(functionType) @@ -168,6 +179,43 @@ class ImportedFunction : public Function { void* m_data; }; +class WasiFunction : public Function { +public: + typedef std::function WasiFunctionCallback; + + static WasiFunction* createWasiFunction(Store* store, + FunctionType* functionType, + WasiFunctionCallback callback, + Instance* instance); + + virtual bool isWasiFunction() const override + { + return true; + } + + void setRunningInstance(Instance* instance) + { + m_runningInstance = instance; + } + + virtual void call(ExecutionState& state, Value* argv, Value* result) override; + virtual void interpreterCall(ExecutionState& state, uint8_t* bp, ByteCodeStackOffset* offsets, + uint16_t parameterOffsetCount, uint16_t resultOffsetCount) override; + +protected: + WasiFunction(FunctionType* functionType, + WasiFunctionCallback callback, + Instance* instance) + : Function(functionType) + , m_callback(callback) + , m_runningInstance(instance) + { + } + + WasiFunctionCallback m_callback; + Instance* m_runningInstance; +}; + } // namespace Walrus #endif // __WalrusFunction__ diff --git a/src/runtime/Module.cpp b/src/runtime/Module.cpp index 9c24b8e1c..07760db39 100644 --- a/src/runtime/Module.cpp +++ b/src/runtime/Module.cpp @@ -27,6 +27,7 @@ #include "interpreter/ByteCode.h" #include "interpreter/Interpreter.h" #include "parser/WASMParser.h" +#include "wasi/Wasi.h" namespace Walrus { @@ -137,7 +138,11 @@ Instance* Module::instantiate(ExecutionState& state, const ExternVector& imports if (!imports[i]->asFunction()->functionType()->equals(m_imports[i]->functionType())) { Trap::throwException(state, "imported function type mismatch"); } - instance->m_functions[funcIndex++] = imports[i]->asFunction(); + instance->m_functions[funcIndex] = imports[i]->asFunction(); + if (imports[i]->asFunction()->isWasiFunction()) { + instance->m_functions[funcIndex]->asWasiFunction()->setRunningInstance(instance); + } + funcIndex++; break; } case ImportType::Global: { diff --git a/src/runtime/SpecTest.h b/src/runtime/SpecTest.h index 65062014d..94193a401 100644 --- a/src/runtime/SpecTest.h +++ b/src/runtime/SpecTest.h @@ -26,6 +26,8 @@ class SpecTestFunctionTypes { // The R is meant to represent the results, after R are the result types. NONE = 0, I32R, + I32_RI32, + I32I32I32I32_RI32, RI32, I64R, F32R, @@ -57,6 +59,25 @@ class SpecTestFunctionTypes { param->push_back(Value::Type::I32); m_vector[index++] = new FunctionType(param, result); } + { + // I32_RI32 + param = new ValueTypeVector(); + result = new ValueTypeVector(); + param->push_back(Value::Type::I32); + result->push_back(Value::Type::I32); + m_vector[index++] = new FunctionType(param, result); + } + { + // I32I32I32I32_RI32 + param = new ValueTypeVector(); + result = new ValueTypeVector(); + param->push_back(Value::Type::I32); + param->push_back(Value::Type::I32); + param->push_back(Value::Type::I32); + param->push_back(Value::Type::I32); + result->push_back(Value::Type::I32); + m_vector[index++] = new FunctionType(param, result); + } { // RI32 param = new ValueTypeVector(); diff --git a/src/shell/Shell.cpp b/src/shell/Shell.cpp index 34cc33a65..c35f8f185 100644 --- a/src/shell/Shell.cpp +++ b/src/shell/Shell.cpp @@ -107,7 +107,7 @@ static void printF64(double v) printf("%s : f64\n", formatDecmialString(ss.str()).c_str()); } -static Trap::TrapResult executeWASM(Store* store, const std::string& filename, const std::vector& src, SpecTestFunctionTypes& functionTypes, WASI* wasi, +static Trap::TrapResult executeWASM(Store* store, const std::string& filename, const std::vector& src, SpecTestFunctionTypes& functionTypes, std::map* registeredInstanceMap = nullptr) { auto parseResult = WASMParser::parseBinary(store, filename, src.data(), src.size()); @@ -231,11 +231,11 @@ static Trap::TrapResult executeWASM(Store* store, const std::string& filename, c nullptr)); } } else if (import->moduleName() == "wasi_snapshot_preview1") { - Walrus::WASI::WasiFunc* wasiImportFunc = wasi->find(import->fieldName()); + Walrus::WASI::WasiFunc* wasiImportFunc = WASI::find(import->fieldName()); if (wasiImportFunc != nullptr) { FunctionType* fn = functionTypes[wasiImportFunc->functionType]; if (fn->equals(import->functionType())) { - importValues.push_back(ImportedFunction::createImportedFunction( + importValues.push_back(WasiFunction::createWasiFunction( store, const_cast(import->functionType()), wasiImportFunc->ptr, @@ -639,7 +639,7 @@ static Instance* fetchInstance(wabt::Var& moduleVar, std::map return registeredInstanceMap[moduleVar.name()]; } -static void executeWAST(Store* store, const std::string& filename, const std::vector& src, SpecTestFunctionTypes& functionTypes, WASI* wasi) +static void executeWAST(Store* store, const std::string& filename, const std::vector& src, SpecTestFunctionTypes& functionTypes) { auto lexer = wabt::WastLexer::CreateBufferLexer("test.wabt", src.data(), src.size()); if (lexer == nullptr) { @@ -671,7 +671,7 @@ static void executeWAST(Store* store, const std::string& filename, const std::ve case wabt::CommandType::ScriptModule: { auto* moduleCommand = static_cast(command.get()); auto buf = readModuleData(&moduleCommand->module); - auto trapResult = executeWASM(store, filename, buf->data, functionTypes, wasi, ®isteredInstanceMap); + auto trapResult = executeWASM(store, filename, buf->data, functionTypes, ®isteredInstanceMap); if (trapResult.exception) { std::string& errorMessage = trapResult.exception->message(); printf("Error: %s\n", errorMessage.c_str()); @@ -752,7 +752,7 @@ static void executeWAST(Store* store, const std::string& filename, const std::ve RELEASE_ASSERT_NOT_REACHED(); } auto buf = readModuleData(&tsm->module); - auto trapResult = executeWASM(store, filename, buf->data, functionTypes, wasi, ®isteredInstanceMap); + auto trapResult = executeWASM(store, filename, buf->data, functionTypes, ®isteredInstanceMap); RELEASE_ASSERT(trapResult.exception); std::string& s = trapResult.exception->message(); if (s.find(assertModuleUninstantiable->text) != 0) { @@ -800,7 +800,7 @@ static void executeWAST(Store* store, const std::string& filename, const std::ve } else { buf = dsm->data; } - auto trapResult = executeWASM(store, filename, buf, functionTypes, wasi); + auto trapResult = executeWASM(store, filename, buf, functionTypes); if (trapResult.exception == nullptr) { printf("Execute WASM returned nullptr (in wabt::CommandType::AssertInvalid case)\n"); printf("Expected exception:%s\n", assertModuleInvalid->text.data()); @@ -831,7 +831,7 @@ static void executeWAST(Store* store, const std::string& filename, const std::ve } else { buf = dsm->data; } - auto trapResult = executeWASM(store, filename, buf, functionTypes, wasi); + auto trapResult = executeWASM(store, filename, buf, functionTypes); if (trapResult.exception == nullptr) { printf("Execute WASM returned nullptr (in wabt::CommandType::AssertUnlinkable case)\n"); printf("Expected exception:%s\n", assertUnlinkable->text.data()); @@ -1018,14 +1018,14 @@ int main(int argc, char* argv[]) if (!argParser.exportToRun.empty()) { runExports(store, filePath, buf, argParser.exportToRun); } else { - auto trapResult = executeWASM(store, filePath, buf, functionTypes, wasi); + auto trapResult = executeWASM(store, filePath, buf, functionTypes); if (trapResult.exception) { fprintf(stderr, "Uncaught Exception: %s\n", trapResult.exception->message().data()); return -1; } } } else if (endsWith(filePath, "wat") || endsWith(filePath, "wast")) { - executeWAST(store, filePath, buf, functionTypes, wasi); + executeWAST(store, filePath, buf, functionTypes); } } else { printf("Cannot open file %s\n", filePath.data()); diff --git a/src/wasi/Fd.h b/src/wasi/Fd.h new file mode 100644 index 000000000..63c154aee --- /dev/null +++ b/src/wasi/Fd.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023-present Samsung Electronics Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Walrus { + +void WASI::fd_write(ExecutionState& state, Value* argv, Value* result, Instance* instance) +{ + int32_t fd = argv[0].asI32(); + int32_t iovptr = argv[1].asI32(); + int32_t iovcnt = argv[2].asI32(); + WASI::wasi_iovec_t wasi_iovs; + + if (!WASI::checkMemOffset(instance->memory(0), iovptr, iovcnt)) { + result[0] = Value(static_cast(WASI::wasi_errno::inval)); + result[1] = Value(static_cast(0)); + return; + } + + wasi_iovs.buf = reinterpret_cast(iovptr); + wasi_iovs.len = iovcnt; + + std::vector iovs(iovcnt); + for (int i = 0; i < iovcnt; i++) { + iovs[i].buf_len = wasi_iovs.len; + iovs[0].buf = wasi_iovs.buf; + } + + uvwasi_size_t out_addr; + result[0] = Value(static_cast(uvwasi_fd_write(WASI::m_uvwasi, fd, iovs.data(), iovs.size(), &out_addr))); + result[1] = Value(static_cast(out_addr)); +} + +} // namespace Walrus diff --git a/src/wasi/Wasi.cpp b/src/wasi/Wasi.cpp index 48a60a028..7dc7b73df 100644 --- a/src/wasi/Wasi.cpp +++ b/src/wasi/Wasi.cpp @@ -15,11 +15,15 @@ */ #include "wasi/Wasi.h" +#include "wasi/Fd.h" // https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md namespace Walrus { +WASI::WasiFunc WASI::m_wasiFunctions[FuncEnd]; +uvwasi_t* WASI::m_uvwasi; + WASI::WasiFunc* WASI::find(std::string funcName) { for (unsigned i = 0; i < WasiFuncName::FuncEnd; ++i) { @@ -30,7 +34,30 @@ WASI::WasiFunc* WASI::find(std::string funcName) return nullptr; } -void WASI::proc_exit(ExecutionState& state, Value* argv, Value* result, void* data) +bool WASI::checkStr(Memory* memory, WASI::wasi_size_t memoryOffset, std::string& str) +{ + for (uint32_t i = memoryOffset; i < memory->sizeInByte(); ++i) { + if (memoryOffset >= memory->sizeInByte()) { + return false; + } else if (*reinterpret_cast(memory->buffer() + memoryOffset + i) == '\0') { + str = std::string(reinterpret_cast(memory->buffer() + memoryOffset)); + break; + } + } + + return true; +} + +bool WASI::checkMemOffset(Memory* memory, WASI::wasi_size_t memoryOffset, WASI::wasi_size_t length) +{ + if (memoryOffset + length >= memory->sizeInByte()) { + return false; + } + + return true; +} + +void WASI::proc_exit(ExecutionState& state, Value* argv, Value* result, Instance* instance) { ASSERT(argv[0].type() == Value::I32); exit(argv[0].asI32()); @@ -50,6 +77,13 @@ void WASI::fillWasiFuncTable() WASI::WASI() { fillWasiFuncTable(); + + ::uvwasi_options_t init_options; + init_options.in = 0; + init_options.out = 1; + init_options.err = 2; + + ::uvwasi_init(m_uvwasi, &init_options); } } // namespace Walrus diff --git a/src/wasi/Wasi.h b/src/wasi/Wasi.h index 78a5857da..5b380c3ea 100644 --- a/src/wasi/Wasi.h +++ b/src/wasi/Wasi.h @@ -19,6 +19,9 @@ #include "runtime/Function.h" #include "runtime/ObjectType.h" #include "runtime/SpecTest.h" +#include "runtime/Memory.h" +#include "runtime/Instance.h" +#include namespace Walrus { @@ -112,22 +115,115 @@ class WASI { } wasi_errno_t; #undef TO_ENUM + typedef uint32_t wasi_size_t; + + typedef uint32_t wasi_fd_t; + + typedef enum wasi_oflags : uint16_t { + oflag_create, + oflag_directory, + oflag_excl, + oflag_trunc, + } wasi_oflags_t; + + typedef enum wasi_lookupflags : uint32_t { + symlink_follow, + } wasi_lookupflags_t; + + typedef enum wasi_rights : uint64_t { + right_fd_datasync = (0 << 1), + right_fd_read, + right_fd_seek, + right_fd_fdstat_set_flags, + right_fd_sync, + right_fd_tell, + right_fd_write, + right_fd_allocate, + right_path_create_directory, + right_path_create_file, + right_path_link_source, + right_path_link_target, + right_path_open, + right_fd_readdir, + right_path_readlink, + right_path_rename_source, + right_path_rename_target, + right_path_filestat_get, + right_path_filestat_set_size, + right_path_filesta_set_time, + right_fd_filestat_get, + right_fd_filestat_set_size, + right_fd_filestat_set_times, + right_path_symlink, + right_path_remove_directory, + right_poll_fd_readwrite, + right_sock_shutdown, + right_sock_accept, + } wasi_rights_t; + + typedef enum wasi_fdflags : uint16_t { + append, + dsync, + nonblock, + rsync, + sync, + } wasi_fdflags_t; + + typedef uint64_t wasi_device_t; + + typedef uint64_t wasi_inode_t; + + typedef enum wasi_filetype { + filetype_unknown, + filetype_block_device, + filetype_character_device, + filetype_directory, + filetype_regular_file, + filetype_socket_dgram, + filetype_socket_stream, + filetype_symbolic_link + } wasi_filetype_t; + + typedef uint64_t wasi_linkcount_t; + + typedef uint64_t wasi_filesize_t; + + typedef uint64_t wasi_timestamp_t; + + typedef struct wasi_filestat { + wasi_device_t dev; + wasi_inode_t ino; + wasi_filetype_t filetype; + wasi_linkcount_t nlink; + wasi_filesize_t size; + wasi_timestamp_t atim; + wasi_timestamp_t mtim; + wasi_timestamp_t ctim; + } wasi_filestat_t; + + typedef struct wasi_iovec { + uint8_t* buf; + wasi_size_t len; + } wasi_iovec_t; + // end of type definitions WASI(); ~WASI() { + ::uvwasi_destroy(m_uvwasi); } struct WasiFunc { std::string name; SpecTestFunctionTypes::Index functionType; - ImportedFunction::ImportedFunctionCallback ptr; + WasiFunction::WasiFunctionCallback ptr; }; #define FOR_EACH_WASI_FUNC(F) \ - F(proc_exit, I32R) + F(proc_exit, I32R) \ + F(fd_write, I32I32I32I32_RI32) enum WasiFuncName : size_t { #define DECLARE_FUNCTION(NAME, FUNCTYPE) NAME##FUNC, @@ -137,11 +233,15 @@ class WASI { }; void fillWasiFuncTable(); - WasiFunc* find(std::string funcName); + static WasiFunc* find(std::string funcName); + static bool checkStr(Memory* memory, WASI::wasi_size_t memoryOffset, std::string& str); + static bool checkMemOffset(Memory* memory, WASI::wasi_size_t memoryOffset, WASI::wasi_size_t length); - static void proc_exit(ExecutionState& state, Value* argv, Value* result, void* data); + static void proc_exit(ExecutionState& state, Value* argv, Value* result, Instance* instance); + static void fd_write(ExecutionState& state, Value* argv, Value* result, Instance* instance); - WasiFunc m_wasiFunctions[FuncEnd]; + static WasiFunc m_wasiFunctions[FuncEnd]; + static uvwasi_t* m_uvwasi; }; } // namespace Walrus diff --git a/third_party/uvwasi b/third_party/uvwasi new file mode 160000 index 000000000..de39e5f0e --- /dev/null +++ b/third_party/uvwasi @@ -0,0 +1 @@ +Subproject commit de39e5f0e926afcf2fa8b877053a81f5ae0df99f diff --git a/third_party/wabt/include/wabt/common.h b/third_party/wabt/include/wabt/common.h index a0ca2ea67..ef1610676 100644 --- a/third_party/wabt/include/wabt/common.h +++ b/third_party/wabt/include/wabt/common.h @@ -173,11 +173,16 @@ Dst WABT_VECTORCALL Bitcast(Src&& value) { return result; } +/* +This function caused build problems on windows, +probably because libuv implements a function with the same name. + template void ZeroMemory(T& v) { WABT_STATIC_ASSERT(std::is_pod::value); memset(&v, 0, sizeof(v)); } +*/ // Placement construct template diff --git a/third_party/wabt/src/binary-reader.cc b/third_party/wabt/src/binary-reader.cc index 4c3ebd8c7..ab5f82a1a 100644 --- a/third_party/wabt/src/binary-reader.cc +++ b/third_party/wabt/src/binary-reader.cc @@ -873,7 +873,8 @@ Result BinaryReader::ReadInstructions(bool stop_on_end, case Opcode::V128Const: { v128 value_bits; - ZeroMemory(value_bits); + WABT_STATIC_ASSERT(std::is_pod::value); + memset(&value_bits, 0, sizeof(v128)); CHECK_RESULT(ReadV128(&value_bits, "v128.const value")); CALLBACK(OnV128ConstExpr, value_bits); CALLBACK(OnOpcodeV128, value_bits);