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..4704f0c18 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/nodejs/uvwasi.git diff --git a/CMakeLists.txt b/CMakeLists.txt index d60946949..8c9f22373 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,4 +74,5 @@ PROCESSORCOUNT (NPROCS) # INCLUDE CMAKE FILES INCLUDE (${PROJECT_SOURCE_DIR}/build/config.cmake) +INCLUDE (${PROJECT_SOURCE_DIR}/build/wasi.cmake) INCLUDE (${PROJECT_SOURCE_DIR}/build/walrus.cmake) diff --git a/build/walrus.cmake b/build/walrus.cmake index 939734451..09f598089 100644 --- a/build/walrus.cmake +++ b/build/walrus.cmake @@ -64,7 +64,12 @@ ENDIF() SET (WABT_DEFINITIONS ${WALRUS_DEFINITIONS}) SET (WITH_EXCEPTIONS TRUE) ADD_SUBDIRECTORY (third_party/wabt) -SET (WALRUS_LIBRARIES ${WALRUS_LIBRARIES} wabt) + +# 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/build/wasi.cmake b/build/wasi.cmake new file mode 100644 index 000000000..81c432e2b --- /dev/null +++ b/build/wasi.cmake @@ -0,0 +1,214 @@ +cmake_minimum_required(VERSION 3.11) +project ( + uvwasi + DESCRIPTION "WASI syscall API built atop libuv" + VERSION 0.0.19 + LANGUAGES C +) + +# Point CMake at any custom modules we may ship +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +# This can be a commit hash or tag +set(LIBUV_VERSION v1.44.2) + +include(CMakeDependentOption) +cmake_dependent_option(UVWASI_BUILD_TESTS + "Build the unit tests when uvwasi is the root project" ON + "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF) + +if(CMAKE_C_COMPILER_ID MATCHES "AppleClang|Clang|GNU") + list(APPEND uvwasi_cflags -fvisibility=hidden --std=gnu89) + list(APPEND uvwasi_cflags -Wall -Wsign-compare -Wextra -Wstrict-prototypes) + list(APPEND uvwasi_cflags -Wno-unused-parameter) +endif() + +set( CMAKE_C_FLAGS -fPIC) + +if(DEFINED WALRUS_ARCH) + if(${WALRUS_ARCH} STREQUAL "x86") + set( CMAKE_C_FLAGS "-fPIC -m32") + endif() +endif() + +if(DEFINED WALRUS_MODE) + if(${WALRUS_MODE} STREQUAL "debug") + set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") + endif() +endif() + +if(APPLE) + set(CMAKE_MACOSX_RPATH ON) +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + list(APPEND uvwasi_defines _GNU_SOURCE _POSIX_C_SOURCE=200112) +endif() + +# find_package(LIBUV QUIET) +# if(LIBUV_FOUND) +# include_directories(${LIBUV_INCLUDE_DIR}) +# else() + include(FetchContent) + ## https://libuv.org + FetchContent_Declare( + libuv + GIT_REPOSITORY https://github.com/libuv/libuv.git + GIT_TAG ${LIBUV_VERSION}) + + FetchContent_GetProperties(libuv) + if(NOT libuv_POPULATED) + FetchContent_Populate(libuv) + include_directories("${libuv_SOURCE_DIR}/include") + add_subdirectory(${libuv_SOURCE_DIR} ${libuv_BINARY_DIR} EXCLUDE_FROM_ALL) + endif() + set(LIBUV_INCLUDE_DIR ${libuv_SOURCE_DIR}/include) + set(LIBUV_LIBRARIES uv_a) +# endif() + +## uvwasi source code files. +set(uvwasi_sources + third_party/uvwasi/src/clocks.c + third_party/uvwasi/src/fd_table.c + third_party/uvwasi/src/path_resolver.c + third_party/uvwasi/src/poll_oneoff.c + third_party/uvwasi/src/sync_helpers.c + third_party/uvwasi/src/uv_mapping.c + third_party/uvwasi/src/uvwasi.c + third_party/uvwasi/src/wasi_rights.c + third_party/uvwasi/src/wasi_serdes.c +) + +option(UVWASI_DEBUG_LOG "Enable debug logging" OFF) +if(UVWASI_DEBUG_LOG) + list(APPEND uvwasi_cflags -DUVWASI_DEBUG_LOG) +endif() + +# Code Coverage Configuration +add_library(coverage_config INTERFACE) + +option(CODE_COVERAGE "Enable coverage reporting" OFF) +if(CODE_COVERAGE AND CMAKE_C_COMPILER_ID MATCHES "AppleClang|GNU|Clang") + # Add required flags (GCC & LLVM/Clang) + target_compile_options(coverage_config INTERFACE + -O0 # no optimization + -g # generate debug info + --coverage # sets all required flags + ) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) + target_link_options(coverage_config INTERFACE --coverage) + else() + target_link_libraries(coverage_config INTERFACE --coverage) + endif() +endif() + +# ASAN Support +option(ASAN "Enable code asan" OFF) +if(ASAN AND CMAKE_C_COMPILER_ID MATCHES "AppleClang|GNU|Clang") + set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") + set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") +endif() + +## Static library target. +add_library(uvwasi_a STATIC ${uvwasi_sources}) +target_compile_definitions(uvwasi_a PRIVATE ${uvwasi_defines}) +target_compile_options(uvwasi_a PRIVATE ${uvwasi_cflags}) +target_include_directories(uvwasi_a PRIVATE ${PROJECT_SOURCE_DIR}/include) +if(CODE_COVERAGE) + target_link_libraries(uvwasi_a PUBLIC ${LIBUV_LIBRARIES} coverage_config) +else() + target_link_libraries(uvwasi_a PRIVATE ${LIBUV_LIBRARIES}) +endif() + +## Shared library target. +add_library(uvwasi SHARED ${uvwasi_sources}) +target_compile_definitions(uvwasi PRIVATE ${uvwasi_defines}) +target_compile_options(uvwasi PRIVATE ${uvwasi_cflags}) +target_include_directories(uvwasi PRIVATE ${PROJECT_SOURCE_DIR}/include) +if(CODE_COVERAGE) + target_link_libraries(uvwasi PUBLIC ${LIBUV_LIBRARIES} coverage_config) +else() + target_link_libraries(uvwasi PRIVATE ${LIBUV_LIBRARIES}) +endif() + + +## Test targets. +if(UVWASI_BUILD_TESTS) + enable_testing() + file(GLOB test_files "test/test-*.c") + foreach(file ${test_files}) + get_filename_component(test_name ${file} NAME_WE) + add_executable(${test_name} ${file}) + add_test(NAME ${test_name} + COMMAND ${test_name}) + target_include_directories(${test_name} + PRIVATE + ${PROJECT_SOURCE_DIR}/include) + target_link_libraries(${test_name} PRIVATE ${LIBUV_LIBRARIES} uvwasi_a) + list(APPEND test_list ${test_name}) + endforeach() + + add_custom_target(check + COMMAND ctest -VV -C Debug -R test- + DEPENDS ${test_list} + ) +endif() + +option(INSTALL_UVWASI "Enable installation of uvwasi. (Projects embedding uvwasi may want to turn this OFF.)" ON) +if(INSTALL_UVWASI AND NOT CODE_COVERAGE) + include(GNUInstallDirs) + include(CMakePackageConfigHelpers) + + set(target_export_name ${PROJECT_NAME}Targets CACHE INTERNAL "") + set(cmake_files_install_dir ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) + + set(pkg_config ${PROJECT_BINARY_DIR}/uvwasi.pc) + set(version_file ${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake) + set(config_file ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake) + + configure_file(${PROJECT_SOURCE_DIR}/third_party/uvwasi/cmake/uvwasi.pc.in ${pkg_config} @ONLY) + write_basic_package_version_file(${version_file} VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion) + configure_package_config_file(${PROJECT_SOURCE_DIR}/third_party/uvwasi/cmake/Config.cmake.in ${config_file} INSTALL_DESTINATION ${cmake_files_install_dir}) + + install( + TARGETS uvwasi_a uvwasi + EXPORT ${target_export_name} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/uvwasi + FILES_MATCHING PATTERN "*.h" + ) + install( + FILES ${pkg_config} + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig + ) + install( + EXPORT ${target_export_name} + NAMESPACE ${PROJECT_NAME}:: + DESTINATION ${cmake_files_install_dir} + ) + install( + FILES ${version_file} ${config_file} + DESTINATION ${cmake_files_install_dir} + ) +endif() + +message(STATUS "summary of uvwasi build options: + + Install prefix: ${CMAKE_INSTALL_PREFIX} + Target system: ${CMAKE_SYSTEM_NAME} + Compiler: + C compiler: ${CMAKE_C_COMPILER} + CFLAGS: ${CMAKE_C_FLAGS_${_build_type}} ${CMAKE_C_FLAGS} + + LibUV libraries: ${LIBUV_LIBRARIES} + LibUV includes: ${LIBUV_INCLUDE_DIR} + Debug logging: ${UVWASI_DEBUG_LOG} + Code coverage: ${CODE_COVERAGE} + ASAN: ${ASAN} + Build tests: ${UVWASI_BUILD_TESTS} +") 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..aaf345e32 --- /dev/null +++ b/src/wasi/Fd.h @@ -0,0 +1,48 @@ +/* + * 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) +{ + wasi_fd_t fd = argv[0].asI32(); + int32_t iovptr = argv[1].asI32(); + int32_t iovcnt = argv[2].asI32(); + wasi_size_t out = argv[3].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_size_t offset = *reinterpret_cast(instance->memory(0)->buffer() + iovptr); + wasi_iovs.buf = reinterpret_cast(instance->memory(0)->buffer() + offset); + wasi_iovs.len = *reinterpret_cast(instance->memory(0)->buffer() + iovptr + sizeof(uvwasi_size_t)); + + 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))); + *(instance->memory(0)->buffer() + out) = out_addr; +} + +} // namespace Walrus diff --git a/src/wasi/Wasi.cpp b/src/wasi/Wasi.cpp index 48a60a028..7b1990efd 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,24 @@ void WASI::fillWasiFuncTable() WASI::WASI() { fillWasiFuncTable(); + + uvwasi_t uvwasi; + WASI::m_uvwasi = reinterpret_cast(malloc(sizeof(uvwasi_t))); + + uvwasi_options_t init_options; + init_options.in = 0; + init_options.out = 1; + init_options.err = 2; + init_options.fd_table_size = 3; + init_options.argc = 0; + init_options.argv = nullptr; + init_options.envp = nullptr; + init_options.preopenc = 0; + init_options.preopen_socketc = 0; + init_options.allocator = nullptr; + + uvwasi_errno_t err = uvwasi_init(WASI::m_uvwasi, &init_options); + assert(err == UVWASI_ESUCCESS); } } // 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/test/wasi/hello_world.wast b/test/wasi/hello_world.wast new file mode 100644 index 000000000..49367b31d --- /dev/null +++ b/test/wasi/hello_world.wast @@ -0,0 +1,24 @@ +(module + (import "wasi_snapshot_preview1" "fd_write" (func $__wasi_fd_write (param i32 i32 i32 i32) (result i32))) + (memory 1) + + (export "memory" (memory 0)) + (export "_start" (func $_start)) + (data (i32.const 0) "Hello World!\n") + + (func $_start (result i32) + (i32.store (i32.const 24) (i32.const 13)) ;; Lenght of "Hello World!\n" + (i32.store (i32.const 20) (i32.const 0)) ;; memory offset of "Hello World!\n" + (call $__wasi_fd_write + (i32.const 1) ;;file descriptor + (i32.const 20) ;;offset of str offset + (i32.const 1) ;;iovec length + (i32.const 30) ;;result offset + ) + drop + i32.const 32 + i32.load + ) +) + +(assert_return (invoke "_start") (i32.const 0)) 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);