diff --git a/js/src/vm/AtomsTable.h b/js/src/vm/AtomsTable.h index a035b6a31d49..0fa2987de61b 100644 --- a/js/src/vm/AtomsTable.h +++ b/js/src/vm/AtomsTable.h @@ -11,6 +11,8 @@ #ifndef vm_AtomsTable_h #define vm_AtomsTable_h +#include // std::{enable_if,is_const} + #include "js/GCHashTable.h" #include "js/TypeDecls.h" #include "vm/JSAtom.h" @@ -162,12 +164,22 @@ class AtomsTable { ~AtomsTable(); bool init(); - template + template MOZ_ALWAYS_INLINE JSAtom* atomizeAndCopyChars( - JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin, + JSContext* cx, Chars chars, size_t length, PinningBehavior pin, const mozilla::Maybe& indexValue, const AtomHasher::Lookup& lookup); + template ::value>::type> + MOZ_ALWAYS_INLINE JSAtom* atomizeAndCopyChars( + JSContext* cx, CharT* chars, size_t length, PinningBehavior pin, + const mozilla::Maybe& indexValue, + const AtomHasher::Lookup& lookup) { + return atomizeAndCopyChars(cx, const_cast(chars), length, pin, + indexValue, lookup); + } + void pinExistingAtom(JSContext* cx, JSAtom* atom); void tracePinnedAtoms(JSTracer* trc, const AutoAccessAtomsZone& access); diff --git a/js/src/vm/JSAtom.cpp b/js/src/vm/JSAtom.cpp index 295a9f0f1bcc..252f13128b98 100644 --- a/js/src/vm/JSAtom.cpp +++ b/js/src/vm/JSAtom.cpp @@ -60,9 +60,10 @@ struct js::AtomHasher::Lookup { union { const JS::Latin1Char* latin1Chars; const char16_t* twoByteChars; + LittleEndianChars littleEndianChars; const char* utf8Bytes; }; - enum { TwoByteChar, Latin1, UTF8, WTF8 } type; + enum { TwoByteChar, LittleEndianTwoByte, Latin1, UTF8, WTF8 } type; size_t length; size_t byteLength; const JSAtom* atom; /* Optional. */ @@ -107,6 +108,13 @@ struct js::AtomHasher::Lookup { MOZ_ASSERT(mozilla::HashString(twoByteChars, length) == hash); } } + + MOZ_ALWAYS_INLINE Lookup(LittleEndianChars chars, size_t length) + : littleEndianChars(chars), + type(LittleEndianTwoByte), + length(length), + atom(nullptr), + hash(mozilla::HashStringKnownLength(chars, length)) {} }; inline HashNumber js::AtomHasher::hash(const Lookup& l) { return l.hash; } @@ -121,6 +129,15 @@ MOZ_ALWAYS_INLINE bool js::AtomHasher::match(const AtomStateEntry& entry, return false; } + auto EqualsLittleEndianChars = [&lookup](auto keyChars) { + for (size_t i = 0, len = lookup.length; i < len; i++) { + if (keyChars[i] != lookup.littleEndianChars[i]) { + return false; + } + } + return true; + }; + if (key->hasLatin1Chars()) { const Latin1Char* keyChars = key->latin1Chars(lookup.nogc); switch (lookup.type) { @@ -128,6 +145,8 @@ MOZ_ALWAYS_INLINE bool js::AtomHasher::match(const AtomStateEntry& entry, return mozilla::ArrayEqual(keyChars, lookup.latin1Chars, lookup.length); case Lookup::TwoByteChar: return EqualChars(keyChars, lookup.twoByteChars, lookup.length); + case Lookup::LittleEndianTwoByte: + return EqualsLittleEndianChars(keyChars); case Lookup::UTF8: { JS::UTF8Chars utf8(lookup.utf8Bytes, lookup.byteLength); return UTF8OrWTF8EqualsChars(utf8, keyChars); @@ -145,6 +164,8 @@ MOZ_ALWAYS_INLINE bool js::AtomHasher::match(const AtomStateEntry& entry, return EqualChars(lookup.latin1Chars, keyChars, lookup.length); case Lookup::TwoByteChar: return mozilla::ArrayEqual(keyChars, lookup.twoByteChars, lookup.length); + case Lookup::LittleEndianTwoByte: + return EqualsLittleEndianChars(keyChars); case Lookup::UTF8: { JS::UTF8Chars utf8(lookup.utf8Bytes, lookup.byteLength); return UTF8OrWTF8EqualsChars(utf8, keyChars); @@ -607,42 +628,39 @@ bool JSRuntime::initMainAtomsTables(JSContext* cx) { return atoms_ && atoms_->init(); } -template -MOZ_NEVER_INLINE static JSAtom* PermanentlyAtomizeAndCopyChars( - JSContext* cx, Maybe& zonePtr, const CharT* tbchars, - size_t length, const Maybe& indexValue, - const AtomHasher::Lookup& lookup); +template +static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyCharsFromLookup( + JSContext* cx, Chars chars, size_t length, const AtomHasher::Lookup& lookup, + PinningBehavior pin, const Maybe& indexValue); -template -MOZ_ALWAYS_INLINE static JSAtom* AllocateNewAtom( - JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin, - const Maybe& indexValue, const AtomHasher::Lookup& lookup); - -template -MOZ_ALWAYS_INLINE static JSAtom* AtomizeAndCopyCharsFromLookup( - JSContext* cx, const CharT* tbchars, size_t length, +template ::value>::type> +static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyCharsFromLookup( + JSContext* cx, CharT* chars, size_t length, const AtomHasher::Lookup& lookup, PinningBehavior pin, - const Maybe& indexValue); - -/* |tbchars| must not point into an inline or short string. */ -template -MOZ_ALWAYS_INLINE static JSAtom* AtomizeAndCopyChars( - JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin, const Maybe& indexValue) { - if (JSAtom* s = cx->staticStrings().lookup(tbchars, length)) { - return s; - } - - AtomHasher::Lookup lookup(tbchars, length); - return AtomizeAndCopyCharsFromLookup(cx, tbchars, length, lookup, pin, - indexValue); + return AtomizeAndCopyCharsFromLookup(cx, const_cast(chars), + length, lookup, pin, indexValue); } -template -MOZ_ALWAYS_INLINE static JSAtom* AtomizeAndCopyCharsFromLookup( - JSContext* cx, const CharT* tbchars, size_t length, - const AtomHasher::Lookup& lookup, PinningBehavior pin, - const Maybe& indexValue) { +template +static MOZ_NEVER_INLINE JSAtom* PermanentlyAtomizeAndCopyChars( + JSContext* cx, Maybe& zonePtr, Chars chars, size_t length, + const Maybe& indexValue, const AtomHasher::Lookup& lookup); + +template ::value>::type> +static JSAtom* PermanentlyAtomizeAndCopyChars( + JSContext* cx, Maybe& zonePtr, CharT* chars, size_t length, + const Maybe& indexValue, const AtomHasher::Lookup& lookup) { + return PermanentlyAtomizeAndCopyChars( + cx, zonePtr, const_cast(chars), length, indexValue, lookup); +} + +template +static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyCharsFromLookup( + JSContext* cx, Chars chars, size_t length, const AtomHasher::Lookup& lookup, + PinningBehavior pin, const Maybe& indexValue) { // Try the per-Zone cache first. If we find the atom there we can avoid the // atoms lock, the markAtom call, and the multiple HashSet lookups below. // We don't use the per-Zone cache if we want a pinned atom: handling that @@ -665,7 +683,7 @@ MOZ_ALWAYS_INLINE static JSAtom* AtomizeAndCopyCharsFromLookup( // atoms table is being created. In this case all atoms created are added to // the permanent atoms table. if (!cx->permanentAtomsPopulated()) { - return PermanentlyAtomizeAndCopyChars(cx, zonePtr, tbchars, length, + return PermanentlyAtomizeAndCopyChars(cx, zonePtr, chars, length, indexValue, lookup); } @@ -687,7 +705,7 @@ MOZ_ALWAYS_INLINE static JSAtom* AtomizeAndCopyCharsFromLookup( return nullptr; } - JSAtom* atom = cx->atoms().atomizeAndCopyChars(cx, tbchars, length, pin, + JSAtom* atom = cx->atoms().atomizeAndCopyChars(cx, chars, length, pin, indexValue, lookup); if (!atom) { return nullptr; @@ -707,9 +725,23 @@ MOZ_ALWAYS_INLINE static JSAtom* AtomizeAndCopyCharsFromLookup( return atom; } -template +template +static MOZ_ALWAYS_INLINE JSAtom* AllocateNewAtom( + JSContext* cx, Chars chars, size_t length, PinningBehavior pin, + const Maybe& indexValue, const AtomHasher::Lookup& lookup); + +template ::value>::type> +static MOZ_ALWAYS_INLINE JSAtom* AllocateNewAtom( + JSContext* cx, CharT* chars, size_t length, PinningBehavior pin, + const Maybe& indexValue, const AtomHasher::Lookup& lookup) { + return AllocateNewAtom(cx, const_cast(chars), length, pin, + indexValue, lookup); +} + +template MOZ_ALWAYS_INLINE JSAtom* AtomsTable::atomizeAndCopyChars( - JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin, + JSContext* cx, Chars chars, size_t length, PinningBehavior pin, const Maybe& indexValue, const AtomHasher::Lookup& lookup) { Partition& part = *partitions[getPartitionIndex(lookup)]; AutoLock lock(cx->runtime(), part.lock); @@ -746,7 +778,7 @@ MOZ_ALWAYS_INLINE JSAtom* AtomsTable::atomizeAndCopyChars( return atom; } - JSAtom* atom = AllocateNewAtom(cx, tbchars, length, pin, indexValue, lookup); + JSAtom* atom = AllocateNewAtom(cx, chars, length, pin, indexValue, lookup); if (!atom) { return nullptr; } @@ -764,19 +796,24 @@ MOZ_ALWAYS_INLINE JSAtom* AtomsTable::atomizeAndCopyChars( return atom; } -template JSAtom* AtomizeAndCopyChars(JSContext* cx, const char16_t* tbchars, - size_t length, PinningBehavior pin, - const Maybe& indexValue); - -template JSAtom* AtomizeAndCopyChars(JSContext* cx, const Latin1Char* tbchars, - size_t length, PinningBehavior pin, - const Maybe& indexValue); - +/* |chars| must not point into an inline or short string. */ template -MOZ_NEVER_INLINE static JSAtom* PermanentlyAtomizeAndCopyChars( - JSContext* cx, Maybe& zonePtr, const CharT* tbchars, - size_t length, const Maybe& indexValue, - const AtomHasher::Lookup& lookup) { +static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyChars( + JSContext* cx, const CharT* chars, size_t length, PinningBehavior pin, + const Maybe& indexValue) { + if (JSAtom* s = cx->staticStrings().lookup(chars, length)) { + return s; + } + + AtomHasher::Lookup lookup(chars, length); + return AtomizeAndCopyCharsFromLookup(cx, chars, length, lookup, pin, + indexValue); +} + +template +static MOZ_NEVER_INLINE JSAtom* PermanentlyAtomizeAndCopyChars( + JSContext* cx, Maybe& zonePtr, Chars chars, size_t length, + const Maybe& indexValue, const AtomHasher::Lookup& lookup) { MOZ_ASSERT(!cx->permanentAtomsPopulated()); MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); @@ -788,7 +825,7 @@ MOZ_NEVER_INLINE static JSAtom* PermanentlyAtomizeAndCopyChars( } JSAtom* atom = - AllocateNewAtom(cx, tbchars, length, DoNotPinAtom, indexValue, lookup); + AllocateNewAtom(cx, chars, length, DoNotPinAtom, indexValue, lookup); if (!atom) { return nullptr; } @@ -825,13 +862,20 @@ struct AtomizeUTF8OrWTF8CharsWrapper { // MakeFlatStringForAtomization has 4 variants. // This is used by Latin1Char and char16_t. template -MOZ_ALWAYS_INLINE static JSFlatString* MakeFlatStringForAtomization( - JSContext* cx, const CharT* tbchars, size_t length) { - return NewStringCopyN(cx, tbchars, length); +static MOZ_ALWAYS_INLINE JSFlatString* MakeFlatStringForAtomization( + JSContext* cx, const CharT* chars, size_t length) { + return NewStringCopyN(cx, chars, length); +} + +// MakeFlatStringForAtomization has one further variant -- a non-template +// overload accepting LittleEndianChars. +static MOZ_ALWAYS_INLINE JSFlatString* MakeFlatStringForAtomization( + JSContext* cx, LittleEndianChars chars, size_t length) { + return NewStringFromLittleEndianNoGC(cx, chars, length); } template -MOZ_ALWAYS_INLINE static JSFlatString* MakeUTF8AtomHelper(JSContext* cx, +static MOZ_ALWAYS_INLINE JSFlatString* MakeUTF8AtomHelper(JSContext* cx, const WrapperT* chars, size_t length) { if (JSInlineString::lengthFits(length)) { @@ -870,11 +914,9 @@ MOZ_ALWAYS_INLINE static JSFlatString* MakeUTF8AtomHelper(JSContext* cx, // Another 2 variants of MakeFlatStringForAtomization. // This is used by AtomizeUTF8OrWTF8CharsWrapper with UTF8Chars or WTF8Chars. template -MOZ_ALWAYS_INLINE - /* static */ JSFlatString* - MakeFlatStringForAtomization( - JSContext* cx, const AtomizeUTF8OrWTF8CharsWrapper* chars, - size_t length) { +/* static */ MOZ_ALWAYS_INLINE JSFlatString* MakeFlatStringForAtomization( + JSContext* cx, const AtomizeUTF8OrWTF8CharsWrapper* chars, + size_t length) { if (length == 0) { return cx->emptyString(); } @@ -885,13 +927,13 @@ MOZ_ALWAYS_INLINE return MakeUTF8AtomHelper(cx, chars, length); } -template -MOZ_ALWAYS_INLINE static JSAtom* AllocateNewAtom( - JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin, +template +static MOZ_ALWAYS_INLINE JSAtom* AllocateNewAtom( + JSContext* cx, Chars chars, size_t length, PinningBehavior pin, const Maybe& indexValue, const AtomHasher::Lookup& lookup) { AutoAllocInAtomsZone ac(cx); - JSFlatString* flat = MakeFlatStringForAtomization(cx, tbchars, length); + JSFlatString* flat = MakeFlatStringForAtomization(cx, chars, length); if (!flat) { // Grudgingly forgo last-ditch GC. The alternative would be to release // the lock, manually GC here, and retry from the top. If you fix this, @@ -1126,6 +1168,22 @@ template JSAtom* js::ToAtom(JSContext* cx, HandleValue v); template JSAtom* js::ToAtom(JSContext* cx, const Value& v); +static JSAtom* AtomizeLittleEndianTwoByteChars(JSContext* cx, + const uint8_t* leTwoByte, + size_t length) { + CHECK_THREAD(cx); + + LittleEndianChars chars(leTwoByte); + + if (JSAtom* s = cx->staticStrings().lookup(chars, length)) { + return s; + } + + AtomHasher::Lookup lookup(chars, length); + return AtomizeAndCopyCharsFromLookup(cx, chars, length, lookup, DoNotPinAtom, + Nothing()); +} + template XDRResult js::XDRAtom(XDRState* xdr, MutableHandleAtom atomp) { bool latin1 = false; @@ -1170,51 +1228,13 @@ XDRResult js::XDRAtom(XDRState* xdr, MutableHandleAtom atomp) { } atom = AtomizeChars(cx, chars, length); } else { -#if MOZ_LITTLE_ENDIAN - /* Directly access the little endian chars in the XDR buffer. */ - const char16_t* chars = nullptr; + const uint8_t* twoByteCharsLE = nullptr; if (length) { - // In the |mode == XDR_ENCODE| case above, when |nchars > 0|, - // |XDRState::codeChars(char16_t*, size_t nchars)| will align the - // buffer. This code never calls that function, but it must act - // *as if* it had, so we must align manually here. - MOZ_TRY(xdr->codeAlign(sizeof(char16_t))); - - const uint8_t* ptr; size_t nbyte = length * sizeof(char16_t); - MOZ_TRY(xdr->peekData(&ptr, nbyte)); - MOZ_ASSERT(reinterpret_cast(ptr) % sizeof(char16_t) == 0, - "non-aligned buffer during JSAtom decoding"); - chars = reinterpret_cast(ptr); - } - atom = AtomizeChars(cx, chars, length); -#else - /* - * We must copy chars to a temporary buffer to convert between little and - * big endian data. - */ - char16_t* chars; - char16_t stackChars[256]; - UniqueTwoByteChars heapChars; - if (length <= ArrayLength(stackChars)) { - chars = stackChars; - } else { - /* - * This is very uncommon. Don't use the tempLifoAlloc arena for this as - * most allocations here will be bigger than tempLifoAlloc's default - * chunk size. - */ - heapChars.reset(cx->pod_malloc(length)); - if (!heapChars) { - return xdr->fail(JS::TranscodeResult_Throw); - } - - chars = heapChars.get(); + MOZ_TRY(xdr->peekData(&twoByteCharsLE, nbyte)); } - MOZ_TRY(xdr->codeChars(chars, length)); - atom = AtomizeChars(cx, chars, length); -#endif /* !MOZ_LITTLE_ENDIAN */ + atom = AtomizeLittleEndianTwoByteChars(cx, twoByteCharsLE, length); } if (!atom) { diff --git a/js/src/vm/StringType-inl.h b/js/src/vm/StringType-inl.h index 369daa038efa..c707e8ec4522 100644 --- a/js/src/vm/StringType-inl.h +++ b/js/src/vm/StringType-inl.h @@ -86,9 +86,10 @@ static MOZ_ALWAYS_INLINE JSInlineString* NewInlineString( return s; } -template -static MOZ_ALWAYS_INLINE JSFlatString* TryEmptyOrStaticString( - JSContext* cx, const CharT* chars, size_t n) { +template +static MOZ_ALWAYS_INLINE JSFlatString* TryEmptyOrStaticString(JSContext* cx, + Chars chars, + size_t n) { // Measurements on popular websites indicate empty strings are pretty common // and most strings with length 1 or 2 are in the StaticStrings table. For // length 3 strings that's only about 1%, so we check n <= 2. @@ -105,6 +106,14 @@ static MOZ_ALWAYS_INLINE JSFlatString* TryEmptyOrStaticString( return nullptr; } +template ::value>::type> +static MOZ_ALWAYS_INLINE JSFlatString* TryEmptyOrStaticString(JSContext* cx, + CharT* chars, + size_t n) { + return TryEmptyOrStaticString(cx, const_cast(chars), n); +} + } /* namespace js */ MOZ_ALWAYS_INLINE bool JSString::validateLength(JSContext* maybecx, diff --git a/js/src/vm/StringType.cpp b/js/src/vm/StringType.cpp index 330b72ce883d..25ce08f243af 100644 --- a/js/src/vm/StringType.cpp +++ b/js/src/vm/StringType.cpp @@ -1539,6 +1539,16 @@ static bool CanStoreCharsAsLatin1(const Latin1Char* s, size_t length) { MOZ_CRASH("Shouldn't be called for Latin1 chars"); } +static bool CanStoreCharsAsLatin1(LittleEndianChars chars, size_t length) { + for (size_t i = 0; i < length; i++) { + if (chars[i] > JSString::MAX_LATIN1_CHAR) { + return false; + } + } + + return true; +} + template static MOZ_ALWAYS_INLINE JSInlineString* NewInlineStringDeflated( JSContext* cx, mozilla::Range chars) { @@ -1589,6 +1599,38 @@ static JSFlatString* NewStringDeflated(JSContext* cx, const Latin1Char* s, MOZ_CRASH("Shouldn't be called for Latin1 chars"); } +static JSFlatString* NewStringDeflatedFromLittleEndianNoGC( + JSContext* cx, LittleEndianChars chars, size_t length) { + MOZ_ASSERT(CanStoreCharsAsLatin1(chars, length)); + + if (JSInlineString::lengthFits(length)) { + Latin1Char* storage; + JSInlineString* str = AllocateInlineString(cx, length, &storage); + if (!str) { + return nullptr; + } + + FillFromCompatibleAndTerminate(storage, chars, length); + return str; + } + + auto news = cx->make_pod_array(length + 1); + if (!news) { + cx->recoverFromOutOfMemory(); + return nullptr; + } + + FillFromCompatibleAndTerminate(news.get(), chars, length); + + JSFlatString* str = JSFlatString::new_(cx, news.get(), length); + if (!str) { + return nullptr; + } + + mozilla::Unused << news.release(); + return str; +} + template JSFlatString* js::NewStringDontDeflate(JSContext* cx, CharT* chars, size_t length) { @@ -1756,6 +1798,36 @@ template JSFlatString* NewStringCopyNDontDeflate(JSContext* cx, const Latin1Char* s, size_t n); +static JSFlatString* NewUndeflatedStringFromLittleEndianNoGC( + JSContext* cx, LittleEndianChars chars, size_t length) { + if (JSInlineString::lengthFits(length)) { + char16_t* storage; + JSInlineString* str = AllocateInlineString(cx, length, &storage); + if (!str) { + return nullptr; + } + + FillAndTerminate(storage, chars, length); + return str; + } + + auto news = cx->make_pod_array(length + 1); + if (!news) { + cx->recoverFromOutOfMemory(); + return nullptr; + } + + FillAndTerminate(news.get(), chars, length); + + if (JSFlatString* str = JSFlatString::new_(cx, news.get(), length)) { + // Successful JSFlatString::new_ took ownership of |news.release()|. + mozilla::Unused << news.release(); + return str; + } + + return nullptr; +} + JSFlatString* NewLatin1StringZ(JSContext* cx, UniqueChars chars) { size_t length = strlen(chars.get()); UniqueLatin1Chars latin1(reinterpret_cast(chars.release())); @@ -1783,6 +1855,20 @@ template JSFlatString* NewStringCopyN(JSContext* cx, const Latin1Char* s, template JSFlatString* NewStringCopyN(JSContext* cx, const Latin1Char* s, size_t n); +JSFlatString* NewStringFromLittleEndianNoGC(JSContext* cx, + LittleEndianChars chars, + size_t length) { + if (JSFlatString* str = TryEmptyOrStaticString(cx, chars, length)) { + return str; + } + + if (CanStoreCharsAsLatin1(chars, length)) { + return NewStringDeflatedFromLittleEndianNoGC(cx, chars, length); + } + + return NewUndeflatedStringFromLittleEndianNoGC(cx, chars, length); +} + template JSFlatString* NewStringCopyUTF8N(JSContext* cx, const JS::UTF8Chars utf8) { JS::SmallestEncoding encoding = JS::FindSmallestEncoding(utf8); diff --git a/js/src/vm/StringType.h b/js/src/vm/StringType.h index 6f367d5da3ac..e04a06405ccf 100644 --- a/js/src/vm/StringType.h +++ b/js/src/vm/StringType.h @@ -11,6 +11,8 @@ #include "mozilla/Range.h" #include "mozilla/TextUtils.h" +#include // std::is_same + #include "jsapi.h" #include "jsfriendapi.h" @@ -1331,6 +1333,24 @@ MOZ_ALWAYS_INLINE JSAtom* JSFlatString::morphAtomizedStringIntoPermanentAtom( namespace js { +/** + * An indexable characters class exposing unaligned, little-endian encoded + * char16_t data. + */ +class LittleEndianChars { + public: + explicit constexpr LittleEndianChars(const uint8_t* leTwoByte) + : current(leTwoByte) {} + + constexpr char16_t operator[](size_t index) const { + size_t offset = index * sizeof(char16_t); + return (current[offset + 1] << 8) | current[offset]; + } + + private: + const uint8_t* current; +}; + class StaticStrings { private: /* Bigger chars cannot be in a length-2 string. */ @@ -1382,8 +1402,14 @@ class StaticStrings { static bool isStatic(JSAtom* atom); /* Return null if no static atom exists for the given (chars, length). */ - template - MOZ_ALWAYS_INLINE JSAtom* lookup(const CharT* chars, size_t length) { + template + MOZ_ALWAYS_INLINE JSAtom* lookup(Chars chars, size_t length) { + static_assert(std::is_same::value || + std::is_same::value || + std::is_same::value, + "for understandability, |chars| must be one of a few " + "identified types"); + switch (length) { case 1: { char16_t c = chars[0]; @@ -1423,6 +1449,19 @@ class StaticStrings { return nullptr; } + template ::value || + std::is_same::value || + !std::is_const::value>::type> + MOZ_ALWAYS_INLINE JSAtom* lookup(CharT* chars, size_t length) { + // Collapse calls for |char*| or |const char*| into |const unsigned char*| + // to avoid excess instantiations. Collapse the remaining |CharT*| to + // |const CharT*| for the same reason. + using UnsignedCharT = typename std::make_unsigned::type; + UnsignedCharT* unsignedChars = reinterpret_cast(chars); + return lookup(const_cast(unsignedChars), length); + } + private: typedef uint8_t SmallChar; static const SmallChar INVALID_SMALL_CHAR = -1; @@ -1560,6 +1599,13 @@ JSString* NewMaybeExternalString(JSContext* cx, const char16_t* s, size_t n, const JSStringFinalizer* fin, bool* allocatedExternal); +/** + * Allocate a new string consisting of |chars[0..length]| characters. + */ +extern JSFlatString* NewStringFromLittleEndianNoGC(JSContext* cx, + LittleEndianChars chars, + size_t length); + JS_STATIC_ASSERT(sizeof(HashNumber) == 4); template diff --git a/js/src/vm/Xdr.cpp b/js/src/vm/Xdr.cpp index 0753203101c4..cef583e62174 100644 --- a/js/src/vm/Xdr.cpp +++ b/js/src/vm/Xdr.cpp @@ -86,21 +86,22 @@ XDRResult XDRState::codeChars(char16_t* chars, size_t nchars) { return Ok(); } - // Align the buffer to avoid unaligned loads. - MOZ_TRY(codeAlign(sizeof(char16_t))); - size_t nbytes = nchars * sizeof(char16_t); if (mode == XDR_ENCODE) { uint8_t* ptr = buf.write(nbytes); if (!ptr) { return fail(JS::TranscodeResult_Throw); } + + // |mozilla::NativeEndian| correctly handles writing into unaligned |ptr|. mozilla::NativeEndian::copyAndSwapToLittleEndian(ptr, chars, nchars); } else { const uint8_t* ptr = buf.read(nbytes); if (!ptr) { return fail(JS::TranscodeResult_Failure_BadDecode); } + + // |mozilla::NativeEndian| correctly handles reading from unaligned |ptr|. mozilla::NativeEndian::copyAndSwapFromLittleEndian(chars, ptr, nchars); } return Ok(); diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 57bb91cd7e7a..ce07bdc03148 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -484,8 +484,6 @@ class XDRState : public XDRCoderBase { XDRResult codeChars(JS::Latin1Char* chars, size_t nchars); XDRResult codeChars(mozilla::Utf8Unit* units, size_t nchars); - // If |nchars > 0|, this calls |codeAlign(sizeof(char16_t))| so callers - // don't have to. XDRResult codeChars(char16_t* chars, size_t nchars); XDRResult codeFunction(JS::MutableHandleFunction objp, diff --git a/js/xpconnect/loader/ScriptPreloader.cpp b/js/xpconnect/loader/ScriptPreloader.cpp index ad31e44462b5..b80cc725b830 100644 --- a/js/xpconnect/loader/ScriptPreloader.cpp +++ b/js/xpconnect/loader/ScriptPreloader.cpp @@ -60,15 +60,6 @@ using namespace mozilla::loader; ProcessType ScriptPreloader::sProcessType; -// This type correspond to js::vm::XDRAlignment type, which is used as a size -// reference for alignment of XDR buffers. -using XDRAlign = uint16_t; -static const uint8_t sAlignPadding[sizeof(XDRAlign)] = {0, 0}; - -static inline size_t ComputeByteAlignment(size_t bytes, size_t align) { - return (align - (bytes % align)) % align; -} - nsresult ScriptPreloader::CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) { MOZ_COLLECT_REPORT( @@ -540,8 +531,6 @@ Result ScriptPreloader::InitCacheInternal( } auto data = mCacheData.get(); - uint8_t* start = data.get(); - MOZ_ASSERT(reinterpret_cast(start) % sizeof(XDRAlign) == 0); auto end = data + size; if (memcmp(MAGIC, data.get(), sizeof(MAGIC))) { @@ -566,10 +555,6 @@ Result ScriptPreloader::InitCacheInternal( InputBuffer buf(header); - size_t len = data.get() - start; - size_t alignLen = ComputeByteAlignment(len, sizeof(XDRAlign)); - data += alignLen; - size_t offset = 0; while (!buf.finished()) { auto script = MakeUnique(*this, buf); @@ -587,9 +572,6 @@ Result ScriptPreloader::InitCacheInternal( } offset += script->mSize; - MOZ_ASSERT(reinterpret_cast(scriptData.get()) % - sizeof(XDRAlign) == - 0); script->mXDRRange.emplace(scriptData, scriptData + script->mSize); // Don't pre-decode the script unless it was used in this process type @@ -739,7 +721,6 @@ Result ScriptPreloader::WriteCache() { OutputBuffer buf; size_t offset = 0; for (auto script : scripts) { - MOZ_ASSERT(offset % sizeof(XDRAlign) == 0); script->mOffset = offset; script->Code(buf); @@ -749,22 +730,11 @@ Result ScriptPreloader::WriteCache() { uint8_t headerSize[4]; LittleEndian::writeUint32(headerSize, buf.cursor()); - size_t len = 0; MOZ_TRY(Write(fd, MAGIC, sizeof(MAGIC))); - len += sizeof(MAGIC); MOZ_TRY(Write(fd, headerSize, sizeof(headerSize))); - len += sizeof(headerSize); MOZ_TRY(Write(fd, buf.Get(), buf.cursor())); - len += buf.cursor(); - size_t alignLen = ComputeByteAlignment(len, sizeof(XDRAlign)); - if (alignLen) { - MOZ_TRY(Write(fd, sAlignPadding, alignLen)); - len += alignLen; - } for (auto script : scripts) { - MOZ_ASSERT(script->mSize % sizeof(XDRAlign) == 0); MOZ_TRY(Write(fd, script->Range().begin().get(), script->mSize)); - len += script->mSize; if (script->mScript) { script->FreeData(); diff --git a/mfbt/EndianUtils.h b/mfbt/EndianUtils.h index f823ef9e9db2..8a8a0e4a8452 100644 --- a/mfbt/EndianUtils.h +++ b/mfbt/EndianUtils.h @@ -404,7 +404,9 @@ class Endian : private EndianUtils { /* * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting - * them to little-endian format if ThisEndian is Big. + * them to little-endian format if ThisEndian is Big. |aSrc| as a typed + * pointer must be aligned; |aDest| need not be. + * * As with memcpy, |aDest| and |aSrc| must not overlap. */ template @@ -431,7 +433,9 @@ class Endian : private EndianUtils { /* * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting - * them to big-endian format if ThisEndian is Little. + * them to big-endian format if ThisEndian is Little. |aSrc| as a typed + * pointer must be aligned; |aDest| need not be. + * * As with memcpy, |aDest| and |aSrc| must not overlap. */ template @@ -479,7 +483,9 @@ class Endian : private EndianUtils { /* * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting - * them to little-endian format if ThisEndian is Big. + * them to little-endian format if ThisEndian is Big. |aDest| as a typed + * pointer must be aligned; |aSrc| need not be. + * * As with memcpy, |aDest| and |aSrc| must not overlap. */ template @@ -506,7 +512,9 @@ class Endian : private EndianUtils { /* * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting - * them to big-endian format if ThisEndian is Little. + * them to big-endian format if ThisEndian is Little. |aDest| as a typed + * pointer must be aligned; |aSrc| need not be. + * * As with memcpy, |aDest| and |aSrc| must not overlap. */ template