mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-30 19:17:31 +09:00
Issue #1738 - Part 1: Improve performance of JSON stringify
- Use some pointer voodoo and instead of stringbuffer append() - Use a lookup table instead of char comparisons for chr < 256 - Stop using a Hashtable/MovableCellHasher for JSON CycleDetector
This commit is contained in:
parent
ebf2abb0e3
commit
feab62b009
3 changed files with 111 additions and 90 deletions
181
js/src/json.cpp
181
js/src/json.cpp
|
|
@ -39,78 +39,81 @@ const Class js::JSONClass = {
|
||||||
JSCLASS_HAS_CACHED_PROTO(JSProto_JSON)
|
JSCLASS_HAS_CACHED_PROTO(JSProto_JSON)
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline bool
|
/* ES5 15.12.3 Quote.
|
||||||
IsQuoteSpecialCharacter(char16_t c)
|
* Requires that the destination has enough space allocated for src after escaping
|
||||||
|
* (that is, `2 + 6 * (srcEnd - srcBegin)` characters).
|
||||||
|
*/
|
||||||
|
template <typename SrcCharT, typename DstCharT>
|
||||||
|
static MOZ_ALWAYS_INLINE RangedPtr<DstCharT>
|
||||||
|
InfallibleQuote(RangedPtr<const SrcCharT> srcBegin, RangedPtr<const SrcCharT> srcEnd, RangedPtr<DstCharT> dstPtr)
|
||||||
{
|
{
|
||||||
static_assert('\b' < ' ', "'\\b' must be treated as special below");
|
// Maps characters < 256 to the value that must follow the '\\' in the quoted string.
|
||||||
static_assert('\f' < ' ', "'\\f' must be treated as special below");
|
// Entries with 'u' are handled as \\u00xy, and entries with 0 are not escaped in any way.
|
||||||
static_assert('\n' < ' ', "'\\n' must be treated as special below");
|
// Characters >= 256 are all assumed to be unescaped.
|
||||||
static_assert('\r' < ' ', "'\\r' must be treated as special below");
|
static const Latin1Char escapeLookup[256] = {
|
||||||
static_assert('\t' < ' ', "'\\t' must be treated as special below");
|
'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't',
|
||||||
|
'n', 'u', 'f', 'r', 'u', 'u', 'u', 'u', 'u', 'u',
|
||||||
return c == '"' || c == '\\' || c < ' ';
|
'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u',
|
||||||
}
|
'u', 'u', 0, 0, '\"', 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
/* ES5 15.12.3 Quote. */
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
template <typename CharT>
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
static bool
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
Quote(StringBuffer& sb, JSLinearString* str)
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
{
|
0, 0, '\\', // rest are all zeros
|
||||||
size_t len = str->length();
|
};
|
||||||
|
|
||||||
/* Step 1. */
|
/* Step 1. */
|
||||||
if (!sb.append('"'))
|
*dstPtr++ = '"';
|
||||||
return false;
|
|
||||||
|
|
||||||
/* Step 2. */
|
/* Step 2. */
|
||||||
JS::AutoCheckCannotGC nogc;
|
while (srcBegin != srcEnd) {
|
||||||
const RangedPtr<const CharT> buf(str->chars<CharT>(nogc), len);
|
SrcCharT c = *srcBegin++;
|
||||||
for (size_t i = 0; i < len; ++i) {
|
size_t escapeIndex = c % sizeof(escapeLookup);
|
||||||
/* Batch-append maximal character sequences containing no escapes. */
|
Latin1Char escaped = escapeLookup[escapeIndex];
|
||||||
size_t mark = i;
|
if (MOZ_LIKELY((escapeIndex != size_t(c)) || !escaped)) {
|
||||||
do {
|
*dstPtr++ = c;
|
||||||
if (IsQuoteSpecialCharacter(buf[i]))
|
continue;
|
||||||
break;
|
|
||||||
} while (++i < len);
|
|
||||||
if (i > mark) {
|
|
||||||
if (!sb.appendSubstring(str, mark, i - mark))
|
|
||||||
return false;
|
|
||||||
if (i == len)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
*dstPtr++ = '\\';
|
||||||
char16_t c = buf[i];
|
*dstPtr++ = escaped;
|
||||||
if (c == '"' || c == '\\') {
|
if (escaped == 'u') {
|
||||||
if (!sb.append('\\') || !sb.append(c))
|
|
||||||
return false;
|
|
||||||
} else if (c == '\b' || c == '\f' || c == '\n' || c == '\r' || c == '\t') {
|
|
||||||
char16_t abbrev = (c == '\b')
|
|
||||||
? 'b'
|
|
||||||
: (c == '\f')
|
|
||||||
? 'f'
|
|
||||||
: (c == '\n')
|
|
||||||
? 'n'
|
|
||||||
: (c == '\r')
|
|
||||||
? 'r'
|
|
||||||
: 't';
|
|
||||||
if (!sb.append('\\') || !sb.append(abbrev))
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
MOZ_ASSERT(c < ' ');
|
MOZ_ASSERT(c < ' ');
|
||||||
if (!sb.append("\\u00"))
|
|
||||||
return false;
|
|
||||||
MOZ_ASSERT((c >> 4) < 10);
|
MOZ_ASSERT((c >> 4) < 10);
|
||||||
uint8_t x = c >> 4, y = c % 16;
|
uint8_t x = c >> 4, y = c % 16;
|
||||||
if (!sb.append(Latin1Char('0' + x)) ||
|
*dstPtr++ = '0';
|
||||||
!sb.append(Latin1Char(y < 10 ? '0' + y : 'a' + (y - 10))))
|
*dstPtr++ = '0';
|
||||||
{
|
*dstPtr++ = '0' + x;
|
||||||
return false;
|
*dstPtr++ = y < 10 ? '0' + y : 'a' + (y - 10);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Steps 3-4. */
|
/* Steps 3-4. */
|
||||||
return sb.append('"');
|
*dstPtr++ = '"';
|
||||||
|
return dstPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SrcCharT, typename CharVectorT>
|
||||||
|
static bool
|
||||||
|
Quote(CharVectorT& sb, JSLinearString* str)
|
||||||
|
{
|
||||||
|
// We resize the backing buffer to the maximum size we could possibly need,
|
||||||
|
// write the escaped string into it, and shrink it back to the size we ended
|
||||||
|
// up needing.
|
||||||
|
size_t len = str->length();
|
||||||
|
size_t sbInitialLen = sb.length();
|
||||||
|
if (!sb.growByUninitialized(len * 6 + 2))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
typedef typename CharVectorT::ElementType DstCharT;
|
||||||
|
|
||||||
|
JS::AutoCheckCannotGC nogc;
|
||||||
|
RangedPtr<const SrcCharT> srcBegin{str->chars<SrcCharT>(nogc), len};
|
||||||
|
RangedPtr<DstCharT> dstBegin{sb.begin(), sb.begin(), sb.end()};
|
||||||
|
RangedPtr<DstCharT> dstEnd = InfallibleQuote(srcBegin, srcBegin + len, dstBegin + sbInitialLen);
|
||||||
|
size_t newSize = dstEnd - dstBegin;
|
||||||
|
sb.shrinkTo(newSize);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
|
|
@ -120,14 +123,23 @@ Quote(JSContext* cx, StringBuffer& sb, JSString* str)
|
||||||
if (!linear)
|
if (!linear)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return linear->hasLatin1Chars()
|
// Check if either has non-latin1 before calling ensure, so that the buffer's
|
||||||
? Quote<Latin1Char>(sb, linear)
|
// hasEnsured flag is set if the converstion to twoByte was automatic.
|
||||||
: Quote<char16_t>(sb, linear);
|
if (!sb.isUnderlyingBufferLatin1() || linear->hasTwoByteChars()) {
|
||||||
|
if (!sb.ensureTwoByteChars())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (linear->hasTwoByteChars())
|
||||||
|
return Quote<char16_t>(sb.rawTwoByteBuffer(), linear);
|
||||||
|
|
||||||
|
return sb.isUnderlyingBufferLatin1()
|
||||||
|
? Quote<Latin1Char>(sb.latin1Chars(), linear)
|
||||||
|
: Quote<Latin1Char>(sb.rawTwoByteBuffer(), linear);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using ObjectSet = GCHashSet<JSObject*, MovableCellHasher<JSObject*>, SystemAllocPolicy>;
|
using ObjectVector = GCVector<JSObject*, 8>;
|
||||||
|
|
||||||
class StringifyContext
|
class StringifyContext
|
||||||
{
|
{
|
||||||
|
|
@ -138,7 +150,7 @@ class StringifyContext
|
||||||
: sb(sb),
|
: sb(sb),
|
||||||
gap(gap),
|
gap(gap),
|
||||||
replacer(cx, replacer),
|
replacer(cx, replacer),
|
||||||
stack(cx),
|
stack(cx, ObjectVector(cx)),
|
||||||
propertyList(propertyList),
|
propertyList(propertyList),
|
||||||
depth(0),
|
depth(0),
|
||||||
maybeSafely(maybeSafely)
|
maybeSafely(maybeSafely)
|
||||||
|
|
@ -147,14 +159,10 @@ class StringifyContext
|
||||||
MOZ_ASSERT_IF(maybeSafely, gap.empty());
|
MOZ_ASSERT_IF(maybeSafely, gap.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool init() {
|
|
||||||
return stack.init(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuffer& sb;
|
StringBuffer& sb;
|
||||||
const StringBuffer& gap;
|
const StringBuffer& gap;
|
||||||
RootedObject replacer;
|
RootedObject replacer;
|
||||||
Rooted<ObjectSet> stack;
|
Rooted<ObjectVector> stack;
|
||||||
const AutoIdVector& propertyList;
|
const AutoIdVector& propertyList;
|
||||||
uint32_t depth;
|
uint32_t depth;
|
||||||
bool maybeSafely;
|
bool maybeSafely;
|
||||||
|
|
@ -302,29 +310,34 @@ class CycleDetector
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CycleDetector(StringifyContext* scx, HandleObject obj)
|
CycleDetector(StringifyContext* scx, HandleObject obj)
|
||||||
: stack(&scx->stack), obj_(obj) {
|
: stack_(&scx->stack)
|
||||||
}
|
, obj_(obj)
|
||||||
|
, appended_(false)
|
||||||
|
{}
|
||||||
|
|
||||||
bool foundCycle(JSContext* cx) {
|
MOZ_ALWAYS_INLINE bool foundCycle(JSContext* cx) {
|
||||||
auto addPtr = stack.lookupForAdd(obj_);
|
JSObject* obj = obj_;
|
||||||
if (addPtr) {
|
for (JSObject* obj2 : stack_) {
|
||||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_JSON_CYCLIC_VALUE);
|
if (MOZ_UNLIKELY(obj == obj2)) {
|
||||||
return false;
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_JSON_CYCLIC_VALUE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!stack.add(addPtr, obj_)) {
|
appended_ = stack_.append(obj);
|
||||||
ReportOutOfMemory(cx);
|
return appended_;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~CycleDetector() {
|
~CycleDetector() {
|
||||||
stack.remove(obj_);
|
if (MOZ_LIKELY(appended_)) {
|
||||||
|
MOZ_ASSERT(stack_.back() == obj_);
|
||||||
|
stack_.popBack();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MutableHandle<ObjectSet> stack;
|
MutableHandle<ObjectVector> stack_;
|
||||||
HandleObject obj_;
|
HandleObject obj_;
|
||||||
|
bool appended_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ES5 15.12.3 JO. */
|
/* ES5 15.12.3 JO. */
|
||||||
|
|
@ -747,8 +760,6 @@ js::Stringify(JSContext* cx, MutableHandleValue vp, JSObject* replacer_, const V
|
||||||
/* Step 12. */
|
/* Step 12. */
|
||||||
StringifyContext scx(cx, sb, gap, replacer, propertyList,
|
StringifyContext scx(cx, sb, gap, replacer, propertyList,
|
||||||
stringifyBehavior == StringifyBehavior::RestrictedSafe);
|
stringifyBehavior == StringifyBehavior::RestrictedSafe);
|
||||||
if (!scx.init())
|
|
||||||
return false;
|
|
||||||
if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx))
|
if (!PreprocessValue(cx, wrapper, HandleId(emptyId), vp, &scx))
|
||||||
return false;
|
return false;
|
||||||
if (IsFilteredValue(vp))
|
if (IsFilteredValue(vp))
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,9 @@ assertStringify({'mmm\\mmm':"hmm"}, '{"mmm\\\\mmm":"hmm"}');
|
||||||
assertStringify({'mmm\\mmm\\mmm':"hmm"}, '{"mmm\\\\mmm\\\\mmm":"hmm"}');
|
assertStringify({'mmm\\mmm\\mmm':"hmm"}, '{"mmm\\\\mmm\\\\mmm":"hmm"}');
|
||||||
assertStringify({"mm\u000bmm":"hmm"}, '{"mm\\u000bmm":"hmm"}');
|
assertStringify({"mm\u000bmm":"hmm"}, '{"mm\\u000bmm":"hmm"}');
|
||||||
assertStringify({"mm\u0000mm":"hmm"}, '{"mm\\u0000mm":"hmm"}');
|
assertStringify({"mm\u0000mm":"hmm"}, '{"mm\\u0000mm":"hmm"}');
|
||||||
|
assertStringify({"\u0000\u000b":""}, '{"\\u0000\\u000b":""}');
|
||||||
|
assertStringify({"\u000b\ufdfd":"hmm"}, '{"\\u000b\ufdfd":"hmm"}');
|
||||||
|
assertStringify({"\u000b\ufdfd":"h\xfc\ufdfdm"}, '{"\\u000b\ufdfd":"h\xfc\ufdfdm"}');
|
||||||
|
|
||||||
var x = {"free":"variable"};
|
var x = {"free":"variable"};
|
||||||
assertStringify(x, '{"free":"variable"}');
|
assertStringify(x, '{"free":"variable"}');
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,8 @@ class StringBuffer
|
||||||
MOZ_ALWAYS_INLINE bool isLatin1() const { return cb.constructed<Latin1CharBuffer>(); }
|
MOZ_ALWAYS_INLINE bool isLatin1() const { return cb.constructed<Latin1CharBuffer>(); }
|
||||||
MOZ_ALWAYS_INLINE bool isTwoByte() const { return !isLatin1(); }
|
MOZ_ALWAYS_INLINE bool isTwoByte() const { return !isLatin1(); }
|
||||||
|
|
||||||
MOZ_ALWAYS_INLINE Latin1CharBuffer& latin1Chars() { return cb.ref<Latin1CharBuffer>(); }
|
|
||||||
MOZ_ALWAYS_INLINE TwoByteCharBuffer& twoByteChars() { return cb.ref<TwoByteCharBuffer>(); }
|
MOZ_ALWAYS_INLINE TwoByteCharBuffer& twoByteChars() { return cb.ref<TwoByteCharBuffer>(); }
|
||||||
|
|
||||||
MOZ_ALWAYS_INLINE const Latin1CharBuffer& latin1Chars() const {
|
|
||||||
return cb.ref<Latin1CharBuffer>();
|
|
||||||
}
|
|
||||||
MOZ_ALWAYS_INLINE const TwoByteCharBuffer& twoByteChars() const {
|
MOZ_ALWAYS_INLINE const TwoByteCharBuffer& twoByteChars() const {
|
||||||
return cb.ref<TwoByteCharBuffer>();
|
return cb.ref<TwoByteCharBuffer>();
|
||||||
}
|
}
|
||||||
|
|
@ -84,6 +80,12 @@ class StringBuffer
|
||||||
cb.construct<Latin1CharBuffer>(cx);
|
cb.construct<Latin1CharBuffer>(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MOZ_ALWAYS_INLINE Latin1CharBuffer& latin1Chars() { return cb.ref<Latin1CharBuffer>(); }
|
||||||
|
|
||||||
|
MOZ_ALWAYS_INLINE const Latin1CharBuffer& latin1Chars() const {
|
||||||
|
return cb.ref<Latin1CharBuffer>();
|
||||||
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
if (isLatin1())
|
if (isLatin1())
|
||||||
latin1Chars().clear();
|
latin1Chars().clear();
|
||||||
|
|
@ -134,6 +136,11 @@ class StringBuffer
|
||||||
return append(Latin1Char(c));
|
return append(Latin1Char(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TwoByteCharBuffer& rawTwoByteBuffer() {
|
||||||
|
MOZ_ASSERT(hasEnsuredTwoByteChars_);
|
||||||
|
return twoByteChars();
|
||||||
|
}
|
||||||
|
|
||||||
inline MOZ_MUST_USE bool append(const char16_t* begin, const char16_t* end);
|
inline MOZ_MUST_USE bool append(const char16_t* begin, const char16_t* end);
|
||||||
|
|
||||||
MOZ_MUST_USE bool append(const char16_t* chars, size_t len) {
|
MOZ_MUST_USE bool append(const char16_t* chars, size_t len) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue