diff --git a/CMakeLists.txt b/CMakeLists.txt index 07c4a51..86a5e02 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ -cmake_minimum_required(VERSION 3.9) +cmake_minimum_required(VERSION 3.11) option(BUILD_WASI_SUPPORT "Build with WASI support" ON) +option(BUILD_UVWASI_SUPPORT "Build with UVWASI support" OFF) set(OUT_FILE "wasm3") set(APP_DIR "platforms/app") @@ -144,7 +145,7 @@ else() endif() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wparentheses -Wundef -Wpointer-arith -Wstrict-aliasing=2") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=shadow -Werror=implicit-function-declaration") # -Werror=cast-align + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=implicit-function-declaration") # -Werror=cast-align set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-function -Wno-unused-variable -Wno-unused-parameter -Wno-missing-field-initializers") if (CMAKE_C_COMPILER_ID MATCHES "Clang") # TODO: Place clang-specific options here @@ -153,6 +154,11 @@ else() endif() set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -O0") + if(NOT BUILD_UVWASI_SUPPORT) + # -Werror=shadow fails when building libuv. Enable it only if libuv is not present. + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=shadow") + endif() + set(CMAKE_C_FLAGS_RELEASE "-O3 -march=native -Wfatal-errors -fomit-frame-pointer -fno-stack-check -fno-stack-protector") #-fno-inline set(CMAKE_EXE_LINKER_FLAGS_DEBUG "-O0") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-O3") @@ -161,6 +167,28 @@ else() endif() +if(BUILD_UVWASI_SUPPORT) + if(BUILD_WASI_SUPPORT) + message(FATAL_ERROR "BUILD_UVWASI_SUPPORT is incompatible with BUILD_WASI_SUPPORT") + endif() + + include(FetchContent) + FetchContent_Declare( + uvwasi + GIT_REPOSITORY https://github.com/cjihrig/uvwasi.git + GIT_TAG ${v0.0.7} + ) + + FetchContent_GetProperties(uvwasi) + if(NOT uvwasi_POPULATED) + FetchContent_Populate(uvwasi) + include_directories("${uvwasi_SOURCE_DIR}/include") + add_subdirectory(${uvwasi_SOURCE_DIR} ${uvwasi_BINARY_DIR} EXCLUDE_FROM_ALL) + endif() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Dd_m3HasUVWASI -Dd_m3HasTracer") + target_link_libraries(${OUT_FILE} uvwasi_a uv_a) +endif() + check_ipo_supported(RESULT result) if(result AND NOT WASIENV) # TODO: LTO breaks wasm imports set_property(TARGET ${OUT_FILE} PROPERTY INTERPROCEDURAL_OPTIMIZATION True) diff --git a/platforms/app/main.c b/platforms/app/main.c index 4ed999c..328bb8c 100644 --- a/platforms/app/main.c +++ b/platforms/app/main.c @@ -233,7 +233,7 @@ void print_usage() { int main (int i_argc, const char* i_argv[]) { M3Result result = m3Err_none; - + IM3Environment env = m3_NewEnvironment (); IM3Runtime runtime = NULL; bool argRepl = false; @@ -286,7 +286,7 @@ int main (int i_argc, const char* i_argv[]) result = repl_load(runtime, argFile); if (result) FATAL("repl_load: %s", result); -#if defined(d_m3HasWASI) || defined(d_m3HasMetaWASI) +#if defined(d_m3HasWASI) || defined(d_m3HasMetaWASI) || defined(d_m3HasUVWASI) result = m3_LinkWASI (runtime->modules); if (result) FATAL("m3_LinkWASI: %s", result); #endif @@ -378,6 +378,6 @@ _onfatal: m3_FreeRuntime (runtime); m3_FreeEnvironment (env); - + return 0; } diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 8945966..30b7efe 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -3,6 +3,7 @@ set(srcs "m3_api_meta_wasi.c" "m3_api_wasi.c" "m3_api_tracer.c" + "m3_api_uvwasi.c" "m3_bind.c" "m3_code.c" "m3_compile.c" diff --git a/source/m3_api_uvwasi.c b/source/m3_api_uvwasi.c new file mode 100644 index 0000000..8a864bb --- /dev/null +++ b/source/m3_api_uvwasi.c @@ -0,0 +1,410 @@ +// +// m3_api_uvwasi.c +// +// Created by Colin J. Ihrig on 4/20/20. +// Copyright © 2020 Colin J. Ihrig. All rights reserved. + +// + +#define _POSIX_C_SOURCE 200809L + +#include "m3_api_wasi.h" + +#include "m3_api_defs.h" +#include "m3_env.h" +#include "m3_exception.h" + +#if defined(d_m3HasUVWASI) + +typedef uint32_t __wasi_size_t; +#include "extra/wasi_core.h" + +#include +#include + +#include "uvwasi.h" + +#ifdef __APPLE__ +# include +# define environ (*_NSGetEnviron()) +#elif !defined(_MSC_VER) +extern char** environ; +#endif + +uvwasi_t uvwasi; + +typedef struct wasi_iovec_t +{ + __wasi_size_t buf; + __wasi_size_t buf_len; +} wasi_iovec_t; + +/* + * WASI API implementation + */ + +m3ApiRawFunction(m3_wasi_unstable_args_get) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArgMem (u32* , argv) + m3ApiGetArgMem (char* , argv_buf) + + if (runtime == NULL) { m3ApiReturn(__WASI_EINVAL); } + + for (u32 i = 0; i < runtime->argc; ++i) + { + argv[i] = m3ApiPtrToOffset (argv_buf); + + size_t len = strlen (runtime->argv [i]); + memcpy (argv_buf, runtime->argv [i], len); + argv_buf += len; + * argv_buf++ = 0; + } + + m3ApiReturn(UVWASI_ESUCCESS); +} + +m3ApiRawFunction(m3_wasi_unstable_args_sizes_get) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArgMem (__wasi_size_t* , argc) + m3ApiGetArgMem (__wasi_size_t* , argv_buf_size) + + if (runtime == NULL) { m3ApiReturn(__WASI_EINVAL); } + + *argc = runtime->argc; + *argv_buf_size = 0; + for (u32 i = 0; i < runtime->argc; ++i) + { + * argv_buf_size += strlen (runtime->argv [i]) + 1; + } + m3ApiReturn(UVWASI_ESUCCESS); +} + +m3ApiRawFunction(m3_wasi_unstable_environ_get) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArgMem (u32* , env) + m3ApiGetArgMem (char* , env_buf) + + char **environment; + uvwasi_errno_t err; + + environment = calloc(uvwasi.envc, sizeof(char *)); + if (environment == NULL) { + m3ApiReturn(UVWASI_ENOMEM); + } + + err = uvwasi_environ_get(&uvwasi, environment, env_buf); + if (err != UVWASI_ESUCCESS) { + free(environment); + m3ApiReturn(err); + } + + uint32_t environ_buf_offset = m3ApiPtrToOffset(env_buf); + + for (u32 i = 0; i < uvwasi.envc; ++i) + { + uint32_t offset = environ_buf_offset + + (environment[i] - environment[0]); + env[i] = offset; + } + + free(environment); + m3ApiReturn(UVWASI_ESUCCESS); +} + +m3ApiRawFunction(m3_wasi_unstable_environ_sizes_get) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArgMem (__wasi_size_t* , env_count) + m3ApiGetArgMem (__wasi_size_t* , env_buf_size) + + size_t count; + size_t buf_size; + uvwasi_errno_t err; + + err = uvwasi_environ_sizes_get(&uvwasi, &count, &buf_size); + *env_count = count; + *env_buf_size = buf_size; + m3ApiReturn(err); +} + +m3ApiRawFunction(m3_wasi_unstable_fd_prestat_dir_name) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_fd_t , fd) + m3ApiGetArgMem (char* , path) + m3ApiGetArg (size_t , path_len) + + m3ApiReturn(uvwasi_fd_prestat_dir_name(&uvwasi, fd, path, path_len)); +} + +m3ApiRawFunction(m3_wasi_unstable_fd_prestat_get) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_fd_t , fd) + m3ApiGetArgMem (uint32_t * , buf) + + uvwasi_prestat_t prestat; + uvwasi_errno_t err; + + err = uvwasi_fd_prestat_get(&uvwasi, fd, &prestat); + if (err != UVWASI_ESUCCESS) { + m3ApiReturn(err); + } + + // TODO(cjihrig): This memory writing logic is wrong. + *buf = prestat.pr_type; + *(buf + 4) = prestat.u.dir.pr_name_len; + m3ApiReturn(UVWASI_ESUCCESS); +} + +m3ApiRawFunction(m3_wasi_unstable_fd_fdstat_get) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_fd_t , fd) + m3ApiGetArgMem (uvwasi_fdstat_t* , fdstat) + + m3ApiReturn(uvwasi_fd_fdstat_get(&uvwasi, fd, fdstat)); +} + +m3ApiRawFunction(m3_wasi_unstable_fd_fdstat_set_flags) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_fd_t , fd) + m3ApiGetArg (uvwasi_fdflags_t , flags) + + m3ApiReturn(uvwasi_fd_fdstat_set_flags(&uvwasi, fd, flags)); +} + +m3ApiRawFunction(m3_wasi_unstable_fd_seek) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_fd_t , fd) + m3ApiGetArg (uvwasi_filedelta_t , offset) + m3ApiGetArg (uvwasi_whence_t , whence) + m3ApiGetArgMem (uvwasi_filesize_t* , result) + + m3ApiReturn(uvwasi_fd_seek(&uvwasi, fd, offset, whence, result)); +} + + +m3ApiRawFunction(m3_wasi_unstable_path_open) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_fd_t , dirfd) + m3ApiGetArg (uvwasi_lookupflags_t , dirflags) + m3ApiGetArgMem (const char * , path) + m3ApiGetArg (__wasi_size_t , path_len) + m3ApiGetArg (uvwasi_oflags_t , oflags) + m3ApiGetArg (uvwasi_rights_t , fs_rights_base) + m3ApiGetArg (uvwasi_rights_t , fs_rights_inheriting) + m3ApiGetArg (uvwasi_fdflags_t , fs_flags) + m3ApiGetArgMem (uvwasi_fd_t * , fd) + + m3ApiReturn(uvwasi_path_open(&uvwasi, + dirfd, + dirflags, + path, + path_len, + oflags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + fd)); +} + +m3ApiRawFunction(m3_wasi_unstable_fd_read) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_fd_t , fd) + m3ApiGetArgMem (wasi_iovec_t* , wasi_iovs) + m3ApiGetArg (__wasi_size_t , iovs_len) + m3ApiGetArgMem (__wasi_size_t* , nread) + + uvwasi_iovec_t* iovs = calloc(iovs_len, sizeof(uvwasi_iovec_t)); + size_t num_read; + uvwasi_errno_t err; + + if (iovs == NULL) { + m3ApiReturn(UVWASI_ENOMEM); + } + + for (__wasi_size_t i = 0; i < iovs_len; ++i) { + iovs[i].buf = m3ApiOffsetToPtr(wasi_iovs[i].buf); + iovs[i].buf_len = wasi_iovs[i].buf_len; + } + + err = uvwasi_fd_read(&uvwasi, fd, iovs, iovs_len, &num_read); + *nread = num_read; + free(iovs); + m3ApiReturn(err); +} + +m3ApiRawFunction(m3_wasi_unstable_fd_write) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_fd_t , fd) + m3ApiGetArgMem (wasi_iovec_t* , wasi_iovs) + m3ApiGetArg (__wasi_size_t , iovs_len) + m3ApiGetArgMem (__wasi_size_t* , nwritten) + + uvwasi_ciovec_t* iovs = calloc(iovs_len, sizeof(uvwasi_ciovec_t)); + size_t num_written; + uvwasi_errno_t err; + + if (iovs == NULL) { + m3ApiReturn(UVWASI_ENOMEM); + } + + for (__wasi_size_t i = 0; i < iovs_len; ++i) { + iovs[i].buf = m3ApiOffsetToPtr(wasi_iovs[i].buf); + iovs[i].buf_len = wasi_iovs[i].buf_len; + } + + err = uvwasi_fd_write(&uvwasi, fd, iovs, iovs_len, &num_written); + *nwritten = num_written; + free(iovs); + m3ApiReturn(err); +} + +m3ApiRawFunction(m3_wasi_unstable_fd_close) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_fd_t, fd) + + m3ApiReturn(uvwasi_fd_close(&uvwasi, fd)); +} + +m3ApiRawFunction(m3_wasi_unstable_fd_datasync) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_fd_t, fd) + + m3ApiReturn(uvwasi_fd_datasync(&uvwasi, fd)); +} + +m3ApiRawFunction(m3_wasi_unstable_random_get) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArgMem (uint8_t* , buf) + m3ApiGetArg (__wasi_size_t , buflen) + + m3ApiReturn(uvwasi_random_get(&uvwasi, buf, buflen)); +} + +m3ApiRawFunction(m3_wasi_unstable_clock_res_get) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_clockid_t , wasi_clk_id) + m3ApiGetArgMem (uvwasi_timestamp_t* , resolution) + + m3ApiReturn(uvwasi_clock_res_get(&uvwasi, wasi_clk_id, resolution)); +} + +m3ApiRawFunction(m3_wasi_unstable_clock_time_get) +{ + m3ApiReturnType (uint32_t) + m3ApiGetArg (uvwasi_clockid_t , wasi_clk_id) + m3ApiGetArg (uvwasi_timestamp_t , precision) + m3ApiGetArgMem (uvwasi_timestamp_t* , time) + + m3ApiReturn(uvwasi_clock_time_get(&uvwasi, wasi_clk_id, precision, time)); +} + +m3ApiRawFunction(m3_wasi_unstable_proc_exit) +{ + m3ApiGetArg (uint32_t, code) + + runtime->exit_code = code; + + m3ApiTrap(m3Err_trapExit); +} + + +static +M3Result SuppressLookupFailure(M3Result i_result) +{ + if (i_result == m3Err_functionLookupFailed) + return m3Err_none; + else + return i_result; +} + + +M3Result m3_LinkWASI (IM3Module module) +{ + M3Result result = m3Err_none; + + // TODO(cjihrig): uvwasi currently implements 'wasi_snapshot_preview1' but + // call it 'wasi_unstable' here for compatibility with the other wasm3 WASI + // implementations. + const char* wasi = "wasi_unstable"; + + uvwasi_options_t init_options; + uvwasi_errno_t err; + + init_options.in = 0; + init_options.out = 1; + init_options.err = 2; + init_options.fd_table_size = 3; + // runtime->argc is not initialized at this point. However, it's fine to + // use the runtime instead of uvwasi to implement the two WASI functions for + // working with command line arguments. + init_options.argc = 0; + init_options.argv = NULL; + init_options.envp = (char**) environ; + init_options.preopenc = 1; + // TODO(cjihrig): This requires better support for the --dir command line + // flag to implement properly. For now, just let WASI applications access + // the current working directory as the sandboxed root directory. + init_options.preopens = calloc(1, sizeof(uvwasi_preopen_t)); + if (init_options.preopens == NULL) { + result = m3Err_mallocFailed; + return result; + } + + init_options.preopens[0].mapped_path = "/"; + init_options.preopens[0].real_path = "."; + init_options.allocator = NULL; + err = uvwasi_init(&uvwasi, &init_options); + free(init_options.preopens); + + // uvwasi_init() returns WASI errors, which don't really map to m3 Errors, + // so return unknown error for now. + if (err != UVWASI_ESUCCESS) { + result = m3Err_unknownError; + return result; + } + +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "args_sizes_get", "i(**)", &m3_wasi_unstable_args_sizes_get))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "environ_sizes_get", "i(**)", &m3_wasi_unstable_environ_sizes_get))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "args_get", "i(**)", &m3_wasi_unstable_args_get))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "environ_get", "i(**)", &m3_wasi_unstable_environ_get))); + +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "fd_prestat_dir_name", "i(i*i)", &m3_wasi_unstable_fd_prestat_dir_name))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "fd_prestat_get", "i(i*)", &m3_wasi_unstable_fd_prestat_get))); + +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "path_open", "i(ii*iiIIi*)", &m3_wasi_unstable_path_open))); + +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "fd_fdstat_get", "i(i*)", &m3_wasi_unstable_fd_fdstat_get))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "fd_fdstat_set_flags", "i(ii)", &m3_wasi_unstable_fd_fdstat_set_flags))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "fd_write", "i(i*i*)", &m3_wasi_unstable_fd_write))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "fd_read", "i(i*i*)", &m3_wasi_unstable_fd_read))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "fd_seek", "i(iIi*)", &m3_wasi_unstable_fd_seek))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "fd_datasync", "i(i)", &m3_wasi_unstable_fd_datasync))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "fd_close", "i(i)", &m3_wasi_unstable_fd_close))); + +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "random_get", "i(*i)", &m3_wasi_unstable_random_get))); + +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "clock_res_get", "i(i*)", &m3_wasi_unstable_clock_res_get))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "clock_time_get", "i(iI*)", &m3_wasi_unstable_clock_time_get))); +_ (SuppressLookupFailure (m3_LinkRawFunction (module, wasi, "proc_exit", "v(i)", &m3_wasi_unstable_proc_exit))); + +_catch: + return result; +} + +#endif // d_m3HasUVWASI + diff --git a/source/m3_api_wasi.c b/source/m3_api_wasi.c index 912da66..b59f166 100644 --- a/source/m3_api_wasi.c +++ b/source/m3_api_wasi.c @@ -681,4 +681,3 @@ _catch: } #endif // d_m3HasWASI - diff --git a/source/wasm3.h b/source/wasm3.h index 5959107..46a03c4 100644 --- a/source/wasm3.h +++ b/source/wasm3.h @@ -129,6 +129,7 @@ d_m3ErrorConst (wasmMemoryOverflow, "runtime ran out of memory") d_m3ErrorConst (globalMemoryNotAllocated, "global memory is missing from a module") d_m3ErrorConst (globaIndexOutOfBounds, "global index is too large") d_m3ErrorConst (argumentCountMismatch, "argument count mismatch") +d_m3ErrorConst (unknownError, "unknown error") // traps d_m3ErrorConst (trapOutOfBoundsMemoryAccess, "[trap] out of bounds memory access")