Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "frontend/StencilXdr.h" // StencilXDR
#include "mozilla/ArrayUtils.h" // mozilla::ArrayEqual
#include "mozilla/OperatorNewExtensions.h" // mozilla::KnownNotNull
#include "mozilla/ScopeExit.h" // mozilla::MakeScopeExit
#include "mozilla/Try.h" // MOZ_TRY
#include <stddef.h> // size_t
#include <stdint.h> // uint8_t, uint16_t, uint32_t
#include <type_traits> // std::has_unique_object_representations
#include <utility> // std::forward
#include "ds/LifoAlloc.h" // LifoAlloc
#include "frontend/CompilationStencil.h" // CompilationStencil, ExtensibleCompilationStencil
#include "frontend/ScriptIndex.h" // ScriptIndex
#include "vm/Scope.h" // SizeOfParserScopeData
#include "vm/StencilEnums.h" // js::ImmutableScriptFlagsEnum
using namespace js;
using namespace js::frontend;
using mozilla::Utf8Unit;
template <typename NameType>
struct CanEncodeNameType {
static constexpr bool value = false;
};
template <>
struct CanEncodeNameType<TaggedParserAtomIndex> {
static constexpr bool value = true;
};
template <XDRMode mode, typename T, size_t N, class AP>
static XDRResult XDRVectorUninitialized(XDRState<mode>* xdr,
Vector<T, N, AP>& vec,
uint32_t& length) {
if (mode == XDR_ENCODE) {
MOZ_ASSERT(vec.length() <= UINT32_MAX);
length = vec.length();
}
MOZ_TRY(xdr->codeUint32(&length));
if (mode == XDR_DECODE) {
MOZ_ASSERT(vec.empty());
if (!vec.resizeUninitialized(length)) {
js::ReportOutOfMemory(xdr->fc());
return xdr->fail(JS::TranscodeResult::Throw);
}
}
return Ok();
}
template <XDRMode mode, typename T, size_t N, class AP>
static XDRResult XDRVectorInitialized(XDRState<mode>* xdr,
Vector<T, N, AP>& vec, uint32_t length) {
MOZ_ASSERT_IF(mode == XDR_ENCODE, length == vec.length());
if (mode == XDR_DECODE) {
MOZ_ASSERT(vec.empty());
if (!vec.resize(length)) {
js::ReportOutOfMemory(xdr->fc());
return xdr->fail(JS::TranscodeResult::Throw);
}
}
return Ok();
}
template <XDRMode mode, typename T, size_t N, class AP>
static XDRResult XDRVectorInitialized(XDRState<mode>* xdr,
Vector<T, N, AP>& vec) {
uint32_t length;
if (mode == XDR_ENCODE) {
MOZ_ASSERT(vec.length() <= UINT32_MAX);
length = vec.length();
}
MOZ_TRY(xdr->codeUint32(&length));
return XDRVectorInitialized(xdr, vec, length);
}
template <XDRMode mode, typename T, size_t N, class AP>
static XDRResult XDRVectorContent(XDRState<mode>* xdr, Vector<T, N, AP>& vec) {
static_assert(CanCopyDataToDisk<T>::value,
"Vector content cannot be bulk-copied to disk.");
uint32_t length;
MOZ_TRY(XDRVectorUninitialized(xdr, vec, length));
MOZ_TRY(xdr->codeBytes(vec.begin(), sizeof(T) * length));
return Ok();
}
template <XDRMode mode, typename T>
static XDRResult XDRSpanInitialized(XDRState<mode>* xdr, LifoAlloc& alloc,
mozilla::Span<T>& span, uint32_t size) {
MOZ_ASSERT_IF(mode == XDR_ENCODE, size == span.size());
if (mode == XDR_DECODE) {
MOZ_ASSERT(span.empty());
if (size > 0) {
auto* p = alloc.template newArrayUninitialized<T>(size);
if (!p) {
js::ReportOutOfMemory(xdr->fc());
return xdr->fail(JS::TranscodeResult::Throw);
}
span = mozilla::Span(p, size);
for (size_t i = 0; i < size; i++) {
new (mozilla::KnownNotNull, &span[i]) T();
}
}
}
return Ok();
}
template <XDRMode mode, typename T>
static XDRResult XDRSpanContent(XDRState<mode>* xdr, LifoAlloc& alloc,
mozilla::Span<T>& span, uint32_t size) {
static_assert(CanCopyDataToDisk<T>::value,
"Span cannot be bulk-copied to disk.");
MOZ_ASSERT_IF(mode == XDR_ENCODE, size == span.size());
if (size) {
MOZ_TRY(xdr->align32());
T* data;
if constexpr (mode == XDR_ENCODE) {
data = span.data();
MOZ_TRY(xdr->codeBytes(data, sizeof(T) * size));
} else {
const auto& options = static_cast<XDRStencilDecoder*>(xdr)->options();
if (options.borrowBuffer) {
MOZ_TRY(xdr->borrowedData(&data, sizeof(T) * size));
} else {
data = alloc.template newArrayUninitialized<T>(size);
if (!data) {
js::ReportOutOfMemory(xdr->fc());
return xdr->fail(JS::TranscodeResult::Throw);
}
MOZ_TRY(xdr->codeBytes(data, sizeof(T) * size));
}
}
if (mode == XDR_DECODE) {
span = mozilla::Span(data, size);
}
}
return Ok();
}
template <XDRMode mode, typename T>
static XDRResult XDRSpanContent(XDRState<mode>* xdr, LifoAlloc& alloc,
mozilla::Span<T>& span) {
uint32_t size;
if (mode == XDR_ENCODE) {
MOZ_ASSERT(span.size() <= UINT32_MAX);
size = span.size();
}
MOZ_TRY(xdr->codeUint32(&size));
return XDRSpanContent(xdr, alloc, span, size);
}
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeBigInt(XDRState<mode>* xdr,
LifoAlloc& alloc,
BigIntStencil& stencil) {
uint32_t size;
if (mode == XDR_ENCODE) {
size = stencil.source_.size();
}
MOZ_TRY(xdr->codeUint32(&size));
return XDRSpanContent(xdr, alloc, stencil.source_, size);
}
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeObjLiteral(XDRState<mode>* xdr,
LifoAlloc& alloc,
ObjLiteralStencil& stencil) {
uint8_t kindAndFlags = 0;
if (mode == XDR_ENCODE) {
static_assert(sizeof(ObjLiteralKindAndFlags) == sizeof(uint8_t));
kindAndFlags = stencil.kindAndFlags_.toRaw();
}
MOZ_TRY(xdr->codeUint8(&kindAndFlags));
if (mode == XDR_DECODE) {
stencil.kindAndFlags_.setRaw(kindAndFlags);
}
MOZ_TRY(xdr->codeUint32(&stencil.propertyCount_));
MOZ_TRY(XDRSpanContent(xdr, alloc, stencil.code_));
return Ok();
}
template <typename ScopeT>
/* static */ void AssertScopeSpecificDataIsEncodable() {
using ScopeDataT = typename ScopeT::ParserData;
static_assert(CanEncodeNameType<typename ScopeDataT::NameType>::value);
static_assert(CanCopyDataToDisk<ScopeDataT>::value,
"ScopeData cannot be bulk-copied to disk");
}
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeScopeData(
XDRState<mode>* xdr, LifoAlloc& alloc, ScopeStencil& stencil,
BaseParserScopeData*& baseScopeData) {
// WasmInstanceScope & WasmFunctionScope should not appear in stencils.
MOZ_ASSERT(stencil.kind_ != ScopeKind::WasmInstance);
MOZ_ASSERT(stencil.kind_ != ScopeKind::WasmFunction);
if (stencil.kind_ == ScopeKind::With) {
return Ok();
}
MOZ_TRY(xdr->align32());
static_assert(offsetof(BaseParserScopeData, length) == 0,
"length should be the first field");
uint32_t length;
if (mode == XDR_ENCODE) {
length = baseScopeData->length;
} else {
MOZ_TRY(xdr->peekUint32(&length));
}
AssertScopeSpecificDataIsEncodable<FunctionScope>();
AssertScopeSpecificDataIsEncodable<VarScope>();
AssertScopeSpecificDataIsEncodable<LexicalScope>();
AssertScopeSpecificDataIsEncodable<ClassBodyScope>();
AssertScopeSpecificDataIsEncodable<EvalScope>();
AssertScopeSpecificDataIsEncodable<GlobalScope>();
AssertScopeSpecificDataIsEncodable<ModuleScope>();
// In both decoding and encoding, stencil.kind_ is now known, and
// can be assumed. This allows the encoding to write out the bytes
// for the specialized scope-data type without needing to encode
// a distinguishing prefix.
uint32_t totalLength = SizeOfParserScopeData(stencil.kind_, length);
if constexpr (mode == XDR_ENCODE) {
MOZ_TRY(xdr->codeBytes(baseScopeData, totalLength));
} else {
const auto& options = static_cast<XDRStencilDecoder*>(xdr)->options();
if (options.borrowBuffer) {
MOZ_TRY(xdr->borrowedData(&baseScopeData, totalLength));
} else {
baseScopeData =
reinterpret_cast<BaseParserScopeData*>(alloc.alloc(totalLength));
if (!baseScopeData) {
js::ReportOutOfMemory(xdr->fc());
return xdr->fail(JS::TranscodeResult::Throw);
}
MOZ_TRY(xdr->codeBytes(baseScopeData, totalLength));
}
}
return Ok();
}
template <XDRMode mode>
/* static */
XDRResult StencilXDR::codeSharedData(XDRState<mode>* xdr,
RefPtr<SharedImmutableScriptData>& sisd) {
static_assert(frontend::CanCopyDataToDisk<ImmutableScriptData>::value,
"ImmutableScriptData cannot be bulk-copied to disk");
static_assert(frontend::CanCopyDataToDisk<jsbytecode>::value,
"jsbytecode cannot be bulk-copied to disk");
static_assert(frontend::CanCopyDataToDisk<SrcNote>::value,
"SrcNote cannot be bulk-copied to disk");
static_assert(frontend::CanCopyDataToDisk<ScopeNote>::value,
"ScopeNote cannot be bulk-copied to disk");
static_assert(frontend::CanCopyDataToDisk<TryNote>::value,
"TryNote cannot be bulk-copied to disk");
uint32_t size;
uint32_t hash;
if (mode == XDR_ENCODE) {
if (sisd) {
size = sisd->immutableDataLength();
hash = sisd->hash();
} else {
size = 0;
hash = 0;
}
}
MOZ_TRY(xdr->codeUint32(&size));
// A size of zero is used when the `sisd` is nullptr. This can occur for
// certain outer container modes. In this case, there is no further
// transcoding to do.
if (!size) {
MOZ_ASSERT(!sisd);
return Ok();
}
MOZ_TRY(xdr->align32());
static_assert(alignof(ImmutableScriptData) <= alignof(uint32_t));
MOZ_TRY(xdr->codeUint32(&hash));
if constexpr (mode == XDR_ENCODE) {
uint8_t* data = const_cast<uint8_t*>(sisd->get()->immutableData().data());
MOZ_ASSERT(data == reinterpret_cast<const uint8_t*>(sisd->get()),
"Decode below relies on the data placement");
MOZ_TRY(xdr->codeBytes(data, size));
} else {
sisd = SharedImmutableScriptData::create(xdr->fc());
if (!sisd) {
return xdr->fail(JS::TranscodeResult::Throw);
}
const auto& options = static_cast<XDRStencilDecoder*>(xdr)->options();
if (options.usePinnedBytecode) {
MOZ_ASSERT(options.borrowBuffer);
ImmutableScriptData* isd;
MOZ_TRY(xdr->borrowedData(&isd, size));
sisd->setExternal(isd, hash);
} else {
auto isd = ImmutableScriptData::new_(xdr->fc(), size);
if (!isd) {
return xdr->fail(JS::TranscodeResult::Throw);
}
uint8_t* data = reinterpret_cast<uint8_t*>(isd.get());
MOZ_TRY(xdr->codeBytes(data, size));
sisd->setOwn(std::move(isd), hash);
}
if (!sisd->get()->validateLayout(size)) {
MOZ_ASSERT(false, "Bad ImmutableScriptData");
return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
}
}
if (mode == XDR_DECODE) {
if (!SharedImmutableScriptData::shareScriptData(xdr->fc(), sisd)) {
return xdr->fail(JS::TranscodeResult::Throw);
}
}
return Ok();
}
// Called from js::XDRScript.
template /* static */ XDRResult StencilXDR::codeSharedData(
XDRState<XDR_ENCODE>* xdr, RefPtr<SharedImmutableScriptData>& sisd);
template /* static */ XDRResult StencilXDR::codeSharedData(
XDRState<XDR_DECODE>* xdr, RefPtr<SharedImmutableScriptData>& sisd);
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeSharedDataContainer(
XDRState<mode>* xdr, SharedDataContainer& sharedData) {
if (mode == XDR_ENCODE) {
if (sharedData.isBorrow()) {
return codeSharedDataContainer(xdr, *sharedData.asBorrow());
}
}
enum Kind {
Single,
Vector,
Map,
};
Kind kind;
if (mode == XDR_ENCODE) {
if (sharedData.isSingle()) {
kind = Kind::Single;
} else if (sharedData.isVector()) {
kind = Kind::Vector;
} else {
MOZ_ASSERT(sharedData.isMap());
kind = Kind::Map;
}
}
MOZ_TRY(xdr->codeEnum32(&kind));
switch (kind) {
case Kind::Single: {
RefPtr<SharedImmutableScriptData> ref;
if (mode == XDR_ENCODE) {
ref = sharedData.asSingle();
}
MOZ_TRY(codeSharedData<mode>(xdr, ref));
if (mode == XDR_DECODE) {
sharedData.setSingle(ref.forget());
}
break;
}
case Kind::Vector: {
if (mode == XDR_DECODE) {
if (!sharedData.initVector(xdr->fc())) {
return xdr->fail(JS::TranscodeResult::Throw);
}
}
auto& vec = *sharedData.asVector();
MOZ_TRY(XDRVectorInitialized(xdr, vec));
for (auto& entry : vec) {
// NOTE: There can be nullptr, even if we don't perform syntax parsing,
// because of constant folding.
MOZ_TRY(codeSharedData<mode>(xdr, entry));
}
break;
}
case Kind::Map: {
if (mode == XDR_DECODE) {
if (!sharedData.initMap(xdr->fc())) {
return xdr->fail(JS::TranscodeResult::Throw);
}
}
auto& map = *sharedData.asMap();
uint32_t count;
if (mode == XDR_ENCODE) {
count = map.count();
}
MOZ_TRY(xdr->codeUint32(&count));
if (mode == XDR_DECODE) {
if (!map.reserve(count)) {
js::ReportOutOfMemory(xdr->fc());
return xdr->fail(JS::TranscodeResult::Throw);
}
}
if (mode == XDR_ENCODE) {
for (auto iter = map.iter(); !iter.done(); iter.next()) {
uint32_t index = iter.get().key().index;
auto& data = iter.get().value();
MOZ_TRY(xdr->codeUint32(&index));
MOZ_TRY(codeSharedData<mode>(xdr, data));
}
} else {
for (uint32_t i = 0; i < count; i++) {
ScriptIndex index;
MOZ_TRY(xdr->codeUint32(&index.index));
RefPtr<SharedImmutableScriptData> data;
MOZ_TRY(codeSharedData<mode>(xdr, data));
if (!map.putNew(index, data)) {
js::ReportOutOfMemory(xdr->fc());
return xdr->fail(JS::TranscodeResult::Throw);
}
}
}
break;
}
default:
return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
}
return Ok();
}
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeParserAtom(XDRState<mode>* xdr,
LifoAlloc& alloc,
ParserAtom** atomp) {
static_assert(CanCopyDataToDisk<ParserAtom>::value,
"ParserAtom cannot be bulk-copied to disk.");
MOZ_TRY(xdr->align32());
const ParserAtom* header;
if (mode == XDR_ENCODE) {
header = *atomp;
} else {
MOZ_TRY(xdr->peekData(&header));
}
const uint32_t CharSize =
header->hasLatin1Chars() ? sizeof(JS::Latin1Char) : sizeof(char16_t);
uint32_t totalLength = sizeof(ParserAtom) + (CharSize * header->length());
if constexpr (mode == XDR_ENCODE) {
MOZ_TRY(xdr->codeBytes(*atomp, totalLength));
} else {
const auto& options = static_cast<XDRStencilDecoder*>(xdr)->options();
if (options.borrowBuffer) {
MOZ_TRY(xdr->borrowedData(atomp, totalLength));
} else {
*atomp = reinterpret_cast<ParserAtom*>(alloc.alloc(totalLength));
if (!*atomp) {
js::ReportOutOfMemory(xdr->fc());
return xdr->fail(JS::TranscodeResult::Throw);
}
MOZ_TRY(xdr->codeBytes(*atomp, totalLength));
}
}
return Ok();
}
template <XDRMode mode>
static XDRResult XDRAtomCount(XDRState<mode>* xdr, uint32_t* atomCount) {
return xdr->codeUint32(atomCount);
}
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeParserAtomSpan(
XDRState<mode>* xdr, LifoAlloc& alloc, ParserAtomSpan& parserAtomData) {
if (mode == XDR_ENCODE) {
uint32_t atomVectorLength = parserAtomData.size();
MOZ_TRY(XDRAtomCount(xdr, &atomVectorLength));
uint32_t atomCount = 0;
for (const auto& entry : parserAtomData) {
if (!entry) {
continue;
}
if (entry->isUsedByStencil()) {
atomCount++;
}
}
MOZ_TRY(XDRAtomCount(xdr, &atomCount));
for (uint32_t i = 0; i < atomVectorLength; i++) {
auto& entry = parserAtomData[i];
if (!entry) {
continue;
}
if (entry->isUsedByStencil()) {
MOZ_TRY(xdr->codeUint32(&i));
MOZ_TRY(codeParserAtom(xdr, alloc, &entry));
}
}
return Ok();
}
uint32_t atomVectorLength;
MOZ_TRY(XDRAtomCount(xdr, &atomVectorLength));
frontend::ParserAtomSpanBuilder builder(parserAtomData);
if (!builder.allocate(xdr->fc(), alloc, atomVectorLength)) {
return xdr->fail(JS::TranscodeResult::Throw);
}
uint32_t atomCount;
MOZ_TRY(XDRAtomCount(xdr, &atomCount));
for (uint32_t i = 0; i < atomCount; i++) {
frontend::ParserAtom* entry = nullptr;
uint32_t index;
MOZ_TRY(xdr->codeUint32(&index));
MOZ_TRY(codeParserAtom(xdr, alloc, &entry));
if (mode == XDR_DECODE) {
if (index >= atomVectorLength) {
return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
}
}
builder.set(frontend::ParserAtomIndex(index), entry);
}
return Ok();
}
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeModuleRequest(
XDRState<mode>* xdr, StencilModuleRequest& stencil) {
MOZ_TRY(xdr->codeUint32(stencil.specifier.rawDataRef()));
MOZ_TRY(xdr->codeUint32(stencil.firstUnsupportedAttributeKey.rawDataRef()));
MOZ_TRY(XDRVectorContent(xdr, stencil.attributes));
return Ok();
}
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeModuleRequestVector(
XDRState<mode>* xdr, StencilModuleMetadata::RequestVector& vector) {
MOZ_TRY(XDRVectorInitialized(xdr, vector));
for (auto& entry : vector) {
MOZ_TRY(codeModuleRequest<mode>(xdr, entry));
}
return Ok();
}
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeModuleEntry(
XDRState<mode>* xdr, StencilModuleEntry& stencil) {
MOZ_TRY(xdr->codeUint32(&stencil.moduleRequest));
MOZ_TRY(xdr->codeUint32(stencil.localName.rawDataRef()));
MOZ_TRY(xdr->codeUint32(stencil.importName.rawDataRef()));
MOZ_TRY(xdr->codeUint32(stencil.exportName.rawDataRef()));
MOZ_TRY(xdr->codeUint32(&stencil.lineno));
MOZ_TRY(xdr->codeUint32(stencil.column.addressOfValueForTranscode()));
return Ok();
}
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeModuleEntryVector(
XDRState<mode>* xdr, StencilModuleMetadata::EntryVector& vector) {
MOZ_TRY(XDRVectorInitialized(xdr, vector));
for (auto& entry : vector) {
MOZ_TRY(codeModuleEntry<mode>(xdr, entry));
}
return Ok();
}
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeModuleMetadata(
XDRState<mode>* xdr, StencilModuleMetadata& stencil) {
MOZ_TRY(codeModuleRequestVector(xdr, stencil.moduleRequests));
MOZ_TRY(codeModuleEntryVector(xdr, stencil.requestedModules));
MOZ_TRY(codeModuleEntryVector(xdr, stencil.importEntries));
MOZ_TRY(codeModuleEntryVector(xdr, stencil.localExportEntries));
MOZ_TRY(codeModuleEntryVector(xdr, stencil.indirectExportEntries));
MOZ_TRY(codeModuleEntryVector(xdr, stencil.starExportEntries));
MOZ_TRY(XDRVectorContent(xdr, stencil.functionDecls));
uint8_t isAsync = 0;
if (mode == XDR_ENCODE) {
if (stencil.isAsync) {
isAsync = stencil.isAsync ? 1 : 0;
}
}
MOZ_TRY(xdr->codeUint8(&isAsync));
if (mode == XDR_DECODE) {
stencil.isAsync = isAsync == 1;
}
return Ok();
}
template <XDRMode mode>
XDRResult XDRCompilationStencilSpanSize(
XDRState<mode>* xdr, uint32_t* scriptSize, uint32_t* gcThingSize,
uint32_t* scopeSize, uint32_t* scriptExtraSize, uint32_t* regExpSize,
uint32_t* bigIntSize, uint32_t* objLiteralSize) {
// Compress the series of span sizes, to avoid consuming extra space for
// unused/small span sizes.
// There will be align32 shortly after this section, so try to make the
// padding smaller.
enum XDRSpanSizeKind {
// All of the size values fit in 1 byte each. The entire section takes 7
// bytes, and expect no padding.
All8Kind,
// Other cases. All of the size values fit in 4 bytes each. Expect 3 bytes
// padding for `sizeKind`.
All32Kind,
};
uint8_t sizeKind = All32Kind;
if (mode == XDR_ENCODE) {
uint32_t mask = (*scriptSize) | (*gcThingSize) | (*scopeSize) |
(*scriptExtraSize) | (*regExpSize) | (*bigIntSize) |
(*objLiteralSize);
if (mask <= 0xff) {
sizeKind = All8Kind;
}
}
MOZ_TRY(xdr->codeUint8(&sizeKind));
if (sizeKind == All32Kind) {
MOZ_TRY(xdr->codeUint32(scriptSize));
MOZ_TRY(xdr->codeUint32(gcThingSize));
MOZ_TRY(xdr->codeUint32(scopeSize));
MOZ_TRY(xdr->codeUint32(scriptExtraSize));
MOZ_TRY(xdr->codeUint32(regExpSize));
MOZ_TRY(xdr->codeUint32(bigIntSize));
MOZ_TRY(xdr->codeUint32(objLiteralSize));
} else {
uint8_t scriptSize8 = 0;
uint8_t gcThingSize8 = 0;
uint8_t scopeSize8 = 0;
uint8_t scriptExtraSize8 = 0;
uint8_t regExpSize8 = 0;
uint8_t bigIntSize8 = 0;
uint8_t objLiteralSize8 = 0;
if (mode == XDR_ENCODE) {
scriptSize8 = uint8_t(*scriptSize);
gcThingSize8 = uint8_t(*gcThingSize);
scopeSize8 = uint8_t(*scopeSize);
scriptExtraSize8 = uint8_t(*scriptExtraSize);
regExpSize8 = uint8_t(*regExpSize);
bigIntSize8 = uint8_t(*bigIntSize);
objLiteralSize8 = uint8_t(*objLiteralSize);
}
MOZ_TRY(xdr->codeUint8(&scriptSize8));
MOZ_TRY(xdr->codeUint8(&gcThingSize8));
MOZ_TRY(xdr->codeUint8(&scopeSize8));
MOZ_TRY(xdr->codeUint8(&scriptExtraSize8));
MOZ_TRY(xdr->codeUint8(®ExpSize8));
MOZ_TRY(xdr->codeUint8(&bigIntSize8));
MOZ_TRY(xdr->codeUint8(&objLiteralSize8));
if (mode == XDR_DECODE) {
*scriptSize = scriptSize8;
*gcThingSize = gcThingSize8;
*scopeSize = scopeSize8;
*scriptExtraSize = scriptExtraSize8;
*regExpSize = regExpSize8;
*bigIntSize = bigIntSize8;
*objLiteralSize = objLiteralSize8;
}
}
return Ok();
}
// Marker between each section inside CompilationStencil.
//
// These values should meet the following requirement:
// * No same value (differ more than single bit flip)
// * Bit pattern that won't frequently appear inside other XDR data
//
// Currently they're randomly chosen prime numbers that doesn't have same
// byte pattern.
enum class SectionMarker : uint32_t {
ParserAtomData = 0xD9C098D3,
ScopeData = 0x892C25EF,
ScopeNames = 0x638C4FB3,
RegExpData = 0xB030C2AF,
BigIntData = 0x4B24F449,
ObjLiteralData = 0x9AFAAE45,
SharedData = 0xAAD52687,
GCThingData = 0x1BD8F533,
ScriptData = 0x840458FF,
ScriptExtra = 0xA90E489D,
ModuleMetadata = 0x94FDCE6D,
End = 0x16DDA135,
};
template <XDRMode mode>
static XDRResult CodeMarker(XDRState<mode>* xdr, SectionMarker marker) {
return xdr->codeMarker(uint32_t(marker));
}
template <XDRMode mode>
/* static */ XDRResult StencilXDR::codeCompilationStencil(
XDRState<mode>* xdr, CompilationStencil& stencil) {
MOZ_ASSERT(!stencil.asmJS);
if constexpr (mode == XDR_DECODE) {
const auto& options = static_cast<XDRStencilDecoder*>(xdr)->options();
if (options.borrowBuffer) {
stencil.storageType = CompilationStencil::StorageType::Borrowed;
} else {
stencil.storageType = CompilationStencil::StorageType::Owned;
}
}
MOZ_TRY(CodeMarker(xdr, SectionMarker::ParserAtomData));
MOZ_TRY(codeParserAtomSpan(xdr, stencil.alloc, stencil.parserAtomData));
uint8_t canLazilyParse = 0;
if (mode == XDR_ENCODE) {
canLazilyParse = stencil.canLazilyParse;
}
MOZ_TRY(xdr->codeUint8(&canLazilyParse));
if (mode == XDR_DECODE) {
stencil.canLazilyParse = canLazilyParse;
}
MOZ_TRY(xdr->codeUint32(&stencil.functionKey));
uint32_t scriptSize, gcThingSize, scopeSize, scriptExtraSize;
uint32_t regExpSize, bigIntSize, objLiteralSize;
if (mode == XDR_ENCODE) {
scriptSize = stencil.scriptData.size();
gcThingSize = stencil.gcThingData.size();
scopeSize = stencil.scopeData.size();
MOZ_ASSERT(scopeSize == stencil.scopeNames.size());
scriptExtraSize = stencil.scriptExtra.size();
regExpSize = stencil.regExpData.size();
bigIntSize = stencil.bigIntData.size();
objLiteralSize = stencil.objLiteralData.size();
}
MOZ_TRY(XDRCompilationStencilSpanSize(
xdr, &scriptSize, &gcThingSize, &scopeSize, &scriptExtraSize, ®ExpSize,
&bigIntSize, &objLiteralSize));
// All of the vector-indexed data elements referenced by the
// main script tree must be materialized first.
MOZ_TRY(CodeMarker(xdr, SectionMarker::ScopeData));
MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.scopeData, scopeSize));
MOZ_TRY(CodeMarker(xdr, SectionMarker::ScopeNames));
MOZ_TRY(
XDRSpanInitialized(xdr, stencil.alloc, stencil.scopeNames, scopeSize));
MOZ_ASSERT(stencil.scopeData.size() == stencil.scopeNames.size());
for (uint32_t i = 0; i < scopeSize; i++) {
MOZ_TRY(codeScopeData(xdr, stencil.alloc, stencil.scopeData[i],
stencil.scopeNames[i]));
}
MOZ_TRY(CodeMarker(xdr, SectionMarker::RegExpData));
MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.regExpData, regExpSize));
MOZ_TRY(CodeMarker(xdr, SectionMarker::BigIntData));
MOZ_TRY(
XDRSpanInitialized(xdr, stencil.alloc, stencil.bigIntData, bigIntSize));
for (auto& entry : stencil.bigIntData) {
MOZ_TRY(codeBigInt(xdr, stencil.alloc, entry));
}
MOZ_TRY(CodeMarker(xdr, SectionMarker::ObjLiteralData));
MOZ_TRY(XDRSpanInitialized(xdr, stencil.alloc, stencil.objLiteralData,
objLiteralSize));
for (auto& entry : stencil.objLiteralData) {
MOZ_TRY(codeObjLiteral(xdr, stencil.alloc, entry));
}
MOZ_TRY(CodeMarker(xdr, SectionMarker::SharedData));
MOZ_TRY(codeSharedDataContainer(xdr, stencil.sharedData));
MOZ_TRY(CodeMarker(xdr, SectionMarker::GCThingData));
MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.gcThingData, gcThingSize));
// Now serialize the vector of ScriptStencils.
MOZ_TRY(CodeMarker(xdr, SectionMarker::ScriptData));
MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.scriptData, scriptSize));
MOZ_TRY(CodeMarker(xdr, SectionMarker::ScriptExtra));
MOZ_TRY(
XDRSpanContent(xdr, stencil.alloc, stencil.scriptExtra, scriptExtraSize));
// We don't support coding non-initial CompilationStencil.
MOZ_ASSERT(stencil.isInitialStencil());
if (stencil.scriptExtra[CompilationStencil::TopLevelIndex].isModule()) {
if (mode == XDR_DECODE) {
stencil.moduleMetadata =
xdr->fc()->getAllocator()->template new_<StencilModuleMetadata>();
if (!stencil.moduleMetadata) {
return xdr->fail(JS::TranscodeResult::Throw);
}
}
MOZ_TRY(CodeMarker(xdr, SectionMarker::ModuleMetadata));
MOZ_TRY(codeModuleMetadata(xdr, *stencil.moduleMetadata));
// codeModuleMetadata doesn't guarantee alignment.
MOZ_TRY(xdr->align32());
}
MOZ_TRY(CodeMarker(xdr, SectionMarker::End));
// The result should be aligned.
//
// NOTE:
// If the top-level isn't a module, ScriptData/ScriptExtra sections
// guarantee the alignment because there should be at least 1 item,
// and XDRSpanContent adds alignment before span content, and the struct size
// should also be aligned.
static_assert(sizeof(ScriptStencil) % 4 == 0,
"size of ScriptStencil should be aligned");
static_assert(sizeof(ScriptStencilExtra) % 4 == 0,
"size of ScriptStencilExtra should be aligned");
MOZ_RELEASE_ASSERT(xdr->isAligned32());
return Ok();
}
template <typename Unit>
struct UnretrievableSourceDecoder {
XDRState<XDR_DECODE>* const xdr_;
ScriptSource* const scriptSource_;
const uint32_t uncompressedLength_;
public:
UnretrievableSourceDecoder(XDRState<XDR_DECODE>* xdr,
ScriptSource* scriptSource,
uint32_t uncompressedLength)
: xdr_(xdr),
scriptSource_(scriptSource),
uncompressedLength_(uncompressedLength) {}
XDRResult decode() {
auto sourceUnits = xdr_->fc()->getAllocator()->make_pod_array<Unit>(
std::max<size_t>(uncompressedLength_, 1));
if (!sourceUnits) {
return xdr_->fail(JS::TranscodeResult::Throw);
}
MOZ_TRY(xdr_->codeChars(sourceUnits.get(), uncompressedLength_));
if (!scriptSource_->initializeUnretrievableUncompressedSource(
xdr_->fc(), std::move(sourceUnits), uncompressedLength_)) {
return xdr_->fail(JS::TranscodeResult::Throw);
}
return Ok();
}
};
template <>
XDRResult StencilXDR::codeSourceUnretrievableUncompressed<XDR_DECODE>(
XDRState<XDR_DECODE>* xdr, ScriptSource* ss, uint8_t sourceCharSize,
uint32_t uncompressedLength) {
MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2);
if (sourceCharSize == 1) {
UnretrievableSourceDecoder<Utf8Unit> decoder(xdr, ss, uncompressedLength);
return decoder.decode();
}
UnretrievableSourceDecoder<char16_t> decoder(xdr, ss, uncompressedLength);
return decoder.decode();
}
template <typename Unit>
struct UnretrievableSourceEncoder {
XDRState<XDR_ENCODE>* const xdr_;
ScriptSource* const source_;
const uint32_t uncompressedLength_;
UnretrievableSourceEncoder(XDRState<XDR_ENCODE>* xdr, ScriptSource* source,
uint32_t uncompressedLength)
: xdr_(xdr), source_(source), uncompressedLength_(uncompressedLength) {}
XDRResult encode() {
Unit* sourceUnits =
const_cast<Unit*>(source_->uncompressedData<Unit>()->units());
return xdr_->codeChars(sourceUnits, uncompressedLength_);
}
};
template <>
/* static */
XDRResult StencilXDR::codeSourceUnretrievableUncompressed<XDR_ENCODE>(
XDRState<XDR_ENCODE>* xdr, ScriptSource* ss, uint8_t sourceCharSize,
uint32_t uncompressedLength) {
MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2);
if (sourceCharSize == 1) {
UnretrievableSourceEncoder<Utf8Unit> encoder(xdr, ss, uncompressedLength);
return encoder.encode();
}
UnretrievableSourceEncoder<char16_t> encoder(xdr, ss, uncompressedLength);
return encoder.encode();
}
template <typename Unit, XDRMode mode>
/* static */
XDRResult StencilXDR::codeSourceUncompressedData(XDRState<mode>* const xdr,
ScriptSource* const ss) {
static_assert(
std::is_same_v<Unit, Utf8Unit> || std::is_same_v<Unit, char16_t>,
"should handle UTF-8 and UTF-16");
if (mode == XDR_ENCODE) {
MOZ_ASSERT(ss->isUncompressed<Unit>());
} else {
MOZ_ASSERT(ss->data.is<ScriptSource::Missing>());
}
uint32_t uncompressedLength;
if (mode == XDR_ENCODE) {
uncompressedLength = ss->uncompressedData<Unit>()->length();
}
MOZ_TRY(xdr->codeUint32(&uncompressedLength));
return codeSourceUnretrievableUncompressed(xdr, ss, sizeof(Unit),
uncompressedLength);
}
template <typename Unit, XDRMode mode>
/* static */
XDRResult StencilXDR::codeSourceCompressedData(XDRState<mode>* const xdr,
ScriptSource* const ss) {
static_assert(
std::is_same_v<Unit, Utf8Unit> || std::is_same_v<Unit, char16_t>,
"should handle UTF-8 and UTF-16");
if (mode == XDR_ENCODE) {
MOZ_ASSERT(ss->isCompressed<Unit>());
} else {
MOZ_ASSERT(ss->data.is<ScriptSource::Missing>());
}
uint32_t uncompressedLength;
if (mode == XDR_ENCODE) {
uncompressedLength =
ss->data.as<ScriptSource::Compressed<Unit, SourceRetrievable::No>>()
.uncompressedLength;
}
MOZ_TRY(xdr->codeUint32(&uncompressedLength));
uint32_t compressedLength;
if (mode == XDR_ENCODE) {
compressedLength =
ss->data.as<ScriptSource::Compressed<Unit, SourceRetrievable::No>>()
.raw.length();
}
MOZ_TRY(xdr->codeUint32(&compressedLength));
if (mode == XDR_DECODE) {
// Compressed data is always single-byte chars.