Commit 386c747b authored by rossberg's avatar rossberg Committed by Commit bot

Upgrade Wasm JS API, step 1

Implements:
- WebAssembly object,
- WebAssembly.Module constructor,
- WebAssembly.Instance constructor,
- WebAssembly.compile async method,
- and Module and Instance instance objects.

Also, changes ErrorThrower to support capturing errors in a promise reject.

Since we cannot yet compile without fixing the Wasm memory, and cannot validate a module without compiling, the Module constructor and compile method don't do anything yet but checking that their argument is a suitable BufferSource. Instead of a compiled module, the hidden state of a Module object currently is just that buffer.

BUG=

Review-Url: https://codereview.chromium.org/2084573002
Cr-Commit-Position: refs/heads/master@{#37143}
parent 61386fb8
......@@ -239,6 +239,10 @@ enum BindingFlags {
V(SLOPPY_FUNCTION_WITH_READONLY_PROTOTYPE_MAP_INDEX, Map, \
sloppy_function_with_readonly_prototype_map) \
V(WASM_FUNCTION_MAP_INDEX, Map, wasm_function_map) \
V(WASM_MODULE_CONSTRUCTOR_INDEX, JSFunction, wasm_module_constructor) \
V(WASM_INSTANCE_CONSTRUCTOR_INDEX, JSFunction, wasm_instance_constructor) \
V(WASM_MODULE_SYM_INDEX, Symbol, wasm_module_sym) \
V(WASM_INSTANCE_SYM_INDEX, Symbol, wasm_instance_sym) \
V(SLOPPY_ASYNC_FUNCTION_MAP_INDEX, Map, sloppy_async_function_map) \
V(SLOPPY_GENERATOR_FUNCTION_MAP_INDEX, Map, sloppy_generator_function_map) \
V(SLOW_ALIASED_ARGUMENTS_MAP_INDEX, Map, slow_aliased_arguments_map) \
......
......@@ -35,30 +35,25 @@ struct RawBuffer {
size_t size() { return static_cast<size_t>(end - start); }
};
RawBuffer GetRawBufferArgument(
ErrorThrower& thrower, const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() < 1) {
thrower.Error("Argument 0 must be an array buffer");
return {nullptr, nullptr};
}
RawBuffer GetRawBufferSource(
v8::Local<v8::Value> source, ErrorThrower* thrower) {
const byte* start = nullptr;
const byte* end = nullptr;
if (args[0]->IsArrayBuffer()) {
if (source->IsArrayBuffer()) {
// A raw array buffer was passed.
Local<ArrayBuffer> buffer = Local<ArrayBuffer>::Cast(args[0]);
Local<ArrayBuffer> buffer = Local<ArrayBuffer>::Cast(source);
ArrayBuffer::Contents contents = buffer->GetContents();
start = reinterpret_cast<const byte*>(contents.Data());
end = start + contents.ByteLength();
if (start == nullptr || end == start) {
thrower.Error("ArrayBuffer argument is empty");
thrower->Error("ArrayBuffer argument is empty");
}
} else if (args[0]->IsTypedArray()) {
} else if (source->IsTypedArray()) {
// A TypedArray was passed.
Local<TypedArray> array = Local<TypedArray>::Cast(args[0]);
Local<TypedArray> array = Local<TypedArray>::Cast(source);
Local<ArrayBuffer> buffer = array->Buffer();
ArrayBuffer::Contents contents = buffer->GetContents();
......@@ -68,10 +63,10 @@ RawBuffer GetRawBufferArgument(
end = start + array->ByteLength();
if (start == nullptr || end == start) {
thrower.Error("ArrayBuffer argument is empty");
thrower->Error("ArrayBuffer argument is empty");
}
} else {
thrower.Error("Argument 0 must be an ArrayBuffer or Uint8Array");
thrower->Error("Argument 0 must be an ArrayBuffer or Uint8Array");
}
return {start, end};
......@@ -80,9 +75,13 @@ RawBuffer GetRawBufferArgument(
void VerifyModule(const v8::FunctionCallbackInfo<v8::Value>& args) {
HandleScope scope(args.GetIsolate());
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
ErrorThrower thrower(isolate, "WASM.verifyModule()");
ErrorThrower thrower(isolate, "Wasm.verifyModule()");
RawBuffer buffer = GetRawBufferArgument(thrower, args);
if (args.Length() < 1) {
thrower.Error("Argument 0 must be a buffer source");
return;
}
RawBuffer buffer = GetRawBufferSource(args[0], &thrower);
if (thrower.error()) return;
i::Zone zone(isolate->allocator());
......@@ -100,9 +99,13 @@ void VerifyModule(const v8::FunctionCallbackInfo<v8::Value>& args) {
void VerifyFunction(const v8::FunctionCallbackInfo<v8::Value>& args) {
HandleScope scope(args.GetIsolate());
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
ErrorThrower thrower(isolate, "WASM.verifyFunction()");
ErrorThrower thrower(isolate, "Wasm.verifyFunction()");
RawBuffer buffer = GetRawBufferArgument(thrower, args);
if (args.Length() < 1) {
thrower.Error("Argument 0 must be a buffer source");
return;
}
RawBuffer buffer = GetRawBufferSource(args[0], &thrower);
if (thrower.error()) return;
internal::wasm::FunctionResult result;
......@@ -160,16 +163,9 @@ v8::internal::wasm::ZoneBuffer* TranslateAsmModule(
i::MaybeHandle<i::JSObject> InstantiateModuleCommon(
const v8::FunctionCallbackInfo<v8::Value>& args, const byte* start,
const byte* end, ErrorThrower* thrower,
internal::wasm::ModuleOrigin origin) {
internal::wasm::ModuleOrigin origin = i::wasm::kWasmOrigin) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
i::Handle<i::JSArrayBuffer> memory = i::Handle<i::JSArrayBuffer>::null();
if (args.Length() > 2 && args[2]->IsArrayBuffer()) {
Local<Object> obj = Local<Object>::Cast(args[2]);
i::Handle<i::Object> mem_obj = v8::Utils::OpenHandle(*obj);
memory = i::Handle<i::JSArrayBuffer>(i::JSArrayBuffer::cast(*mem_obj));
}
// Decode but avoid a redundant pass over function bodies for verification.
// Verification will happen during compilation.
i::Zone zone(isolate->allocator());
......@@ -189,8 +185,14 @@ i::MaybeHandle<i::JSObject> InstantiateModuleCommon(
ffi = i::Handle<i::JSReceiver>::cast(v8::Utils::OpenHandle(*obj));
}
object = result.val->Instantiate(isolate, ffi, memory);
i::Handle<i::JSArrayBuffer> memory = i::Handle<i::JSArrayBuffer>::null();
if (args.Length() > 2 && args[2]->IsArrayBuffer()) {
Local<Object> obj = Local<Object>::Cast(args[2]);
i::Handle<i::Object> mem_obj = v8::Utils::OpenHandle(*obj);
memory = i::Handle<i::JSArrayBuffer>(i::JSArrayBuffer::cast(*mem_obj));
}
object = result.val->Instantiate(isolate, ffi, memory);
if (!object.is_null()) {
args.GetReturnValue().Set(v8::Utils::ToLocal(object.ToHandleChecked()));
}
......@@ -203,7 +205,7 @@ i::MaybeHandle<i::JSObject> InstantiateModuleCommon(
void InstantiateModuleFromAsm(const v8::FunctionCallbackInfo<v8::Value>& args) {
HandleScope scope(args.GetIsolate());
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
ErrorThrower thrower(isolate, "WASM.instantiateModuleFromAsm()");
ErrorThrower thrower(isolate, "Wasm.instantiateModuleFromAsm()");
if (!args[0]->IsString()) {
thrower.Error("Asm module text should be a string");
......@@ -275,13 +277,104 @@ void InstantiateModuleFromAsm(const v8::FunctionCallbackInfo<v8::Value>& args) {
void InstantiateModule(const v8::FunctionCallbackInfo<v8::Value>& args) {
HandleScope scope(args.GetIsolate());
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(args.GetIsolate());
ErrorThrower thrower(isolate, "WASM.instantiateModule()");
ErrorThrower thrower(isolate, "Wasm.instantiateModule()");
if (args.Length() < 1) {
thrower.Error("Argument 0 must be a buffer source");
return;
}
RawBuffer buffer = GetRawBufferSource(args[0], &thrower);
if (buffer.start == nullptr) return;
InstantiateModuleCommon(args, buffer.start, buffer.end, &thrower);
}
static i::MaybeHandle<i::JSObject> CreateModuleObject(
v8::Isolate* isolate, const v8::Local<v8::Value> source,
ErrorThrower* thrower) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
RawBuffer buffer = GetRawBufferSource(source, thrower);
if (buffer.start == nullptr) return i::MaybeHandle<i::JSObject>();
// TODO(rossberg): Once we can, do compilation here.
DCHECK(source->IsArrayBuffer() || source->IsTypedArray());
Local<Context> context = isolate->GetCurrentContext();
i::Handle<i::Context> i_context = Utils::OpenHandle(*context);
i::Handle<i::JSFunction> module_cons(i_context->wasm_module_constructor());
i::Handle<i::JSObject> module_obj =
i_isolate->factory()->NewJSObject(module_cons);
i::Handle<i::Object> module_ref = Utils::OpenHandle(*source);
i::Handle<i::Symbol> module_sym(i_context->wasm_module_sym());
i::Object::SetProperty(module_obj, module_sym, module_ref, i::STRICT).Check();
return module_obj;
}
void WebAssemblyCompile(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
ErrorThrower thrower(reinterpret_cast<i::Isolate*>(isolate),
"WebAssembly.compile()");
if (args.Length() < 1) {
thrower.Error("Argument 0 must be a buffer source");
return;
}
i::MaybeHandle<i::JSObject> module_obj =
CreateModuleObject(isolate, args[0], &thrower);
if (module_obj.is_null()) return;
Local<Context> context = isolate->GetCurrentContext();
v8::Local<v8::Promise::Resolver> resolver;
if (!v8::Promise::Resolver::New(context).ToLocal(&resolver)) return;
resolver->Resolve(context, Utils::ToLocal(module_obj.ToHandleChecked()));
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(resolver->GetPromise());
}
void WebAssemblyModule(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
ErrorThrower thrower(reinterpret_cast<i::Isolate*>(isolate),
"WebAssembly.Module()");
if (args.Length() < 1) {
thrower.Error("Argument 0 must be a buffer source");
return;
}
i::MaybeHandle<i::JSObject> module_obj =
CreateModuleObject(isolate, args[0], &thrower);
if (module_obj.is_null()) return;
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(Utils::ToLocal(module_obj.ToHandleChecked()));
}
void WebAssemblyInstance(const v8::FunctionCallbackInfo<v8::Value>& args) {
HandleScope scope(args.GetIsolate());
v8::Isolate* isolate = args.GetIsolate();
ErrorThrower thrower(reinterpret_cast<i::Isolate*>(isolate),
"WebAssembly.Instance()");
RawBuffer buffer = GetRawBufferArgument(thrower, args);
if (args.Length() < 1) {
thrower.Error("Argument 0 must be a WebAssembly.Module");
return;
}
Local<Context> context = isolate->GetCurrentContext();
i::Handle<i::Context> i_context = Utils::OpenHandle(*context);
i::Handle<i::Symbol> module_sym(i_context->wasm_module_sym());
i::MaybeHandle<i::Object> source =
i::Object::GetProperty(Utils::OpenHandle(*args[0]), module_sym);
if (source.is_null()) return;
RawBuffer buffer =
GetRawBufferSource(Utils::ToLocal(source.ToHandleChecked()), &thrower);
if (buffer.start == nullptr) return;
InstantiateModuleCommon(args, buffer.start, buffer.end, &thrower,
internal::wasm::kWasmOrigin);
InstantiateModuleCommon(args, buffer.start, buffer.end, &thrower);
}
} // namespace
......@@ -299,7 +392,7 @@ static Handle<String> v8_str(Isolate* isolate, const char* str) {
return isolate->factory()->NewStringFromAsciiChecked(str);
}
static void InstallFunc(Isolate* isolate, Handle<JSObject> object,
static Handle<JSFunction> InstallFunc(Isolate* isolate, Handle<JSObject> object,
const char* str, FunctionCallback func) {
Handle<String> name = v8_str(isolate, str);
Handle<FunctionTemplateInfo> temp = NewTemplate(isolate, func);
......@@ -308,15 +401,19 @@ static void InstallFunc(Isolate* isolate, Handle<JSObject> object,
PropertyAttributes attributes =
static_cast<PropertyAttributes>(DONT_DELETE | READ_ONLY);
JSObject::AddProperty(object, name, function, attributes);
return function;
}
void WasmJs::Install(Isolate* isolate, Handle<JSGlobalObject> global) {
Factory* factory = isolate->factory();
// Setup wasm function map.
Handle<Context> context(global->native_context(), isolate);
InstallWasmFunctionMap(isolate, context);
// Bind the WASM object.
Factory* factory = isolate->factory();
// Bind the experimental WASM object.
// TODO(rossberg, titzer): remove once it's no longer needed.
{
Handle<String> name = v8_str(isolate, "Wasm");
Handle<JSFunction> cons = factory->NewFunction(name);
JSFunction::SetInstancePrototype(
......@@ -338,9 +435,36 @@ void WasmJs::Install(Isolate* isolate, Handle<JSGlobalObject> global) {
Handle<String> name = v8_str(isolate, "experimentalVersion");
PropertyAttributes attributes =
static_cast<PropertyAttributes>(DONT_DELETE | READ_ONLY);
Handle<Smi> value = Handle<Smi>(Smi::FromInt(wasm::kWasmVersion), isolate);
Handle<Smi> value =
Handle<Smi>(Smi::FromInt(wasm::kWasmVersion), isolate);
JSObject::AddProperty(wasm_object, name, value, attributes);
}
}
// Create private symbols.
Handle<Symbol> module_sym = isolate->factory()->NewPrivateSymbol();
Handle<Symbol> instance_sym = isolate->factory()->NewPrivateSymbol();
context->set_wasm_module_sym(*module_sym);
context->set_wasm_instance_sym(*instance_sym);
// Bind the WebAssembly object.
Handle<String> name = v8_str(isolate, "WebAssembly");
Handle<JSFunction> cons = factory->NewFunction(name);
JSFunction::SetInstancePrototype(
cons, Handle<Object>(context->initial_object_prototype(), isolate));
cons->shared()->set_instance_class_name(*name);
Handle<JSObject> wasm_object = factory->NewJSObject(cons, TENURED);
PropertyAttributes attributes = static_cast<PropertyAttributes>(DONT_ENUM);
JSObject::AddProperty(global, name, wasm_object, attributes);
// Install static methods on WebAssembly object.
InstallFunc(isolate, wasm_object, "compile", WebAssemblyCompile);
Handle<JSFunction> module_constructor =
InstallFunc(isolate, wasm_object, "Module", WebAssemblyModule);
Handle<JSFunction> instance_constructor =
InstallFunc(isolate, wasm_object, "Instance", WebAssemblyInstance);
context->set_wasm_module_constructor(*module_constructor);
context->set_wasm_instance_constructor(*instance_constructor);
}
void WasmJs::InstallWasmFunctionMap(Isolate* isolate, Handle<Context> context) {
......
......@@ -28,11 +28,10 @@ std::ostream& operator<<(std::ostream& os, const ErrorCode& error_code) {
}
void ErrorThrower::Error(const char* format, ...) {
// only report the first error.
if (error_ || isolate_->has_pending_exception()) return;
error_ = true;
char buffer[256];
// Only report the first error.
if (error()) return;
char buffer[256];
va_list arguments;
va_start(arguments, format);
base::OS::VSNPrintF(buffer, 255, format, arguments);
......@@ -44,8 +43,13 @@ void ErrorThrower::Error(const char* format, ...) {
}
str << buffer;
isolate_->ScheduleThrow(
*isolate_->factory()->NewStringFromAsciiChecked(str.str().c_str()));
message_ = isolate_->factory()->NewStringFromAsciiChecked(str.str().c_str());
}
ErrorThrower::~ErrorThrower() {
if (error() && !isolate_->has_pending_exception()) {
isolate_->ScheduleThrow(*message_);
}
}
} // namespace wasm
} // namespace internal
......
......@@ -8,6 +8,7 @@
#include "src/base/compiler-specific.h"
#include "src/base/smart-pointers.h"
#include "src/handles.h"
#include "src/globals.h"
namespace v8 {
......@@ -91,7 +92,8 @@ std::ostream& operator<<(std::ostream& os, const ErrorCode& error_code);
class ErrorThrower {
public:
ErrorThrower(Isolate* isolate, const char* context)
: isolate_(isolate), context_(context), error_(false) {}
: isolate_(isolate), context_(context) {}
~ErrorThrower();
PRINTF_FORMAT(2, 3) void Error(const char* fmt, ...);
......@@ -102,12 +104,18 @@ class ErrorThrower {
return Error("%s", str.str().c_str());
}
bool error() const { return error_; }
i::Handle<i::String> Reify() {
auto result = message_;
message_ = i::Handle<i::String>();
return result;
}
bool error() const { return !message_.is_null(); }
private:
Isolate* isolate_;
const char* context_;
bool error_;
i::Handle<i::String> message_;
};
} // namespace wasm
} // namespace internal
......
......@@ -80,7 +80,7 @@ bytecodes: [
/* 15 S> */ B(LdrUndefined), R(0),
B(CreateArrayLiteral), U8(0), U8(0), U8(3),
B(Star), R(1),
B(CallJSRuntime), U8(124), R(0), U8(2),
B(CallJSRuntime), U8(128), R(0), U8(2),
/* 44 S> */ B(Return),
]
constant pool: [
......
......@@ -7,44 +7,54 @@
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
var kReturnValue = 117;
var module = (function Build() {
var builder = new WasmModuleBuilder();
let kReturnValue = 117;
let buffer = (() => {
let builder = new WasmModuleBuilder();
builder.addMemory(1, 1, true);
builder.addFunction("main", kSig_i)
.addBody([kExprI8Const, kReturnValue])
.exportFunc();
return builder.instantiate();
})();
// Check the module exists.
assertFalse(module === undefined);
assertFalse(module === null);
assertFalse(module === 0);
assertEquals("object", typeof module);
// Check the memory is an ArrayBuffer.
var mem = module.exports.memory;
assertFalse(mem === undefined);
assertFalse(mem === null);
assertFalse(mem === 0);
assertEquals("object", typeof mem);
assertTrue(mem instanceof ArrayBuffer);
for (var i = 0; i < 4; i++) {
module.exports.memory = 0; // should be ignored
assertEquals(mem, module.exports.memory);
return builder.toBuffer();
})()
function CheckInstance(instance) {
assertFalse(instance === undefined);
assertFalse(instance === null);
assertFalse(instance === 0);
assertEquals("object", typeof instance);
// Check the memory is an ArrayBuffer.
var mem = instance.exports.memory;
assertFalse(mem === undefined);
assertFalse(mem === null);
assertFalse(mem === 0);
assertEquals("object", typeof mem);
assertTrue(mem instanceof ArrayBuffer);
for (let i = 0; i < 4; i++) {
instance.exports.memory = 0; // should be ignored
assertSame(mem, instance.exports.memory);
}
assertEquals(65536, instance.exports.memory.byteLength);
// Check the properties of the main function.
let main = instance.exports.main;
assertFalse(main === undefined);
assertFalse(main === null);
assertFalse(main === 0);
assertEquals("function", typeof main);
assertEquals(kReturnValue, main());
}
assertEquals(65536, module.exports.memory.byteLength);
// Deprecated experimental API.
CheckInstance(Wasm.instantiateModule(buffer));
// Check the properties of the main function.
var main = module.exports.main;
assertFalse(main === undefined);
assertFalse(main === null);
assertFalse(main === 0);
assertEquals("function", typeof main);
// Official API
let module = new WebAssembly.Module(buffer);
CheckInstance(new WebAssembly.Instance(module));
assertEquals(kReturnValue, main());
let promise = WebAssembly.compile(buffer);
promise.then(module => CheckInstance(new WebAssembly.Instance(module)));
......@@ -342,11 +342,8 @@ WasmModuleBuilder.prototype.toBuffer = function(debug) {
return buffer;
}
WasmModuleBuilder.prototype.instantiate = function(ffi, memory) {
var buffer = this.toBuffer();
if (memory != undefined) {
return Wasm.instantiateModule(buffer, ffi, memory);
} else {
return Wasm.instantiateModule(buffer, ffi);
}
WasmModuleBuilder.prototype.instantiate = function(...args) {
var module = new WebAssembly.Module(this.toBuffer());
var instance = new WebAssembly.Instance(module, ...args);
return instance;
}
......@@ -11,3 +11,8 @@ assertEquals("function", typeof Wasm.verifyFunction);
assertEquals("function", typeof Wasm.instantiateModule);
assertEquals("function", typeof Wasm.instantiateModuleFromAsm);
assertFalse(undefined == Wasm.experimentalVersion);
assertEquals('object', typeof WebAssembly);
assertEquals('function', typeof WebAssembly.Module);
assertEquals('function', typeof WebAssembly.Instance);
assertEquals('function', typeof WebAssembly.compile);
......@@ -4,6 +4,7 @@
#include "test/unittests/test-utils.h"
#include "src/objects-inl.h"
#include "src/wasm/decoder.h"
#include "src/wasm/wasm-macro-gen.h"
......
......@@ -4,6 +4,7 @@
#include "test/unittests/test-utils.h"
#include "src/objects-inl.h"
#include "src/wasm/decoder.h"
#include "src/wasm/leb-helper.h"
......
......@@ -4,6 +4,8 @@
#include "test/unittests/test-utils.h"
#include "src/handles.h"
#include "src/objects-inl.h"
#include "src/wasm/module-decoder.h"
#include "src/wasm/wasm-macro-gen.h"
#include "src/wasm/wasm-opcodes.h"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment