wasi: use WasmMemoryObject handle for perf (#43544)

PR-URL: #43544
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
snek 2022-06-25 11:01:55 -07:00 committed by Gus Caplan
parent b6bc44f8bc
commit f8bb0b183c
No known key found for this signature in database
GPG Key ID: F00BD11880E82F0E
5 changed files with 22 additions and 129 deletions

View File

@ -10,14 +10,12 @@ const {
} = primordials;
const {
ERR_INVALID_ARG_TYPE,
ERR_WASI_ALREADY_STARTED
} = require('internal/errors').codes;
const {
emitExperimentalWarning,
kEmptyObject,
} = require('internal/util');
const { isArrayBuffer } = require('internal/util/types');
const {
validateArray,
validateBoolean,
@ -39,20 +37,6 @@ function setupInstance(self, instance) {
validateObject(instance, 'instance');
validateObject(instance.exports, 'instance.exports');
// WASI::_SetMemory() in src/node_wasi.cc only expects that |memory| is
// an object. It will try to look up the .buffer property when needed
// and fail with UVWASI_EINVAL when the property is missing or is not
// an ArrayBuffer. Long story short, we don't need much validation here
// but we type-check anyway because it helps catch bugs in the user's
// code early.
validateObject(instance.exports.memory, 'instance.exports.memory');
if (!isArrayBuffer(instance.exports.memory.buffer)) {
throw new ERR_INVALID_ARG_TYPE(
'instance.exports.memory.buffer',
['WebAssembly.Memory'],
instance.exports.memory.buffer);
}
self[kInstance] = instance;
self[kSetMemory](instance.exports.memory);
}

View File

@ -72,9 +72,7 @@ inline void Debug(WASI* wasi, Args&&... args) {
} \
} while (0)
using v8::Array;
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::BigInt;
using v8::Context;
@ -89,7 +87,7 @@ using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;
using v8::WasmMemoryObject;
static MaybeLocal<Value> WASIException(Local<Context> context,
int errorno,
@ -1642,26 +1640,22 @@ void WASI::SockShutdown(const FunctionCallbackInfo<Value>& args) {
void WASI::_SetMemory(const FunctionCallbackInfo<Value>& args) {
WASI* wasi;
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsObject());
ASSIGN_OR_RETURN_UNWRAP(&wasi, args.This());
wasi->memory_.Reset(wasi->env()->isolate(), args[0].As<Object>());
CHECK_EQ(args.Length(), 1);
if (!args[0]->IsWasmMemoryObject()) {
return node::THROW_ERR_INVALID_ARG_TYPE(
wasi->env(),
"\"instance.exports.memory\" property must be a WebAssembly.Memory "
"object");
}
wasi->memory_.Reset(wasi->env()->isolate(), args[0].As<WasmMemoryObject>());
}
uvwasi_errno_t WASI::backingStore(char** store, size_t* byte_length) {
Environment* env = this->env();
Local<Object> memory = PersistentToLocal::Strong(this->memory_);
Local<Value> prop;
if (!memory->Get(env->context(), env->buffer_string()).ToLocal(&prop))
return UVWASI_EINVAL;
if (!prop->IsArrayBuffer())
return UVWASI_EINVAL;
Local<ArrayBuffer> ab = prop.As<ArrayBuffer>();
std::shared_ptr<BackingStore> backing_store = ab->GetBackingStore();
Local<WasmMemoryObject> memory = PersistentToLocal::Strong(this->memory_);
std::shared_ptr<BackingStore> backing_store =
memory->Buffer()->GetBackingStore();
*byte_length = backing_store->ByteLength();
*store = static_cast<char*>(backing_store->Data());
CHECK_NOT_NULL(*store);

View File

@ -94,7 +94,7 @@ class WASI : public BaseObject,
inline void writeUInt64(char* memory, uint64_t value, uint32_t offset);
uvwasi_errno_t backingStore(char** store, size_t* byte_length);
uvwasi_t uvw_;
v8::Global<v8::Object> memory_;
v8::Global<v8::WasmMemoryObject> memory_;
uvwasi_mem_t alloc_info_;
size_t current_uvwasi_memory_ = 0;
};

View File

@ -47,7 +47,10 @@ const bufferSource = fixtures.readSync('simple.wasm');
Object.defineProperty(instance, 'exports', {
get() {
return { _initialize: 5, memory: new Uint8Array() };
return {
_initialize: 5,
memory: new WebAssembly.Memory({ initial: 1 }),
};
},
});
assert.throws(
@ -70,7 +73,7 @@ const bufferSource = fixtures.readSync('simple.wasm');
return {
_start() {},
_initialize() {},
memory: new Uint8Array(),
memory: new WebAssembly.Memory({ initial: 1 }),
};
}
});
@ -97,55 +100,11 @@ const bufferSource = fixtures.readSync('simple.wasm');
() => { wasi.initialize(instance); },
{
code: 'ERR_INVALID_ARG_TYPE',
message: /"instance\.exports\.memory" property must be of type object/
message: /"instance\.exports\.memory" property must be a WebAssembly\.Memory object/
}
);
}
{
// Verify that a non-ArrayBuffer memory.buffer is rejected.
const wasi = new WASI({});
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);
Object.defineProperty(instance, 'exports', {
get() {
return {
_initialize() {},
memory: {},
};
}
});
// The error message is a little white lie because any object
// with a .buffer property of type ArrayBuffer is accepted,
// but 99% of the time a WebAssembly.Memory object is used.
assert.throws(
() => { wasi.initialize(instance); },
{
code: 'ERR_INVALID_ARG_TYPE',
message: /"instance\.exports\.memory\.buffer" property must be an WebAssembly\.Memory/
}
);
}
{
// Verify that an argument that duck-types as a WebAssembly.Instance
// is accepted.
const wasi = new WASI({});
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);
Object.defineProperty(instance, 'exports', {
get() {
return {
_initialize() {},
memory: { buffer: new ArrayBuffer(0) },
};
}
});
wasi.initialize(instance);
}
{
// Verify that a WebAssembly.Instance from another VM context is accepted.
const wasi = new WASI({});

View File

@ -47,7 +47,7 @@ const bufferSource = fixtures.readSync('simple.wasm');
Object.defineProperty(instance, 'exports', {
get() {
return { memory: new Uint8Array() };
return { memory: new WebAssembly.Memory({ initial: 1 }) };
},
});
assert.throws(
@ -70,7 +70,7 @@ const bufferSource = fixtures.readSync('simple.wasm');
return {
_start() {},
_initialize() {},
memory: new Uint8Array(),
memory: new WebAssembly.Memory({ initial: 1 }),
};
}
});
@ -97,55 +97,11 @@ const bufferSource = fixtures.readSync('simple.wasm');
() => { wasi.start(instance); },
{
code: 'ERR_INVALID_ARG_TYPE',
message: /"instance\.exports\.memory" property must be of type object/
message: /"instance\.exports\.memory" property must be a WebAssembly\.Memory object/
}
);
}
{
// Verify that a non-ArrayBuffer memory.buffer is rejected.
const wasi = new WASI({});
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);
Object.defineProperty(instance, 'exports', {
get() {
return {
_start() {},
memory: {},
};
}
});
// The error message is a little white lie because any object
// with a .buffer property of type ArrayBuffer is accepted,
// but 99% of the time a WebAssembly.Memory object is used.
assert.throws(
() => { wasi.start(instance); },
{
code: 'ERR_INVALID_ARG_TYPE',
message: /"instance\.exports\.memory\.buffer" property must be an WebAssembly\.Memory/
}
);
}
{
// Verify that an argument that duck-types as a WebAssembly.Instance
// is accepted.
const wasi = new WASI({});
const wasm = await WebAssembly.compile(bufferSource);
const instance = await WebAssembly.instantiate(wasm);
Object.defineProperty(instance, 'exports', {
get() {
return {
_start() {},
memory: { buffer: new ArrayBuffer(0) },
};
}
});
wasi.start(instance);
}
{
// Verify that a WebAssembly.Instance from another VM context is accepted.
const wasi = new WASI({});