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
parent d5d7bb5e47
commit 993476283d
22 changed files with 1224 additions and 171 deletions
+238
View File
@@ -123,6 +123,244 @@ nsJSUtils::CompileFunction(AutoJSAPI& jsapi,
return NS_OK;
}
static nsresult
EvaluationExceptionToNSResult(JSContext* aCx)
{
if (JS_IsExceptionPending(aCx)) {
return NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW;
}
return NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE;
}
nsJSUtils::ExecutionContext::ExecutionContext(JSContext* aCx,
JS::Handle<JSObject*> aGlobal)
: mCx(aCx)
, mCompartment(aCx, aGlobal)
, mRetValue(aCx)
, mScopeChain(aCx)
, mRv(NS_OK)
, mSkip(false)
, mCoerceToString(false)
#ifdef DEBUG
, mWantsReturnValue(false)
, mExpectScopeChain(false)
#endif
{
MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(nsContentUtils::IsInMicroTask());
MOZ_ASSERT(mRetValue.isUndefined());
MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aGlobal) == aGlobal);
if (MOZ_UNLIKELY(!xpc::Scriptability::Get(aGlobal).Allowed())) {
mSkip = true;
mRv = NS_OK;
}
}
void
nsJSUtils::ExecutionContext::SetScopeChain(
const JS::AutoObjectVector& aScopeChain)
{
if (mSkip) {
return;
}
#ifdef DEBUG
mExpectScopeChain = true;
#endif
// Now make sure to wrap the scope chain into the right compartment.
if (!mScopeChain.reserve(aScopeChain.length())) {
mSkip = true;
mRv = NS_ERROR_OUT_OF_MEMORY;
return;
}
for (size_t i = 0; i < aScopeChain.length(); ++i) {
JS::ExposeObjectToActiveJS(aScopeChain[i]);
mScopeChain.infallibleAppend(aScopeChain[i]);
if (!JS_WrapObject(mCx, mScopeChain[i])) {
mSkip = true;
mRv = NS_ERROR_OUT_OF_MEMORY;
return;
}
}
}
nsresult
nsJSUtils::ExecutionContext::JoinAndExec(void **aOffThreadToken,
JS::MutableHandle<JSScript*> aScript)
{
if (mSkip) {
return mRv;
}
MOZ_ASSERT(!mWantsReturnValue);
MOZ_ASSERT(!mExpectScopeChain);
aScript.set(JS::FinishOffThreadScript(mCx, *aOffThreadToken));
*aOffThreadToken = nullptr; // Mark the token as having been finished.
if (!aScript || !JS_ExecuteScript(mCx, mScopeChain, aScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
return NS_OK;
}
nsresult
nsJSUtils::ExecutionContext::CompileAndExec(JS::CompileOptions& aCompileOptions,
JS::SourceBufferHolder& aSrcBuf)
{
if (mSkip) {
return mRv;
}
MOZ_ASSERT_IF(aCompileOptions.versionSet,
aCompileOptions.version != JSVERSION_UNKNOWN);
MOZ_ASSERT(aSrcBuf.get());
MOZ_ASSERT(mRetValue.isUndefined());
#ifdef DEBUG
mWantsReturnValue = !aCompileOptions.noScriptRval;
#endif
MOZ_ASSERT(!mCoerceToString || mWantsReturnValue);
if (!JS::Evaluate(mCx, mScopeChain, aCompileOptions, aSrcBuf, &mRetValue)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
return NS_OK;
}
nsresult
nsJSUtils::ExecutionContext::CompileAndExec(JS::CompileOptions& aCompileOptions,
const nsAString& aScript)
{
if (mSkip) {
return mRv;
}
const nsPromiseFlatString& flatScript = PromiseFlatString(aScript);
JS::SourceBufferHolder srcBuf(flatScript.get(), aScript.Length(),
JS::SourceBufferHolder::NoOwnership);
return CompileAndExec(aCompileOptions, srcBuf);
}
nsresult
nsJSUtils::ExecutionContext::DecodeAndExec(JS::CompileOptions& aCompileOptions,
mozilla::Vector<uint8_t>& aBytecodeBuf,
size_t aBytecodeIndex)
{
if (mSkip) {
return mRv;
}
MOZ_ASSERT(!mWantsReturnValue);
JS::Rooted<JSScript*> script(mCx);
JS::TranscodeResult tr = JS::DecodeScript(mCx, aBytecodeBuf, &script, aBytecodeIndex);
// These errors are external parameters which should be handled before the
// decoding phase, and which are the only reasons why you might want to
// fallback on decoding failures.
MOZ_ASSERT(tr != JS::TranscodeResult_Failure_BadBuildId &&
tr != JS::TranscodeResult_Failure_WrongCompileOption);
if (tr != JS::TranscodeResult_Ok) {
mSkip = true;
mRv = NS_ERROR_DOM_JS_DECODING_ERROR;
return mRv;
}
if (!JS_ExecuteScript(mCx, mScopeChain, script)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
return mRv;
}
nsresult
nsJSUtils::ExecutionContext::DecodeJoinAndExec(void **aOffThreadToken)
{
if (mSkip) {
return mRv;
}
MOZ_ASSERT(!mWantsReturnValue);
MOZ_ASSERT(!mExpectScopeChain);
JS::Rooted<JSScript*> script(mCx);
script.set(JS::FinishOffThreadScriptDecoder(mCx, *aOffThreadToken));
*aOffThreadToken = nullptr; // Mark the token as having been finished.
if (!script || !JS_ExecuteScript(mCx, mScopeChain, script)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
return NS_OK;
}
nsresult
nsJSUtils::ExecutionContext::JoinEncodeAndExec(void **aOffThreadToken,
mozilla::Vector<uint8_t>& aBytecodeBuf,
JS::MutableHandle<JSScript*> aScript)
{
MOZ_ASSERT_IF(aOffThreadToken, !mWantsReturnValue);
aScript.set(JS::FinishOffThreadScript(mCx, *aOffThreadToken));
*aOffThreadToken = nullptr; // Mark the token as having been finished.
if (!aScript) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
if (!StartIncrementalEncoding(mCx, aBytecodeBuf, aScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
if (!JS_ExecuteScript(mCx, mScopeChain, aScript)) {
mSkip = true;
mRv = EvaluationExceptionToNSResult(mCx);
return mRv;
}
return mRv;
}
nsresult
nsJSUtils::ExecutionContext::ExtractReturnValue(JS::MutableHandle<JS::Value> aRetValue)
{
MOZ_ASSERT(aRetValue.isUndefined());
if (mSkip) {
// Repeat earlier result, as NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW are not
// failures cases.
#ifdef DEBUG
mWantsReturnValue = false;
#endif
return mRv;
}
MOZ_ASSERT(mWantsReturnValue);
#ifdef DEBUG
mWantsReturnValue = false;
#endif
if (mCoerceToString && !mRetValue.isUndefined()) {
JSString* str = JS::ToString(mCx, mRetValue);
if (!str) {
// ToString can be a function call, so an exception can be raised while
// executing the function.
mSkip = true;
return EvaluationExceptionToNSResult(mCx);
}
mRetValue.set(JS::StringValue(str));
}
aRetValue.set(mRetValue);
return NS_OK;
}
nsresult
nsJSUtils::EvaluateString(JSContext* aCx,
const nsAString& aScript,
+101
View File
@@ -63,6 +63,107 @@ public:
const nsAString& aBody,
JSObject** aFunctionObject);
// ExecutionContext is used to switch compartment.
class MOZ_STACK_CLASS ExecutionContext {
JSContext* mCx;
// Handles switching to our global's compartment.
JSAutoCompartment mCompartment;
// Set to a valid handle if a return value is expected.
JS::Rooted<JS::Value> mRetValue;
// Scope chain in which the execution takes place.
JS::AutoObjectVector mScopeChain;
// returned value forwarded when we have to interupt the execution eagerly
// with mSkip.
nsresult mRv;
// Used to skip upcoming phases in case of a failure. In such case the
// result is carried by mRv.
bool mSkip;
// Should the result be serialized before being returned.
bool mCoerceToString;
#ifdef DEBUG
// Should we set the return value.
bool mWantsReturnValue;
bool mExpectScopeChain;
#endif
public:
// Enter compartment in which the code would be executed. The JSContext
// must come from an AutoEntryScript that has had
// TakeOwnershipOfErrorReporting() called on it.
ExecutionContext(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
ExecutionContext(const ExecutionContext&) = delete;
ExecutionContext(ExecutionContext&&) = delete;
~ExecutionContext() {
// This flag is resetted, when the returned value is extracted.
MOZ_ASSERT(!mWantsReturnValue);
}
// The returned value would be converted to a string if the
// |aCoerceToString| is flag set.
ExecutionContext& SetCoerceToString(bool aCoerceToString) {
mCoerceToString = aCoerceToString;
return *this;
}
// Set the scope chain in which the code should be executed.
void SetScopeChain(const JS::AutoObjectVector& aScopeChain);
// Copy the returned value in the mutable handle argument, in case of a
// evaluation failure either during the execution or the conversion of the
// result to a string, the nsresult would be set to the corresponding result
// code, and the mutable handle argument would remain unchanged.
//
// The value returned in the mutable handle argument is part of the
// compartment given as argument to the ExecutionContext constructor. If the
// caller is in a different compartment, then the out-param value should be
// wrapped by calling |JS_WrapValue|.
MOZ_MUST_USE nsresult
ExtractReturnValue(JS::MutableHandle<JS::Value> aRetValue);
// After getting a notification that an off-thread compilation terminated,
// this function will take the result of the parser by moving it to the main
// thread before starting the execution of the script.
//
// The compiled script would be returned in the |aScript| out-param.
MOZ_MUST_USE nsresult JoinAndExec(void **aOffThreadToken,
JS::MutableHandle<JSScript*> aScript);
// Compile a script contained in a SourceBuffer, and execute it.
nsresult CompileAndExec(JS::CompileOptions& aCompileOptions,
JS::SourceBufferHolder& aSrcBuf);
// Compile a script contained in a string, and execute it.
nsresult CompileAndExec(JS::CompileOptions& aCompileOptions,
const nsAString& aScript);
// Decode a script contained in a buffer, and execute it.
MOZ_MUST_USE nsresult DecodeAndExec(JS::CompileOptions& aCompileOptions,
mozilla::Vector<uint8_t>& aBytecodeBuf,
size_t aBytecodeIndex);
// After getting a notification that an off-thread decoding terminated, this
// function will get the result of the decoder by moving it to the main
// thread before starting the execution of the script.
MOZ_MUST_USE nsresult DecodeJoinAndExec(void **aOffThreadToken);
// Similar to JoinAndExec, except that in addition to fecthing the source,
// we register the fact that we plan to encode its bytecode later.
MOZ_MUST_USE nsresult JoinEncodeAndExec(void **aOffThreadToken,
mozilla::Vector<uint8_t>& aBytecodeBuf,
JS::MutableHandle<JSScript*> aScript);
};
struct MOZ_STACK_CLASS EvaluateOptions {
bool coerceToString;
JS::AutoObjectVector scopeChain;
-3
View File
@@ -79,9 +79,6 @@ public:
StructuredCloneData&
operator=(const StructuredCloneData& aOther) = delete;
StructuredCloneData&
operator=(StructuredCloneData&& aOther) = default;
const nsTArray<RefPtr<BlobImpl>>& BlobImpls() const
{
return mBlobImplArray;
+11 -9
View File
@@ -62,8 +62,6 @@ using JS::SourceBufferHolder;
namespace mozilla {
namespace dom {
static LazyLogModule gCspPRLog("CSP");
void
ImplCycleCollectionUnlink(ScriptLoadRequestList& aField);
@@ -2343,17 +2341,21 @@ ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest)
FinishDynamicImport(cx, request, rv);
}
} else {
// Update our current script.
AutoCurrentScriptUpdater scriptUpdater(this, aRequest->Element());
JS::CompileOptions options(cx);
rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
if (NS_SUCCEEDED(rv)) {
nsAutoString inlineData;
SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData);
rv = nsJSUtils::EvaluateString(cx, srcBuf, global, options,
aRequest->OffThreadTokenPtr());
{
nsJSUtils::ExecutionContext exec(cx, global);
if (aRequest->mOffThreadToken) {
JS::Rooted<JSScript*> script(cx);
rv = exec.JoinAndExec(&aRequest->mOffThreadToken, &script);
} else {
nsAutoString inlineData;
SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData);
rv = exec.CompileAndExec(options, srcBuf);
}
}
}
}
}
+7
View File
@@ -687,6 +687,13 @@ frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const cha
if (!NameFunctions(cx, pn))
return false;
// XDR the newly delazified function.
if (script->scriptSource()->hasEncoder() &&
!script->scriptSource()->xdrEncodeFunction(cx, fun, sourceObject))
{
return false;
}
return true;
}
+1 -1
View File
@@ -8854,7 +8854,7 @@ Parser<ParseHandler>::generatorComprehensionLambda(unsigned begin)
ParseContext* outerpc = pc;
// If we are off the main thread, the generator meta-objects have
// already been created by js::StartOffThreadParseScript, so cx will not
// already been created by js::StartOffThreadParseTask, so cx will not
// be necessary.
RootedObject proto(context);
JSContext* cx = context->maybeJSContext();
+45
View File
@@ -4202,6 +4202,31 @@ JS::CancelOffThreadModule(JSContext* cx, void* token)
HelperThreadState().cancelParseTask(cx, 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_PUBLIC_API(JSScript*)
JS::FinishOffThreadScriptDecoder(JSContext* cx, void* token)
{
MOZ_ASSERT(cx);
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx));
return HelperThreadState().finishScriptDecodeTask(cx, token);
}
JS_PUBLIC_API(void)
JS::CancelOffThreadScriptDecoder(JSContext* cx, void* token)
{
MOZ_ASSERT(cx);
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx));
HelperThreadState().cancelParseTask(cx, ParseTaskKind::ScriptDecode, token);
}
JS_PUBLIC_API(bool)
JS_CompileScript(JSContext* cx, const char* ascii, size_t length,
const JS::CompileOptions& options, MutableHandleScript script)
@@ -7101,6 +7126,26 @@ JS::DecodeInterpretedFunction(JSContext* cx, TranscodeBuffer& buffer,
return decoder.resultCode();
}
JS_PUBLIC_API(bool)
JS::StartIncrementalEncoding(JSContext* cx, TranscodeBuffer& buffer, JS::HandleScript script)
{
if (!script)
return false;
if (!script->scriptSource()->xdrEncodeTopLevel(cx, buffer, script))
return false;
return true;
}
JS_PUBLIC_API(bool)
JS::FinishIncrementalEncoding(JSContext* cx, JS::HandleScript script)
{
if (!script)
return false;
if (!script->scriptSource()->xdrFinalizeEncoder())
return false;
return true;
}
JS_PUBLIC_API(void)
JS::SetBuildIdOp(JSContext* cx, JS::BuildIdOp buildIdOp)
{
+35 -2
View File
@@ -3748,7 +3748,7 @@ namespace JS {
* addrefs/copies/tracing/etc.
*
* Furthermore, in some cases compile options are propagated from one entity to
* another (e.g. from a scriipt to a function defined in that script). This
* another (e.g. from a script to a function defined in that script). This
* involves copying over some, but not all, of the options.
*
* So, we have a class hierarchy that reflects these four use cases:
@@ -4186,6 +4186,17 @@ FinishOffThreadModule(JSContext* cx, void* token);
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(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.
* envChain must contain objects in the current compartment of cx. The actual
@@ -6168,7 +6179,10 @@ enum TranscodeResult
TranscodeResult_Failure_AsmJSNotSupported = TranscodeResult_Failure | 0x3,
TranscodeResult_Failure_BadDecode = TranscodeResult_Failure | 0x4,
// A error, the JSContext has a pending exception.
TranscodeResult_Failure_WrongCompileOption = TranscodeResult_Failure | 0x5,
TranscodeResult_Failure_NotInterpretedFun = TranscodeResult_Failure | 0x6,
// There is a pending exception on the context.
TranscodeResult_Throw = 0x200
};
@@ -6186,6 +6200,25 @@ 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
// main thread in a non-interruptible way.
//
// The |script| argument of |StartIncrementalEncoding| and
// |FinishIncrementalEncoding| should be the top-level script returned either as
// an out-param of any of the |Compile| functions, or the result of
// |FinishOffThreadScript|.
//
// The |buffer| argument should not be used before until
// |FinishIncrementalEncoding| is called on the same script, and returns
// successfully. If any of these functions failed, the |buffer| content is
// undefined.
extern JS_PUBLIC_API(bool)
StartIncrementalEncoding(JSContext* cx, TranscodeBuffer& buffer, JS::HandleScript script);
extern JS_PUBLIC_API(bool)
FinishIncrementalEncoding(JSContext* cx, JS::HandleScript script);
} /* namespace JS */
namespace js {
+2 -2
View File
@@ -538,7 +538,7 @@ js::XDRAtom(XDRState<mode>* xdr, MutableHandleAtom atomp)
uint32_t length = lengthAndEncoding >> 1;
bool latin1 = lengthAndEncoding & 0x1;
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
JSAtom* atom;
if (latin1) {
const Latin1Char* chars = nullptr;
@@ -567,7 +567,7 @@ js::XDRAtom(XDRState<mode>* xdr, MutableHandleAtom atomp)
* most allocations here will be bigger than tempLifoAlloc's default
* chunk size.
*/
chars = cx->runtime()->pod_malloc<char16_t>(length);
chars = cx->pod_malloc<char16_t>(length);
if (!chars)
return false;
}
+17 -15
View File
@@ -530,7 +530,7 @@ fun_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp)
template<XDRMode mode>
bool
js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope,
HandleScript enclosingScript, MutableHandleFunction objp)
HandleScriptSource sourceObject, MutableHandleFunction objp)
{
enum FirstWordFlag {
HasAtom = 0x1,
@@ -544,21 +544,15 @@ js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope,
uint32_t firstword = 0; /* bitmask of FirstWordFlag */
uint32_t flagsword = 0; /* word for argument count and fun->flags */
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
RootedFunction fun(cx);
RootedScript script(cx);
Rooted<LazyScript*> lazy(cx);
if (mode == XDR_ENCODE) {
fun = objp;
if (!fun->isInterpreted()) {
JSAutoByteString funNameBytes;
if (const char* name = GetFunctionNameBytes(cx, fun, &funNameBytes)) {
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
JSMSG_NOT_SCRIPTED_FUNCTION, name);
}
return false;
}
if (!fun->isInterpreted())
return xdr->fail(JS::TranscodeResult_Failure_NotInterpretedFun);
if (fun->explicitName() || fun->hasCompileTimeName() || fun->hasGuessedAtom())
firstword |= HasAtom;
@@ -590,6 +584,10 @@ js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope,
fun->environment() == nullptr);
}
// Everything added below can substituted by the non-lazy-script version of
// this function later.
js::AutoXDRTree funTree(xdr, xdr->getTreeKey(fun));
if (!xdr->codeUint32(&firstword))
return false;
@@ -601,7 +599,11 @@ js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope,
if (mode == XDR_DECODE) {
RootedObject proto(cx);
if (firstword & HasStarGeneratorProto) {
proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global());
// If we are off the main thread, the generator meta-objects have
// already been created by js::StartOffThreadParseTask, so
// JSContext* will not be necessary.
JSContext* context = cx->maybeJSContext();
proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(context, cx->global());
if (!proto)
return false;
}
@@ -618,10 +620,10 @@ js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope,
}
if (firstword & IsLazy) {
if (!XDRLazyScript(xdr, enclosingScope, enclosingScript, fun, &lazy))
if (!XDRLazyScript(xdr, enclosingScope, sourceObject, fun, &lazy))
return false;
} else {
if (!XDRScript(xdr, enclosingScope, enclosingScript, fun, &script))
if (!XDRScript(xdr, enclosingScope, sourceObject, fun, &script))
return false;
}
@@ -650,10 +652,10 @@ js::XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope,
}
template bool
js::XDRInterpretedFunction(XDRState<XDR_ENCODE>*, HandleScope, HandleScript, MutableHandleFunction);
js::XDRInterpretedFunction(XDRState<XDR_ENCODE>*, HandleScope, HandleScriptSource, MutableHandleFunction);
template bool
js::XDRInterpretedFunction(XDRState<XDR_DECODE>*, HandleScope, HandleScript, MutableHandleFunction);
js::XDRInterpretedFunction(XDRState<XDR_DECODE>*, HandleScope, HandleScriptSource, MutableHandleFunction);
/* ES6 (04-25-16) 19.2.3.6 Function.prototype [ @@hasInstance ] */
bool
+1 -1
View File
@@ -879,7 +879,7 @@ JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool isToSource);
template<XDRMode mode>
bool
XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope,
HandleScript enclosingScript, MutableHandleFunction objp);
HandleScriptSource sourceObject, MutableHandleFunction objp);
/*
* Report an error that call.thisv is not compatible with the specified class,
+4 -5
View File
@@ -1137,7 +1137,7 @@ js::CloneObject(JSContext* cx, HandleObject obj, Handle<js::TaggedProto> proto)
}
static bool
GetScriptArrayObjectElements(JSContext* cx, HandleObject obj, MutableHandle<GCVector<Value>> values)
GetScriptArrayObjectElements(ExclusiveContext* cx, HandleObject obj, MutableHandle<GCVector<Value>> values)
{
MOZ_ASSERT(!obj->isSingleton());
MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
@@ -1155,7 +1155,7 @@ GetScriptArrayObjectElements(JSContext* cx, HandleObject obj, MutableHandle<GCVe
}
static bool
GetScriptPlainObjectProperties(JSContext* cx, HandleObject obj,
GetScriptPlainObjectProperties(ExclusiveContext* cx, HandleObject obj,
MutableHandle<IdValueVector> properties)
{
if (obj->is<PlainObject>()) {
@@ -1330,9 +1330,8 @@ js::XDRObjectLiteral(XDRState<mode>* xdr, MutableHandleObject obj)
{
/* NB: Keep this in sync with DeepCloneObjectLiteral. */
JSContext* cx = xdr->cx();
MOZ_ASSERT_IF(mode == XDR_ENCODE && obj->isSingleton(),
cx->compartment()->behaviors().getSingletonsAsTemplates());
ExclusiveContext* cx = xdr->cx();
assertSameCompartment(cx, obj);
// Distinguish between objects and array classes.
uint32_t isArray = 0;
+129 -45
View File
@@ -72,7 +72,7 @@ template<XDRMode mode>
bool
js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp)
{
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
enum ConstTag {
SCRIPT_INT,
@@ -195,7 +195,7 @@ template<XDRMode mode>
static bool
XDRLazyClosedOverBindings(XDRState<mode>* xdr, MutableHandle<LazyScript*> lazy)
{
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
RootedAtom atom(cx);
for (size_t i = 0; i < lazy->numClosedOverBindings(); i++) {
uint8_t endOfScopeSentinel;
@@ -228,7 +228,7 @@ XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun, HandleScript scri
MOZ_ASSERT_IF(mode == XDR_ENCODE, script->isRelazifiable() && script->maybeLazyScript());
MOZ_ASSERT_IF(mode == XDR_ENCODE, !lazy->numInnerFunctions());
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
uint64_t packedFields;
{
@@ -257,7 +257,8 @@ XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun, HandleScript scri
return false;
if (mode == XDR_DECODE) {
lazy.set(LazyScript::Create(cx, fun, script, enclosingScope, script,
RootedScriptSource sourceObject(cx, &script->scriptSourceUnwrap());
lazy.set(LazyScript::Create(cx, fun, script, enclosingScope, sourceObject,
packedFields, begin, end, toStringStart, lineno, column));
if (!lazy)
@@ -304,8 +305,9 @@ enum XDRClassKind {
template<XDRMode mode>
bool
js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScript enclosingScript,
HandleFunction fun, MutableHandleScript scriptp)
js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
HandleScriptSource sourceObjectArg, HandleFunction fun,
MutableHandleScript scriptp)
{
/* NB: Keep this in sync with CopyScript. */
@@ -348,17 +350,16 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip
uint32_t scriptBits = 0;
uint32_t bodyScopeIndex = 0;
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
RootedScript script(cx);
natoms = nsrcnotes = 0;
nconsts = nobjects = nscopes = nregexps = ntrynotes = nscopenotes = nyieldoffsets = 0;
if (mode == XDR_ENCODE) {
script = scriptp.get();
MOZ_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment());
MOZ_ASSERT(script->functionNonDelazifying() == fun);
if (!fun && script->treatAsRunOnce()) {
if (!fun && script->treatAsRunOnce() && script->hasRunOnce()) {
// This is a toplevel or eval script that's runOnce. We want to
// make sure that we're not XDR-saving an object we emitted for
// JSOP_OBJECT that then got modified. So throw if we're not
@@ -431,7 +432,8 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip
scriptBits |= (1 << FunctionHasThisBinding);
if (script->functionHasExtraBodyVarScope())
scriptBits |= (1 << FunctionHasExtraBodyVarScope);
if (!enclosingScript || enclosingScript->scriptSource() != script->scriptSource())
MOZ_ASSERT_IF(sourceObjectArg, sourceObjectArg->source() == script->scriptSource());
if (!sourceObjectArg)
scriptBits |= (1 << OwnSource);
if (script->isGeneratorExp())
scriptBits |= (1 << IsGeneratorExp);
@@ -492,15 +494,31 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip
if (!xdr->codeUint32(&scriptBits))
return false;
MOZ_ASSERT(!!(scriptBits & (1 << OwnSource)) == !sourceObjectArg);
RootedScriptSource sourceObject(cx, sourceObjectArg);
if (mode == XDR_DECODE) {
JSVersion version_ = JSVersion(version);
MOZ_ASSERT((version_ & VersionFlags::MASK) == unsigned(version_));
CompileOptions options(cx);
options.setVersion(version_)
.setNoScriptRval(!!(scriptBits & (1 << NoScriptRval)))
.setSelfHostingMode(!!(scriptBits & (1 << SelfHosted)));
RootedScriptSource sourceObject(cx);
// When loading from the bytecode cache, we get the CompileOption from
// the document, which specify the version to use. If the version does
// not match, then we should fail.
mozilla::Maybe<CompileOptions> options;
if (xdr->hasOptions()) {
options.emplace(xdr->cx(), xdr->options());
if (options->version != version_ ||
options->noScriptRval != !!(scriptBits & (1 << NoScriptRval)) ||
options->selfHostingMode != !!(scriptBits & (1 << SelfHosted)))
{
return xdr->fail(JS::TranscodeResult_Failure_WrongCompileOption);
}
} else {
options.emplace(xdr->cx()->asJSContext());
(*options).setVersion(version_)
.setNoScriptRval(!!(scriptBits & (1 << NoScriptRval)))
.setSelfHostingMode(!!(scriptBits & (1 << SelfHosted)));
}
if (scriptBits & (1 << OwnSource)) {
ScriptSource* ss = cx->new_<ScriptSource>();
if (!ss)
@@ -513,22 +531,22 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip
* ScriptSourceObject, and those that are (element; elementAttributeName)
* aren't preserved by XDR. So this can be simple.
*/
CompileOptions options(cx);
ss->initFromOptions(cx, options);
ss->initFromOptions(cx, *options);
sourceObject = ScriptSourceObject::create(cx, ss);
if (!sourceObject ||
!ScriptSourceObject::initFromOptions(cx, sourceObject, options))
return false;
} else {
MOZ_ASSERT(enclosingScript);
// When decoding, all the scripts and the script source object
// are in the same compartment, so the script's source object
// should never be a cross-compartment wrapper.
MOZ_ASSERT(enclosingScript->sourceObject()->is<ScriptSourceObject>());
sourceObject = &enclosingScript->sourceObject()->as<ScriptSourceObject>();
if (xdr->hasScriptSourceObjectOut()) {
// When the ScriptSourceObjectOut is provided by ParseTask, it
// is stored in a location which is traced by the GC.
*xdr->scriptSourceObjectOut() = sourceObject;
} else {
if (!sourceObject ||
!ScriptSourceObject::initFromOptions(cx->asJSContext(), sourceObject, *options))
{
return false;
}
}
}
script = JSScript::Create(cx, options, sourceObject, 0, 0, 0, 0);
script = JSScript::Create(cx, *options, sourceObject, 0, 0, 0, 0);
if (!script)
return false;
@@ -536,6 +554,10 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip
// decoded may iterate the static scope chain.
if (fun)
fun->initScript(script);
} else {
// When encoding, we do not mutate any of the JSScript or LazyScript, so
// we can safely unwrap it here.
sourceObject = &script->scriptSourceUnwrap();
}
if (mode == XDR_DECODE) {
@@ -606,7 +628,7 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip
JS_STATIC_ASSERT(sizeof(jssrcnote) == 1);
if (scriptBits & (1 << OwnSource)) {
if (!script->scriptSource()->performXDR<mode>(xdr))
if (!sourceObject->source()->performXDR<mode>(xdr))
return false;
}
if (!xdr->codeUint32(&script->sourceStart_))
@@ -846,7 +868,7 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip
RootedFunction tmp(cx);
if (mode == XDR_ENCODE)
tmp = &(*objp)->as<JSFunction>();
if (!XDRInterpretedFunction(xdr, funEnclosingScope, script, &tmp))
if (!XDRInterpretedFunction(xdr, funEnclosingScope, sourceObject, &tmp))
return false;
*objp = tmp;
break;
@@ -922,27 +944,28 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, HandleScrip
scriptp.set(script);
/* see BytecodeEmitter::tellDebuggerAboutCompiledScript */
if (!fun)
Debugger::onNewScript(cx, script);
if (!fun && cx->isJSContext())
Debugger::onNewScript(cx->asJSContext(), script);
}
return true;
}
template bool
js::XDRScript(XDRState<XDR_ENCODE>*, HandleScope, HandleScript, HandleFunction,
js::XDRScript(XDRState<XDR_ENCODE>*, HandleScope, HandleScriptSource, HandleFunction,
MutableHandleScript);
template bool
js::XDRScript(XDRState<XDR_DECODE>*, HandleScope, HandleScript, HandleFunction,
js::XDRScript(XDRState<XDR_DECODE>*, HandleScope, HandleScriptSource, HandleFunction,
MutableHandleScript);
template<XDRMode mode>
bool
js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope, HandleScript enclosingScript,
HandleFunction fun, MutableHandle<LazyScript*> lazy)
js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope,
HandleScriptSource sourceObject, HandleFunction fun,
MutableHandle<LazyScript*> lazy)
{
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
{
uint32_t begin;
@@ -979,7 +1002,7 @@ js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope, HandleScript
}
if (mode == XDR_DECODE) {
lazy.set(LazyScript::Create(cx, fun, nullptr, enclosingScope, enclosingScript,
lazy.set(LazyScript::Create(cx, fun, nullptr, enclosingScope, sourceObject,
packedFields, begin, end, toStringStart, lineno, column));
if (!lazy)
return false;
@@ -1013,11 +1036,11 @@ js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope, HandleScript
}
template bool
js::XDRLazyScript(XDRState<XDR_ENCODE>*, HandleScope, HandleScript,
js::XDRLazyScript(XDRState<XDR_ENCODE>*, HandleScope, HandleScriptSource,
HandleFunction, MutableHandle<LazyScript*>);
template bool
js::XDRLazyScript(XDRState<XDR_DECODE>*, HandleScope, HandleScript,
js::XDRLazyScript(XDRState<XDR_DECODE>*, HandleScope, HandleScriptSource,
HandleFunction, MutableHandle<LazyScript*>);
void
@@ -1934,6 +1957,64 @@ ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
info->numScripts++;
}
bool
ScriptSource::xdrEncodeTopLevel(ExclusiveContext* cx, JS::TranscodeBuffer& buffer,
HandleScript script)
{
xdrEncoder_ = js::MakeUnique<XDRIncrementalEncoder>(cx, buffer, buffer.length());
if (!xdrEncoder_) {
ReportOutOfMemory(cx);
return false;
}
MOZ_ASSERT(hasEncoder());
auto failureCase = mozilla::MakeScopeExit([&] {
xdrEncoder_.reset(nullptr);
});
if (!xdrEncoder_->init()) {
ReportOutOfMemory(cx);
return false;
}
RootedScript s(cx, script);
if (!xdrEncoder_->codeScript(&s))
return false;
failureCase.release();
return true;
}
bool
ScriptSource::xdrEncodeFunction(ExclusiveContext* cx, HandleFunction fun, HandleScriptSource sourceObject)
{
MOZ_ASSERT(sourceObject->source() == this);
MOZ_ASSERT(hasEncoder());
auto failureCase = mozilla::MakeScopeExit([&] {
xdrEncoder_.reset(nullptr);
});
RootedFunction f(cx, fun);
if (!xdrEncoder_->codeFunction(&f, sourceObject))
return false;
failureCase.release();
return true;
}
bool
ScriptSource::xdrFinalizeEncoder()
{
MOZ_ASSERT(hasEncoder());
auto cleanup = mozilla::MakeScopeExit([&] {
xdrEncoder_.reset(nullptr);
});
if (!xdrEncoder_->linearize())
return false;
return true;
}
template<XDRMode mode>
bool
ScriptSource::performXDR(XDRState<mode>* xdr)
@@ -2073,7 +2154,10 @@ ScriptSource::performXDR(XDRState<mode>* xdr)
const char* fn = filename();
if (!xdr->codeCString(&fn))
return false;
if (mode == XDR_DECODE && !setFilename(xdr->cx(), fn))
// Note: If the decoder has an option, then the filename is defined by
// the CompileOption from the document.
MOZ_ASSERT_IF(mode == XDR_DECODE && xdr->hasOptions(), filename());
if (mode == XDR_DECODE && !xdr->hasOptions() && !setFilename(xdr->cx(), fn))
return false;
}
@@ -4135,7 +4219,7 @@ LazyScript::Create(ExclusiveContext* cx, HandleFunction fun,
/* static */ LazyScript*
LazyScript::Create(ExclusiveContext* cx, HandleFunction fun,
HandleScript script, HandleScope enclosingScope,
HandleScript enclosingScript,
HandleScriptSource sourceObject,
uint64_t packedFields, uint32_t begin, uint32_t end,
uint32_t toStringStart, uint32_t lineno, uint32_t column)
{
@@ -4166,11 +4250,11 @@ LazyScript::Create(ExclusiveContext* cx, HandleFunction fun,
// values should only be non-null if we have a non-lazy enclosing script.
// AddLazyFunctionsForCompartment relies on the source object being null
// if we're nested inside another lazy function.
MOZ_ASSERT(!!enclosingScript == !!enclosingScope);
MOZ_ASSERT(!!sourceObject == !!enclosingScope);
MOZ_ASSERT(!res->sourceObject());
MOZ_ASSERT(!res->enclosingScope());
if (enclosingScript)
res->setEnclosingScopeAndSource(enclosingScope, &enclosingScript->scriptSourceUnwrap());
if (sourceObject)
res->setEnclosingScopeAndSource(enclosingScope, sourceObject);
MOZ_ASSERT(!res->hasScript());
if (script)
+36 -7
View File
@@ -431,6 +431,11 @@ class ScriptSource
// memory management.
const char* introductionType_;
// The bytecode cache encoder is used to encode only the content of function
// which are delazified. If this value is not nullptr, then each delazified
// function should be recorded before their first execution.
UniquePtr<XDRIncrementalEncoder> xdrEncoder_;
// True if we can call JSRuntime::sourceHook to load the source on
// demand. If sourceRetrievable_ and hasSourceData() are false, it is not
// possible to get source at all.
@@ -452,6 +457,7 @@ class ScriptSource
parameterListEnd_(0),
introducerFilename_(nullptr),
introductionType_(nullptr),
xdrEncoder_(nullptr),
sourceRetrievable_(false),
hasIntroductionOffset_(false)
{
@@ -574,6 +580,29 @@ class ScriptSource
introductionOffset_ = offset;
hasIntroductionOffset_ = true;
}
// Return wether an XDR encoder is present or not.
bool hasEncoder() const { return bool(xdrEncoder_); }
// Create a new XDR encoder, and encode the top-level JSScript. The result
// of the encoding would be available in the |buffer| provided as argument,
// as soon as |xdrFinalize| is called and all xdr function calls returned
// successfully.
bool xdrEncodeTopLevel(ExclusiveContext* cx, JS::TranscodeBuffer& buffer, HandleScript script);
// Encode a delazified JSFunction. In case of errors, the XDR encoder is
// freed and the |buffer| provided as argument to |xdrEncodeTopLevel| is
// considered undefined.
//
// The |sourceObject| argument is the object holding the current
// ScriptSource.
bool xdrEncodeFunction(ExclusiveContext* cx, HandleFunction fun,
HandleScriptSource sourceObject);
// Linearize the encoded content in the |buffer| provided as argument to
// |xdrEncodeTopLevel|, and free the XDR encoder. In case of errors, the
// |buffer| is considered undefined.
bool xdrFinalizeEncoder();
};
class ScriptSourceHolder
@@ -695,12 +724,12 @@ AsyncKindFromBits(unsigned val) {
*/
template<XDRMode mode>
bool
XDRScript(XDRState<mode>* xdr, HandleScope enclosingScope, HandleScript enclosingScript,
XDRScript(XDRState<mode>* xdr, HandleScope enclosingScope, HandleScriptSource sourceObject,
HandleFunction fun, MutableHandleScript scriptp);
template<XDRMode mode>
bool
XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope, HandleScript enclosingScript,
XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope, HandleScriptSource sourceObject,
HandleFunction fun, MutableHandle<LazyScript*> lazy);
/*
@@ -808,7 +837,7 @@ class JSScript : public js::gc::TenuredCell
friend
bool
js::XDRScript(js::XDRState<mode>* xdr, js::HandleScope enclosingScope,
js::HandleScript enclosingScript, js::HandleFunction fun,
js::HandleScriptSource sourceObject, js::HandleFunction fun,
js::MutableHandleScript scriptp);
friend bool
@@ -1211,11 +1240,11 @@ class JSScript : public js::gc::TenuredCell
return funLength_;
}
size_t sourceStart() const {
uint32_t sourceStart() const {
return sourceStart_;
}
size_t sourceEnd() const {
uint32_t sourceEnd() const {
return sourceEnd_;
}
@@ -2076,11 +2105,11 @@ class LazyScript : public gc::TenuredCell
// The "script" argument to this function can be null. If it's non-null,
// then this LazyScript should be associated with the given JSScript.
//
// The enclosingScript and enclosingScope arguments may be null if the
// The sourceObject and enclosingScope arguments may be null if the
// enclosing function is also lazy.
static LazyScript* Create(ExclusiveContext* cx, HandleFunction fun,
HandleScript script, HandleScope enclosingScope,
HandleScript enclosingScript,
HandleScriptSource sourceObject,
uint64_t packedData, uint32_t begin, uint32_t end,
uint32_t toStringStart, uint32_t lineno, uint32_t column);
+29 -2
View File
@@ -1605,6 +1605,7 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp)
bool catchTermination = false;
bool loadBytecode = false;
bool saveBytecode = false;
bool saveIncrementalBytecode = false;
bool assertEqBytecode = false;
RootedObject callerGlobal(cx, cx->global());
@@ -1668,6 +1669,11 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp)
if (!v.isUndefined())
saveBytecode = ToBoolean(v);
if (!JS_GetProperty(cx, opts, "saveIncrementalBytecode", &v))
return false;
if (!v.isUndefined())
saveIncrementalBytecode = ToBoolean(v);
if (!JS_GetProperty(cx, opts, "assertEqBytecode", &v))
return false;
if (!v.isUndefined())
@@ -1675,12 +1681,17 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp)
// We cannot load or save the bytecode if we have no object where the
// bytecode cache is stored.
if (loadBytecode || saveBytecode) {
if (loadBytecode || saveBytecode || saveIncrementalBytecode) {
if (!cacheEntry) {
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
"evaluate");
return false;
}
if (saveIncrementalBytecode && saveBytecode) {
JS_ReportErrorASCII(cx, "saveIncrementalBytecode and saveBytecode cannot be used"
" at the same time.");
return false;
}
}
}
@@ -1758,6 +1769,15 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp)
if (!script->scriptSource()->setSourceMapURL(cx, smurl))
return false;
}
// If we want to save the bytecode incrementally, then we should
// register ahead the fact that every JSFunction which is being
// delazified should be encoded at the end of the delazification.
if (saveIncrementalBytecode) {
if (!StartIncrementalEncoding(cx, saveBuffer, script))
return false;
}
if (!JS_ExecuteScript(cx, script, args.rval())) {
if (catchTermination && !JS_IsExceptionPending(cx)) {
JSAutoCompartment ac1(cx, callerGlobal);
@@ -1770,14 +1790,21 @@ Evaluate(JSContext* cx, unsigned argc, Value* vp)
return false;
}
// Encode the bytecode after the execution of the script.
if (saveBytecode) {
JS::TranscodeResult rv = JS::EncodeScript(cx, saveBuffer, script);
if (!ConvertTranscodeResultToJSException(cx, rv))
return false;
}
// Serialize the encoded bytecode, recorded before the execution, into a
// buffer which can be deserialized linearly.
if (saveIncrementalBytecode) {
if (!FinishIncrementalEncoding(cx, script))
return false;
}
}
if (saveBytecode) {
if (saveBytecode || saveIncrementalBytecode) {
// If we are both loading and saving, we assert that we are going to
// replace the current bytecode by the same stream of bytes.
if (loadBytecode && assertEqBytecode) {
+76 -36
View File
@@ -304,6 +304,18 @@ ParseTask::ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusi
{
}
ParseTask::ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor,
JS::OffThreadCompileCallback callback, void* callbackData)
: kind(kind), cx(cx), options(initCx), buffer(&buffer), cursor(cursor),
alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
exclusiveContextGlobal(exclusiveContextGlobal),
callback(callback), callbackData(callbackData),
script(nullptr), sourceObject(nullptr),
errors(cx), overRecursed(false), outOfMemory(false)
{
}
bool
ParseTask::init(JSContext* cx, const ReadOnlyCompileOptions& options)
{
@@ -389,6 +401,29 @@ ModuleParseTask::parse()
script = module->script();
}
ScriptDecodeTask::ScriptDecodeTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor,
JS::OffThreadCompileCallback callback, void* callbackData)
: ParseTask(ParseTaskKind::ScriptDecode, cx, exclusiveContextGlobal, initCx,
buffer, cursor, callback, callbackData)
{
}
void
ScriptDecodeTask::parse()
{
RootedScript resultScript(cx);
XDROffThreadDecoder decoder(cx, alloc, &options, /* sourceObjectOut = */ &sourceObject,
*buffer, cursor);
decoder.codeScript(&resultScript);
MOZ_ASSERT(bool(resultScript) == (decoder.resultCode() == JS::TranscodeResult_Ok));
if (decoder.resultCode() == JS::TranscodeResult_Ok) {
script = resultScript.get();
} else {
sourceObject = nullptr;
}
}
void
js::CancelOffThreadParses(JSRuntime* rt)
{
@@ -557,10 +592,10 @@ QueueOffThreadParseTask(JSContext* cx, ParseTask* task)
return true;
}
template <typename TaskFunctor>
bool
js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
StartOffThreadParseTask(JSContext* cx, const ReadOnlyCompileOptions& options,
ParseTaskKind kind, TaskFunctor& taskFunctor)
{
// Suppress GC so that calls below do not trigger a new incremental GC
// which could require barriers on the atoms compartment.
@@ -568,7 +603,7 @@ js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& optio
gc::AutoAssertNoNurseryAlloc noNurseryAlloc(cx->runtime());
AutoSuppressAllocationMetadataBuilder suppressMetadata(cx);
JSObject* global = CreateGlobalForOffThreadParse(cx, ParseTaskKind::Script, nogc);
JSObject* global = CreateGlobalForOffThreadParse(cx, kind, nogc);
if (!global)
return false;
@@ -578,9 +613,7 @@ js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& optio
if (!helpercx)
return false;
ScopedJSDeletePtr<ParseTask> task(
cx->new_<ScriptParseTask>(helpercx.get(), global, cx, chars, length,
callback, callbackData));
ScopedJSDeletePtr<ParseTask> task(taskFunctor(helpercx.get(), global));
if (!task)
return false;
@@ -594,41 +627,40 @@ js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& optio
return true;
}
bool
js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
{
auto functor = [&](ExclusiveContext* helpercx, JSObject* global) -> ScriptParseTask* {
return cx->new_<ScriptParseTask>(helpercx, global, cx, chars, length,
callback, callbackData);
};
return StartOffThreadParseTask(cx, options, ParseTaskKind::Script, functor);
}
bool
js::StartOffThreadParseModule(JSContext* cx, const ReadOnlyCompileOptions& options,
const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData)
{
// Suppress GC so that calls below do not trigger a new incremental GC
// which could require barriers on the atoms compartment.
gc::AutoSuppressGC nogc(cx);
gc::AutoAssertNoNurseryAlloc noNurseryAlloc(cx->runtime());
AutoSuppressAllocationMetadataBuilder suppressMetadata(cx);
auto functor = [&](ExclusiveContext* helpercx, JSObject* global) -> ModuleParseTask* {
return cx->new_<ModuleParseTask>(helpercx, global, cx, chars, length,
callback, callbackData);
};
return StartOffThreadParseTask(cx, options, ParseTaskKind::Module, functor);
}
JSObject* global = CreateGlobalForOffThreadParse(cx, ParseTaskKind::Module, nogc);
if (!global)
return false;
ScopedJSDeletePtr<ExclusiveContext> helpercx(
cx->new_<ExclusiveContext>(cx->runtime(), (PerThreadData*) nullptr,
ExclusiveContext::Context_Exclusive, cx->options()));
if (!helpercx)
return false;
ScopedJSDeletePtr<ParseTask> task(
cx->new_<ModuleParseTask>(helpercx.get(), global, cx, chars, length,
callback, callbackData));
if (!task)
return false;
helpercx.forget();
if (!task->init(cx, options) || !QueueOffThreadParseTask(cx, task))
return false;
task.forget();
return true;
bool
js::StartOffThreadDecodeScript(JSContext* cx, const ReadOnlyCompileOptions& options,
JS::TranscodeBuffer& buffer, size_t cursor,
JS::OffThreadCompileCallback callback, void* callbackData)
{
auto functor = [&](ExclusiveContext* helpercx, JSObject* global) -> ScriptDecodeTask* {
return cx->new_<ScriptDecodeTask>(helpercx, global, cx, buffer, cursor,
callback, callbackData);
};
return StartOffThreadParseTask(cx, options, ParseTaskKind::ScriptDecode, functor);
}
void
@@ -1280,6 +1312,14 @@ GlobalHelperThreadState::finishScriptParseTask(JSContext* cx, void* token)
return script;
}
JSScript*
GlobalHelperThreadState::finishScriptDecodeTask(JSContext* cx, void* token)
{
JSScript* script = finishParseTask(cx, ParseTaskKind::ScriptDecode, token);
MOZ_ASSERT_IF(script, script->isGlobalCode());
return script;
}
JSObject*
GlobalHelperThreadState::finishModuleParseTask(JSContext* cx, void* token)
{
+34 -3
View File
@@ -48,7 +48,8 @@ namespace wasm {
enum class ParseTaskKind
{
Script,
Module
Module,
ScriptDecode
};
// Per-process state for off thread work items.
@@ -244,6 +245,7 @@ class GlobalHelperThreadState
public:
JSScript* finishScriptParseTask(JSContext* cx, void* token);
JSScript* finishScriptDecodeTask(JSContext* cx, void* token);
JSObject* finishModuleParseTask(JSContext* cx, void* token);
bool compressionInProgress(SourceCompressionTask* task, const AutoLockHelperThreadState& lock);
SourceCompressionTask* compressionTaskForSource(ScriptSource* ss, const AutoLockHelperThreadState& lock);
@@ -482,6 +484,11 @@ 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,
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.
@@ -534,8 +541,21 @@ struct ParseTask
ParseTaskKind kind;
ExclusiveContext* cx;
OwningCompileOptions options;
const char16_t* chars;
size_t length;
// 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;
};
};
LifoAlloc alloc;
// Rooted pointer to the global object used by 'cx'.
@@ -562,6 +582,9 @@ struct ParseTask
ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, const char16_t* chars, size_t length,
JS::OffThreadCompileCallback callback, void* callbackData);
ParseTask(ParseTaskKind kind, ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor,
JS::OffThreadCompileCallback callback, void* callbackData);
bool init(JSContext* cx, const ReadOnlyCompileOptions& options);
void activate(JSRuntime* rt);
@@ -593,6 +616,14 @@ struct ModuleParseTask : public ParseTask
void parse() override;
};
struct ScriptDecodeTask : public ParseTask
{
ScriptDecodeTask(ExclusiveContext* cx, JSObject* exclusiveContextGlobal,
JSContext* initCx, JS::TranscodeBuffer& buffer, size_t cursor,
JS::OffThreadCompileCallback callback, void* callbackData);
void parse() 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
+1 -1
View File
@@ -1552,7 +1552,7 @@ js::XDRScriptRegExpObject(XDRState<mode>* xdr, MutableHandle<RegExpObject*> objp
if (mode == XDR_DECODE) {
RegExpFlag flags = RegExpFlag(flagsword);
RegExpObject* reobj = RegExpObject::create(xdr->cx(), source, flags, nullptr,
xdr->cx()->tempLifoAlloc());
xdr->lifoAlloc());
if (!reobj)
return false;
+8 -8
View File
@@ -202,7 +202,7 @@ NewEmptyScopeData(ExclusiveContext* cx, uint32_t length = 0)
static bool
XDRBindingName(XDRState<XDR_ENCODE>* xdr, BindingName* bindingName)
{
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
RootedAtom atom(cx, bindingName->name());
bool hasAtom = !!atom;
@@ -220,7 +220,7 @@ XDRBindingName(XDRState<XDR_ENCODE>* xdr, BindingName* bindingName)
static bool
XDRBindingName(XDRState<XDR_DECODE>* xdr, BindingName* bindingName)
{
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
uint8_t u8;
if (!xdr->codeUint8(&u8))
@@ -254,7 +254,7 @@ Scope::XDRSizedBindingNames(XDRState<mode>* xdr, Handle<ConcreteScope*> scope,
{
MOZ_ASSERT(!data);
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
uint32_t length;
if (mode == XDR_ENCODE)
@@ -542,7 +542,7 @@ template <XDRMode mode>
LexicalScope::XDR(XDRState<mode>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope)
{
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
Rooted<Data*> data(cx);
if (!XDRSizedBindingNames<LexicalScope>(xdr, scope.as<LexicalScope>(), &data))
@@ -725,7 +725,7 @@ template <XDRMode mode>
FunctionScope::XDR(XDRState<mode>* xdr, HandleFunction fun, HandleScope enclosing,
MutableHandleScope scope)
{
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
Rooted<Data*> data(cx);
if (!XDRSizedBindingNames<FunctionScope>(xdr, scope.as<FunctionScope>(), &data))
return false;
@@ -861,7 +861,7 @@ template <XDRMode mode>
VarScope::XDR(XDRState<mode>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope)
{
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
Rooted<Data*> data(cx);
if (!XDRSizedBindingNames<VarScope>(xdr, scope.as<VarScope>(), &data))
return false;
@@ -962,7 +962,7 @@ GlobalScope::XDR(XDRState<mode>* xdr, ScopeKind kind, MutableHandleScope scope)
{
MOZ_ASSERT((mode == XDR_DECODE) == !scope);
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
Rooted<Data*> data(cx);
if (!XDRSizedBindingNames<GlobalScope>(xdr, scope.as<GlobalScope>(), &data))
return false;
@@ -1084,7 +1084,7 @@ template <XDRMode mode>
EvalScope::XDR(XDRState<mode>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope)
{
JSContext* cx = xdr->cx();
ExclusiveContext* cx = xdr->cx();
Rooted<Data*> data(cx);
{
+213 -15
View File
@@ -10,6 +10,7 @@
#include <string.h>
#include "jsapi.h"
#include "jscntxt.h"
#include "jsscript.h"
#include "vm/Debugger.h"
@@ -18,11 +19,17 @@
using namespace js;
using mozilla::PodEqual;
template<XDRMode mode>
LifoAlloc&
XDRState<mode>::lifoAlloc() const {
return buf.cx()->asJSContext()->tempLifoAlloc();
}
template<XDRMode mode>
void
XDRState<mode>::postProcessContextErrors(JSContext* cx)
XDRState<mode>::postProcessContextErrors(ExclusiveContext* cx)
{
if (cx->isExceptionPending()) {
if (cx->isJSContext() && cx->asJSContext()->isExceptionPending()) {
MOZ_ASSERT(resultCode_ == JS::TranscodeResult_Ok);
resultCode_ = JS::TranscodeResult_Throw;
}
@@ -40,7 +47,7 @@ XDRState<mode>::codeChars(const Latin1Char* chars, size_t nchars)
return true;
uint8_t* ptr = buf.write(nchars);
if (!ptr)
return false;
return fail(JS::TranscodeResult_Throw);
mozilla::PodCopy(ptr, chars, nchars);
return true;
@@ -56,7 +63,7 @@ XDRState<mode>::codeChars(char16_t* chars, size_t nchars)
if (mode == XDR_ENCODE) {
uint8_t* ptr = buf.write(nbytes);
if (!ptr)
return false;
return fail(JS::TranscodeResult_Throw);
mozilla::NativeEndian::copyAndSwapToLittleEndian(ptr, chars, nchars);
} else {
const uint8_t* ptr = buf.read(nbytes);
@@ -70,11 +77,8 @@ static bool
VersionCheck(XDRState<mode>* xdr)
{
JS::BuildIdCharVector buildId;
if (!xdr->cx()->buildIdOp() || !xdr->cx()->buildIdOp()(&buildId)) {
JS_ReportErrorNumberASCII(xdr->cx(), GetErrorMessage, nullptr,
JSMSG_BUILD_ID_NOT_AVAILABLE);
return false;
}
if (!xdr->cx()->buildIdOp() || !xdr->cx()->buildIdOp()(&buildId))
return xdr->fail(JS::TranscodeResult_Failure_BadBuildId);
MOZ_ASSERT(!buildId.empty());
uint32_t buildIdLength;
@@ -97,7 +101,7 @@ VersionCheck(XDRState<mode>* xdr)
// buildId.
if (!decodedBuildId.resize(buildIdLength)) {
ReportOutOfMemory(xdr->cx());
return false;
return xdr->fail(JS::TranscodeResult_Throw);
}
if (!xdr->codeBytes(decodedBuildId.begin(), buildIdLength))
@@ -113,20 +117,26 @@ VersionCheck(XDRState<mode>* xdr)
template<XDRMode mode>
bool
XDRState<mode>::codeFunction(MutableHandleFunction funp)
XDRState<mode>::codeFunction(MutableHandleFunction funp, HandleScriptSource sourceObject)
{
if (mode == XDR_DECODE)
RootedScope scope(cx(), &cx()->global()->emptyGlobalScope());
if (mode == XDR_DECODE) {
MOZ_ASSERT(!sourceObject);
funp.set(nullptr);
else
} else if (getTreeKey(funp) != AutoXDRTree::noKey) {
MOZ_ASSERT(sourceObject);
scope = funp->nonLazyScript()->enclosingScope();
} else {
MOZ_ASSERT(!sourceObject);
MOZ_ASSERT(funp->nonLazyScript()->enclosingScope()->is<GlobalScope>());
}
if (!VersionCheck(this)) {
postProcessContextErrors(cx());
return false;
}
RootedScope scope(cx(), &cx()->global()->emptyGlobalScope());
if (!XDRInterpretedFunction(this, scope, nullptr, funp)) {
if (!XDRInterpretedFunction(this, scope, sourceObject, funp)) {
postProcessContextErrors(cx());
funp.set(nullptr);
return false;
@@ -144,6 +154,8 @@ XDRState<mode>::codeScript(MutableHandleScript scriptp)
else
MOZ_ASSERT(!scriptp->enclosingScope());
AutoXDRTree scriptTree(this, getTopLevelTreeKey());
if (!VersionCheck(this)) {
postProcessContextErrors(cx());
return false;
@@ -167,3 +179,189 @@ XDRState<mode>::codeConstValue(MutableHandleValue vp)
template class js::XDRState<XDR_ENCODE>;
template class js::XDRState<XDR_DECODE>;
AutoXDRTree::AutoXDRTree(XDRCoderBase* xdr, AutoXDRTree::Key key)
: key_(key),
parent_(this),
xdr_(xdr)
{
if (key_ != AutoXDRTree::noKey)
xdr->createOrReplaceSubTree(this);
}
AutoXDRTree::~AutoXDRTree()
{
if (key_ != AutoXDRTree::noKey)
xdr_->endSubTree();
}
constexpr AutoXDRTree::Key AutoXDRTree::noKey;
constexpr AutoXDRTree::Key AutoXDRTree::noSubTree;
constexpr AutoXDRTree::Key AutoXDRTree::topLevel;
AutoXDRTree::Key
XDRIncrementalEncoder::getTopLevelTreeKey() const
{
return AutoXDRTree::topLevel;
}
AutoXDRTree::Key
XDRIncrementalEncoder::getTreeKey(JSFunction* fun) const
{
if (fun->isInterpretedLazy()) {
static_assert(sizeof(fun->lazyScript()->begin()) == 4 ||
sizeof(fun->lazyScript()->end()) == 4,
"AutoXDRTree key requires LazyScripts positions to be uint32");
return uint64_t(fun->lazyScript()->begin()) << 32 | fun->lazyScript()->end();
}
if (fun->isInterpreted()) {
static_assert(sizeof(fun->nonLazyScript()->sourceStart()) == 4 ||
sizeof(fun->nonLazyScript()->sourceEnd()) == 4,
"AutoXDRTree key requires JSScripts positions to be uint32");
return uint64_t(fun->nonLazyScript()->sourceStart()) << 32 | fun->nonLazyScript()->sourceEnd();
}
return AutoXDRTree::noKey;
}
bool
XDRIncrementalEncoder::init()
{
if (!tree_.init())
return false;
return true;
}
void
XDRIncrementalEncoder::createOrReplaceSubTree(AutoXDRTree* child)
{
AutoXDRTree* parent = scope_;
child->parent_ = parent;
scope_ = child;
if (oom_)
return;
size_t cursor = buf.cursor();
// End the parent slice here, set the key to the child.
if (parent) {
Slice& last = node_->back();
last.sliceLength = cursor - last.sliceBegin;
last.child = child->key_;
MOZ_ASSERT_IF(uint32_t(parent->key_) != 0,
uint32_t(parent->key_ >> 32) <= uint32_t(child->key_ >> 32) &&
uint32_t(child->key_) <= uint32_t(parent->key_));
}
// Create or replace the part with what is going to be encoded next.
SlicesTree::AddPtr p = tree_.lookupForAdd(child->key_);
SlicesNode tmp;
if (!p) {
// Create a new sub-tree node.
if (!tree_.add(p, child->key_, mozilla::Move(tmp))) {
oom_ = true;
return;
}
} else {
// Replace an exisiting sub-tree.
p->value() = mozilla::Move(tmp);
}
node_ = &p->value();
// Add content to the root of the new sub-tree,
// i-e an empty slice with no children.
if (!node_->append(Slice { cursor, 0, AutoXDRTree::noSubTree }))
MOZ_CRASH("SlicesNode have a reserved space of 1.");
}
void
XDRIncrementalEncoder::endSubTree()
{
AutoXDRTree* child = scope_;
AutoXDRTree* parent = child->parent_;
scope_ = parent;
if (oom_)
return;
size_t cursor = buf.cursor();
// End the child sub-tree.
Slice& last = node_->back();
last.sliceLength = cursor - last.sliceBegin;
MOZ_ASSERT(last.child == AutoXDRTree::noSubTree);
// Stop at the top-level.
if (!parent) {
node_ = nullptr;
return;
}
// Restore the parent node.
SlicesTree::Ptr p = tree_.lookup(parent->key_);
node_ = &p->value();
// Append the new slice in the parent node.
if (!node_->append(Slice { cursor, 0, AutoXDRTree::noSubTree })) {
oom_ = true;
return;
}
}
bool
XDRIncrementalEncoder::linearize()
{
if (oom_) {
ReportOutOfMemory(cx());
return fail(JS::TranscodeResult_Throw);
}
// Do not linearize while we are currently adding bytes.
MOZ_ASSERT(scope_ == nullptr);
// Visit the tree parts in a depth first order, to linearize the bits.
Vector<SlicesNode::ConstRange> depthFirst(cx());
SlicesTree::Ptr p = tree_.lookup(AutoXDRTree::topLevel);
MOZ_ASSERT(p);
if (!depthFirst.append(((const SlicesNode&) p->value()).all())) {
ReportOutOfMemory(cx());
return fail(JS::TranscodeResult_Throw);
}
while (!depthFirst.empty()) {
SlicesNode::ConstRange& iter = depthFirst.back();
Slice slice = iter.popCopyFront();
// These fields have different meaning, but they should be correlated if
// the tree is well formatted.
MOZ_ASSERT_IF(slice.child == AutoXDRTree::noSubTree, iter.empty());
if (iter.empty())
depthFirst.popBack();
// Copy the bytes associated with the current slice to the transcode
// buffer which would be serialized.
MOZ_ASSERT(slice.sliceBegin <= slices_.length());
MOZ_ASSERT(slice.sliceBegin + slice.sliceLength <= slices_.length());
if (!buffer_.append(slices_.begin() + slice.sliceBegin, slice.sliceLength)) {
ReportOutOfMemory(cx());
return fail(JS::TranscodeResult_Throw);
}
// If we are at the end, go to back to the parent script.
if (slice.child == AutoXDRTree::noSubTree)
continue;
// Visit the sub-parts before visiting the rest of the current slice.
SlicesTree::Ptr p = tree_.lookup(slice.child);
MOZ_ASSERT(p);
if (!depthFirst.append(((const SlicesNode&) p->value()).all())) {
ReportOutOfMemory(cx());
return fail(JS::TranscodeResult_Throw);
}
}
tree_.finish();
slices_.clearAndFree();
return true;
}
+229 -16
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 */
+7
View File
@@ -568,6 +568,13 @@
ERROR(NS_ERROR_DOM_INVALID_STATE_XHR_CHUNKED_RESPONSETYPES_UNSUPPORTED_FOR_SYNC, FAILURE(1027)),
ERROR(NS_ERROR_DOM_INVALID_ACCESS_XHR_TIMEOUT_AND_RESPONSETYPE_UNSUPPORTED_FOR_SYNC, FAILURE(1028)),
/* When manipulating the bytecode cache with the JS API, some transcoding
* errors, such as a different bytecode format can cause failures of the
* decoding process.
*/
ERROR(NS_ERROR_DOM_JS_DECODING_ERROR, FAILURE(1030)),
/* May be used to indicate when e.g. setting a property value didn't
* actually change the value, like for obj.foo = "bar"; obj.foo = "bar";
* the second assignment throws NS_SUCCESS_DOM_NO_OPERATION.