diff options
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | CMakeLists.txt | 16 | ||||
-rw-r--r-- | src/interp/interp-wasi.cc | 250 | ||||
-rw-r--r-- | src/interp/interp-wasi.h | 46 | ||||
-rw-r--r-- | src/interp/interp.cc | 6 | ||||
-rw-r--r-- | src/interp/interp.h | 2 | ||||
-rw-r--r-- | src/tools/wasm-interp.cc | 125 | ||||
-rw-r--r-- | test/help/wasm-interp.txt | 1 | ||||
-rwxr-xr-x | test/run-tests.py | 18 | ||||
-rw-r--r-- | test/wasi/empty.txt | 4 | ||||
-rw-r--r-- | test/wasi/exit.txt | 15 | ||||
-rw-r--r-- | test/wasi/oob_trap.txt | 29 | ||||
-rw-r--r-- | test/wasi/write_stdout.txt | 38 | ||||
m--------- | third_party/uvwasi | 0 |
14 files changed, 522 insertions, 31 deletions
diff --git a/.gitmodules b/.gitmodules index 7352235c..a47f2eca 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "third_party/wasm-c-api"] path = third_party/wasm-c-api url = https://github.com/WebAssembly/wasm-c-api +[submodule "third_party/uvwasi"] + path = third_party/uvwasi + url = https://github.com/cjihrig/uvwasi diff --git a/CMakeLists.txt b/CMakeLists.txt index 65f456bd..ddf077fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,9 @@ option(USE_UBSAN "Use undefined behavior sanitizer" OFF) option(CODE_COVERAGE "Build with code coverage enabled" OFF) option(WITH_EXCEPTIONS "Build with exceptions enabled" OFF) option(WERROR "Build with warnings as errors" OFF) +# WASI support is still a work in progress. +# Only a handful of syscalls are supported at this point. +option(WITH_WASI "Build WASI support via uvwasi" OFF) if (MSVC) set(COMPILER_IS_CLANG 0) @@ -419,10 +422,21 @@ if (NOT EMSCRIPTEN) INSTALL ) + if(WITH_WASI) + add_subdirectory("third_party/uvwasi" EXCLUDE_FROM_ALL) + include_directories("${libuv_SOURCE_DIR}/include") + include_directories(third_party/uvwasi/include) + add_definitions(-DWITH_WASI) + set(INTERP_LIBS uvwasi_a) + set(EXTRA_INTERP_SRC src/interp/interp-wasi.cc) + endif() + # wasm-interp + wabt_executable( NAME wasm-interp - SOURCES src/tools/wasm-interp.cc + SOURCES src/tools/wasm-interp.cc ${EXTRA_INTERP_SRC} + LIBS ${INTERP_LIBS} WITH_LIBM INSTALL ) diff --git a/src/interp/interp-wasi.cc b/src/interp/interp-wasi.cc new file mode 100644 index 00000000..9fa3e7f9 --- /dev/null +++ b/src/interp/interp-wasi.cc @@ -0,0 +1,250 @@ +/* + * Copyright 2020 WebAssembly Community Group participants + * + * 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. + */ + +#include "src/interp/interp-wasi.h" +#include "src/interp/interp-util.h" + +#ifdef WITH_WASI + +#include "uvwasi.h" + +#include <cinttypes> +#include <unordered_map> + +using namespace wabt; +using namespace wabt::interp; + +namespace { + +// Types that align with WASI specis on size and alignment. +// TODO(sbc): Auto-generate this from witx. +typedef uint32_t __wasi_size_t; +typedef uint32_t __wasi_ptr_t; + +_Static_assert(sizeof(__wasi_size_t) == 4, "witx calculated size"); +_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"); + +struct __wasi_iovec_t { + __wasi_ptr_t buf; + __wasi_size_t buf_len; +}; + +_Static_assert(sizeof(__wasi_iovec_t) == 8, "witx calculated size"); +_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"); + +class WasiInstance { + public: + WasiInstance(Instance::Ptr instance, + uvwasi_s* uvwasi, + Memory* memory, + Stream* trace_stream) + : trace_stream(trace_stream), + instance(instance), + uvwasi(uvwasi), + memory(memory) {} + + 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 fd_write(const Values& params, Values& results, Trap::Ptr* trap) { + const Value arg0 = params[0]; + int32_t iovptr = params[1].i32_; + int32_t iovcnt = params[2].i32_; + __wasi_iovec_t* wasm_iovs = getMemPtr<__wasi_iovec_t>(iovptr, iovcnt, trap); + if (!wasm_iovs) { + return Result::Error; + } + uvwasi_ciovec_t* iovs = new uvwasi_ciovec_t[iovcnt]; + for (int i = 0; i < iovcnt; i++) { + iovs[0].buf_len = wasm_iovs[0].buf_len; + iovs[0].buf = + getMemPtr<uint8_t>(wasm_iovs[0].buf, wasm_iovs[0].buf_len, trap); + if (!iovs[0].buf) { + return Result::Error; + } + } + __wasi_ptr_t* out_addr = getMemPtr<__wasi_ptr_t>(params[3].i32_, 1, trap); + if (!out_addr) { + return Result::Error; + } + uvwasi_fd_write(uvwasi, arg0.i32_, iovs, iovcnt, out_addr); + delete[] iovs; + return Result::Ok; + } + + // The trace stream accosiated with the instance. + Stream* trace_stream; + + Instance::Ptr instance; + private: + template <typename T> + T* getMemPtr(uint32_t address, uint32_t size, 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())); + return nullptr; + } + return reinterpret_cast<T*>(memory->UnsafeData() + address); + } + + uvwasi_s* uvwasi; + // The memory accociated with the instance. Looked up once on startup + // and cached here. + Memory* memory; +}; + +std::unordered_map<Instance*, WasiInstance*> wasiInstances; + +// TODO(sbc): Auto-generate this. + +#define WASI_CALLBACK(NAME) \ + Result NAME(Thread& thread, const Values& params, Values& results, \ + Trap::Ptr* trap) { \ + Instance* instance = thread.GetCallerInstance(); \ + assert(instance); \ + WasiInstance* wasi_instance = wasiInstances[instance]; \ + if (wasi_instance->trace_stream) { \ + wasi_instance->trace_stream->Writef( \ + ">>> running wasi function \"%s\":\n", #NAME); \ + } \ + return wasi_instance->NAME(params, results, trap); \ + } + +WASI_CALLBACK(proc_exit); +WASI_CALLBACK(fd_write); + +} // namespace + +namespace wabt { +namespace interp { + +Result WasiBindImports(const Module::Ptr& module, + RefVec& imports, + Stream* stream, + Stream* trace_stream) { + Store* store = module.store(); + for (auto&& import : module->desc().imports) { + if (import.type.type->kind != ExternKind::Func) { + stream->Writef("wasi error: invalid import type: %s\n", + import.type.name.c_str()); + return Result::Error; + } + + if (import.type.module != "wasi_snapshot_preview1") { + stream->Writef("wasi error: unknown module import: %s\n", + import.type.module.c_str()); + return Result::Error; + } + + auto func_type = *cast<FuncType>(import.type.type.get()); + auto import_name = StringPrintf("%s.%s", import.type.module.c_str(), + import.type.name.c_str()); + HostFunc::Ptr host_func; + + // TODO(sbc): Auto-generate this. + if (import.type.name == "proc_exit") { + host_func = HostFunc::New(*store, func_type, proc_exit); + } else if (import.type.name == "fd_write") { + host_func = HostFunc::New(*store, func_type, fd_write); + } else { + stream->Writef("unknown wasi_snapshot_preview1 import: %s\n", + import.type.name.c_str()); + return Result::Error; + } + imports.push_back(host_func.ref()); + } + + return Result::Ok; +} + +Result WasiRunStart(const Instance::Ptr& instance, + uvwasi_s* uvwasi, + Stream* stream, + Stream* trace_stream) { + Store* store = instance.store(); + auto module = store->UnsafeGet<Module>(instance->module()); + auto&& module_desc = module->desc(); + + Func::Ptr start; + Memory::Ptr memory; + for (auto&& export_ : module_desc.exports) { + if (export_.type.name == "memory") { + if (export_.type.type->kind != ExternalKind::Memory) { + stream->Writef("wasi error: memory export has incorrect type\n"); + return Result::Error; + } + memory = store->UnsafeGet<Memory>(instance->memories()[export_.index]); + } + if (export_.type.name == "_start") { + if (export_.type.type->kind != ExternalKind::Func) { + stream->Writef("wasi error: _start export is not a function\n"); + return Result::Error; + } + start = store->UnsafeGet<Func>(instance->funcs()[export_.index]); + } + if (start && memory) { + break; + } + } + + if (!start) { + stream->Writef("wasi error: _start export not found\n"); + return Result::Error; + } + + if (!memory) { + stream->Writef("wasi error: memory export not found\n"); + return Result::Error; + } + + if (start->type().params.size() || start->type().results.size()) { + stream->Writef("wasi error: invalid _start signature\n"); + return Result::Error; + } + + // Register memory + auto* wasi = new WasiInstance(instance, uvwasi, memory.get(), trace_stream); + wasiInstances[instance.get()] = wasi; + + // Call start ([] -> []) + Values params; + Values results; + Trap::Ptr trap; + Result res = start->Call(*store, params, results, &trap, trace_stream); + if (trap) { + WriteTrap(stream, " error", trap); + } + + // Unregister memory + wasiInstances.erase(instance.get()); + delete wasi; + return res; +} + +} // namespace interp +} // namespace wabt + +#endif diff --git a/src/interp/interp-wasi.h b/src/interp/interp-wasi.h new file mode 100644 index 00000000..c07a3e6e --- /dev/null +++ b/src/interp/interp-wasi.h @@ -0,0 +1,46 @@ +/* + * Copyright 2020 WebAssembly Community Group participants + * + * 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. + */ + +#ifndef WABT_INTERP_WASI_H_ +#define WABT_INTERP_WASI_H_ + +#include "src/common.h" +#include "src/error.h" +#include "src/interp/interp.h" + +#ifdef WITH_WASI + +struct uvwasi_s; + +namespace wabt { +namespace interp { + +Result WasiBindImports(const Module::Ptr& module, + RefVec& imports, + Stream* stream, + Stream* trace_stream); + +Result WasiRunStart(const Instance::Ptr& instance, + uvwasi_s* uvwasi, + Stream* stream, + Stream* trace_stream); + +} // namespace interp +} // namespace wabt + +#endif + +#endif /* WABT_INTERP_WASI_H_ */ diff --git a/src/interp/interp.cc b/src/interp/interp.cc index 4704500e..d9cb9797 100644 --- a/src/interp/interp.cc +++ b/src/interp/interp.cc @@ -932,6 +932,12 @@ void Thread::PushValues(const ValueTypes& types, const Values& values) { } #define TRAP_UNLESS(cond, msg) TRAP_IF(!(cond), msg) +Instance* Thread::GetCallerInstance() { + if (frames_.size() < 2) + return nullptr; + return frames_[frames_.size() - 2].inst; +} + RunResult Thread::PushCall(Ref func, u32 offset, Trap::Ptr* out_trap) { TRAP_IF(frames_.size() == frames_.capacity(), "call stack exhausted"); frames_.emplace_back(func, values_.size(), offset, inst_, mod_); diff --git a/src/interp/interp.h b/src/interp/interp.h index c0ca9d0f..ec7a6f78 100644 --- a/src/interp/interp.h +++ b/src/interp/interp.h @@ -1021,6 +1021,8 @@ class Thread : public Object { Store& store(); + Instance* GetCallerInstance(); + private: friend Store; friend DefinedFunc; diff --git a/src/tools/wasm-interp.cc b/src/tools/wasm-interp.cc index b7182af6..36ebbb9b 100644 --- a/src/tools/wasm-interp.cc +++ b/src/tools/wasm-interp.cc @@ -27,10 +27,15 @@ #include "src/feature.h" #include "src/interp/binary-reader-interp.h" #include "src/interp/interp-util.h" +#include "src/interp/interp-wasi.h" #include "src/interp/interp.h" #include "src/option-parser.h" #include "src/stream.h" +#ifdef WITH_WASI +#include "uvwasi.h" +#endif + using namespace wabt; using namespace wabt::interp; @@ -42,6 +47,7 @@ static bool s_run_all_exports; static bool s_host_print; static bool s_dummy_import_func; static Features s_features; +static bool s_wasi; static std::unique_ptr<FileStream> s_log_stream; static std::unique_ptr<FileStream> s_stdout_stream; @@ -89,6 +95,10 @@ static void ParseOptions(int argc, char** argv) { }); parser.AddOption('t', "trace", "Trace execution", []() { s_trace_stream = s_stdout_stream.get(); }); + parser.AddOption("wasi", + "Assume input module is WASI compliant (Export " + " WASI API the the module and invoke _start function)", + []() { s_wasi = true; }); parser.AddOption( "run-all-exports", "Run all the exported functions, in order. Useful for testing", @@ -137,30 +147,10 @@ Result RunAllExports(const Instance::Ptr& instance, Errors* errors) { return result; } -Result ReadAndInstantiateModule(const char* module_filename, - Errors* errors, - Instance::Ptr* out_instance) { +static void BindImports(const Module::Ptr& module, RefVec& imports) { auto* stream = s_stdout_stream.get(); - std::vector<uint8_t> file_data; - CHECK_RESULT(ReadFile(module_filename, &file_data)); - - ModuleDesc module_desc; - const bool kReadDebugNames = true; - const bool kStopOnFirstError = true; - const bool kFailOnCustomSectionError = true; - ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames, - kStopOnFirstError, kFailOnCustomSectionError); - CHECK_RESULT(ReadBinaryInterp(file_data.data(), file_data.size(), options, - errors, &module_desc)); - - if (s_verbose) { - module_desc.istream.Disassemble(stream); - } - - auto module = Module::New(s_store, module_desc); - RefVec imports; - for (auto&& import : module_desc.imports) { + for (auto&& import : module->desc().imports) { if (import.type.type->kind == ExternKind::Func && ((s_host_print && import.type.module == "host" && import.type.name == "print") || @@ -186,26 +176,105 @@ Result ReadAndInstantiateModule(const char* module_filename, // instantiation will fail. imports.push_back(Ref::Null); } +} + +static Result ReadModule(const char* module_filename, + Errors* errors, + Module::Ptr* out_module) { + auto* stream = s_stdout_stream.get(); + std::vector<uint8_t> file_data; + CHECK_RESULT(ReadFile(module_filename, &file_data)); + ModuleDesc module_desc; + const bool kReadDebugNames = true; + const bool kStopOnFirstError = true; + const bool kFailOnCustomSectionError = true; + ReadBinaryOptions options(s_features, s_log_stream.get(), kReadDebugNames, + kStopOnFirstError, kFailOnCustomSectionError); + CHECK_RESULT(ReadBinaryInterp(file_data.data(), file_data.size(), options, + errors, &module_desc)); + + if (s_verbose) { + module_desc.istream.Disassemble(stream); + } + + *out_module = Module::New(s_store, module_desc); + return Result::Ok; +} + +static Result InstantiateModule(RefVec& imports, + const Module::Ptr& module, + Instance::Ptr* out_instance) { RefPtr<Trap> trap; *out_instance = Instance::Instantiate(s_store, module.ref(), imports, &trap); if (!*out_instance) { - WriteTrap(stream, "error initializing module", trap); + WriteTrap(s_stdout_stream.get(), "error initializing module", trap); return Result::Error; } - return Result::Ok; } static Result ReadAndRunModule(const char* module_filename) { Errors errors; + Module::Ptr module; + Result result = ReadModule(module_filename, &errors, &module); + if (!Succeeded(result)) { + FormatErrorsToFile(errors, Location::Type::Binary); + return result; + } + + RefVec imports; + +#if WITH_WASI + uvwasi_t uvwasi; +#endif + + if (s_wasi) { +#if WITH_WASI + uvwasi_errno_t err; + uvwasi_options_t init_options; + /* Setup the initialization 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 = NULL; + init_options.envp = NULL; + init_options.preopenc = 0; + init_options.preopens = NULL; + init_options.allocator = NULL; + + err = uvwasi_init(&uvwasi, &init_options); + if (err != UVWASI_ESUCCESS) { + s_stdout_stream.get()->Writef("error initialiazing uvwasi: %d\n", err); + return Result::Error; + } + CHECK_RESULT(WasiBindImports(module, imports, s_stdout_stream.get(), + s_trace_stream)); +#else + s_stdout_stream.get()->Writef("wasi support not compiled in\n"); + return Result::Error; +#endif + } else { + BindImports(module, imports); + } + BindImports(module, imports); + Instance::Ptr instance; - Result result = ReadAndInstantiateModule(module_filename, &errors, &instance); - if (Succeeded(result) && s_run_all_exports) { + CHECK_RESULT(InstantiateModule(imports, module, &instance)); + + if (s_run_all_exports) { RunAllExports(instance, &errors); } - FormatErrorsToFile(errors, Location::Type::Binary); - return result; +#ifdef WITH_WASI + if (s_wasi) { + CHECK_RESULT( + WasiRunStart(instance, &uvwasi, s_stdout_stream.get(), s_trace_stream)); + } +#endif + + return Result::Ok; } int ProgramMain(int argc, char** argv) { diff --git a/test/help/wasm-interp.txt b/test/help/wasm-interp.txt index 27c5573c..b6d246a5 100644 --- a/test/help/wasm-interp.txt +++ b/test/help/wasm-interp.txt @@ -40,6 +40,7 @@ options: -V, --value-stack-size=SIZE Size in elements of the value stack -C, --call-stack-size=SIZE Size in elements of the call stack -t, --trace Trace execution + --wasi Assume input module is WASI compliant (Export WASI API the the module and invoke _start function) --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/run-tests.py b/test/run-tests.py index 1b49f4cc..9d01b564 100755 --- a/test/run-tests.py +++ b/test/run-tests.py @@ -88,6 +88,11 @@ TOOLS = { ('RUN', '%(wasm-interp)s %(temp_file)s.wasm --run-all-exports'), ('VERBOSE-ARGS', ['--print-cmd', '-v']), ], + 'run-interp-wasi': [ + ('RUN', '%(wat2wasm)s %(in_file)s -o %(temp_file)s.wasm'), + ('RUN', '%(wasm-interp)s --wasi %(temp_file)s.wasm'), + ('VERBOSE-ARGS', ['--print-cmd', '-v']), + ], 'run-interp-spec': [ ('RUN', '%(wast2json)s %(in_file)s -o %(temp_file)s.json'), ('RUN', '%(spectest-interp)s %(temp_file)s.json'), @@ -673,9 +678,13 @@ class Status(object): sys.stderr.write('\r%s\r' % (' ' * self.last_length)) -def FindTestFiles(ext, filter_pattern_re): +def FindTestFiles(ext, filter_pattern_re, exclude_dirs): tests = [] for root, dirs, files in os.walk(TEST_DIR): + for ex in exclude_dirs: + if ex in dirs: + # Filtering out dirs here causes os.walk not to descend into them + dirs.remove(ex) for f in files: path = os.path.join(root, f) if os.path.splitext(f)[1] == ext: @@ -908,12 +917,17 @@ def main(args): parser.error('--stop-interactive only works with -j1') if options.patterns: + exclude_dirs = [] pattern_re = '|'.join( fnmatch.translate('*%s*' % p) for p in options.patterns) else: pattern_re = '.*' + # By default, exclude wasi tests because WASI support is not include + # by int the build by default. + # TODO(sbc): Find some way to detect the WASI support. + exclude_dirs = ['wasi'] - test_names = FindTestFiles('.txt', pattern_re) + test_names = FindTestFiles('.txt', pattern_re, exclude_dirs) if options.list: for test_name in test_names: diff --git a/test/wasi/empty.txt b/test/wasi/empty.txt new file mode 100644 index 00000000..cc5eb846 --- /dev/null +++ b/test/wasi/empty.txt @@ -0,0 +1,4 @@ +;;; TOOL: run-interp-wasi +(memory (export "memory") 1) +(func (export "_start") +) diff --git a/test/wasi/exit.txt b/test/wasi/exit.txt new file mode 100644 index 00000000..5e7be912 --- /dev/null +++ b/test/wasi/exit.txt @@ -0,0 +1,15 @@ +;;; TOOL: run-interp-wasi +;;; ARGS: --trace +;;; ERROR: 42 +(import "wasi_snapshot_preview1" "proc_exit" (func $exit (param i32))) + +(memory (export "memory") 1) + +(func (export "_start") + (call $exit (i32.const 42)) +) +(;; STDOUT ;;; +#0. 0: V:0 | i32.const 42 +#0. 8: V:1 | call_import $0 +>>> running wasi function "proc_exit": +;;; STDOUT ;;) diff --git a/test/wasi/oob_trap.txt b/test/wasi/oob_trap.txt new file mode 100644 index 00000000..eb191b07 --- /dev/null +++ b/test/wasi/oob_trap.txt @@ -0,0 +1,29 @@ +;;; TOOL: run-interp-wasi +;;; ARGS: --trace +;;; ERROR: 1 +;; +;; Just like the write_stdout.txt, but with an interior point that is OOB. +;; Specifically rather than pointing to the `hello` string at address zero, +;; iovec[0].buf points to memsize - 1 (65536 - 1 = 65535 = 0xffff) +;; + +(import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32))) +(memory (export "memory") 1) +(data (i32.const 0) "hello\n\00\00") +(data (i32.const 8) "\08\00\00\00") +(data (i32.const 12) "\ff\ff\00\00\06\00\00\00") +(data (i32.const 20) "\00\00\00\00") + +(func (export "_start") + (call $fd_write (i32.const 1) (i32.const 12) (i32.const 1) (i32.const 20)) + drop +) +(;; STDOUT ;;; +#0. 0: V:0 | i32.const 1 +#0. 8: V:1 | i32.const 12 +#0. 16: V:2 | i32.const 1 +#0. 24: V:3 | i32.const 20 +#0. 32: V:4 | call_import $0 +>>> running wasi function "fd_write": + error: out of bounds memory access: [65535, 65541) >= max value 65536 +;;; STDOUT ;;) diff --git a/test/wasi/write_stdout.txt b/test/wasi/write_stdout.txt new file mode 100644 index 00000000..44afc5de --- /dev/null +++ b/test/wasi/write_stdout.txt @@ -0,0 +1,38 @@ +;;; TOOL: run-interp-wasi +;;; ARGS: --trace +;; +;; For details of fs_write API see: +;; https://github.com/WebAssembly/WASI/blob/master/phases/snapshot/docs.md#fd_write +;; +;; It takes 4 args: fd, iovec, iovec_len, out_len +;; +;; Data Layout: +;; +;; 0-8 : "hello\n\0\0" +;; 8-12 : iovs ptr : 8 +;; 12-20: iovs[0] : 0, 4 +;; 20-24: bytes written out param +;; + +(import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32))) +(memory (export "memory") 1) +(data (i32.const 0) "hello\n\00\00") +(data (i32.const 8) "\08\00\00\00") +(data (i32.const 12) "\00\00\00\00\06\00\00\00") +(data (i32.const 20) "\00\00\00\00") + +(func (export "_start") + (call $fd_write (i32.const 1) (i32.const 12) (i32.const 1) (i32.const 20)) + drop +) +(;; STDOUT ;;; +hello +#0. 0: V:0 | i32.const 1 +#0. 8: V:1 | i32.const 12 +#0. 16: V:2 | i32.const 1 +#0. 24: V:3 | i32.const 20 +#0. 32: V:4 | call_import $0 +>>> running wasi function "fd_write": +#0. 40: V:1 | drop +#0. 44: V:0 | return +;;; STDOUT ;;) diff --git a/third_party/uvwasi b/third_party/uvwasi new file mode 160000 +Subproject 89f039dc2ac8eef7f49cd510cb69385a2c30fa4 |