Bug 1276029 - Baldr: add JS API to support structured clone of wasm objects (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Fri, 21 Oct 2016 22:29:52 -0500
changeset 319031 f325d73d76d14f2b4ee2e05d6c23a8f8f521907f
parent 319030 cc32d7a9d319a27c3a3f5a2f05288ab1454f9950
child 319032 ad249ce81497259edc016f4c6957a256a455dbc0
push id30858
push userryanvm@gmail.com
push dateSun, 23 Oct 2016 17:17:41 +0000
treeherdermozilla-central@a9a41b69f3f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1276029
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1276029 - Baldr: add JS API to support structured clone of wasm objects (r=bbouvier) MozReview-Commit-ID: B2coukYF6mG
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmModule.h
js/src/asmjs/WasmSerialize.h
js/src/jsapi.cpp
js/src/jsapi.h
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -13,16 +13,19 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "asmjs/WasmModule.h"
 
+#include "jsnspr.h"
+
+#include "asmjs/WasmCompile.h"
 #include "asmjs/WasmInstance.h"
 #include "asmjs/WasmJS.h"
 #include "asmjs/WasmSerialize.h"
 #include "jit/JitOptions.h"
 
 #include "jsatominlines.h"
 
 #include "vm/ArrayBufferObject-inl.h"
@@ -252,38 +255,45 @@ ElemSegment::deserialize(const uint8_t* 
 
 size_t
 ElemSegment::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return elemFuncIndices.sizeOfExcludingThis(mallocSizeOf) +
            elemCodeRangeIndices.sizeOfExcludingThis(mallocSizeOf);
 }
 
-void
+/* virtual */ void
 Module::serializedSize(size_t* bytecodeSize, size_t* compiledSize) const
 {
     *bytecodeSize = SerializedPodVectorSize(bytecode_->bytes);
 
     *compiledSize = assumptions_.serializedSize() +
                     SerializedPodVectorSize(code_) +
                     linkData_.serializedSize() +
                     SerializedVectorSize(imports_) +
                     SerializedVectorSize(exports_) +
                     SerializedPodVectorSize(dataSegments_) +
                     SerializedVectorSize(elemSegments_) +
                     metadata_->serializedSize();
 }
 
-void
+/* virtual */ void
 Module::serialize(uint8_t* bytecodeBegin, size_t bytecodeSize,
                   uint8_t* compiledBegin, size_t compiledSize) const
 {
+    // Bytecode deserialization is not guarded by Assumptions and thus must not
+    // change incompatibly between builds.
+
     uint8_t* bytecodeEnd = SerializePodVector(bytecodeBegin, bytecode_->bytes);
     MOZ_RELEASE_ASSERT(bytecodeEnd == bytecodeBegin + bytecodeSize);
 
+    // Assumption must be serialized at the beginning of the compiled bytes so
+    // that compiledAssumptionsMatch can detect a build-id mismatch before any
+    // other decoding occurs.
+
     uint8_t* cursor = compiledBegin;
     cursor = assumptions_.serialize(cursor);
     cursor = SerializePodVector(cursor, code_);
     cursor = linkData_.serialize(cursor);
     cursor = SerializeVector(cursor, imports_);
     cursor = SerializeVector(cursor, exports_);
     cursor = SerializePodVector(cursor, dataSegments_);
     cursor = SerializeVector(cursor, elemSegments_);
@@ -302,33 +312,26 @@ Module::assumptionsMatch(const Assumptio
     return current == cached;
 }
 
 /* static */ SharedModule
 Module::deserialize(const uint8_t* bytecodeBegin, size_t bytecodeSize,
                     const uint8_t* compiledBegin, size_t compiledSize,
                     Metadata* maybeMetadata)
 {
-    // Bytecode deserialization is not guarded by Assumptions and thus must not
-    // change incompatibly between builds.
-
     MutableBytes bytecode = js_new<ShareableBytes>();
     if (!bytecode)
         return nullptr;
 
     const uint8_t* bytecodeEnd = DeserializePodVector(bytecodeBegin, &bytecode->bytes);
     if (!bytecodeEnd)
         return nullptr;
 
     MOZ_RELEASE_ASSERT(bytecodeEnd == bytecodeBegin + bytecodeSize);
 
-    // Assumption must be serialized at the beginning of the compiled bytes so
-    // that compiledAssumptionsMatch can detect a build-id mismatch before any
-    // other decoding occurs.
-
     const uint8_t* cursor = compiledBegin;
 
     Assumptions assumptions;
     cursor = assumptions.deserialize(cursor);
     if (!cursor)
         return nullptr;
 
     Bytes code;
@@ -382,16 +385,107 @@ Module::deserialize(const uint8_t* bytec
                           Move(imports),
                           Move(exports),
                           Move(dataSegments),
                           Move(elemSegments),
                           *metadata,
                           *bytecode);
 }
 
+/* virtual */ JSObject*
+Module::createObject(JSContext* cx)
+{
+    if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly))
+        return nullptr;
+
+    RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
+    return WasmModuleObject::create(cx, *this, proto);
+}
+
+struct MemUnmap
+{
+    uint32_t size;
+    MemUnmap() : size(0) {}
+    explicit MemUnmap(uint32_t size) : size(size) {}
+    void operator()(uint8_t* p) { MOZ_ASSERT(size); PR_MemUnmap(p, size); }
+};
+
+typedef UniquePtr<uint8_t, MemUnmap> UniqueMapping;
+
+static UniqueMapping
+MapFile(PRFileDesc* file, PRFileInfo* info)
+{
+    if (PR_GetOpenFileInfo(file, info) != PR_SUCCESS)
+        return nullptr;
+
+    PRFileMap* map = PR_CreateFileMap(file, info->size, PR_PROT_READONLY);
+    if (!map)
+        return nullptr;
+
+    // PRFileMap objects do not need to be kept alive after the memory has been
+    // mapped, so unconditionally close the PRFileMap, regardless of whether
+    // PR_MemMap succeeds.
+    uint8_t* memory = (uint8_t*)PR_MemMap(map, 0, info->size);
+    MOZ_ALWAYS_TRUE(PR_CloseFileMap(map));
+    return UniqueMapping(memory, MemUnmap(info->size));
+}
+
+bool
+wasm::CompiledModuleAssumptionsMatch(PRFileDesc* compiled, JS::BuildIdCharVector&& buildId)
+{
+    PRFileInfo info;
+    UniqueMapping mapping = MapFile(compiled, &info);
+    if (!mapping)
+        return false;
+
+    Assumptions assumptions(Move(buildId));
+    return Module::assumptionsMatch(assumptions, mapping.get());
+}
+
+SharedModule
+wasm::DeserializeModule(PRFileDesc* bytecodeFile, PRFileDesc* maybeCompiledFile,
+                        JS::BuildIdCharVector&& buildId, UniqueChars filename,
+                        unsigned line, unsigned column)
+{
+    PRFileInfo bytecodeInfo;
+    UniqueMapping bytecodeMapping = MapFile(bytecodeFile, &bytecodeInfo);
+    if (!bytecodeMapping)
+        return nullptr;
+
+    if (PRFileDesc* compiledFile = maybeCompiledFile) {
+        PRFileInfo compiledInfo;
+        UniqueMapping compiledMapping = MapFile(compiledFile, &compiledInfo);
+        if (!compiledMapping)
+            return nullptr;
+
+        return Module::deserialize(bytecodeMapping.get(), bytecodeInfo.size,
+                                   compiledMapping.get(), compiledInfo.size);
+    }
+
+    MutableBytes bytecode = js_new<ShareableBytes>();
+    if (!bytecode)
+        return nullptr;
+
+    const uint8_t* bytecodeEnd = DeserializePodVector(bytecodeMapping.get(), &bytecode->bytes);
+    if (!bytecodeEnd)
+        return nullptr;
+
+    MOZ_RELEASE_ASSERT(bytecodeEnd == bytecodeMapping.get() + bytecodeInfo.size);
+
+    ScriptedCaller scriptedCaller;
+    scriptedCaller.filename = Move(filename);
+    scriptedCaller.line = line;
+    scriptedCaller.column = column;
+
+    CompileArgs args(Assumptions(Move(buildId)), Move(scriptedCaller));
+
+    UniqueChars error;
+    return Compile(*bytecode, Move(args), &error);
+}
+
 /* virtual */ void
 Module::addSizeOfMisc(MallocSizeOf mallocSizeOf,
                       Metadata::SeenSet* seenMetadata,
                       ShareableBytes::SeenSet* seenBytes,
                       size_t* code,
                       size_t* data) const
 {
     *data += mallocSizeOf(this) +
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -170,17 +170,17 @@ typedef Vector<ElemSegment, 0, SystemAll
 // to produce a new, equivalent Module.
 //
 // Since fully linked-and-instantiated code (represented by CodeSegment) cannot
 // be shared between instances, Module stores an unlinked, uninstantiated copy
 // of the code (represented by the Bytes) and creates a new CodeSegment each
 // time it is instantiated. In the future, Module will store a shareable,
 // immutable CodeSegment that can be shared by all its instances.
 
-class Module : public RefCounted<Module>
+class Module : public JS::WasmModule
 {
     const Assumptions       assumptions_;
     const Bytes             code_;
     const LinkData          linkData_;
     const ImportVector      imports_;
     const ExportVector      exports_;
     const DataSegmentVector dataSegments_;
     const ElemSegmentVector elemSegments_;
@@ -213,50 +213,61 @@ class Module : public RefCounted<Module>
         linkData_(Move(linkData)),
         imports_(Move(imports)),
         exports_(Move(exports)),
         dataSegments_(Move(dataSegments)),
         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_; }
 
     // Instantiate this module with the given imports:
 
     bool instantiate(JSContext* cx,
                      Handle<FunctionVector> funcImports,
                      HandleWasmTableObject tableImport,
                      HandleWasmMemoryObject memoryImport,
                      const ValVector& globalImports,
                      HandleObject instanceProto,
                      MutableHandleWasmInstanceObject instanceObj) const;
 
     // Structured clone support:
 
-    void serializedSize(size_t* bytecodeSize, size_t* compiledSize) const;
+    void serializedSize(size_t* bytecodeSize, size_t* compiledSize) const override;
     void serialize(uint8_t* bytecodeBegin, size_t bytecodeSize,
-                   uint8_t* compiledBegin, size_t compiledSize) const;
+                   uint8_t* compiledBegin, size_t compiledSize) const override;
     static bool assumptionsMatch(const Assumptions& current, const uint8_t* compiledBegin);
     static RefPtr<Module> deserialize(const uint8_t* bytecodeBegin, size_t bytecodeSize,
                                       const uint8_t* compiledBegin, size_t compiledSize,
                                       Metadata* maybeMetadata = nullptr);
+    JSObject* createObject(JSContext* cx) override;
 
     // about:memory reporting:
 
     void addSizeOfMisc(MallocSizeOf mallocSizeOf,
                        Metadata::SeenSet* seenMetadata,
                        ShareableBytes::SeenSet* seenBytes,
                        size_t* code, size_t* data) const;
 
     // Generated code analysis support:
 
     bool extractCode(JSContext* cx, MutableHandleValue vp);
 };
 
 typedef RefPtr<Module> SharedModule;
 
+// JS API implementations:
+
+bool
+CompiledModuleAssumptionsMatch(PRFileDesc* compiled, JS::BuildIdCharVector&& buildId);
+
+SharedModule
+DeserializeModule(PRFileDesc* bytecode, PRFileDesc* maybeCompiled, JS::BuildIdCharVector&& buildId,
+                  UniqueChars filename, unsigned line, unsigned column);
+
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_module_h
--- a/js/src/asmjs/WasmSerialize.h
+++ b/js/src/asmjs/WasmSerialize.h
@@ -111,17 +111,17 @@ SerializedPodVectorSize(const mozilla::V
            vec.length() * sizeof(T);
 }
 
 template <class T, size_t N>
 static inline uint8_t*
 SerializePodVector(uint8_t* cursor, const mozilla::Vector<T, N, SystemAllocPolicy>& vec)
 {
     // This binary format must not change without taking into consideration the
-    // constraints in Assumptions::serialize.
+    // constraints in Assumptions::serialize and Module::serialize.
 
     cursor = WriteScalar<uint32_t>(cursor, vec.length());
     cursor = WriteBytes(cursor, vec.begin(), vec.length() * sizeof(T));
     return cursor;
 }
 
 template <class T, size_t N>
 static inline const uint8_t*
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -38,16 +38,17 @@
 #include "jsstr.h"
 #include "jstypes.h"
 #include "jsutil.h"
 #include "jswatchpoint.h"
 #include "jsweakmap.h"
 #include "jswrapper.h"
 
 #include "asmjs/AsmJS.h"
+#include "asmjs/WasmModule.h"
 #include "builtin/AtomicsObject.h"
 #include "builtin/Eval.h"
 #include "builtin/Intl.h"
 #include "builtin/MapObject.h"
 #include "builtin/Promise.h"
 #include "builtin/RegExp.h"
 #include "builtin/SymbolObject.h"
 #ifdef ENABLE_SIMD
@@ -6642,16 +6643,42 @@ JS::SetBuildIdOp(JSContext* cx, JS::Buil
 }
 
 JS_PUBLIC_API(void)
 JS::SetAsmJSCacheOps(JSContext* cx, const JS::AsmJSCacheOps* ops)
 {
     cx->runtime()->asmJSCacheOps = *ops;
 }
 
+bool
+JS::IsWasmModuleObject(HandleObject obj)
+{
+    return obj->is<WasmModuleObject>();
+}
+
+JS_PUBLIC_API(RefPtr<JS::WasmModule>)
+JS::GetWasmModule(HandleObject obj)
+{
+    return &obj->as<WasmModuleObject>().module();
+}
+
+JS_PUBLIC_API(bool)
+JS::CompiledWasmModuleAssumptionsMatch(PRFileDesc* compiled, JS::BuildIdCharVector&& buildId)
+{
+    return wasm::CompiledModuleAssumptionsMatch(compiled, Move(buildId));
+}
+
+JS_PUBLIC_API(RefPtr<JS::WasmModule>)
+JS::DeserializeWasmModule(PRFileDesc* bytecode, PRFileDesc* maybeCompiled,
+                          JS::BuildIdCharVector&& buildId, UniqueChars file,
+                          unsigned line, unsigned column)
+{
+    return wasm::DeserializeModule(bytecode, maybeCompiled, Move(buildId), Move(file), line, column);
+}
+
 char*
 JSAutoByteString::encodeLatin1(ExclusiveContext* cx, JSString* str)
 {
     mBytes = EncodeLatin1(cx, str);
     return mBytes;
 }
 
 JS_PUBLIC_API(void)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5944,43 +5944,94 @@ enum AsmJSCacheResult
  */
 typedef AsmJSCacheResult
 (* OpenAsmJSCacheEntryForWriteOp)(HandleObject global, bool installed,
                                   const char16_t* begin, const char16_t* end,
                                   size_t size, uint8_t** memory, intptr_t* handle);
 typedef void
 (* CloseAsmJSCacheEntryForWriteOp)(size_t size, uint8_t* memory, intptr_t handle);
 
-typedef js::Vector<char, 0, js::SystemAllocPolicy> BuildIdCharVector;
-
-/**
- * Return the buildId (represented as a sequence of characters) associated with
- * the currently-executing build. If the JS engine is embedded such that a
- * single cache entry can be observed by different compiled versions of the JS
- * engine, it is critical that the buildId shall change for each new build of
- * the JS engine.
- */
-typedef bool
-(* BuildIdOp)(BuildIdCharVector* buildId);
-
 struct AsmJSCacheOps
 {
     OpenAsmJSCacheEntryForReadOp openEntryForRead;
     CloseAsmJSCacheEntryForReadOp closeEntryForRead;
     OpenAsmJSCacheEntryForWriteOp openEntryForWrite;
     CloseAsmJSCacheEntryForWriteOp closeEntryForWrite;
 };
 
 extern JS_PUBLIC_API(void)
 SetAsmJSCacheOps(JSContext* cx, const AsmJSCacheOps* callbacks);
 
+/**
+ * Return the buildId (represented as a sequence of characters) associated with
+ * the currently-executing build. If the JS engine is embedded such that a
+ * single cache entry can be observed by different compiled versions of the JS
+ * engine, it is critical that the buildId shall change for each new build of
+ * the JS engine.
+ */
+typedef js::Vector<char, 0, js::SystemAllocPolicy> BuildIdCharVector;
+
+typedef bool
+(* BuildIdOp)(BuildIdCharVector* buildId);
+
 extern JS_PUBLIC_API(void)
 SetBuildIdOp(JSContext* cx, BuildIdOp buildIdOp);
 
 /**
+ * The WasmModule interface allows the embedding to hold a reference to the
+ * underying C++ implementation of a JS WebAssembly.Module object for purposes
+ * of (de)serialization off the object's JSRuntime's thread.
+ *
+ * - Serialization starts when WebAssembly.Module is passed to the
+ * structured-clone algorithm. JS::GetWasmModule is called on the JSRuntime
+ * thread that initiated the structured clone to get the JS::WasmModule.
+ * This interface is then taken to a background thread where serializedSize()
+ * and serialize() are called to write the object to two files: a bytecode file
+ * that always allows successful deserialization and a compiled-code file keyed
+ * on cpu- and build-id that may become invalid if either of these change between
+ * serialization and deserialization. After serialization, the reference is
+ * dropped from the background thread.
+ *
+ * - Deserialization starts when the structured clone algorithm encounters a
+ * serialized WebAssembly.Module. On a background thread, the compiled-code file
+ * is opened and CompiledWasmModuleAssumptionsMatch is called to see if it is
+ * still valid (as described above). DeserializeWasmModule is then called to
+ * construct a JS::WasmModule (also on the background thread), passing the
+ * bytecode file descriptor and, if valid, the compiled-code file descriptor.
+ * The JS::WasmObject is then transported to the JSRuntime thread (which
+ * originated the request) and the wrapping WebAssembly.Module object is created
+ * by calling createObject().
+ */
+
+struct WasmModule : mozilla::external::AtomicRefCounted<WasmModule>
+{
+    MOZ_DECLARE_REFCOUNTED_TYPENAME(WasmModule)
+    virtual ~WasmModule() {}
+
+    virtual void serializedSize(size_t* bytecodeSize, size_t* compiledSize) const = 0;
+    virtual void serialize(uint8_t* bytecodeBegin, size_t bytecodeSize,
+                           uint8_t* compiledBegin, size_t compiledSize) const = 0;
+
+    virtual JSObject* createObject(JSContext* cx) = 0;
+};
+
+extern JS_PUBLIC_API(bool)
+IsWasmModuleObject(HandleObject obj);
+
+extern JS_PUBLIC_API(RefPtr<WasmModule>)
+GetWasmModule(HandleObject obj);
+
+extern JS_PUBLIC_API(bool)
+CompiledWasmModuleAssumptionsMatch(PRFileDesc* compiled, BuildIdCharVector&& buildId);
+
+extern JS_PUBLIC_API(RefPtr<WasmModule>)
+DeserializeWasmModule(PRFileDesc* bytecode, PRFileDesc* maybeCompiled, BuildIdCharVector&& buildId,
+                      JS::UniqueChars filename, unsigned line, unsigned column);
+
+/**
  * Convenience class for imitating a JS level for-of loop. Typical usage:
  *
  *     ForOfIterator it(cx);
  *     if (!it.init(iterable))
  *       return false;
  *     RootedValue val(cx);
  *     while (true) {
  *       bool done;