diff options
author | Sam Clegg <sbc@chromium.org> | 2020-05-21 12:58:02 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-21 09:58:02 -0700 |
commit | 7639690bbc2a2a35f315e2ee061cfa8cfeb1c5b1 (patch) | |
tree | cc17293bc25af39a1bb9a6a2fcae9e8d5204facd | |
parent | 3625539c176839c9a45d58143767a63c04b95559 (diff) | |
download | wabt-7639690bbc2a2a35f315e2ee061cfa8cfeb1c5b1.tar.gz wabt-7639690bbc2a2a35f315e2ee061cfa8cfeb1c5b1.tar.bz2 wabt-7639690bbc2a2a35f315e2ee061cfa8cfeb1c5b1.zip |
wasi: Implement more of the wasi API (#1430)
I've been used the tests from wasi-sdk to get started:
https://github.com/WebAssembly/wasi-sdk/tree/master/tests
All these tests now pass. Still this isn't saying much, there are still
some big missing pieces. Started using the new serdes
(serialize/deserialze) API in uvwasi. I think we are almost at the
point were we can look at auto-generating some of this stuff from witx
to avoid having the hand code all this marshelling stuff.
Add support for ArgumentCount::ZeroOrMore to OptionParser and also the
ability to force the end of option processing using `--`. This allows
is to pass options through the underlying wasi program when running
wasm-interp.
-rw-r--r-- | src/interp/interp-wasi.cc | 417 | ||||
-rw-r--r-- | src/interp/wasi_api.def | 20 | ||||
-rw-r--r-- | src/option-parser.cc | 170 | ||||
-rw-r--r-- | src/option-parser.h | 2 | ||||
-rw-r--r-- | src/test-option-parser.cc | 29 | ||||
-rw-r--r-- | src/tools/wasm-interp.cc | 31 | ||||
-rw-r--r-- | test/help/wasm-interp.txt | 3 | ||||
-rw-r--r-- | test/wasi/clock.txt | 21 | ||||
m--------- | third_party/uvwasi | 0 |
9 files changed, 590 insertions, 103 deletions
diff --git a/src/interp/interp-wasi.cc b/src/interp/interp-wasi.cc index 28d94584..3e841769 100644 --- a/src/interp/interp-wasi.cc +++ b/src/interp/interp-wasi.cc @@ -43,8 +43,12 @@ using namespace wabt::interp; namespace { -// Types that align with WASI specis on size and alignment. +// Types that align with WASI spec on size and alignment. These are +// copied directly from wasi-lib's auto-generated api.h // TODO(sbc): Auto-generate this from witx. + +// BEGIN: wasi.h types from wasi-libc + typedef uint32_t __wasi_size_t; typedef uint32_t __wasi_ptr_t; @@ -53,6 +57,49 @@ _Static_assert(_Alignof(__wasi_size_t) == 4, "witx calculated align"); _Static_assert(sizeof(__wasi_ptr_t) == 4, "witx calculated size"); _Static_assert(_Alignof(__wasi_ptr_t) == 4, "witx calculated align"); +typedef struct __wasi_prestat_dir_t { + __wasi_size_t pr_name_len; +} __wasi_prestat_dir_t; + +typedef uint8_t __wasi_preopentype_t; +typedef uint64_t __wasi_rights_t; +typedef uint16_t __wasi_fdflags_t; +typedef uint8_t __wasi_filetype_t; +typedef uint16_t __wasi_oflags_t; +typedef uint32_t __wasi_lookupflags_t; +typedef uint32_t __wasi_fd_t; +typedef uint64_t __wasi_timestamp_t; +typedef uint8_t __wasi_whence_t; +typedef int64_t __wasi_filedelta_t; +typedef uint64_t __wasi_filesize_t; + +typedef union __wasi_prestat_u_t { + __wasi_prestat_dir_t dir; +} __wasi_prestat_u_t; + +struct __wasi_prestat_t { + __wasi_preopentype_t tag; + __wasi_prestat_u_t u; +}; + +typedef struct __wasi_fdstat_t { + __wasi_filetype_t fs_filetype; + __wasi_fdflags_t fs_flags; + __wasi_rights_t fs_rights_base; + __wasi_rights_t fs_rights_inheriting; +} __wasi_fdstat_t; + +_Static_assert(sizeof(__wasi_fdstat_t) == 24, "witx calculated size"); +_Static_assert(_Alignof(__wasi_fdstat_t) == 8, "witx calculated align"); +_Static_assert(offsetof(__wasi_fdstat_t, fs_filetype) == 0, + "witx calculated offset"); +_Static_assert(offsetof(__wasi_fdstat_t, fs_flags) == 2, + "witx calculated offset"); +_Static_assert(offsetof(__wasi_fdstat_t, fs_rights_base) == 8, + "witx calculated offset"); +_Static_assert(offsetof(__wasi_fdstat_t, fs_rights_inheriting) == 16, + "witx calculated offset"); + struct __wasi_iovec_t { __wasi_ptr_t buf; __wasi_size_t buf_len; @@ -63,8 +110,54 @@ _Static_assert(_Alignof(__wasi_iovec_t) == 4, "witx calculated align"); _Static_assert(offsetof(__wasi_iovec_t, buf) == 0, "witx calculated offset"); _Static_assert(offsetof(__wasi_iovec_t, buf_len) == 4, "witx calculated offset"); +typedef uint64_t __wasi_device_t; + +_Static_assert(sizeof(__wasi_device_t) == 8, "witx calculated size"); +_Static_assert(_Alignof(__wasi_device_t) == 8, "witx calculated align"); + +typedef uint64_t __wasi_inode_t; + +_Static_assert(sizeof(__wasi_inode_t) == 8, "witx calculated size"); +_Static_assert(_Alignof(__wasi_inode_t) == 8, "witx calculated align"); + +typedef uint64_t __wasi_linkcount_t; + +_Static_assert(sizeof(__wasi_linkcount_t) == 8, "witx calculated size"); +_Static_assert(_Alignof(__wasi_linkcount_t) == 8, "witx calculated align"); + +typedef struct __wasi_filestat_t { + __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; + +_Static_assert(sizeof(__wasi_filestat_t) == 64, "witx calculated size"); +_Static_assert(_Alignof(__wasi_filestat_t) == 8, "witx calculated align"); +_Static_assert(offsetof(__wasi_filestat_t, dev) == 0, "witx calculated offset"); +_Static_assert(offsetof(__wasi_filestat_t, ino) == 8, "witx calculated offset"); +_Static_assert(offsetof(__wasi_filestat_t, filetype) == 16, + "witx calculated offset"); +_Static_assert(offsetof(__wasi_filestat_t, nlink) == 24, + "witx calculated offset"); +_Static_assert(offsetof(__wasi_filestat_t, size) == 32, + "witx calculated offset"); +_Static_assert(offsetof(__wasi_filestat_t, atim) == 40, + "witx calculated offset"); +_Static_assert(offsetof(__wasi_filestat_t, mtim) == 48, + "witx calculated offset"); +_Static_assert(offsetof(__wasi_filestat_t, ctim) == 56, + "witx calculated offset"); + +#define __WASI_ERRNO_SUCCESS (UINT16_C(0)) #define __WASI_ERRNO_NOENT (UINT16_C(44)) +// END wasi.h types from wasi-lib + class WasiInstance { public: WasiInstance(Instance::Ptr instance, @@ -76,18 +169,259 @@ class WasiInstance { uvwasi(uvwasi), memory(memory) {} + Result random_get(const Values& params, Values& results, Trap::Ptr* trap) { + /* __wasi_errno_t __wasi_random_get(uint8_t * buf, __wasi_size_t buf_len) */ + assert(false); + return Result::Ok; + } + Result proc_exit(const Values& params, Values& results, Trap::Ptr* trap) { const Value arg0 = params[0]; uvwasi_proc_exit(uvwasi, arg0.i32_); return Result::Ok; } + Result poll_oneoff(const Values& params, Values& results, Trap::Ptr* trap) { + assert(false); + return Result::Ok; + } + + Result clock_time_get(const Values& params, + Values& results, + Trap::Ptr* trap) { + /* __wasi_errno_t __wasi_clock_time_get(__wasi_clockid_t id, + * __wasi_timestamp_t precision, + * __wasi_timestamp_t *time) + */ + __wasi_timestamp_t t; + results[0].i32_ = + uvwasi_clock_time_get(uvwasi, params[0].i32_, params[1].i64_, &t); + uint32_t time_ptr = params[2].i32_; + CHECK_RESULT(writeValue<__wasi_timestamp_t>(t, time_ptr, trap)); + return Result::Ok; + } + + Result path_rename(const Values& params, Values& results, Trap::Ptr* trap) { + assert(false); + return Result::Ok; + } + + Result path_open(const Values& params, Values& results, Trap::Ptr* trap) { + /* __wasi_errno_t __wasi_path_open(__wasi_fd_t fd, + __wasi_lookupflags_t dirflags, + const char *path, + size_t path_len, + __wasi_oflags_t oflags, + __wasi_rights_t fs_rights_base, + __wasi_rights_t fs_rights_inherting, + __wasi_fdflags_t fdflags, + __wasi_fd_t *opened_fd) */ + uvwasi_fd_t dirfd = params[0].i32_; + __wasi_lookupflags_t dirflags = params[1].i32_; + uint32_t path_ptr = params[2].i32_; + __wasi_size_t path_len = params[3].i32_; + __wasi_oflags_t oflags = params[4].i32_; + __wasi_rights_t fs_rights_base = params[5].i32_; + __wasi_rights_t fs_rights_inherting = params[6].i32_; + __wasi_fdflags_t fs_flags = params[7].i32_; + uint32_t out_ptr = params[8].i32_; + char* path; + CHECK_RESULT(getMemPtr<char>(path_ptr, path_len, &path, trap)); + if (trace_stream) { + trace_stream->Writef("path_open : %s\n", path); + } + uvwasi_fd_t outfd; + results[0].i32_ = + uvwasi_path_open(uvwasi, dirfd, dirflags, path, path_len, oflags, + fs_rights_base, fs_rights_inherting, fs_flags, &outfd); + if (trace_stream) { + trace_stream->Writef("path_open -> %d\n", results[0].i32_); + } + CHECK_RESULT(writeValue<__wasi_fd_t>(outfd, out_ptr, trap)); + return Result::Ok; + } + + Result path_filestat_get(const Values& params, + Values& results, + Trap::Ptr* trap) { + /* __wasi_errno_t __wasi_path_filestat_get(__wasi_fd_t fd, + * __wasi_lookupflags_t flags, + * const char *path, + * size_t path_len, + * __wasi_filestat_t *buf + */ + uvwasi_fd_t fd = params[0].i32_; + __wasi_lookupflags_t flags = params[1].i32_; + uint32_t path_ptr = params[2].i32_; + uvwasi_size_t path_len = params[3].i32_; + uint32_t filestat_ptr = params[4].i32_; + char* path; + CHECK_RESULT(getMemPtr<char>(path_ptr, path_len, &path, trap)); + if (trace_stream) { + trace_stream->Writef("path_filestat_get : %d %s\n", fd, path); + } + uvwasi_filestat_t buf; + results[0].i32_ = + uvwasi_path_filestat_get(uvwasi, fd, flags, path, path_len, &buf); + __wasi_filestat_t* filestat; + CHECK_RESULT(getMemPtr<__wasi_filestat_t>( + filestat_ptr, sizeof(__wasi_filestat_t), &filestat, trap)); + uvwasi_serdes_write_filestat_t(filestat, 0, &buf); + if (trace_stream) { + trace_stream->Writef("path_filestat_get -> size=%lu %d\n", buf.st_size, + results[0].i32_); + } + return Result::Ok; + } + + Result path_symlink(const Values& params, Values& results, Trap::Ptr* trap) { + /* __wasi_errno_t __wasi_path_symlink(const char *old_path, + * size_t old_path_len, + * __wasi_fd_t fd, + * const char *new_path, + * size_t new_path_len); + */ + + uint32_t old_path_ptr = params[0].i32_; + __wasi_size_t old_path_len = params[1].i32_; + uvwasi_fd_t fd = params[2].i32_; + uint32_t new_path_ptr = params[3].i32_; + __wasi_size_t new_path_len = params[4].i32_; + char* old_path; + char* new_path; + CHECK_RESULT(getMemPtr<char>(old_path_ptr, old_path_len, &old_path, trap)); + CHECK_RESULT(getMemPtr<char>(new_path_ptr, new_path_len, &new_path, trap)); + if (trace_stream) { + trace_stream->Writef("path_symlink %d %s : %s\n", fd, old_path, new_path); + } + results[0].i32_ = uvwasi_path_symlink(uvwasi, old_path, old_path_len, fd, + new_path, new_path_len); + if (trace_stream) { + trace_stream->Writef("path_symlink -> %d\n", results[0].i32_); + } + return Result::Ok; + } + + Result path_readlink(const Values& params, Values& results, Trap::Ptr* trap) { + assert(false); + return Result::Ok; + } + + Result path_create_directory(const Values& params, + Values& results, + Trap::Ptr* trap) { + assert(false); + return Result::Ok; + } + + Result path_remove_directory(const Values& params, + Values& results, + Trap::Ptr* trap) { + assert(false); + return Result::Ok; + } + + Result path_unlink_file(const Values& params, + Values& results, + Trap::Ptr* trap) { + /* __wasi_errno_t __wasi_path_unlink_file(__wasi_fd_t fd, + * const char *path, + * size_t path_len) + */ + uvwasi_fd_t fd = params[0].i32_; + uint32_t path_ptr = params[1].i32_; + __wasi_size_t path_len = params[2].i32_; + char* path; + CHECK_RESULT(getMemPtr<char>(path_ptr, path_len, &path, trap)); + if (trace_stream) { + trace_stream->Writef("path_unlink_file %d %s\n", fd, path); + } + results[0].i32_ = uvwasi_path_unlink_file(uvwasi, fd, path, path_len); + return Result::Ok; + } + + Result fd_prestat_get(const Values& params, + Values& results, + Trap::Ptr* trap) { + /* __wasi_errno_t __wasi_fd_prestat_get(__wasi_fd_t fd, + * __wasi_prestat_t *buf)) + */ + uvwasi_fd_t fd = params[0].i32_; + uint32_t prestat_ptr = params[1].i32_; + if (trace_stream) { + trace_stream->Writef("fd_prestat_get %d\n", fd); + } + uvwasi_prestat_t buf; + results[0].i32_ = uvwasi_fd_prestat_get(uvwasi, fd, &buf); + __wasi_prestat_t* prestat; + CHECK_RESULT(getMemPtr<__wasi_prestat_t>(prestat_ptr, 1, &prestat, trap)); + uvwasi_serdes_write_prestat_t(prestat, 0, &buf); + if (trace_stream) { + trace_stream->Writef("fd_prestat_get -> %d\n", results[0].i32_); + } + return Result::Ok; + } + + Result fd_prestat_dir_name(const Values& params, + Values& results, + Trap::Ptr* trap) { + uvwasi_fd_t fd = params[0].i32_; + uint32_t path_ptr = params[1].i32_; + uvwasi_size_t path_len = params[2].i32_; + if (trace_stream) { + trace_stream->Writef("fd_prestat_dir_name %d %d %d\n", fd, path_ptr, + path_len); + } + char* path; + CHECK_RESULT(getMemPtr<char>(path_ptr, path_len, &path, trap)); + results[0].i32_ = uvwasi_fd_prestat_dir_name(uvwasi, fd, path, path_len); + if (trace_stream) { + trace_stream->Writef("fd_prestat_dir_name %d -> %d %s %d\n", fd, + results[0].i32_, path, path_len); + } + return Result::Ok; + } + + Result fd_filestat_get(const Values& params, + Values& results, + Trap::Ptr* trap) { + /* __wasi_fd_filestat_get(__wasi_fd_t f, __wasi_filestat_t *buf) */ + uvwasi_fd_t fd = params[0].i32_; + uint32_t filestat_ptr = params[1].i32_; + uvwasi_filestat_t buf; + results[0].i32_ = uvwasi_fd_filestat_get(uvwasi, fd, &buf); + __wasi_filestat_t* filestat; + CHECK_RESULT(getMemPtr<__wasi_filestat_t>( + filestat_ptr, sizeof(__wasi_filestat_t), &filestat, trap)); + uvwasi_serdes_write_filestat_t(filestat, 0, &buf); + if (trace_stream) { + trace_stream->Writef("fd_filestat_get -> size=%lu %d\n", buf.st_size, + results[0].i32_); + } + return Result::Ok; + } + + Result fd_fdstat_set_flags(const Values& params, + Values& results, + Trap::Ptr* trap) { + assert(false); + return Result::Ok; + } + Result fd_fdstat_get(const Values& params, Values& results, Trap::Ptr* trap) { int32_t fd = params[0].i32_; + uint32_t stat_ptr = params[1].i32_; if (trace_stream) { trace_stream->Writef("fd_fdstat_get %d\n", fd); } - results[0].i32_ = __WASI_ERRNO_NOENT; + CHECK_RESULT(getMemPtr<__wasi_fdstat_t>(stat_ptr, 1, nullptr, trap)); + uvwasi_fdstat_t host_statbuf; + results[0].i32_ = uvwasi_fd_fdstat_get(uvwasi, fd, &host_statbuf); + + // Write the host statbuf into the target wasm memory + __wasi_fdstat_t* statbuf; + CHECK_RESULT(getMemPtr<__wasi_fdstat_t>(stat_ptr, 1, &statbuf, trap)); + uvwasi_serdes_write_fdstat_t(statbuf, 0, &host_statbuf); return Result::Ok; } @@ -95,6 +429,7 @@ class WasiInstance { int32_t fd = params[0].i32_; int32_t iovptr = params[1].i32_; int32_t iovcnt = params[2].i32_; + int32_t out_ptr = params[2].i32_; if (trace_stream) { trace_stream->Writef("fd_read %d [%d]\n", fd, iovcnt); } @@ -109,7 +444,7 @@ class WasiInstance { trap)); } __wasi_ptr_t* out_addr; - CHECK_RESULT(getMemPtr<__wasi_ptr_t>(params[3].i32_, 1, &out_addr, trap)); + CHECK_RESULT(getMemPtr<__wasi_ptr_t>(out_ptr, 1, &out_addr, trap)); results[0].i32_ = uvwasi_fd_read(uvwasi, fd, iovs.data(), iovs.size(), out_addr); if (trace_stream) { @@ -118,6 +453,16 @@ class WasiInstance { return Result::Ok; } + Result fd_pread(const Values& params, Values& results, Trap::Ptr* trap) { + assert(false); + return Result::Ok; + } + + Result fd_readdir(const Values& params, Values& results, Trap::Ptr* trap) { + assert(false); + return Result::Ok; + } + Result fd_write(const Values& params, Values& results, Trap::Ptr* trap) { int32_t fd = params[0].i32_; int32_t iovptr = params[1].i32_; @@ -138,6 +483,32 @@ class WasiInstance { return Result::Ok; } + Result fd_pwrite(const Values& params, Values& results, Trap::Ptr* trap) { + assert(false); + return Result::Ok; + } + + Result fd_close(const Values& params, Values& results, Trap::Ptr* trap) { + assert(false); + return Result::Ok; + } + + Result fd_seek(const Values& params, Values& results, Trap::Ptr* trap) { + /* __wasi_errno_t __wasi_fd_seek(__wasi_fd_t fd, + * __wasi_filedelta_t offset, + * __wasi_whence_t whence, + * __wasi_filesize_t *newoffset) + */ + int32_t fd = params[0].i32_; + __wasi_filedelta_t offset = params[1].i32_; + __wasi_whence_t whence = params[2].i32_; + uint32_t newoffset_ptr = params[3].i32_; + uvwasi_filesize_t newoffset; + results[0].i32_ = uvwasi_fd_seek(uvwasi, fd, offset, whence, &newoffset); + CHECK_RESULT(writeValue<__wasi_filesize_t>(newoffset, newoffset_ptr, trap)); + return Result::Ok; + } + Result environ_get(const Values& params, Values& results, Trap::Ptr* trap) { uvwasi_size_t environc; uvwasi_size_t environ_buf_size; @@ -155,6 +526,7 @@ class WasiInstance { CHECK_RESULT(writeValue<uint32_t>(wasm_buf + rel_address, dest, trap)); } + results[0].i32_ = __WASI_ERRNO_SUCCESS; return Result::Ok; } @@ -170,6 +542,7 @@ class WasiInstance { trace_stream->Writef("environ_sizes_get -> %d %d\n", environc, environ_buf_size); } + results[0].i32_ = __WASI_ERRNO_SUCCESS; return Result::Ok; } @@ -180,8 +553,8 @@ class WasiInstance { uint32_t wasm_buf = params[1].i32_; char* buf; CHECK_RESULT(getMemPtr<char>(wasm_buf, arg_buf_size, &buf, trap)); - char** host_args = new char*[argc]; - uvwasi_args_get(uvwasi, host_args, buf); + std::vector<char*> host_args(argc); + uvwasi_args_get(uvwasi, host_args.data(), buf); // Copy host_args pointer array wasm_args) for (uvwasi_size_t i = 0; i < argc; i++) { @@ -189,7 +562,7 @@ class WasiInstance { uint32_t dest = params[0].i32_ + (i * sizeof(uint32_t)); CHECK_RESULT(writeValue<uint32_t>(wasm_buf + rel_address, dest, trap)); } - + results[0].i32_ = __WASI_ERRNO_SUCCESS; return Result::Ok; } @@ -204,6 +577,7 @@ class WasiInstance { if (trace_stream) { trace_stream->Writef("args_sizes_get -> %d %d\n", argc, arg_buf_size); } + results[0].i32_ = __WASI_ERRNO_SUCCESS; return Result::Ok; } @@ -225,18 +599,21 @@ class WasiInstance { // Result a wasm-memory-local address to an absolute memory location. template <typename T> Result getMemPtr(uint32_t address, - uint32_t size, + uint32_t num_elems, T** result, Trap::Ptr* trap) { - if (!memory->IsValidAccess(address, 0, size * sizeof(T))) { - *trap = Trap::New(*instance.store(), - StringPrintf("out of bounds memory access: " - "[%u, %" PRIu64 ") >= max value %u", - address, u64{address} + size * sizeof(T), - memory->ByteSize())); + if (!memory->IsValidAccess(address, 0, num_elems * sizeof(T))) { + *trap = + Trap::New(*instance.store(), + StringPrintf("out of bounds memory access: " + "[%u, %" PRIu64 ") >= max value %u", + address, u64{address} + num_elems * sizeof(T), + memory->ByteSize())); return Result::Error; } - *result = reinterpret_cast<T*>(memory->UnsafeData() + address); + if (result) { + *result = reinterpret_cast<T*>(memory->UnsafeData() + address); + } return Result::Ok; } @@ -284,7 +661,8 @@ Result WasiBindImports(const Module::Ptr& module, return Result::Error; } - if (import.type.module != "wasi_snapshot_preview1") { + if (import.type.module != "wasi_snapshot_preview1" && + import.type.module != "wasi_unstable") { stream->Writef("wasi error: unknown module import: `%s`\n", import.type.module.c_str()); return Result::Error; @@ -295,6 +673,7 @@ Result WasiBindImports(const Module::Ptr& module, import.type.name.c_str()); HostFunc::Ptr host_func; + // TODO(sbc): Validate signatures of imports. #define WASI_FUNC(NAME) \ if (import.type.name == #NAME) { \ host_func = HostFunc::New(*store, func_type, NAME); \ @@ -303,8 +682,7 @@ Result WasiBindImports(const Module::Ptr& module, #include "wasi_api.def" #undef WASI_FUNC - stream->Writef("unknown `wasi_snapshot_preview1` import: `%s`\n", - import.type.name.c_str()); + stream->Writef("unknown wasi API import: `%s`\n", import.type.name.c_str()); return Result::Error; found: imports.push_back(host_func.ref()); @@ -359,8 +737,8 @@ Result WasiRunStart(const Instance::Ptr& instance, } // Register memory - auto* wasi = new WasiInstance(instance, uvwasi, memory.get(), trace_stream); - wasiInstances[instance.get()] = wasi; + WasiInstance wasi(instance, uvwasi, memory.get(), trace_stream); + wasiInstances[instance.get()] = &wasi; // Call start ([] -> []) Values params; @@ -373,7 +751,6 @@ Result WasiRunStart(const Instance::Ptr& instance, // Unregister memory wasiInstances.erase(instance.get()); - delete wasi; return res; } diff --git a/src/interp/wasi_api.def b/src/interp/wasi_api.def index 6599fc06..008e9fd6 100644 --- a/src/interp/wasi_api.def +++ b/src/interp/wasi_api.def @@ -1,8 +1,28 @@ WASI_FUNC(proc_exit) WASI_FUNC(fd_read) +WASI_FUNC(fd_pread) WASI_FUNC(fd_write) +WASI_FUNC(fd_pwrite) +WASI_FUNC(fd_close) +WASI_FUNC(fd_seek) +WASI_FUNC(fd_prestat_get) +WASI_FUNC(fd_prestat_dir_name) WASI_FUNC(fd_fdstat_get) +WASI_FUNC(fd_fdstat_set_flags) +WASI_FUNC(fd_filestat_get) +WASI_FUNC(fd_readdir) WASI_FUNC(environ_sizes_get) WASI_FUNC(environ_get) WASI_FUNC(args_sizes_get) WASI_FUNC(args_get) +WASI_FUNC(path_open) +WASI_FUNC(path_filestat_get) +WASI_FUNC(path_symlink) +WASI_FUNC(path_unlink_file) +WASI_FUNC(path_remove_directory) +WASI_FUNC(path_create_directory) +WASI_FUNC(path_readlink) +WASI_FUNC(path_rename) +WASI_FUNC(clock_time_get) +WASI_FUNC(poll_oneoff) +WASI_FUNC(random_get) diff --git a/src/option-parser.cc b/src/option-parser.cc index 99c3c74a..d6a38ae8 100644 --- a/src/option-parser.cc +++ b/src/option-parser.cc @@ -162,99 +162,107 @@ void OptionParser::HandleArgument(size_t* arg_index, const char* arg_value) { void OptionParser::Parse(int argc, char* argv[]) { size_t arg_index = 0; + bool processing_options = true; for (int i = 1; i < argc; ++i) { const char* arg = argv[i]; - if (arg[0] == '-') { - if (arg[1] == '-') { - // Long option. - int best_index = -1; - int best_length = 0; - int best_count = 0; - for (size_t j = 0; j < options_.size(); ++j) { - const Option& option = options_[j]; - if (!option.long_name.empty()) { - int match_length = - Match(&arg[2], option.long_name, option.has_argument); - if (match_length > best_length) { - best_index = j; - best_length = match_length; - best_count = 1; - } else if (match_length == best_length && best_length > 0) { - best_count++; - } + if (!processing_options || arg[0] != '-') { + // Non-option argument. + HandleArgument(&arg_index, arg); + continue; + } + + if (arg[1] == '-') { + if (arg[2] == '\0') { + // -- on its own means stop processing args, everything should + // be treated as positional. + processing_options = false; + continue; + } + // Long option. + int best_index = -1; + int best_length = 0; + int best_count = 0; + for (size_t j = 0; j < options_.size(); ++j) { + const Option& option = options_[j]; + if (!option.long_name.empty()) { + int match_length = + Match(&arg[2], option.long_name, option.has_argument); + if (match_length > best_length) { + best_index = j; + best_length = match_length; + best_count = 1; + } else if (match_length == best_length && best_length > 0) { + best_count++; } } + } - if (best_count > 1) { - Errorf("ambiguous option '%s'", arg); - continue; - } else if (best_count == 0) { - Errorf("unknown option '%s'", arg); - continue; - } + if (best_count > 1) { + Errorf("ambiguous option '%s'", arg); + continue; + } else if (best_count == 0) { + Errorf("unknown option '%s'", arg); + continue; + } - const Option& best_option = options_[best_index]; - const char* option_argument = nullptr; - if (best_option.has_argument) { - if (arg[best_length + 1] != 0 && // This byte is 0 on a full match. - arg[best_length + 2] == '=') { // +2 to skip "--". - option_argument = &arg[best_length + 3]; - } else { - if (i + 1 == argc || argv[i + 1][0] == '-') { - Errorf("option '--%s' requires argument", - best_option.long_name.c_str()); - continue; - } - ++i; - option_argument = argv[i]; + const Option& best_option = options_[best_index]; + const char* option_argument = nullptr; + if (best_option.has_argument) { + if (arg[best_length + 1] != 0 && // This byte is 0 on a full match. + arg[best_length + 2] == '=') { // +2 to skip "--". + option_argument = &arg[best_length + 3]; + } else { + if (i + 1 == argc || argv[i + 1][0] == '-') { + Errorf("option '--%s' requires argument", + best_option.long_name.c_str()); + continue; } + ++i; + option_argument = argv[i]; } - best_option.callback(option_argument); - } else { - // Short option. - if (arg[1] == '\0') { - // Just "-". - HandleArgument(&arg_index, arg); - continue; - } + } + best_option.callback(option_argument); + } else { + // Short option. + if (arg[1] == '\0') { + // Just "-". + HandleArgument(&arg_index, arg); + continue; + } - // Allow short names to be combined, e.g. "-d -v" => "-dv". - for (int k = 1; arg[k]; ++k) { - bool matched = false; - for (const Option& option : options_) { - if (option.short_name && arg[k] == option.short_name) { - const char* option_argument = nullptr; - if (option.has_argument) { - // A short option with a required argument cannot be followed - // by other short options_. - if (arg[k + 1] != '\0') { - Errorf("option '-%c' requires argument", option.short_name); - break; - } - - if (i + 1 == argc || argv[i + 1][0] == '-') { - Errorf("option '-%c' requires argument", option.short_name); - break; - } - ++i; - option_argument = argv[i]; + // Allow short names to be combined, e.g. "-d -v" => "-dv". + for (int k = 1; arg[k]; ++k) { + bool matched = false; + for (const Option& option : options_) { + if (option.short_name && arg[k] == option.short_name) { + const char* option_argument = nullptr; + if (option.has_argument) { + // A short option with a required argument cannot be followed + // by other short options_. + if (arg[k + 1] != '\0') { + Errorf("option '-%c' requires argument", option.short_name); + break; } - option.callback(option_argument); - matched = true; - break; + + if (i + 1 == argc || argv[i + 1][0] == '-') { + Errorf("option '-%c' requires argument", option.short_name); + break; + } + ++i; + option_argument = argv[i]; } + option.callback(option_argument); + matched = true; + break; } + } - if (!matched) { - Errorf("unknown option '-%c'", arg[k]); - continue; - } + if (!matched) { + Errorf("unknown option '-%c'", arg[k]); + continue; } } - } else { - // Non-option argument. - HandleArgument(&arg_index, arg); } } @@ -262,7 +270,9 @@ void OptionParser::Parse(int argc, char* argv[]) { // handled at least once. if (!arguments_.empty() && arguments_.back().handled_count == 0) { for (size_t i = arg_index; i < arguments_.size(); ++i) { - Errorf("expected %s argument.", arguments_[i].name.c_str()); + if (arguments_[i].count != ArgumentCount::ZeroOrMore) { + Errorf("expected %s argument.", arguments_[i].name.c_str()); + } } } } @@ -280,6 +290,10 @@ void OptionParser::PrintHelp() { case ArgumentCount::OneOrMore: printf(" %s+", argument.name.c_str()); break; + + case ArgumentCount::ZeroOrMore: + printf(" [%s]...", argument.name.c_str()); + break; } } diff --git a/src/option-parser.h b/src/option-parser.h index 96690781..051ce784 100644 --- a/src/option-parser.h +++ b/src/option-parser.h @@ -28,7 +28,7 @@ namespace wabt { class OptionParser { public: enum class HasArgument { No, Yes }; - enum class ArgumentCount { One, OneOrMore }; + enum class ArgumentCount { One, OneOrMore, ZeroOrMore }; struct Option; typedef std::function<void(const char*)> Callback; diff --git a/src/test-option-parser.cc b/src/test-option-parser.cc index 3d190e71..9295c581 100644 --- a/src/test-option-parser.cc +++ b/src/test-option-parser.cc @@ -150,3 +150,32 @@ TEST(OptionParser, OneOrMoreArguments) { parser.Parse(3, const_cast<char**>(args)); EXPECT_EQ("hellogoodbye", argument); } + +TEST(OptionParser, ZeroOrMoreArguments) { + std::string argument; + OptionParser parser("prog", "desc"); + parser.AddArgument("arg", OptionParser::ArgumentCount::ZeroOrMore, + [&](const char* arg) { argument += arg; }); + + const char* args_none[] = {"prog name"}; + parser.Parse(1, const_cast<char**>(args_none)); + EXPECT_EQ("", argument); + + const char* args_many[] = {"prog name", "hello", "goodbye"}; + parser.Parse(3, const_cast<char**>(args_many)); + EXPECT_EQ("hellogoodbye", argument); +} + +TEST(OptionParser, StopProccessing) { + std::string argument; + bool has_x = false; + OptionParser parser("prog", "desc"); + parser.AddArgument("arg", OptionParser::ArgumentCount::ZeroOrMore, + [&](const char* arg) { argument += arg; }); + parser.AddOption('x', "x", "help", [&]() { has_x = true; }); + + const char* args_many[] = {"prog name", "-x", "--", "foo", "-x", "-y", "bar"}; + parser.Parse(7, const_cast<char**>(args_many)); + EXPECT_TRUE(has_x); + EXPECT_EQ("foo-x-ybar", argument); +} diff --git a/src/tools/wasm-interp.cc b/src/tools/wasm-interp.cc index a976d1b1..f7561d3f 100644 --- a/src/tools/wasm-interp.cc +++ b/src/tools/wasm-interp.cc @@ -49,6 +49,8 @@ static bool s_dummy_import_func; static Features s_features; static bool s_wasi; static std::vector<std::string> s_wasi_env; +static std::vector<std::string> s_wasi_argv; +static std::vector<std::string> s_wasi_dirs; static std::unique_ptr<FileStream> s_log_stream; static std::unique_ptr<FileStream> s_stdout_stream; @@ -106,6 +108,9 @@ static void ParseOptions(int argc, char** argv) { "Pass the given environment string in the WASI runtime", [](const std::string& argument) { s_wasi_env.push_back(argument); }); parser.AddOption( + 'd', "dir", "DIR", "Pass the given directory the the WASI runtime", + [](const std::string& argument) { s_wasi_dirs.push_back(argument); }); + parser.AddOption( "run-all-exports", "Run all the exported functions, in order. Useful for testing", []() { s_run_all_exports = true; }); @@ -121,6 +126,9 @@ static void ParseOptions(int argc, char** argv) { parser.AddArgument("filename", OptionParser::ArgumentCount::One, [](const char* argument) { s_infile = argument; }); + parser.AddArgument( + "arg", OptionParser::ArgumentCount::ZeroOrMore, + [](const char* argument) { s_wasi_argv.push_back(argument); }); parser.Parse(argc, argv); } @@ -242,14 +250,31 @@ static Result ReadAndRunModule(const char* module_filename) { std::vector<const char*> argv; argv.push_back(module_filename); + for (auto& s : s_wasi_argv) { + if (s_trace_stream) { + s_trace_stream->Writef("wasi: arg: \"%s\"\n", s.c_str()); + } + argv.push_back(s.c_str()); + } argv.push_back(nullptr); std::vector<const char*> envp; - for (auto s : s_wasi_env) { + for (auto& s : s_wasi_env) { + if (s_trace_stream) { + s_trace_stream->Writef("wasi: env: \"%s\"\n", s.c_str()); + } envp.push_back(s.c_str()); } envp.push_back(nullptr); + std::vector<uvwasi_preopen_t> dirs; + for (auto& dir : s_wasi_dirs) { + if (s_trace_stream) { + s_trace_stream->Writef("wasi: dir: \"%s\"\n", dir.c_str()); + } + dirs.push_back({dir.c_str(), dir.c_str()}); + } + /* Setup the initialization options. */ init_options.in = 0; init_options.out = 1; @@ -258,8 +283,8 @@ static Result ReadAndRunModule(const char* module_filename) { init_options.argc = argv.size() - 1; init_options.argv = argv.data(); init_options.envp = envp.data(); - init_options.preopenc = 0; - init_options.preopens = NULL; + init_options.preopenc = dirs.size(); + init_options.preopens = dirs.data(); init_options.allocator = NULL; err = uvwasi_init(&uvwasi, &init_options); diff --git a/test/help/wasm-interp.txt b/test/help/wasm-interp.txt index 9412e267..91c91eda 100644 --- a/test/help/wasm-interp.txt +++ b/test/help/wasm-interp.txt @@ -1,7 +1,7 @@ ;;; RUN: %(wasm-interp)s ;;; ARGS: --help (;; STDOUT ;;; -usage: wasm-interp [options] filename +usage: wasm-interp [options] filename [arg]... read a file in the wasm binary format, and run in it a stack-based interpreter. @@ -42,6 +42,7 @@ options: -t, --trace Trace execution --wasi Assume input module is WASI compliant (Export WASI API the the module and invoke _start function) -e, --env=ENV Pass the given environment string in the WASI runtime + -d, --dir=DIR Pass the given directory the the WASI runtime --run-all-exports Run all the exported functions, in order. Useful for testing --host-print Include an importable function named "host.print" for printing to stdout --dummy-import-func Provide a dummy implementation of all imported functions. The function will log the call and return an appropriate zero value. diff --git a/test/wasi/clock.txt b/test/wasi/clock.txt new file mode 100644 index 00000000..ec04edbd --- /dev/null +++ b/test/wasi/clock.txt @@ -0,0 +1,21 @@ +;;; TOOL: run-interp-wasi +;;; ARGS: --trace +;; __WASI_CLOCKID_REALTIME = 0 + +(import "wasi_snapshot_preview1" "clock_time_get" (func $clock_time_get (param i32 i64 i32) (result i32))) +(memory (export "memory") 1) +(data (i32.const 20) "\00\00\00\00\00\00\00\00") + +(func (export "_start") + (call $clock_time_get (i32.const 0) (i64.const 0) (i32.const 20)) + drop +) +(;; STDOUT ;;; +#0. 0: V:0 | i32.const 0 +#0. 8: V:1 | i64.const 0 +#0. 20: V:2 | i32.const 20 +#0. 28: V:3 | call_import $0 +>>> running wasi function "clock_time_get": +#0. 36: V:1 | drop +#0. 40: V:0 | return +;;; STDOUT ;;) diff --git a/third_party/uvwasi b/third_party/uvwasi -Subproject b0684ea4182f5e4e11a12ab552d7ba1487ade02 +Subproject 55eff19f4c7e69ec151424a037f951e0ad006ed |