Skip to content

Commit

Permalink
Add initial MVP of WASI API support to wasm-interp
Browse files Browse the repository at this point in the history
This is proof of concept that only implements the `proc_exit` API.
Extending this to the full WASI API will to follow assuming this
approach seems reasonable.

Fixes #1409
  • Loading branch information
sbc100 committed May 11, 2020
1 parent 831c026 commit f2a29d2
Show file tree
Hide file tree
Showing 14 changed files with 446 additions and 29 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 12 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ 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 experimental
option(WITH_WASI "Build WASI support via uvwasi" ON)

if (MSVC)
set(COMPILER_IS_CLANG 0)
Expand Down Expand Up @@ -419,10 +421,19 @@ 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)
endif()

# wasm-interp
wabt_executable(
NAME wasm-interp
SOURCES src/tools/wasm-interp.cc
SOURCES src/tools/wasm-interp.cc src/interp/interp-wasi.cc
LIBS ${INTERP_LIBS}
WITH_LIBM
INSTALL
)
Expand Down
216 changes: 216 additions & 0 deletions src/interp/interp-wasi.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* 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 <unordered_map>

using namespace wabt;
using namespace wabt::interp;

namespace {

class WasiInstance {
public:
WasiInstance(Instance* instance,
uvwasi_s* uvwasi,
Memory* memory,
Stream* trace_stream)
: instance(instance),
uvwasi(uvwasi),
memory(memory),
trace_stream(trace_stream) {}

Instance* instance;
uvwasi_s* uvwasi;
// The memory accociated with the instance. Looked up once on startup
// and cached here.
Memory* memory;
// The trace stream accosiated with the instance.
Stream* trace_stream;

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_;
uvwasi_ciovec_t* iovs = getMemPtr<uvwasi_ciovec_t>(iovptr, iovcnt);
//printf("got iovs = %p\n", iovs);
//printf("iovs[0].iov_len = %d\n", iovs[0].buf_len);
uint32_t out;
//uvwasi_ciovec_t** iovs = new uvwasi_ciovec_t*[iovcnt];
//uvwasi_ciovec_t* iov_data = new uvwasi_ciovec_t[iovcnt];
// uint32_t* out_size = NULL;//(uint32_t*)getMemoryAddress(arg3);
uvwasi_fd_write(uvwasi, arg0.i32_, iovs, iovcnt, &out);
//delete[] iovs;
return Result::Ok;
}

protected:
template <typename T>
T* getMemPtr(uint32_t address, uint32_t size) {
printf("getMemPtr address=%d size=%d(%lu)\n", address, size,
size * sizeof(T));
if (!memory->IsValidAccess(address, 0, size * sizeof(T))) {
printf("invalid access\n");
}
return reinterpret_cast<T*>(memory->UnsafeData() + address);
}
};

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.get(), 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
46 changes: 46 additions & 0 deletions src/interp/interp-wasi.h
Original file line number Diff line number Diff line change
@@ -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_ */
6 changes: 6 additions & 0 deletions src/interp/interp.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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_);
Expand Down
2 changes: 2 additions & 0 deletions src/interp/interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,8 @@ class Thread : public Object {

Store& store();

Instance* GetCallerInstance();

private:
friend Store;
friend DefinedFunc;
Expand Down
2 changes: 2 additions & 0 deletions src/interp/wasi-api.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

WASI_API(proc_exit, i32)
Loading

0 comments on commit f2a29d2

Please sign in to comment.