Bug 1316447 - Baldr: add Module.{imports,exports} (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Thu, 10 Nov 2016 08:36:45 -0600
changeset 352099 f290780813ad71a516cb0886d52c83e0dc99716b
parent 352098 69481dd6296f472bf6d24cef06eac6d4a8ab37a5
child 352100 4b46d55b13bd99548b00112c2894bb4ccae4f5bf
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1316447
milestone52.0a1
Bug 1316447 - Baldr: add Module.{imports,exports} (r=bbouvier) MozReview-Commit-ID: IZ2LjTgj2TN
js/src/jit-test/tests/wasm/jsapi.js
js/src/wasm/WasmJS.cpp
js/src/wasm/WasmJS.h
js/src/wasm/WasmModule.h
--- a/js/src/jit-test/tests/wasm/jsapi.js
+++ b/js/src/jit-test/tests/wasm/jsapi.js
@@ -87,16 +87,76 @@ assertEq(String(moduleProto), "[object O
 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);
 
+// '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);
+
+// 'WebAssembly.Module.imports' method
+const moduleImports = moduleImportsDesc.value;
+assertEq(moduleImports.length, 1);
+assertErrorMessage(() => moduleImports(), TypeError, /requires more than 0 arguments/);
+assertErrorMessage(() => moduleImports(undefined), TypeError, /first argument must be a WebAssembly.Module/);
+assertErrorMessage(() => moduleImports({}), TypeError, /first argument must be a WebAssembly.Module/);
+var arr = moduleImports(new Module(wasmTextToBinary('(module)')));
+assertEq(arr instanceof Array, true);
+assertEq(arr.length, 0);
+var arr = moduleImports(new Module(wasmTextToBinary('(module (func (import "a" "b")) (memory (import "c" "d") 1) (table (import "e" "f") 1 anyfunc) (global (import "g" "⚡") i32))')));
+assertEq(arr instanceof Array, true);
+assertEq(arr.length, 4);
+assertEq(arr[0].kind, "function");
+assertEq(arr[0].module, "a");
+assertEq(arr[0].name, "b");
+assertEq(arr[1].kind, "memory");
+assertEq(arr[1].module, "c");
+assertEq(arr[1].name, "d");
+assertEq(arr[2].kind, "table");
+assertEq(arr[2].module, "e");
+assertEq(arr[2].name, "f");
+assertEq(arr[3].kind, "global");
+assertEq(arr[3].module, "g");
+assertEq(arr[3].name, "⚡");
+
+// 'WebAssembly.Module.exports' data property
+const moduleExportsDesc = Object.getOwnPropertyDescriptor(Module, 'exports');
+assertEq(typeof moduleExportsDesc.value, "function");
+assertEq(moduleExportsDesc.writable, true);
+assertEq(moduleExportsDesc.enumerable, false);
+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)')));
+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");
+assertEq(arr[1].name, "b");
+assertEq(arr[2].kind, "table");
+assertEq(arr[2].name, "c");
+assertEq(arr[3].kind, "global");
+assertEq(arr[3].name, "⚡");
+
 // 'WebAssembly.Instance' data property
 const instanceDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'Instance');
 assertEq(typeof instanceDesc.value, "function");
 assertEq(instanceDesc.writable, true);
 assertEq(instanceDesc.enumerable, false);
 assertEq(instanceDesc.configurable, true);
 
 // 'WebAssembly.Instance' constructor function
@@ -126,21 +186,21 @@ assertEq(Object.getPrototypeOf(instanceP
 
 // 'WebAssembly.Instance' instance objects
 const i1 = new Instance(m1);
 assertEq(typeof i1, "object");
 assertEq(String(i1), "[object WebAssembly.Instance]");
 assertEq(Object.getPrototypeOf(i1), instanceProto);
 
 // 'WebAssembly.Instance' 'exports' data property
-const exportsDesc = Object.getOwnPropertyDescriptor(i1, 'exports');
-assertEq(typeof exportsDesc.value, "object");
-assertEq(exportsDesc.writable, true);
-assertEq(exportsDesc.enumerable, true);
-assertEq(exportsDesc.configurable, true);
+const instanceExportsDesc = Object.getOwnPropertyDescriptor(i1, '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;
 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/);
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -21,16 +21,17 @@
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Maybe.h"
 
 #include "jsprf.h"
 
 #include "builtin/Promise.h"
 #include "jit/JitOptions.h"
 #include "vm/Interpreter.h"
+#include "vm/String.h"
 #include "wasm/WasmCompile.h"
 #include "wasm/WasmInstance.h"
 #include "wasm/WasmModule.h"
 #include "wasm/WasmSignalHandlers.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
@@ -489,22 +490,202 @@ const Class WasmModuleObject::class_ =
 };
 
 const JSPropertySpec WasmModuleObject::properties[] =
 { JS_PS_END };
 
 const JSFunctionSpec WasmModuleObject::methods[] =
 { JS_FS_END };
 
+const JSFunctionSpec WasmModuleObject::static_methods[] =
+{
+    JS_FN("imports", WasmModuleObject::imports, 1, 0),
+    JS_FN("exports", WasmModuleObject::exports, 1, 0),
+    JS_FS_END
+};
+
 /* static */ void
 WasmModuleObject::finalize(FreeOp* fop, JSObject* obj)
 {
     obj->as<WasmModuleObject>().module().Release();
 }
 
+static bool
+GetModuleArg(JSContext* cx, CallArgs args, const char* name, Module** module)
+{
+    if (!args.requireAtLeast(cx, name, 1))
+        return false;
+
+    if (!args[0].isObject()) {
+        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;
+
+    explicit KindNames(JSContext* cx) : kind(cx), table(cx), memory(cx) {}
+};
+
+static bool
+InitKindNames(JSContext* cx, KindNames* names)
+{
+    JSAtom* kind = Atomize(cx, "kind", strlen("kind"));
+    if (!kind)
+        return false;
+    names->kind = kind->asPropertyName();
+
+    JSAtom* table = Atomize(cx, "table", strlen("table"));
+    if (!table)
+        return false;
+    names->table = table->asPropertyName();
+
+    JSAtom* memory = Atomize(cx, "memory", strlen("memory"));
+    if (!memory)
+        return false;
+    names->memory = memory->asPropertyName();
+
+    return true;
+}
+
+static JSString*
+KindToString(JSContext* cx, const KindNames& names, DefinitionKind kind)
+{
+    switch (kind) {
+      case DefinitionKind::Function:
+        return cx->names().function;
+      case DefinitionKind::Table:
+        return names.table;
+      case DefinitionKind::Memory:
+        return names.memory;
+      case DefinitionKind::Global:
+        return cx->names().global;
+    }
+
+    MOZ_CRASH("invalid kind");
+}
+
+static JSString*
+UTF8CharsToString(JSContext* cx, const char* chars)
+{
+    return NewStringCopyUTF8Z<CanGC>(cx, JS::ConstUTF8CharsZ(chars, strlen(chars)));
+}
+
+/* static */ bool
+WasmModuleObject::imports(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    Module* module;
+    if (!GetModuleArg(cx, args, "WebAssembly.Module.imports", &module))
+        return false;
+
+    KindNames names(cx);
+    if (!InitKindNames(cx, &names))
+        return false;
+
+    AutoValueVector elems(cx);
+    if (!elems.reserve(module->imports().length()))
+        return false;
+
+    for (const Import& import : module->imports()) {
+        Rooted<IdValueVector> props(cx, IdValueVector(cx));
+        if (!props.reserve(3))
+            return false;
+
+        JSString* moduleStr = UTF8CharsToString(cx, import.module.get());
+        if (!moduleStr)
+            return false;
+        props.infallibleAppend(IdValuePair(NameToId(cx->names().module), StringValue(moduleStr)));
+
+        JSString* nameStr = UTF8CharsToString(cx, import.field.get());
+        if (!nameStr)
+            return false;
+        props.infallibleAppend(IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));
+
+        JSString* kindStr = KindToString(cx, names, import.kind);
+        if (!kindStr)
+            return false;
+        props.infallibleAppend(IdValuePair(NameToId(names.kind), StringValue(kindStr)));
+
+        JSObject* obj = ObjectGroup::newPlainObject(cx, props.begin(), props.length(), GenericObject);
+        if (!obj)
+            return false;
+
+        elems.infallibleAppend(ObjectValue(*obj));
+    }
+
+    JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
+    if (!arr)
+        return false;
+
+    args.rval().setObject(*arr);
+    return true;
+}
+
+/* static */ bool
+WasmModuleObject::exports(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    Module* module;
+    if (!GetModuleArg(cx, args, "WebAssembly.Module.exports", &module))
+        return false;
+
+    KindNames names(cx);
+    if (!InitKindNames(cx, &names))
+        return false;
+
+    AutoValueVector elems(cx);
+    if (!elems.reserve(module->exports().length()))
+        return false;
+
+    for (const Export& exp : module->exports()) {
+        Rooted<IdValueVector> props(cx, IdValueVector(cx));
+        if (!props.reserve(2))
+            return false;
+
+        JSString* nameStr = UTF8CharsToString(cx, exp.fieldName());
+        if (!nameStr)
+            return false;
+        props.infallibleAppend(IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));
+
+        JSString* kindStr = KindToString(cx, names, exp.kind());
+        if (!kindStr)
+            return false;
+        props.infallibleAppend(IdValuePair(NameToId(names.kind), StringValue(kindStr)));
+
+        JSObject* obj = ObjectGroup::newPlainObject(cx, props.begin(), props.length(), GenericObject);
+        if (!obj)
+            return false;
+
+        elems.infallibleAppend(ObjectValue(*obj));
+    }
+
+    JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
+    if (!arr)
+        return false;
+
+    args.rval().setObject(*arr);
+    return true;
+}
+
 /* static */ WasmModuleObject*
 WasmModuleObject::create(ExclusiveContext* cx, Module& module, HandleObject proto)
 {
     AutoSetNewObjectMetadata metadata(cx);
     auto* obj = NewObjectWithGivenProto<WasmModuleObject>(cx, proto);
     if (!obj)
         return nullptr;
 
@@ -623,16 +804,19 @@ const Class WasmInstanceObject::class_ =
 };
 
 const JSPropertySpec WasmInstanceObject::properties[] =
 { JS_PS_END };
 
 const JSFunctionSpec WasmInstanceObject::methods[] =
 { JS_FS_END };
 
+const JSFunctionSpec WasmInstanceObject::static_methods[] =
+{ JS_FS_END };
+
 bool
 WasmInstanceObject::isNewborn() const
 {
     MOZ_ASSERT(is<WasmInstanceObject>());
     return getReservedSlot(INSTANCE_SLOT).isUndefined();
 }
 
 /* static */ void
@@ -994,16 +1178,19 @@ WasmMemoryObject::grow(JSContext* cx, un
 }
 
 const JSFunctionSpec WasmMemoryObject::methods[] =
 {
     JS_FN("grow", WasmMemoryObject::grow, 1, 0),
     JS_FS_END
 };
 
+const JSFunctionSpec WasmMemoryObject::static_methods[] =
+{ JS_FS_END };
+
 ArrayBufferObjectMaybeShared&
 WasmMemoryObject::buffer() const
 {
     return getReservedSlot(BUFFER_SLOT).toObject().as<ArrayBufferObjectMaybeShared>();
 }
 
 bool
 WasmMemoryObject::hasObservers() const
@@ -1379,16 +1566,19 @@ WasmTableObject::grow(JSContext* cx, uns
 const JSFunctionSpec WasmTableObject::methods[] =
 {
     JS_FN("get", WasmTableObject::get, 1, 0),
     JS_FN("set", WasmTableObject::set, 2, 0),
     JS_FN("grow", WasmTableObject::grow, 1, 0),
     JS_FS_END
 };
 
+const JSFunctionSpec WasmTableObject::static_methods[] =
+{ JS_FS_END };
+
 Table&
 WasmTableObject::table() const
 {
     return *(Table*)getReservedSlot(TABLE_SLOT).toPrivate();
 }
 
 // ============================================================================
 // WebAssembly class and static methods
@@ -1594,16 +1784,19 @@ InitConstructor(JSContext* cx, HandleObj
     RootedAtom className(cx, Atomize(cx, name, strlen(name)));
     if (!className)
         return false;
 
     RootedFunction ctor(cx, NewNativeConstructor(cx, Class::construct, 1, className));
     if (!ctor)
         return false;
 
+    if (!DefinePropertiesAndFunctions(cx, ctor, nullptr, Class::static_methods))
+        return false;
+
     if (!LinkConstructorAndPrototype(cx, ctor, proto))
         return false;
 
     RootedId id(cx, AtomToId(className));
     RootedValue ctorValue(cx, ObjectValue(*ctor));
     return DefineProperty(cx, wasm, id, ctorValue, nullptr, nullptr, 0);
 }
 
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -112,21 +112,25 @@ InitWebAssemblyClass(JSContext* cx, Hand
 // wasm::Module. These objects are used both as content-facing JS objects and as
 // internal implementation details of asm.js.
 
 class WasmModuleObject : public NativeObject
 {
     static const unsigned MODULE_SLOT = 0;
     static const ClassOps classOps_;
     static void finalize(FreeOp* fop, JSObject* obj);
+    static bool imports(JSContext* cx, unsigned argc, Value* vp);
+    static bool exports(JSContext* cx, unsigned argc, Value* vp);
+
   public:
     static const unsigned RESERVED_SLOTS = 1;
     static const Class class_;
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
+    static const JSFunctionSpec static_methods[];
     static bool construct(JSContext*, unsigned, Value*);
 
     static WasmModuleObject* create(ExclusiveContext* cx,
                                     wasm::Module& module,
                                     HandleObject proto = nullptr);
     wasm::Module& module() const;
 };
 
@@ -153,16 +157,17 @@ class WasmInstanceObject : public Native
     using WeakExportMap = JS::WeakCache<ExportMap>;
     WeakExportMap& exports() const;
 
   public:
     static const unsigned RESERVED_SLOTS = 2;
     static const Class class_;
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
+    static const JSFunctionSpec static_methods[];
     static bool construct(JSContext*, unsigned, Value*);
 
     static WasmInstanceObject* create(JSContext* cx,
                                       UniquePtr<wasm::Code> code,
                                       HandleWasmMemoryObject memory,
                                       Vector<RefPtr<wasm::Table>, 0, SystemAllocPolicy>&& tables,
                                       Handle<FunctionVector> funcImports,
                                       const wasm::ValVector& globalImports,
@@ -199,16 +204,17 @@ class WasmMemoryObject : public NativeOb
     WeakInstanceSet& observers() const;
     WeakInstanceSet* getOrCreateObservers(JSContext* cx);
 
   public:
     static const unsigned RESERVED_SLOTS = 2;
     static const Class class_;
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
+    static const JSFunctionSpec static_methods[];
     static bool construct(JSContext*, unsigned, Value*);
 
     static WasmMemoryObject* create(ExclusiveContext* cx,
                                     Handle<ArrayBufferObjectMaybeShared*> buffer,
                                     HandleObject proto);
     ArrayBufferObjectMaybeShared& buffer() const;
 
     bool movingGrowable() const;
@@ -236,16 +242,17 @@ class WasmTableObject : public NativeObj
     static bool growImpl(JSContext* cx, const CallArgs& args);
     static bool grow(JSContext* cx, unsigned argc, Value* vp);
 
   public:
     static const unsigned RESERVED_SLOTS = 1;
     static const Class class_;
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
+    static const JSFunctionSpec static_methods[];
     static bool construct(JSContext*, unsigned, Value*);
 
     // Note that, after creation, a WasmTableObject's table() is not initialized
     // and must be initialized before use.
 
     static WasmTableObject* create(JSContext* cx, wasm::Limits limits);
     wasm::Table& table() const;
 };
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -206,16 +206,17 @@ class Module : public JS::WasmModule
         elemSegments_(Move(elemSegments)),
         metadata_(&metadata),
         bytecode_(&bytecode)
     {}
     ~Module() override { /* Note: can be called on any thread */ }
 
     const Metadata& metadata() const { return *metadata_; }
     const ImportVector& imports() const { return imports_; }
+    const ExportVector& exports() const { return exports_; }
 
     // Instantiate this module with the given imports:
 
     bool instantiate(JSContext* cx,
                      Handle<FunctionVector> funcImports,
                      HandleWasmTableObject tableImport,
                      HandleWasmMemoryObject memoryImport,
                      const ValVector& globalImports,