diff options
Diffstat (limited to 'src')
-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 |
5 files changed, 401 insertions, 28 deletions
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) { |