Skip to content
This repository has been archived by the owner on Oct 7, 2020. It is now read-only.

wasi: improve JavaScript API #4

Merged
merged 2 commits into from
Sep 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 34 additions & 7 deletions lib/wasi.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// TODO(cjihrig): Put WASI behind a flag.
// TODO(cjihrig): Provide a mechanism to bind to WASM modules.
'use strict';
/* global WebAssembly */
const { Array, ArrayPrototype } = primordials;
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
const { WASI: _WASI } = internalBinding('wasi');
const { isAnyArrayBuffer } = require('internal/util/types');


class WASI {
Expand All @@ -12,7 +14,7 @@ class WASI {
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);

// eslint-disable-next-line prefer-const
let { args, env, preopens } = options;
let { args, env, preopens, memory } = options;

if (Array.isArray(args))
args = ArrayPrototype.map(args, (arg) => { return String(arg); });
Expand Down Expand Up @@ -43,12 +45,37 @@ class WASI {

// TODO(cjihrig): Validate preopen object schema.

// TODO(cjihrig): Temporarily expose these for development.
// eslint-disable-next-line no-undef
const memory = Buffer.alloc(200000);
this._memory = memory;
this._view = new DataView(memory.buffer);
this._wasi = new _WASI(args, envPairs, preopens, memory);
if (memory instanceof WebAssembly.Memory) {
memory = memory.buffer;
} else if (!isAnyArrayBuffer(memory)) {
throw new ERR_INVALID_ARG_TYPE(
'options.memory', 'WebAssembly.Memory', memory);
}

const wrap = new _WASI(args, envPairs, preopens, memory);

for (const prop in wrap) {
wrap[prop] = wrap[prop].bind(wrap);
}

this.wasiImport = wrap;
}

static start(instance) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if instance.memory !== options.memory passed to new WASI? Should this be considered an API hazard?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since start() is a static method, I don't think that should matter, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right I see, but then should there be some memory binding in this method instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I'm assuming that WASI.start() is not the same thing as the API being discussed in #3. I'm thinking of WASI.start() as more of a building block API, and #3 as a higher level API.

With that assumption, consider the following example:

'use strict';
const fs = require('fs');
const { WASI } = require('wasi');
const pathname = '/path/to/something.wasm';
const buffer = fs.readFileSync(pathname);
const memory = new WebAssembly.Memory({ initial: 1 });
const wasi = new WASI({ args: [], env: process.env, memory });

(async () => {
  const compiled = await WebAssembly.compile(buffer);
  const importObject = { wasi_unstable: wasi.wasiImport };
  const instance = await WebAssembly.instantiate(compiled, importObject);

  WASI.start(instance);
})();

In this case, WASI.start() just attempts to call the _start() or __wasi_unstable_reactor_start() export if one exists. By this point, instance should have everything bound properly, or WebAssembly.instantiate() will fail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. For commands that export their memories, a memory injection API would still likely be needed though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I plan to have a WASI.prototype.setMemory() (or something along those lines) eventually.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If setMemory can take a SharedArrayBuffer that could be awesome (although I am not sure if that is allowed)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pushed up 2b0131c to this PR. It allows the memory passed to the WASI constructor to be a SharedArrayBuffer (or any other ArrayBuffer). Once setMemory() is added, it will also support SABs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really cool and useful thanks :]

if (!(instance instanceof WebAssembly.Instance)) {
throw new ERR_INVALID_ARG_TYPE(
'instance', 'WebAssembly.Instance', instance);
}

const exports = instance.exports;

if (exports === null || typeof exports !== 'object')
throw new ERR_INVALID_ARG_TYPE('instance.exports', 'Object', exports);

if (exports._start)
exports._start();
else if (exports.__wasi_unstable_reactor_start)
exports.__wasi_unstable_reactor_start();
}
}

Expand Down
43 changes: 18 additions & 25 deletions src/node_wasi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace node {
namespace wasi {

using v8::Array;
using v8::ArrayBufferView;
using v8::ArrayBuffer;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
Expand All @@ -26,10 +26,7 @@ WASI::WASI(Environment* env,
uvwasi_options_t* options) : BaseObject(env, object) {
/* uvwasi_errno_t err = */ uvwasi_init(&uvw_, options);

if (memory->IsNull())
memory_.Reset();
else
memory_.Reset(env->isolate(), memory.As<ArrayBufferView>());
memory_.Reset(env->isolate(), memory.As<ArrayBuffer>());
}


Expand All @@ -44,7 +41,7 @@ void WASI::New(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsArray());
CHECK(args[1]->IsArray());
// CHECK(args[2]->IsArray());
CHECK(args[3]->IsArrayBufferView() || args[3]->IsNull());
CHECK(args[3]->IsArrayBuffer() || args[3]->IsSharedArrayBuffer());

Environment* env = Environment::GetCurrent(args);
Local<Context> context = env->context();
Expand Down Expand Up @@ -106,17 +103,14 @@ void WASI::ArgsGet(const FunctionCallbackInfo<Value>& args) {
CHECK(args[1]->IsUint32());
ASSIGN_OR_RETURN_UNWRAP(&wasi, args.This());
Environment* env = wasi->env();
Local<ArrayBufferView> abv = PersistentToLocal::Default(env->isolate(),
wasi->memory_);
// if (!abv->HasBuffer())
// return UVWASI_ENOBUFS;
CHECK(abv->HasBuffer());
Local<ArrayBuffer> ab = PersistentToLocal::Default(env->isolate(),
wasi->memory_);

// TODO(cjihrig): Check for buffer overflows.

uint32_t argv_offset = args[0].As<Uint32>()->Value();
uint32_t argv_buf_offset = args[1].As<Uint32>()->Value();
char* buf = static_cast<char*>(abv->Buffer()->GetContents().Data());
char* buf = static_cast<char*>(ab->GetContents().Data());
char** argv = new char*[wasi->uvw_.argc];
char* argv_buf = &buf[argv_buf_offset];
uvwasi_errno_t err = uvwasi_args_get(&wasi->uvw_, argv, argv_buf);
Expand Down Expand Up @@ -171,17 +165,14 @@ void WASI::EnvironGet(const FunctionCallbackInfo<Value>& args) {
CHECK(args[1]->IsUint32());
ASSIGN_OR_RETURN_UNWRAP(&wasi, args.This());
Environment* env = wasi->env();
Local<ArrayBufferView> abv = PersistentToLocal::Default(env->isolate(),
wasi->memory_);
// if (!abv->HasBuffer())
// return UVWASI_ENOBUFS;
CHECK(abv->HasBuffer());
Local<ArrayBuffer> ab = PersistentToLocal::Default(env->isolate(),
wasi->memory_);

// TODO(cjihrig): Check for buffer overflows.

uint32_t environ_offset = args[0].As<Uint32>()->Value();
uint32_t environ_buf_offset = args[1].As<Uint32>()->Value();
char* buf = static_cast<char*>(abv->Buffer()->GetContents().Data());
char* buf = static_cast<char*>(ab->GetContents().Data());
char** environ = new char*[wasi->uvw_.envc];
char* environ_buf = &buf[environ_buf_offset];
uvwasi_errno_t err = uvwasi_environ_get(&wasi->uvw_, environ, environ_buf);
Expand Down Expand Up @@ -380,7 +371,12 @@ void WASI::PollOneoff(const FunctionCallbackInfo<Value>& args) {


void WASI::ProcExit(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(UVWASI_ENOTSUP);
WASI* wasi;
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsUint32());
ASSIGN_OR_RETURN_UNWRAP(&wasi, args.This());
uint32_t code = args[0].As<Uint32>()->Value();
args.GetReturnValue().Set(uvwasi_proc_exit(&wasi->uvw_, code));
}


Expand Down Expand Up @@ -416,12 +412,9 @@ void WASI::SockShutdown(const FunctionCallbackInfo<Value>& args) {

inline uvwasi_errno_t WASI::writeUInt32(uint32_t value, uint32_t offset) {
Environment* env = this->env();
Local<ArrayBufferView> abv = PersistentToLocal::Default(env->isolate(),
this->memory_);
if (!abv->HasBuffer())
return UVWASI_ENOBUFS;

uint8_t* buf = static_cast<uint8_t*>(abv->Buffer()->GetContents().Data());
Local<ArrayBuffer> ab = PersistentToLocal::Default(env->isolate(),
this->memory_);
uint8_t* buf = static_cast<uint8_t*>(ab->GetContents().Data());
// Bounds check. UVWASI_EOVERFLOW

buf[offset++] = value & 0xFF;
Expand Down
2 changes: 1 addition & 1 deletion src/node_wasi.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class WASI : public BaseObject {
~WASI() override;
inline uvwasi_errno_t writeUInt32(uint32_t value, uint32_t offset);
uvwasi_t uvw_;
v8::Persistent<v8::ArrayBufferView> memory_;
v8::Persistent<v8::ArrayBuffer> memory_;
};


Expand Down