Bug 1359653: Part 1 - Use a const Range rather than a Vector for XDR decoding. r?shu draft
authorKris Maglione <maglione.k@gmail.com>
Tue, 02 May 2017 18:07:59 -0700
changeset 571599 d40c3f3c989b545e04242d54ef0bbe274a3e1f16
parent 567047 8f80e3456bfdfa1d4633478d52e288f85c3b71cf
child 571600 205fc86ec4f5e8bf0d87c206b53b861c5d3c2c57
push id56864
push usermaglione.k@gmail.com
push dateWed, 03 May 2017 01:22:20 +0000
reviewersshu
bugs1359653
milestone55.0a1
Bug 1359653: Part 1 - Use a const Range rather than a Vector for XDR decoding. r?shu MozReview-Commit-ID: JkGNmOAKAxD
js/src/jsapi.cpp
js/src/jsapi.h
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
js/src/vm/Xdr.h
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4198,18 +4198,29 @@ JS::CancelOffThreadModule(JSContext* cx,
     HelperThreadState().cancelParseTask(cx->runtime(), ParseTaskKind::Module, token);
 }
 
 JS_PUBLIC_API(bool)
 JS::DecodeOffThreadScript(JSContext* cx, const ReadOnlyCompileOptions& options,
                           mozilla::Vector<uint8_t>& buffer /* TranscodeBuffer& */, size_t cursor,
                           OffThreadCompileCallback callback, void* callbackData)
 {
-    MOZ_ASSERT(CanCompileOffThread(cx, options, buffer.length() - cursor));
-    return StartOffThreadDecodeScript(cx, options, buffer, cursor, callback, callbackData);
+    JS::TranscodeRange range(buffer.begin() + cursor, buffer.length() - cursor);
+
+    MOZ_ASSERT(CanCompileOffThread(cx, options, range.length()));
+    return StartOffThreadDecodeScript(cx, options, range, callback, callbackData);
+}
+
+JS_PUBLIC_API(bool)
+JS::DecodeOffThreadScript(JSContext* cx, const ReadOnlyCompileOptions& options,
+                          const mozilla::Range<uint8_t>& range /* TranscodeRange& */,
+                          OffThreadCompileCallback callback, void* callbackData)
+{
+    MOZ_ASSERT(CanCompileOffThread(cx, options, range.length()));
+    return StartOffThreadDecodeScript(cx, options, range, callback, callbackData);
 }
 
 JS_PUBLIC_API(JSScript*)
 JS::FinishOffThreadScriptDecoder(JSContext* cx, void* token)
 {
     MOZ_ASSERT(cx);
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
     return HelperThreadState().finishScriptDecodeTask(cx, token);
@@ -6959,16 +6970,25 @@ JS::DecodeScript(JSContext* cx, Transcod
 {
     XDRDecoder decoder(cx, buffer, cursorIndex);
     decoder.codeScript(scriptp);
     MOZ_ASSERT(bool(scriptp) == (decoder.resultCode() == TranscodeResult_Ok));
     return decoder.resultCode();
 }
 
 JS_PUBLIC_API(JS::TranscodeResult)
+JS::DecodeScript(JSContext* cx, const TranscodeRange& range, JS::MutableHandleScript scriptp)
+{
+    XDRDecoder decoder(cx, range);
+    decoder.codeScript(scriptp);
+    MOZ_ASSERT(bool(scriptp) == (decoder.resultCode() == TranscodeResult_Ok));
+    return decoder.resultCode();
+}
+
+JS_PUBLIC_API(JS::TranscodeResult)
 JS::DecodeInterpretedFunction(JSContext* cx, TranscodeBuffer& buffer,
                               JS::MutableHandleFunction funp,
                               size_t cursorIndex)
 {
     XDRDecoder decoder(cx, buffer, cursorIndex);
     decoder.codeFunction(funp);
     MOZ_ASSERT(bool(funp) == (decoder.resultCode() == TranscodeResult_Ok));
     return decoder.resultCode();
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4265,16 +4265,21 @@ FinishOffThreadModule(JSContext* cx, voi
 extern JS_PUBLIC_API(void)
 CancelOffThreadModule(JSContext* cx, void* token);
 
 extern JS_PUBLIC_API(bool)
 DecodeOffThreadScript(JSContext* cx, const ReadOnlyCompileOptions& options,
                       mozilla::Vector<uint8_t>& buffer /* TranscodeBuffer& */, size_t cursor,
                       OffThreadCompileCallback callback, void* callbackData);
 
+extern JS_PUBLIC_API(bool)
+DecodeOffThreadScript(JSContext* cx, const ReadOnlyCompileOptions& options,
+                      const mozilla::Range<uint8_t>& range /* TranscodeRange& */,
+                      OffThreadCompileCallback callback, void* callbackData);
+
 extern JS_PUBLIC_API(JSScript*)
 FinishOffThreadScriptDecoder(JSContext* cx, void* token);
 
 extern JS_PUBLIC_API(void)
 CancelOffThreadScriptDecoder(JSContext* cx, void* token);
 
 /**
  * Compile a function with envChain plus the global as its scope chain.
@@ -6138,16 +6143,17 @@ class MOZ_RAII AutoHideScriptedCaller
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 /*
  * Encode/Decode interpreted scripts and functions to/from memory.
  */
 
 typedef mozilla::Vector<uint8_t> TranscodeBuffer;
+typedef mozilla::Range<uint8_t> TranscodeRange;
 
 enum TranscodeResult
 {
     // Successful encoding / decoding.
     TranscodeResult_Ok = 0,
 
     // A warning message, is set to the message out-param.
     TranscodeResult_Failure = 0x100,
@@ -6168,16 +6174,19 @@ EncodeScript(JSContext* cx, TranscodeBuf
 extern JS_PUBLIC_API(TranscodeResult)
 EncodeInterpretedFunction(JSContext* cx, TranscodeBuffer& buffer, JS::HandleObject funobj);
 
 extern JS_PUBLIC_API(TranscodeResult)
 DecodeScript(JSContext* cx, TranscodeBuffer& buffer, JS::MutableHandleScript scriptp,
              size_t cursorIndex = 0);
 
 extern JS_PUBLIC_API(TranscodeResult)
+DecodeScript(JSContext* cx, const TranscodeRange& range, JS::MutableHandleScript scriptp);
+
+extern JS_PUBLIC_API(TranscodeResult)
 DecodeInterpretedFunction(JSContext* cx, TranscodeBuffer& buffer, JS::MutableHandleFunction funp,
                           size_t cursorIndex = 0);
 
 // Register an encoder on the given script source, such that all functions can
 // be encoded as they are parsed. This strategy is used to avoid blocking the
 // active thread in a non-interruptible way.
 //
 // The |script| argument of |StartIncrementalEncoding| and
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -292,35 +292,37 @@ static const JSClassOps parseTaskGlobalC
 static const JSClass parseTaskGlobalClass = {
     "internal-parse-task-global", JSCLASS_GLOBAL_FLAGS,
     &parseTaskGlobalClassOps
 };
 
 ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
                      const char16_t* chars, size_t length,
                      JS::OffThreadCompileCallback callback, void* callbackData)
-  : kind(kind), options(cx), chars(chars), length(length),
+  : kind(kind), options(cx),
     alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     parseGlobal(parseGlobal),
     callback(callback), callbackData(callbackData),
     script(nullptr), sourceObject(nullptr),
     overRecursed(false), outOfMemory(false)
 {
+    data.construct<TwoByteChars>(chars, length);
 }
 
 ParseTask::ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
-                     JS::TranscodeBuffer& buffer, size_t cursor,
+                     const JS::TranscodeRange& range,
                      JS::OffThreadCompileCallback callback, void* callbackData)
-  : kind(kind), options(cx), buffer(&buffer), cursor(cursor),
+  : kind(kind), options(cx),
     alloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     parseGlobal(parseGlobal),
     callback(callback), callbackData(callbackData),
     script(nullptr), sourceObject(nullptr),
     overRecursed(false), outOfMemory(false)
 {
+    data.construct<const JS::TranscodeRange>(range);
 }
 
 bool
 ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options)
 {
     if (!this->options.copy(cx, options))
         return false;
 
@@ -377,53 +379,55 @@ ScriptParseTask::ScriptParseTask(JSConte
   : ParseTask(ParseTaskKind::Script, cx, parseGlobal, chars, length, callback,
               callbackData)
 {
 }
 
 void
 ScriptParseTask::parse(JSContext* cx)
 {
-    SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
+    auto& range = data.ref<TwoByteChars>();
+    SourceBufferHolder srcBuf(range.begin().get(), range.length(), SourceBufferHolder::NoOwnership);
     script = frontend::CompileGlobalScript(cx, alloc, ScopeKind::Global,
                                            options, srcBuf,
                                            /* sourceObjectOut = */ &sourceObject);
 }
 
 ModuleParseTask::ModuleParseTask(JSContext* cx, JSObject* parseGlobal,
                                  const char16_t* chars, size_t length,
                                  JS::OffThreadCompileCallback callback, void* callbackData)
   : ParseTask(ParseTaskKind::Module, cx, parseGlobal, chars, length, callback,
               callbackData)
 {
 }
 
 void
 ModuleParseTask::parse(JSContext* cx)
 {
-    SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
+    auto& range = data.ref<TwoByteChars>();
+    SourceBufferHolder srcBuf(range.begin().get(), range.length(), SourceBufferHolder::NoOwnership);
     ModuleObject* module = frontend::CompileModule(cx, options, srcBuf, alloc, &sourceObject);
     if (module)
         script = module->script();
 }
 
 ScriptDecodeTask::ScriptDecodeTask(JSContext* cx, JSObject* parseGlobal,
-                                   JS::TranscodeBuffer& buffer, size_t cursor,
+                                   const JS::TranscodeRange& range,
                                    JS::OffThreadCompileCallback callback, void* callbackData)
   : ParseTask(ParseTaskKind::ScriptDecode, cx, parseGlobal,
-              buffer, cursor, callback, callbackData)
+              range, callback, callbackData)
 {
 }
 
 void
 ScriptDecodeTask::parse(JSContext* cx)
 {
     RootedScript resultScript(cx);
     XDROffThreadDecoder decoder(cx, alloc, &options, /* sourceObjectOut = */ &sourceObject,
-                                *buffer, cursor);
+                                data.ref<const JS::TranscodeRange>());
     decoder.codeScript(&resultScript);
     MOZ_ASSERT(bool(resultScript) == (decoder.resultCode() == JS::TranscodeResult_Ok));
     if (decoder.resultCode() == JS::TranscodeResult_Ok) {
         script = resultScript.get();
     } else {
         sourceObject = nullptr;
     }
 }
@@ -645,22 +649,21 @@ js::StartOffThreadParseModule(JSContext*
         return cx->new_<ModuleParseTask>(cx, global, chars, length,
                                          callback, callbackData);
     };
     return StartOffThreadParseTask(cx, options, ParseTaskKind::Module, functor);
 }
 
 bool
 js::StartOffThreadDecodeScript(JSContext* cx, const ReadOnlyCompileOptions& options,
-                               JS::TranscodeBuffer& buffer, size_t cursor,
+                               const JS::TranscodeRange& range,
                                JS::OffThreadCompileCallback callback, void* callbackData)
 {
     auto functor = [&](JSObject* global) -> ScriptDecodeTask* {
-        return cx->new_<ScriptDecodeTask>(cx, global, buffer, cursor,
-                                          callback, callbackData);
+        return cx->new_<ScriptDecodeTask>(cx, global, range, callback, callbackData);
     };
     return StartOffThreadParseTask(cx, options, ParseTaskKind::ScriptDecode, functor);
 }
 
 void
 js::EnqueuePendingParseTasksAfterGC(JSRuntime* rt)
 {
     MOZ_ASSERT(!OffThreadParsingMustWaitForGC(rt));
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -10,16 +10,17 @@
  * and are distinct from e.g. web workers.
  */
 
 #ifndef vm_HelperThreads_h
 #define vm_HelperThreads_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/GuardObjects.h"
+#include "mozilla/MaybeOneOf.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Variant.h"
 
 #include "jsapi.h"
 #include "jscntxt.h"
 
 #include "frontend/TokenStream.h"
@@ -530,17 +531,17 @@ StartOffThreadParseScript(JSContext* cx,
 
 bool
 StartOffThreadParseModule(JSContext* cx, const ReadOnlyCompileOptions& options,
                           const char16_t* chars, size_t length,
                           JS::OffThreadCompileCallback callback, void* callbackData);
 
 bool
 StartOffThreadDecodeScript(JSContext* cx, const ReadOnlyCompileOptions& options,
-                           JS::TranscodeBuffer& buffer, size_t cursor,
+                           const JS::TranscodeRange& range,
                            JS::OffThreadCompileCallback callback, void* callbackData);
 
 /*
  * Called at the end of GC to enqueue any Parse tasks that were waiting on an
  * atoms-zone GC to finish.
  */
 void
 EnqueuePendingParseTasksAfterGC(JSRuntime* rt);
@@ -589,31 +590,19 @@ class MOZ_RAII AutoUnlockHelperThreadSta
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 };
 
 struct ParseTask
 {
     ParseTaskKind kind;
     OwningCompileOptions options;
-    // Anonymous union, the only correct interpretation is provided by the
-    // ParseTaskKind value, or from the virtual parse function.
-    union {
-        struct {
-            const char16_t* chars;
-            size_t length;
-        };
-        struct {
-            // This should be a reference, but C++ prevents us from using union
-            // with references as it assumes the reference constness might be
-            // violated.
-            JS::TranscodeBuffer* const buffer;
-            size_t cursor;
-        };
-    };
+
+    mozilla::MaybeOneOf<const JS::TranscodeRange, JS::TwoByteChars> data;
+
     LifoAlloc alloc;
 
     // Rooted pointer to the global object to use while parsing.
     JSObject* parseGlobal;
 
     // Callback invoked off thread when the parse finishes.
     JS::OffThreadCompileCallback callback;
     void* callbackData;
@@ -631,17 +620,17 @@ struct ParseTask
     Vector<CompileError*, 0, SystemAllocPolicy> errors;
     bool overRecursed;
     bool outOfMemory;
 
     ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
               const char16_t* chars, size_t length,
               JS::OffThreadCompileCallback callback, void* callbackData);
     ParseTask(ParseTaskKind kind, JSContext* cx, JSObject* parseGlobal,
-              JS::TranscodeBuffer& buffer, size_t cursor,
+              const JS::TranscodeRange& range,
               JS::OffThreadCompileCallback callback, void* callbackData);
     bool init(JSContext* cx, const ReadOnlyCompileOptions& options);
 
     void activate(JSRuntime* rt);
     virtual void parse(JSContext* cx) = 0;
     bool finish(JSContext* cx);
 
     bool runtimeMatches(JSRuntime* rt) {
@@ -667,17 +656,17 @@ struct ModuleParseTask : public ParseTas
                     const char16_t* chars, size_t length,
                     JS::OffThreadCompileCallback callback, void* callbackData);
     void parse(JSContext* cx) override;
 };
 
 struct ScriptDecodeTask : public ParseTask
 {
     ScriptDecodeTask(JSContext* cx, JSObject* parseGlobal,
-                     JS::TranscodeBuffer& buffer, size_t cursor,
+                     const JS::TranscodeRange& range,
                      JS::OffThreadCompileCallback callback, void* callbackData);
     void parse(JSContext* cx) override;
 };
 
 // Return whether, if a new parse task was started, it would need to wait for
 // an in-progress GC to complete before starting.
 extern bool
 OffThreadParsingMustWaitForGC(JSRuntime* rt);
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -10,60 +10,106 @@
 #include "mozilla/EndianUtils.h"
 #include "mozilla/TypeTraits.h"
 
 #include "jsatom.h"
 #include "jsfriendapi.h"
 
 namespace js {
 
-class XDRBuffer {
+class XDRBufferBase
+{
   public:
-    XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
-      : context_(cx), buffer_(buffer), cursor_(cursor) { }
+    explicit XDRBufferBase(JSContext* cx, size_t cursor = 0)
+      : context_(cx), cursor_(cursor) { }
 
     JSContext* cx() const {
         return context_;
     }
 
-    const uint8_t* read(size_t n) {
-        MOZ_ASSERT(cursor_ < buffer_.length());
-        uint8_t* ptr = &buffer_[cursor_];
-        cursor_ += n;
-        return ptr;
+    size_t cursor() const {
+        return cursor_;
     }
 
-    const char* readCString() {
-        char* ptr = reinterpret_cast<char*>(&buffer_[cursor_]);
-        uint8_t* end = reinterpret_cast<uint8_t*>(strchr(ptr, '\0')) + 1;
-        MOZ_ASSERT(buffer_.begin() < end);
-        MOZ_ASSERT(end <= buffer_.end());
-        cursor_ = end - buffer_.begin();
-        return ptr;
-    }
+  protected:
+    JSContext* const context_;
+    size_t cursor_;
+};
+
+template <XDRMode mode>
+class XDRBuffer;
+
+template <>
+class XDRBuffer<XDR_ENCODE> : public XDRBufferBase
+{
+  public:
+    XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
+      : XDRBufferBase(cx, cursor),
+        buffer_(buffer) { }
 
     uint8_t* write(size_t n) {
         MOZ_ASSERT(n != 0);
         if (!buffer_.growByUninitialized(n)) {
             ReportOutOfMemory(cx());
             return nullptr;
         }
         uint8_t* ptr = &buffer_[cursor_];
         cursor_ += n;
         return ptr;
     }
 
-    size_t cursor() const {
-        return cursor_;
+    const char* readCString() {
+        MOZ_CRASH("Should never read in encode mode");
+        return nullptr;
+    }
+
+    const uint8_t* read(size_t n) {
+        MOZ_CRASH("Should never read in encode mode");
+        return nullptr;
     }
 
   private:
-    JSContext* const context_;
     JS::TranscodeBuffer& buffer_;
-    size_t cursor_;
+};
+
+template <>
+class XDRBuffer<XDR_DECODE> : public XDRBufferBase
+{
+  public:
+    XDRBuffer(JSContext* cx, const JS::TranscodeRange& range)
+      : XDRBufferBase(cx),
+        buffer_(range) { }
+
+    XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
+      : XDRBufferBase(cx, cursor),
+        buffer_(buffer.begin(), buffer.length()) { }
+
+    const char* readCString() {
+        char* ptr = reinterpret_cast<char*>(&buffer_[cursor_]);
+        uint8_t* end = reinterpret_cast<uint8_t*>(strchr(ptr, '\0')) + 1;
+        MOZ_ASSERT(buffer_.begin().get() < end);
+        MOZ_ASSERT(end <= buffer_.end().get());
+        cursor_ = end - buffer_.begin().get();
+        return ptr;
+    }
+
+    const uint8_t* read(size_t n) {
+        MOZ_ASSERT(cursor_ < buffer_.length());
+        uint8_t* ptr = &buffer_[cursor_];
+        cursor_ += n;
+        return ptr;
+    }
+
+    uint8_t* write(size_t n) {
+        MOZ_CRASH("Should never write in decode mode");
+        return nullptr;
+    }
+
+  private:
+    const JS::TranscodeRange buffer_;
 };
 
 class XDRCoderBase;
 class XDRIncrementalEncoder;
 
 // An AutoXDRTree is used to identify section encoded by an XDRIncrementalEncoder.
 //
 // Its primary goal is to identify functions, such that we can first encode them
@@ -119,27 +165,34 @@ class XDRCoderBase
 
 /*
  * XDR serialization state.  All data is encoded in little endian.
  */
 template <XDRMode mode>
 class XDRState : public XDRCoderBase
 {
   public:
-    XDRBuffer buf;
+    XDRBuffer<mode> buf;
   private:
     JS::TranscodeResult resultCode_;
 
   public:
     XDRState(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
       : buf(cx, buffer, cursor),
         resultCode_(JS::TranscodeResult_Ok)
     {
     }
 
+    template <typename RangeType>
+    XDRState(JSContext* cx, const RangeType& range)
+      : buf(cx, range),
+        resultCode_(JS::TranscodeResult_Ok)
+    {
+    }
+
     virtual ~XDRState() {};
 
     JSContext* cx() const {
         return buf.cx();
     }
     virtual LifoAlloc& lifoAlloc() const;
 
     virtual bool hasOptions() const { return false; }
@@ -302,18 +355,18 @@ class XDROffThreadDecoder : public XDRDe
     // initialization with ScriptSourceObject::initFromOptions after the
     // decoding.
     //
     // When providing a sourceObjectOut pointer, you have to ensure that it is
     // marked by the GC to avoid dangling pointers.
     XDROffThreadDecoder(JSContext* cx, LifoAlloc& alloc,
                         const ReadOnlyCompileOptions* options,
                         ScriptSourceObject** sourceObjectOut,
-                        JS::TranscodeBuffer& buffer, size_t cursor = 0)
-      : XDRDecoder(cx, buffer, cursor),
+                        const JS::TranscodeRange& range)
+      : XDRDecoder(cx, range),
         options_(options),
         sourceObjectOut_(sourceObjectOut),
         alloc_(alloc)
     {
         MOZ_ASSERT(options);
         MOZ_ASSERT(sourceObjectOut);
         MOZ_ASSERT(*sourceObjectOut == nullptr);
     }