Bug 1316447 - Baldr: add WebAssembly.instantiate (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Thu, 10 Nov 2016 10:19:14 -0600
changeset 348749 bfd4da2301e55c335edd2d762d53b07a5f07faf1
parent 348748 4b46d55b13bd99548b00112c2894bb4ccae4f5bf
child 348750 487a5da50e56454385118bb8da2e9110f039e83d
push id10298
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:33:03 +0000
treeherdermozilla-aurora@7e29173b1641 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1316447
milestone52.0a1
Bug 1316447 - Baldr: add WebAssembly.instantiate (r=bbouvier) MozReview-Commit-ID: K5gVaOzkaCg
dom/promise/tests/test_webassembly_compile.html
js/src/jit-test/tests/wasm/jsapi.js
js/src/js.msg
js/src/wasm/WasmJS.cpp
--- a/dom/promise/tests/test_webassembly_compile.html
+++ b/dom/promise/tests/test_webassembly_compile.html
@@ -19,16 +19,21 @@ const fooModuleCode = wasmTextToBinary(`
 
 function checkFooModule(m) {
   ok(m instanceof WebAssembly.Module, "got a module");
   var i = new WebAssembly.Instance(m);
   ok(i instanceof WebAssembly.Instance, "got an instance");
   ok(i.exports.foo() === 42, "got 42");
 }
 
+function checkFooInstance(i) {
+  ok(i instanceof WebAssembly.Instance, "got a module");
+  ok(i.exports.foo() === 42, "got 42");
+}
+
 function propertiesExist() {
   if (!wasmIsSupported()) {
     ok(!this["WebAssembly"], "If the device doesn't support, there will be no WebAssembly object");
     SimpleTest.finish();
     return;
   }
 
   ok(WebAssembly, "WebAssembly object should exist");
@@ -109,22 +114,51 @@ function terminateCompileInWorker() {
     w.postMessage(fooModuleCode);
     w.onmessage = e => {
       ok(e.data === "ok", "worker finished first step");
       w.terminate();
       runTest();
     }
 }
 
+function instantiateFail() {
+  WebAssembly.instantiate().then(
+    () => { ok(false, "should have failed"); runTest() }
+  ).catch(
+    err => { ok(err instanceof TypeError, "empty compile failed"); runTest() }
+  );
+}
+
+function instantiateSuccess() {
+  WebAssembly.instantiate(fooModuleCode).then(
+    r => { checkFooModule(r.module); checkFooInstance(r.instance); runTest() }
+  ).catch(
+    err => { ok(false, String(err)); runTest() }
+  );
+}
+
+function chainSuccess() {
+  WebAssembly.compile(fooModuleCode).then(
+    m => WebAssembly.instantiate(m)
+  ).then(
+    i => { checkFooInstance(i); runTest() }
+  ).catch(
+    err => { ok(false, String(err)); runTest() }
+  );
+}
+
 var tests = [ propertiesExist,
               compileFail,
               compileSuccess,
               compileManySuccess,
               compileInWorker,
-              terminateCompileInWorker
+              terminateCompileInWorker,
+              instantiateFail,
+              instantiateSuccess,
+              chainSuccess
             ];
 
 function runTest() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
--- a/js/src/jit-test/tests/wasm/jsapi.js
+++ b/js/src/jit-test/tests/wasm/jsapi.js
@@ -1,13 +1,15 @@
 load(libdir + 'wasm.js');
 
 const WasmPage = 64 * 1024;
 
-const moduleBinary = wasmTextToBinary('(module (func (export "f") (result i32) (i32.const 42)))');
+const emptyModuleBinary = wasmTextToBinary('(module)');
+const exportingModuleBinary = wasmTextToBinary('(module (func (export "f") (result i32) (i32.const 42)))');
+const importingModuleBinary = wasmTextToBinary('(module (func (import "" "f")))');
 
 // 'WebAssembly' data property on global object
 const wasmDesc = Object.getOwnPropertyDescriptor(this, 'WebAssembly');
 assertEq(typeof wasmDesc.value, "object");
 assertEq(wasmDesc.writable, true);
 assertEq(wasmDesc.enumerable, false);
 assertEq(wasmDesc.configurable, true);
 
@@ -65,37 +67,39 @@ assertEq(Module.length, 1);
 assertEq(Module.name, "Module");
 assertErrorMessage(() => Module(), TypeError, /constructor without new is forbidden/);
 assertErrorMessage(() => new Module(), TypeError, /requires more than 0 arguments/);
 assertErrorMessage(() => new Module(undefined), TypeError, "first argument must be an ArrayBuffer or typed array object");
 assertErrorMessage(() => new Module(1), TypeError, "first argument must be an ArrayBuffer or typed array object");
 assertErrorMessage(() => new Module({}), TypeError, "first argument must be an ArrayBuffer or typed array object");
 assertErrorMessage(() => new Module(new Uint8Array()), CompileError, /failed to match magic number/);
 assertErrorMessage(() => new Module(new ArrayBuffer()), CompileError, /failed to match magic number/);
-assertEq(new Module(moduleBinary) instanceof Module, true);
-assertEq(new Module(moduleBinary.buffer) instanceof Module, true);
+assertEq(new Module(emptyModuleBinary) instanceof Module, true);
+assertEq(new Module(emptyModuleBinary.buffer) instanceof Module, true);
 
 // 'WebAssembly.Module.prototype' data property
 const moduleProtoDesc = Object.getOwnPropertyDescriptor(Module, 'prototype');
 assertEq(typeof moduleProtoDesc.value, "object");
 assertEq(moduleProtoDesc.writable, false);
 assertEq(moduleProtoDesc.enumerable, false);
 assertEq(moduleProtoDesc.configurable, false);
 
 // 'WebAssembly.Module.prototype' object
 const moduleProto = Module.prototype;
 assertEq(moduleProto, moduleProtoDesc.value);
 assertEq(String(moduleProto), "[object Object]");
 assertEq(Object.getPrototypeOf(moduleProto), Object.prototype);
 
 // 'WebAssembly.Module' instance objects
-const m1 = new Module(moduleBinary);
-assertEq(typeof m1, "object");
-assertEq(String(m1), "[object WebAssembly.Module]");
-assertEq(Object.getPrototypeOf(m1), moduleProto);
+const emptyModule = new Module(emptyModuleBinary);
+const importingModule = new Module(importingModuleBinary);
+const exportingModule = new Module(exportingModuleBinary);
+assertEq(typeof emptyModule, "object");
+assertEq(String(emptyModule), "[object WebAssembly.Module]");
+assertEq(Object.getPrototypeOf(emptyModule), moduleProto);
 
 // 'WebAssembly.Module.imports' data property
 const moduleImportsDesc = Object.getOwnPropertyDescriptor(Module, 'imports');
 assertEq(typeof moduleImportsDesc.value, "function");
 assertEq(moduleImportsDesc.writable, true);
 assertEq(moduleImportsDesc.enumerable, false);
 assertEq(moduleImportsDesc.configurable, true);
 
@@ -132,17 +136,17 @@ assertEq(moduleExportsDesc.enumerable, f
 assertEq(moduleExportsDesc.configurable, true);
 
 // 'WebAssembly.Module.exports' method
 const moduleExports = moduleExportsDesc.value;
 assertEq(moduleExports.length, 1);
 assertErrorMessage(() => moduleExports(), TypeError, /requires more than 0 arguments/);
 assertErrorMessage(() => moduleExports(undefined), TypeError, /first argument must be a WebAssembly.Module/);
 assertErrorMessage(() => moduleExports({}), TypeError, /first argument must be a WebAssembly.Module/);
-var arr = moduleExports(new Module(wasmTextToBinary('(module)')));
+var arr = moduleExports(emptyModule);
 assertEq(arr instanceof Array, true);
 assertEq(arr.length, 0);
 var arr = moduleExports(new Module(wasmTextToBinary('(module (func (export "a")) (memory (export "b") 1) (table (export "c") 1 anyfunc) (global (export "⚡") i32 (i32.const 0)))')));
 assertEq(arr instanceof Array, true);
 assertEq(arr.length, 4);
 assertEq(arr[0].kind, "function");
 assertEq(arr[0].name, "a");
 assertEq(arr[1].kind, "memory");
@@ -162,48 +166,48 @@ assertEq(instanceDesc.configurable, true
 // 'WebAssembly.Instance' constructor function
 const Instance = WebAssembly.Instance;
 assertEq(Instance, instanceDesc.value);
 assertEq(Instance.length, 1);
 assertEq(Instance.name, "Instance");
 assertErrorMessage(() => Instance(), TypeError, /constructor without new is forbidden/);
 assertErrorMessage(() => new Instance(1), TypeError, "first argument must be a WebAssembly.Module");
 assertErrorMessage(() => new Instance({}), TypeError, "first argument must be a WebAssembly.Module");
-assertErrorMessage(() => new Instance(m1, null), TypeError, "second argument must be an object");
-assertEq(new Instance(m1) instanceof Instance, true);
-assertEq(new Instance(m1, {}) instanceof Instance, true);
+assertErrorMessage(() => new Instance(emptyModule, null), TypeError, "second argument must be an object");
+assertEq(new Instance(emptyModule) instanceof Instance, true);
+assertEq(new Instance(emptyModule, {}) instanceof Instance, true);
 
 // 'WebAssembly.Instance.prototype' data property
 const instanceProtoDesc = Object.getOwnPropertyDescriptor(Instance, 'prototype');
 assertEq(typeof instanceProtoDesc.value, "object");
 assertEq(instanceProtoDesc.writable, false);
 assertEq(instanceProtoDesc.enumerable, false);
 assertEq(instanceProtoDesc.configurable, false);
 
 // 'WebAssembly.Instance.prototype' object
 const instanceProto = Instance.prototype;
 assertEq(instanceProto, instanceProtoDesc.value);
 assertEq(String(instanceProto), "[object Object]");
 assertEq(Object.getPrototypeOf(instanceProto), Object.prototype);
 
 // 'WebAssembly.Instance' instance objects
-const i1 = new Instance(m1);
-assertEq(typeof i1, "object");
-assertEq(String(i1), "[object WebAssembly.Instance]");
-assertEq(Object.getPrototypeOf(i1), instanceProto);
+const exportingInstance = new Instance(exportingModule);
+assertEq(typeof exportingInstance, "object");
+assertEq(String(exportingInstance), "[object WebAssembly.Instance]");
+assertEq(Object.getPrototypeOf(exportingInstance), instanceProto);
 
 // 'WebAssembly.Instance' 'exports' data property
-const instanceExportsDesc = Object.getOwnPropertyDescriptor(i1, 'exports');
+const instanceExportsDesc = Object.getOwnPropertyDescriptor(exportingInstance, 'exports');
 assertEq(typeof instanceExportsDesc.value, "object");
 assertEq(instanceExportsDesc.writable, true);
 assertEq(instanceExportsDesc.enumerable, true);
 assertEq(instanceExportsDesc.configurable, true);
 
 // Exported WebAssembly functions
-const f = i1.exports.f;
+const f = exportingInstance.exports.f;
 assertEq(f instanceof Function, true);
 assertEq(f.length, 0);
 assertEq('name' in f, true);
 assertEq(Function.prototype.call.call(f), 42);
 assertErrorMessage(() => new f(), TypeError, /is not a constructor/);
 
 // 'WebAssembly.Memory' data property
 const memoryDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'Memory');
@@ -422,30 +426,77 @@ assertEq(compileDesc.writable, true);
 assertEq(compileDesc.enumerable, false);
 assertEq(compileDesc.configurable, true);
 
 // 'WebAssembly.compile' function
 const compile = WebAssembly.compile;
 assertEq(compile, compileDesc.value);
 assertEq(compile.length, 1);
 assertEq(compile.name, "compile");
-function assertCompileError(args, msg) {
+function assertCompileError(args, err, msg) {
     var error = null;
     compile(...args).catch(e => error = e);
     drainJobQueue();
-    assertEq(error instanceof TypeError, true);
+    assertEq(error instanceof err, true);
     assertEq(Boolean(error.stack.match("jsapi.js")), true);
     assertEq(Boolean(error.message.match(msg)), true);
 }
-assertCompileError([], /requires more than 0 arguments/);
-assertCompileError([undefined], /first argument must be an ArrayBuffer or typed array object/);
-assertCompileError([1], /first argument must be an ArrayBuffer or typed array object/);
-assertCompileError([{}], /first argument must be an ArrayBuffer or typed array object/);
-assertCompileError([new Uint8Array()], /failed to match magic number/);
-assertCompileError([new ArrayBuffer()], /failed to match magic number/);
+assertCompileError([], TypeError, /requires more than 0 arguments/);
+assertCompileError([undefined], TypeError, /first argument must be an ArrayBuffer or typed array object/);
+assertCompileError([1], TypeError, /first argument must be an ArrayBuffer or typed array object/);
+assertCompileError([{}], TypeError, /first argument must be an ArrayBuffer or typed array object/);
+assertCompileError([new Uint8Array()], CompileError, /failed to match magic number/);
+assertCompileError([new ArrayBuffer()], CompileError, /failed to match magic number/);
 function assertCompileSuccess(bytes) {
     var module = null;
     compile(bytes).then(m => module = m);
     drainJobQueue();
     assertEq(module instanceof Module, true);
 }
-assertCompileSuccess(moduleBinary);
-assertCompileSuccess(moduleBinary.buffer);
+assertCompileSuccess(emptyModuleBinary);
+assertCompileSuccess(emptyModuleBinary.buffer);
+
+// 'WebAssembly.instantiate' data property
+const instantiateDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'instantiate');
+assertEq(typeof instantiateDesc.value, "function");
+assertEq(instantiateDesc.writable, true);
+assertEq(instantiateDesc.enumerable, false);
+assertEq(instantiateDesc.configurable, true);
+
+// 'WebAssembly.instantiate' function
+const instantiate = WebAssembly.instantiate;
+assertEq(instantiate, instantiateDesc.value);
+assertEq(instantiate.length, 2);
+assertEq(instantiate.name, "instantiate");
+function assertInstantiateError(args, err, msg) {
+    var error = null;
+    instantiate(...args).catch(e => error = e);
+    drainJobQueue();
+    assertEq(error instanceof err, true);
+    assertEq(Boolean(error.stack.match("jsapi.js")), true);
+    assertEq(Boolean(error.message.match(msg)), true);
+}
+assertInstantiateError([], TypeError, /requires more than 0 arguments/);
+assertInstantiateError([undefined], TypeError, /first argument must be a WebAssembly.Module, ArrayBuffer or typed array object/);
+assertInstantiateError([1], TypeError, /first argument must be a WebAssembly.Module, ArrayBuffer or typed array object/);
+assertInstantiateError([{}], TypeError, /first argument must be a WebAssembly.Module, ArrayBuffer or typed array object/);
+assertInstantiateError([new Uint8Array()], CompileError, /failed to match magic number/);
+assertInstantiateError([new ArrayBuffer()], CompileError, /failed to match magic number/);
+assertInstantiateError([importingModule], TypeError, /second argument must be an object/);
+assertInstantiateError([importingModule, null], TypeError, /second argument must be an object/);
+assertInstantiateError([importingModuleBinary, null], TypeError, /second argument must be an object/);
+function assertInstantiateSuccess(module, imports) {
+    var result = null;
+    instantiate(module, imports).then(r => result = r);
+    drainJobQueue();
+    if (module instanceof Module) {
+        assertEq(result instanceof Instance, true);
+    } else {
+        assertEq(result.module instanceof Module, true);
+        assertEq(result.instance instanceof Instance, true);
+    }
+}
+assertInstantiateSuccess(emptyModule);
+assertInstantiateSuccess(emptyModuleBinary);
+assertInstantiateSuccess(emptyModuleBinary.buffer);
+assertInstantiateSuccess(importingModule, {"":{f:()=>{}}});
+assertInstantiateSuccess(importingModuleBinary, {"":{f:()=>{}}});
+assertInstantiateSuccess(importingModuleBinary.buffer, {"":{f:()=>{}}});
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -360,16 +360,17 @@ MSG_DEF(JSMSG_WASM_INVALID_CONVERSION, 0
 MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0, JSEXN_WASMRUNTIMEERROR, "integer divide by zero")
 MSG_DEF(JSMSG_WASM_OUT_OF_BOUNDS,      0, JSEXN_WASMRUNTIMEERROR, "index out of bounds")
 MSG_DEF(JSMSG_WASM_UNALIGNED_ACCESS,   0, JSEXN_WASMRUNTIMEERROR, "unaligned memory access")
 MSG_DEF(JSMSG_WASM_BAD_UINT32,         2, JSEXN_RANGEERR,    "bad {0} {1}")
 MSG_DEF(JSMSG_WASM_BAD_GROW,           1, JSEXN_RANGEERR,    "failed to grow {0}")
 MSG_DEF(JSMSG_WASM_BAD_FIT,            2, JSEXN_RANGEERR,    "{0} segment does not fit in {1}")
 MSG_DEF(JSMSG_WASM_BAD_BUF_ARG,        0, JSEXN_TYPEERR,     "first argument must be an ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_MOD_ARG,        0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module")
+MSG_DEF(JSMSG_WASM_BAD_BUF_MOD_ARG,    0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module, ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_DESC_ARG,       1, JSEXN_TYPEERR,     "first argument must be a {0} descriptor")
 MSG_DEF(JSMSG_WASM_BAD_IMP_SIZE,       1, JSEXN_TYPEERR,     "imported {0} with incompatible size")
 MSG_DEF(JSMSG_WASM_BAD_IMP_MAX,        1, JSEXN_TYPEERR,     "imported {0} with incompatible maximum size")
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT,        0, JSEXN_TYPEERR,     "\"element\" property of table descriptor must be \"anyfunc\"")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG,     0, JSEXN_TYPEERR,     "second argument must be an object")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD,   2, JSEXN_TYPEERR,     "import object field '{0}' is not {1}")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_SIG,     0, JSEXN_TYPEERR,     "imported function signature mismatch")
 MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE,    0, JSEXN_TYPEERR,     "can only assign WebAssembly exported functions to Table")
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -504,33 +504,37 @@ const JSFunctionSpec WasmModuleObject::s
 
 /* static */ void
 WasmModuleObject::finalize(FreeOp* fop, JSObject* obj)
 {
     obj->as<WasmModuleObject>().module().Release();
 }
 
 static bool
+IsModuleObject(JSObject* obj, Module** module)
+{
+    JSObject* unwrapped = CheckedUnwrap(obj);
+    if (!unwrapped || !unwrapped->is<WasmModuleObject>())
+        return false;
+
+    *module = &unwrapped->as<WasmModuleObject>().module();
+    return true;
+}
+
+static bool
 GetModuleArg(JSContext* cx, CallArgs args, const char* name, Module** module)
 {
     if (!args.requireAtLeast(cx, name, 1))
         return false;
 
-    if (!args[0].isObject()) {
+    if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), module)) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MOD_ARG);
         return false;
     }
 
-    JSObject* unwrapped = CheckedUnwrap(&args[0].toObject());
-    if (!unwrapped || !unwrapped->is<WasmModuleObject>()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MOD_ARG);
-        return false;
-    }
-
-    *module = &unwrapped->as<WasmModuleObject>().module();
     return true;
 }
 
 struct KindNames
 {
     RootedPropertyName kind;
     RootedPropertyName table;
     RootedPropertyName memory;
@@ -690,66 +694,70 @@ WasmModuleObject::create(ExclusiveContex
         return nullptr;
 
     obj->initReservedSlot(MODULE_SLOT, PrivateValue(&module));
     module.AddRef();
     return obj;
 }
 
 static bool
-GetCompileArgs(JSContext* cx, CallArgs callArgs, const char* name, MutableBytes* bytecode,
-               CompileArgs* compileArgs)
+GetBufferSource(JSContext* cx, JSObject* obj, unsigned errorNumber, MutableBytes* bytecode)
 {
-    if (!callArgs.requireAtLeast(cx, name, 1))
-        return false;
-
-    if (!callArgs[0].isObject()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
-        return false;
-    }
-
     *bytecode = cx->new_<ShareableBytes>();
     if (!*bytecode)
         return false;
 
-    JSObject* unwrapped = CheckedUnwrap(&callArgs[0].toObject());
+    JSObject* unwrapped = CheckedUnwrap(obj);
+
     if (unwrapped && unwrapped->is<TypedArrayObject>()) {
         TypedArrayObject& view = unwrapped->as<TypedArrayObject>();
-        if (!(*bytecode)->append((uint8_t*)view.viewDataEither().unwrap(), view.byteLength()))
-            return false;
-    } else if (unwrapped && unwrapped->is<ArrayBufferObject>()) {
+        return (*bytecode)->append((uint8_t*)view.viewDataEither().unwrap(), view.byteLength());
+    }
+
+    if (unwrapped && unwrapped->is<ArrayBufferObject>()) {
         ArrayBufferObject& buffer = unwrapped->as<ArrayBufferObject>();
-        if (!(*bytecode)->append(buffer.dataPointer(), buffer.byteLength()))
-            return false;
-    } else {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
-        return false;
+        return (*bytecode)->append(buffer.dataPointer(), buffer.byteLength());
     }
 
+    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber);
+    return false;
+}
+
+static bool
+InitCompileArgs(JSContext* cx, CompileArgs* compileArgs)
+{
     ScriptedCaller scriptedCaller;
     if (!DescribeScriptedCaller(cx, &scriptedCaller))
         return false;
 
-    if (!compileArgs->initFromContext(cx, Move(scriptedCaller)))
-        return false;
-
-    return true;
+    return compileArgs->initFromContext(cx, Move(scriptedCaller));
 }
 
 /* static */ bool
 WasmModuleObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs callArgs = CallArgsFromVp(argc, vp);
 
     if (!ThrowIfNotConstructing(cx, callArgs, "Module"))
         return false;
 
+    if (!callArgs.requireAtLeast(cx, "WebAssembly.Module", 1))
+        return false;
+
+    if (!callArgs[0].isObject()) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
+        return false;
+    }
+
     MutableBytes bytecode;
+    if (!GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG, &bytecode))
+        return false;
+
     CompileArgs compileArgs;
-    if (!GetCompileArgs(cx, callArgs, "WebAssembly.Module", &bytecode, &compileArgs))
+    if (!InitCompileArgs(cx, &compileArgs))
         return false;
 
     UniqueChars error;
     SharedModule module = Compile(*bytecode, compileArgs, &error);
     if (!module) {
         if (error) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
                                       error.get());
@@ -874,51 +882,58 @@ WasmInstanceObject::create(JSContext* cx
     MOZ_ASSERT(!obj->isNewborn());
 
     if (!instance->init(cx))
         return nullptr;
 
     return obj;
 }
 
+static bool
+Instantiate(JSContext* cx, const Module& module, HandleObject importObj,
+            MutableHandleWasmInstanceObject instanceObj)
+{
+    RootedObject instanceProto(cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject());
+
+    Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
+    RootedWasmTableObject table(cx);
+    RootedWasmMemoryObject memory(cx);
+    ValVector globals;
+    if (!GetImports(cx, module, importObj, &funcs, &table, &memory, &globals))
+        return false;
+
+    return module.instantiate(cx, funcs, table, memory, globals, instanceProto, instanceObj);
+}
+
 /* static */ bool
 WasmInstanceObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!ThrowIfNotConstructing(cx, args, "Instance"))
         return false;
 
     if (!args.requireAtLeast(cx, "WebAssembly.Instance", 1))
         return false;
 
-    if (!args.get(0).isObject() || !args[0].toObject().is<WasmModuleObject>()) {
+    Module* module;
+    if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), &module)) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MOD_ARG);
         return false;
     }
 
-    const Module& module = args[0].toObject().as<WasmModuleObject>().module();
-
     RootedObject importObj(cx);
     if (!args.get(1).isUndefined()) {
         if (!args[1].isObject())
             return ThrowBadImportArg(cx);
         importObj = &args[1].toObject();
     }
 
-    Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
-    RootedWasmTableObject table(cx);
-    RootedWasmMemoryObject memory(cx);
-    ValVector globals;
-    if (!GetImports(cx, module, importObj, &funcs, &table, &memory, &globals))
-        return false;
-
-    RootedObject instanceProto(cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject());
     RootedWasmInstanceObject instanceObj(cx);
-    if (!module.instantiate(cx, funcs, table, memory, globals, instanceProto, &instanceObj))
+    if (!Instantiate(cx, *module, importObj, &instanceObj))
         return false;
 
     args.rval().setObject(*instanceObj);
     return true;
 }
 
 Instance&
 WasmInstanceObject::instance() const
@@ -1630,26 +1645,26 @@ Reject(JSContext* cx, const CompileArgs&
     if (!str)
         return false;
 
     RootedString message(cx, NewLatin1StringZ(cx, Move(str)));
     if (!message)
         return false;
 
     RootedObject errorObj(cx,
-        ErrorObject::create(cx, JSEXN_TYPEERR, stack, filename, line, column, nullptr, message));
+        ErrorObject::create(cx, JSEXN_WASMCOMPILEERROR, stack, filename, line, column, nullptr, message));
     if (!errorObj)
         return false;
 
     RootedValue rejectionValue(cx, ObjectValue(*errorObj));
     return promise->reject(cx, rejectionValue);
 }
 
 static bool
-Resolve(JSContext* cx, Module& module, Handle<PromiseObject*> promise)
+ResolveCompilation(JSContext* cx, Module& module, Handle<PromiseObject*> promise)
 {
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
     RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto));
     if (!moduleObj)
         return false;
 
     RootedValue resolutionValue(cx, ObjectValue(*moduleObj));
     return promise->resolve(cx, resolutionValue);
@@ -1667,74 +1682,224 @@ struct CompileTask : PromiseTask
     {}
 
     void execute() override {
         module = Compile(*bytecode, compileArgs, &error);
     }
 
     bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) override {
         return module
-               ? Resolve(cx, *module, promise)
+               ? ResolveCompilation(cx, *module, promise)
                : Reject(cx, compileArgs, Move(error), promise);
     }
 };
 
 static bool
+RejectWithPendingException(JSContext* cx, Handle<PromiseObject*> promise)
+{
+    if (!cx->isExceptionPending())
+        return false;
+
+    RootedValue rejectionValue(cx);
+    if (!GetAndClearException(cx, &rejectionValue))
+        return false;
+
+    return promise->reject(cx, rejectionValue);
+}
+
+static bool
+RejectWithPendingException(JSContext* cx, Handle<PromiseObject*> promise, CallArgs& callArgs)
+{
+    if (!RejectWithPendingException(cx, promise))
+        return false;
+
+    callArgs.rval().setObject(*promise);
+    return true;
+}
+
+static bool
+GetBufferSource(JSContext* cx, CallArgs callArgs, const char* name, MutableBytes* bytecode)
+{
+    if (!callArgs.requireAtLeast(cx, name, 1))
+        return false;
+
+    if (!callArgs[0].isObject()) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
+        return false;
+    }
+
+    return GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG, bytecode);
+}
+
+static bool
 WebAssembly_compile(JSContext* cx, unsigned argc, Value* vp)
 {
     if (!cx->startAsyncTaskCallback || !cx->finishAsyncTaskCallback) {
         JS_ReportErrorASCII(cx, "WebAssembly.compile not supported in this runtime.");
         return false;
     }
 
-    CallArgs callArgs = CallArgsFromVp(argc, vp);
-
     RootedFunction nopFun(cx, NewNativeFunction(cx, Nop, 0, nullptr));
     if (!nopFun)
         return false;
 
     Rooted<PromiseObject*> promise(cx, PromiseObject::create(cx, nopFun));
     if (!promise)
         return false;
 
     auto task = cx->make_unique<CompileTask>(cx, promise);
     if (!task)
         return false;
 
-    if (!GetCompileArgs(cx, callArgs, "WebAssembly.compile", &task->bytecode, &task->compileArgs)) {
-        if (!cx->isExceptionPending())
-            return false;
+    CallArgs callArgs = CallArgsFromVp(argc, vp);
 
-        RootedValue rejectionValue(cx);
-        if (!GetAndClearException(cx, &rejectionValue))
-            return false;
+    if (!GetBufferSource(cx, callArgs, "WebAssembly.compile", &task->bytecode))
+        return RejectWithPendingException(cx, promise, callArgs);
 
-        if (!promise->reject(cx, rejectionValue))
-            return false;
-
-        callArgs.rval().setObject(*promise);
-        return true;
-    }
+    if (!InitCompileArgs(cx, &task->compileArgs))
+        return false;
 
     if (!StartPromiseTask(cx, Move(task)))
         return false;
 
     callArgs.rval().setObject(*promise);
     return true;
 }
+
+static bool
+ResolveInstantiation(JSContext* cx, Module& module, HandleObject importObj,
+                     Handle<PromiseObject*> promise)
+{
+    RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
+    RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto));
+    if (!moduleObj)
+        return false;
+
+    RootedWasmInstanceObject instanceObj(cx);
+    if (!Instantiate(cx, module, importObj, &instanceObj))
+        return RejectWithPendingException(cx, promise);
+
+    RootedObject resultObj(cx, JS_NewPlainObject(cx));
+    if (!resultObj)
+        return false;
+
+    RootedValue val(cx, ObjectValue(*moduleObj));
+    if (!JS_DefineProperty(cx, resultObj, "module", val, JSPROP_ENUMERATE))
+        return false;
+
+    val = ObjectValue(*instanceObj);
+    if (!JS_DefineProperty(cx, resultObj, "instance", val, JSPROP_ENUMERATE))
+        return false;
+
+    val = ObjectValue(*resultObj);
+    return promise->resolve(cx, val);
+}
+
+struct InstantiateTask : CompileTask
+{
+    PersistentRootedObject importObj;
+
+    InstantiateTask(JSContext* cx, Handle<PromiseObject*> promise, HandleObject importObj)
+      : CompileTask(cx, promise),
+        importObj(cx, importObj)
+    {}
+
+    bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) override {
+        return module
+               ? ResolveInstantiation(cx, *module, importObj, promise)
+               : Reject(cx, compileArgs, Move(error), promise);
+    }
+};
+
+static bool
+GetInstantiateArgs(JSContext* cx, CallArgs callArgs, MutableHandleObject firstArg,
+                   MutableHandleObject importObj)
+{
+    if (!callArgs.requireAtLeast(cx, "WebAssembly.instantiate", 1))
+        return false;
+
+    if (!callArgs[0].isObject()) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_MOD_ARG);
+        return false;
+    }
+
+    firstArg.set(&callArgs[0].toObject());
+
+    if (!callArgs.get(1).isUndefined()) {
+        if (!callArgs[1].isObject())
+            return ThrowBadImportArg(cx);
+        importObj.set(&callArgs[1].toObject());
+    }
+
+    return true;
+}
+
+static bool
+WebAssembly_instantiate(JSContext* cx, unsigned argc, Value* vp)
+{
+    if (!cx->startAsyncTaskCallback || !cx->finishAsyncTaskCallback) {
+        JS_ReportErrorASCII(cx, "WebAssembly.instantiate not supported in this runtime.");
+        return false;
+    }
+
+    RootedFunction nopFun(cx, NewNativeFunction(cx, Nop, 0, nullptr));
+    if (!nopFun)
+        return false;
+
+    Rooted<PromiseObject*> promise(cx, PromiseObject::create(cx, nopFun));
+    if (!promise)
+        return false;
+
+    CallArgs callArgs = CallArgsFromVp(argc, vp);
+
+    RootedObject firstArg(cx);
+    RootedObject importObj(cx);
+    if (!GetInstantiateArgs(cx, callArgs, &firstArg, &importObj))
+        return RejectWithPendingException(cx, promise, callArgs);
+
+    Module* module;
+    if (IsModuleObject(firstArg, &module)) {
+        RootedWasmInstanceObject instanceObj(cx);
+        if (!Instantiate(cx, *module, importObj, &instanceObj))
+            return RejectWithPendingException(cx, promise, callArgs);
+
+        RootedValue resolutionValue(cx, ObjectValue(*instanceObj));
+        if (!promise->resolve(cx, resolutionValue))
+            return false;
+    } else {
+        auto task = cx->make_unique<InstantiateTask>(cx, promise, importObj);
+        if (!task)
+            return false;
+
+        if (!GetBufferSource(cx, firstArg, JSMSG_WASM_BAD_BUF_MOD_ARG, &task->bytecode))
+            return RejectWithPendingException(cx, promise, callArgs);
+
+        if (!InitCompileArgs(cx, &task->compileArgs))
+            return false;
+
+        if (!StartPromiseTask(cx, Move(task)))
+            return false;
+    }
+
+    callArgs.rval().setObject(*promise);
+    return true;
+}
 #endif
 
 static bool
 WebAssembly_validate(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs callArgs = CallArgsFromVp(argc, vp);
 
     MutableBytes bytecode;
+    if (!GetBufferSource(cx, callArgs, "WebAssembly.validate", &bytecode))
+        return false;
+
     CompileArgs compileArgs;
-    if (!GetCompileArgs(cx, callArgs, "WebAssembly.validate", &bytecode, &compileArgs))
+    if (!InitCompileArgs(cx, &compileArgs))
         return false;
 
     UniqueChars error;
     bool validated = !!Compile(*bytecode, compileArgs, &error);
 
     // If the reason for validation failure was OOM (signalled by null error
     // message), report out-of-memory so that validate's return is always
     // correct.
@@ -1749,16 +1914,17 @@ WebAssembly_validate(JSContext* cx, unsi
 
 static const JSFunctionSpec WebAssembly_static_methods[] =
 {
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str, WebAssembly_toSource, 0, 0),
 #endif
 #ifdef SPIDERMONKEY_PROMISE
     JS_FN("compile", WebAssembly_compile, 1, 0),
+    JS_FN("instantiate", WebAssembly_instantiate, 2, 0),
 #endif
     JS_FN("validate", WebAssembly_validate, 1, 0),
     JS_FS_END
 };
 
 const Class js::WebAssemblyClass =
 {
     js_WebAssembly_str,