summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt16
-rw-r--r--src/interp/interp-wasi.cc250
-rw-r--r--src/interp/interp-wasi.h46
-rw-r--r--src/interp/interp.cc6
-rw-r--r--src/interp/interp.h2
-rw-r--r--src/tools/wasm-interp.cc125
-rw-r--r--test/help/wasm-interp.txt1
-rwxr-xr-xtest/run-tests.py18
-rw-r--r--test/wasi/empty.txt4
-rw-r--r--test/wasi/exit.txt15
-rw-r--r--test/wasi/oob_trap.txt29
-rw-r--r--test/wasi/write_stdout.txt38
m---------third_party/uvwasi0
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