Issue #1691 - Part 7e: Dependencies for required to finish part 7d. https://bugzilla.mozilla.org/show_bug.cgi?id=1331662 Reimplement EvaluateString using the ExecutionContext class. https://bugzilla.mozilla.org/show_bug.cgi?id=1316078 Extract redudant code into StartOffThreadParseTask. Use an ExclusiveContext instead of a JSContext in XDR functions. Add a script decoder as a valid off-main-thread parse-task. https://bugzilla.mozilla.org/show_bug.cgi?id=900784 Add nsJSUtils functions for encoding and decoding the bytecode. https://bugzilla.mozilla.org/show_bug.cgi?id=1316081 Add XDRIncrementalEncoder to replace delazified LazyScript in the encoded XDR buffer. Add an XDRIncrementalEncoder instance on the ScriptSource. Expose a new JSAPI to incrementally encode bytecode when it is generated. https://bugzilla.mozilla.org/show_bug.cgi?id=1334091 XDR function use the sourceObject instead of the enclosingScript as argument.

(cherry picked from commit d6de9a669f4b2f5115670bd771cd53d7cfb3956a)
This commit is contained in:
Brian Smith 2023-04-20 07:38:48 -05:00 • committed by roytam1
commit 993476283d
22 changed files with 1224 additions and 171 deletions

View file

@ -16,10 +16,10 @@ namespace js {
class XDRBuffer {
public:
XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
XDRBuffer(ExclusiveContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
: context_(cx), buffer_(buffer), cursor_(cursor) { }
JSContext* cx() const {
ExclusiveContext* cx() const {
return context_;
}
@ -42,7 +42,7 @@ class XDRBuffer {
uint8_t* write(size_t n) {
MOZ_ASSERT(n != 0);
if (!buffer_.growByUninitialized(n)) {
JS_ReportOutOfMemory(cx());
ReportOutOfMemory(cx());
return nullptr;
}
uint8_t* ptr = &buffer_[cursor_];
@ -50,30 +50,108 @@ class XDRBuffer {
return ptr;
}
size_t cursor() const {
return cursor_;
}
private:
JSContext* const context_;
ExclusiveContext* const context_;
JS::TranscodeBuffer& buffer_;
size_t cursor_;
};
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
// as LazyScript, and later replaced by them by their corresponding bytecode
// once delazified.
//
// As a convenience, this is also used to identify the top-level of the content
// encoded by an XDRIncrementalEncoder.
//
// Sections can be encoded any number of times in an XDRIncrementalEncoder, and
// the latest encoded version would replace all the previous one.
class MOZ_RAII AutoXDRTree
{
public:
// For a JSFunction, a tree key is defined as being:
// script()->begin << 32 | script()->end
//
// Based on the invariant that |begin <= end|, we can make special
// keys, such as the top-level script.
using Key = uint64_t;
AutoXDRTree(XDRCoderBase* xdr, Key key);
~AutoXDRTree();
// Indicate the lack of a key for the current tree.
static constexpr Key noKey = 0;
// Used to end the slices when there is no children.
static constexpr Key noSubTree = Key(1) << 32;
// Used as the root key of the tree in the hash map.
static constexpr Key topLevel = Key(2) << 32;
private:
friend class XDRIncrementalEncoder;
Key key_;
AutoXDRTree* parent_;
XDRCoderBase* xdr_;
};
class XDRCoderBase
{
protected:
XDRCoderBase() {}
public:
virtual AutoXDRTree::Key getTopLevelTreeKey() const { return AutoXDRTree::noKey; }
virtual AutoXDRTree::Key getTreeKey(JSFunction* fun) const { return AutoXDRTree::noKey; }
virtual void createOrReplaceSubTree(AutoXDRTree* child) {};
virtual void endSubTree() {};
};
/*
* XDR serialization state. All data is encoded in little endian.
*/
template <XDRMode mode>
class XDRState {
class XDRState : public XDRCoderBase
{
public:
XDRBuffer buf;
private:
JS::TranscodeResult resultCode_;
XDRState(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
: buf(cx, buffer, cursor), resultCode_(JS::TranscodeResult_Ok) { }
public:
XDRState(ExclusiveContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
: buf(cx, buffer, cursor),
resultCode_(JS::TranscodeResult_Ok)
{
}
JSContext* cx() const {
virtual ~XDRState() {};
ExclusiveContext* cx() const {
return buf.cx();
}
virtual LifoAlloc& lifoAlloc() const;
virtual bool hasOptions() const { return false; }
virtual const ReadOnlyCompileOptions& options() {
MOZ_CRASH("does not have options");
}
virtual bool hasScriptSourceObjectOut() const { return false; }
virtual ScriptSourceObject** scriptSourceObjectOut() {
MOZ_CRASH("does not have scriptSourceObjectOut.");
}
// Record logical failures of XDR.
void postProcessContextErrors(JSContext* cx);
void postProcessContextErrors(ExclusiveContext* cx);
JS::TranscodeResult resultCode() const {
return resultCode_;
}
@ -87,7 +165,7 @@ class XDRState {
if (mode == XDR_ENCODE) {
uint8_t* ptr = buf.write(sizeof(*n));
if (!ptr)
return false;
return fail(JS::TranscodeResult_Throw);
*ptr = *n;
} else {
*n = *buf.read(sizeof(*n));
@ -99,7 +177,7 @@ class XDRState {
if (mode == XDR_ENCODE) {
uint8_t* ptr = buf.write(sizeof(*n));
if (!ptr)
return false;
return fail(JS::TranscodeResult_Throw);
mozilla::LittleEndian::writeUint16(ptr, *n);
} else {
const uint8_t* ptr = buf.read(sizeof(*n));
@ -112,7 +190,7 @@ class XDRState {
if (mode == XDR_ENCODE) {
uint8_t* ptr = buf.write(sizeof(*n));
if (!ptr)
return false;
return fail(JS::TranscodeResult_Throw);
mozilla::LittleEndian::writeUint32(ptr, *n);
} else {
const uint8_t* ptr = buf.read(sizeof(*n));
@ -125,7 +203,7 @@ class XDRState {
if (mode == XDR_ENCODE) {
uint8_t* ptr = buf.write(sizeof(*n));
if (!ptr)
return false;
return fail(JS::TranscodeResult_Throw);
mozilla::LittleEndian::writeUint64(ptr, *n);
} else {
const uint8_t* ptr = buf.read(sizeof(*n));
@ -188,7 +266,7 @@ class XDRState {
if (mode == XDR_ENCODE) {
uint8_t* ptr = buf.write(len);
if (!ptr)
return false;
return fail(JS::TranscodeResult_Throw);
memcpy(ptr, bytes, len);
} else {
memcpy(bytes, buf.read(len), len);
@ -207,7 +285,7 @@ class XDRState {
size_t n = strlen(*sp) + 1;
uint8_t* ptr = buf.write(n);
if (!ptr)
return false;
return fail(JS::TranscodeResult_Throw);
memcpy(ptr, *sp, n);
} else {
*sp = buf.readCString();
@ -218,7 +296,7 @@ class XDRState {
bool codeChars(const JS::Latin1Char* chars, size_t nchars);
bool codeChars(char16_t* chars, size_t nchars);
bool codeFunction(JS::MutableHandleFunction objp);
bool codeFunction(JS::MutableHandleFunction objp, HandleScriptSource sourceObject = nullptr);
bool codeScript(MutableHandleScript scriptp);
bool codeConstValue(MutableHandleValue vp);
};
@ -226,6 +304,141 @@ class XDRState {
using XDREncoder = XDRState<XDR_ENCODE>;
using XDRDecoder = XDRState<XDR_DECODE>;
class XDROffThreadDecoder : public XDRDecoder
{
const ReadOnlyCompileOptions* options_;
ScriptSourceObject** sourceObjectOut_;
LifoAlloc& alloc_;
public:
// Note, when providing an ExclusiveContext, where isJSContext is false,
// then the initialization of the ScriptSourceObject would remain
// incomplete. Thus, the sourceObjectOut must be used to finish the
// 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(ExclusiveContext* cx, LifoAlloc& alloc,
const ReadOnlyCompileOptions* options,
ScriptSourceObject** sourceObjectOut,
JS::TranscodeBuffer& buffer, size_t cursor = 0)
: XDRDecoder(cx, buffer, cursor),
options_(options),
sourceObjectOut_(sourceObjectOut),
alloc_(alloc)
{
MOZ_ASSERT(options);
MOZ_ASSERT(sourceObjectOut);
MOZ_ASSERT(*sourceObjectOut == nullptr);
}
LifoAlloc& lifoAlloc() const override {
return alloc_;
}
bool hasOptions() const override { return true; }
const ReadOnlyCompileOptions& options() override {
return *options_;
}
bool hasScriptSourceObjectOut() const override { return true; }
ScriptSourceObject** scriptSourceObjectOut() override {
return sourceObjectOut_;
}
};
class XDRIncrementalEncoder : public XDREncoder
{
// The incremental encoder encodes the content of scripts and functions in
// the XDRBuffer. It can be used to encode multiple times the same AutoXDRTree,
// and uses its key to identify which part to replace.
//
// Internally, this encoder keeps a tree representation of the scopes. Each
// node is composed of a vector of slices which are interleaved by child
// nodes.
//
// A slice corresponds to an index and a length within the content of the
// slices_ buffer. The index is updated when a slice is created, and the
// length is updated when the slice is ended, either by creating a new scope
// child, or by closing the scope and going back to the parent.
//
// +---+---+---+
// begin | | | |
// length | | | |
// child | . | . | . |
// +-|-+-|-+---+
// | |
// +---------+ +---------+
// | |
// v v
// +---+---+ +---+
// | | | | |
// | | | | |
// | . | . | | . |
// +-|-+---+ +---+
// |
// |
// |
// v
// +---+
// | |
// | |
// | . |
// +---+
//
//
// The tree key is used to identify the child nodes, and to make them
// easily replaceable.
//
// The tree is rooted at the |topLevel| key.
//
struct Slice {
size_t sliceBegin;
size_t sliceLength;
AutoXDRTree::Key child;
};
using SlicesNode = Vector<Slice, 1, SystemAllocPolicy>;
using SlicesTree = HashMap<AutoXDRTree::Key, SlicesNode, DefaultHasher<AutoXDRTree::Key>,
SystemAllocPolicy>;
// Last opened XDR-tree on the stack.
AutoXDRTree* scope_;
// Node corresponding to the opened scope.
SlicesNode* node_;
// Tree of slices.
SlicesTree tree_;
JS::TranscodeBuffer slices_;
JS::TranscodeBuffer& buffer_;
bool oom_;
public:
XDRIncrementalEncoder(ExclusiveContext* cx, JS::TranscodeBuffer& buffer, size_t cursor)
: XDREncoder(cx, slices_, 0),
scope_(nullptr),
node_(nullptr),
buffer_(buffer),
oom_(false)
{
MOZ_ASSERT(buffer.length() == cursor, "NYI");
}
virtual ~XDRIncrementalEncoder() {}
AutoXDRTree::Key getTopLevelTreeKey() const override;
AutoXDRTree::Key getTreeKey(JSFunction* fun) const override;
MOZ_MUST_USE bool init();
void createOrReplaceSubTree(AutoXDRTree* child) override;
void endSubTree() override;
// In the current XDRBuffer, move replaceable-parts to form a linear
// sequence of bytes.
MOZ_MUST_USE bool linearize();
};
} /* namespace js */
#endif /* vm_Xdr_h */