Skip to content

Commit 26fbddd

Browse files
committed
[JSC] Add fast embedded transition cache for JSON.parse
rdar://188881915 https://bugs.webkit.org/show_bug.cgi?id=325901 Reviewed by Vassili Bykov. We deployed transition cache and single-transition cache for JSON object baking. But still loading a StringImpl from a Structure and comparing it with lexed buffer is costly, largely due to dependent load. This patch introduces layered cache mechanism on the top of that. Now we have new Name cache and PrefixedName cache. Name cache covers quick fast path for single-transition cache, and PrefixedName cache covers transition cache pattern. By recording the actual JSON text content directly into the entry, like `"type":`, lookup directly compares the source's string sequence with this content. Also we cache this into the cache's Entry directly (up to 32 characters) and we do comparison with hard-coded optimized SIMD operation. And still transition cache and single-transition cache covers failed cases, and they populate the result to the front cache to accelerate the next lookup. This removes any constly load from Structure*, and keep lookup sufficiently efficient and improves cache locality significantly. Test: JSTests/stress/json-parse-cached-property-name.js * JSTests/stress/json-parse-cached-property-name.js: Added. (shouldBe): (shouldThrow): (roundTrip): (iteration.const.name.of.names.shouldThrow.JSON.parse.shouldBe.roundTrip.i.shouldBe.roundTrip.JSON.stringify): * Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj: * Source/JavaScriptCore/Sources.txt: * Source/JavaScriptCore/runtime/JSONCache.cpp: Added. (JSC::JSONCache::recordableName): (JSC::JSONCache::NameTable<size>::insert): (JSC::JSONCache::makeText): (JSC::JSONCache::textPrefix): (JSC::JSONCache::addPrefixedName): (JSC::JSONCache::addName): * Source/JavaScriptCore/runtime/JSONCache.h: (JSC::JSONCache::clearStrings): (JSC::JSONCache::reconcileTransitionsAtGCEnd): (JSC::JSONCache::atomStringIndex): (JSC::JSONCache::atomStringSlot): (JSC::JSONCache::stringIndex): Deleted. (JSC::JSONCache::stringSlot): Deleted. * Source/JavaScriptCore/runtime/JSONCacheInlines.h: (JSC::JSONCache::makeIdentifier): (JSC::JSONCache::existingIdentifier): (JSC::JSONCache::makeJSString): (JSC::JSONCache::nameIndex): (JSC::JSONCache::prefixedNameIndex): (JSC::JSONCache::textMatches): (JSC::JSONCache::NameTable<size>::match const): (JSC::JSONCache::findName const): (JSC::JSONCache::visitAggregate): * Source/JavaScriptCore/runtime/LiteralParser.cpp: (JSC::reviverMode>::existingIdentifier): (JSC::reviverMode>::makeJSString): (JSC::reviverMode>::Lexer::skipWhitespaceBeforeKey): (JSC::reviverMode>::parsePrimitiveValue): (JSC::requires): * Source/JavaScriptCore/runtime/LiteralParser.h: (JSC::LiteralParser::Lexer::remaining const): (JSC::LiteralParser::Lexer::advance): Canonical link: https://commits.webkit.org/322567@main
1 parent 0ce091b commit 26fbddd

8 files changed

Lines changed: 424 additions & 42 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
function shouldBe(actual, expected) {
2+
if (actual !== expected)
3+
throw new Error("bad value: " + actual + " expected: " + expected);
4+
}
5+
6+
function shouldThrow(func) {
7+
let threw = false;
8+
try {
9+
func();
10+
} catch (error) {
11+
threw = error instanceof SyntaxError;
12+
}
13+
if (!threw)
14+
throw new Error("did not throw SyntaxError");
15+
}
16+
17+
function roundTrip(text) {
18+
return JSON.stringify(JSON.parse(text));
19+
}
20+
21+
const padding = " ".repeat(40);
22+
const names = [
23+
"a",
24+
"ab",
25+
"lineNumber",
26+
"expressionLocation",
27+
"x".repeat(28),
28+
"y".repeat(29),
29+
"z".repeat(30),
30+
"\u00e9t\u00e9",
31+
];
32+
33+
const iterations = Math.min(testLoopCount, 50);
34+
for (let iteration = 0; iteration < iterations; ++iteration) {
35+
for (const name of names) {
36+
const expected = JSON.stringify({ first: 1, [name]: 2, last: 3 });
37+
const quoted = JSON.stringify(name);
38+
39+
shouldBe(roundTrip(`{"first":1,${quoted}:2,"last":3}${padding}`), expected);
40+
shouldBe(roundTrip(`{"first":1,${quoted}:2,"last":3}`), expected);
41+
shouldBe(roundTrip(`{"first":1,${quoted} :2,"last":3}${padding}`), expected);
42+
shouldBe(roundTrip(`{"first":1, ${quoted}:2,"last":3}${padding}`), expected);
43+
shouldBe(roundTrip(`{"first": 1, ${quoted}: 2, "last": 3}${padding}`), expected);
44+
shouldBe(roundTrip(`{\n "first": 1,\n ${quoted}: 2,\n\t"last": 3\r\n}${padding}`), expected);
45+
shouldBe(roundTrip(`{ "first":1 , ${quoted}:2 ,"last":3 }${padding}`), expected);
46+
shouldBe(JSON.stringify(JSON.parse(`{"first": 1, ${quoted}: 2, "last": 3}${padding}`, (key, value) => value)), expected);
47+
48+
const escaped = quoted.slice(0, 1) + "\\u" + name.charCodeAt(0).toString(16).padStart(4, "0") + quoted.slice(2);
49+
shouldBe(roundTrip(`{"first":1,${escaped}:2,"last":3}${padding}`), expected);
50+
51+
const different = JSON.stringify({ first: 1, [name + "q"]: 2, last: 3 });
52+
shouldBe(roundTrip(`{"first":1,${JSON.stringify(name + "q")}:2,"last":3}${padding}`), different);
53+
if (name.length > 1) {
54+
const shorter = JSON.stringify({ first: 1, [name.slice(0, -1)]: 2, last: 3 });
55+
shouldBe(roundTrip(`{"first":1,${JSON.stringify(name.slice(0, -1))}:2,"last":3}${padding}`), shorter);
56+
}
57+
58+
shouldThrow(() => JSON.parse(`{"first":1,${quoted}2,"last":3}${padding}`));
59+
shouldThrow(() => JSON.parse(`{"first":1,${quoted}:`));
60+
shouldThrow(() => JSON.parse(`{"first":1,${quoted}`));
61+
}
62+
63+
const quoteInName = JSON.stringify({ first: 1, 'a"b': 2 });
64+
shouldBe(roundTrip(`{"first":1,"a\\"b":2}${padding}`), quoteInName);
65+
66+
const backslashInName = JSON.stringify({ first: 1, "a\\b": 2 });
67+
shouldBe(roundTrip(`{"first":1,"a\\\\b":2}${padding}`), backslashInName);
68+
69+
const siblings = ["line", "lineNumber", "linear", "li", "l", "lineNumbers", "column", "columnNumber"];
70+
for (let i = 0; i < siblings.length; ++i) {
71+
const name = siblings[(i + iteration) % siblings.length];
72+
const expected = JSON.stringify({ [name]: i, next: [i] });
73+
shouldBe(roundTrip(`{${JSON.stringify(name)}:${i},"next":[${i}]}${padding}`), expected);
74+
shouldBe(roundTrip(`{${JSON.stringify(name)} :${i},"next":[${i}]}${padding}`), expected);
75+
}
76+
77+
if (iteration % 10 === 0)
78+
gc();
79+
}

‎Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6174,6 +6174,7 @@
61746174
E32F713B281B3C6600AFD21D /* UnifiedSource3-c.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "UnifiedSource3-c.c"; path = "DerivedSources/JavaScriptCore/unified-sources/UnifiedSource3-c.c"; sourceTree = BUILT_PRODUCTS_DIR; };
61756175
E32F713C281B3C6600AFD21D /* UnifiedSource2-c.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "UnifiedSource2-c.c"; path = "DerivedSources/JavaScriptCore/unified-sources/UnifiedSource2-c.c"; sourceTree = BUILT_PRODUCTS_DIR; };
61766176
E32FEA2A27448F3600FF41C1 /* JSONCacheInlines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONCacheInlines.h; sourceTree = "<group>"; };
6177+
DD9F6F27F5F64D0882634574 /* JSONCache.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSONCache.cpp; sourceTree = "<group>"; };
61776178
E32FEA2B27448F3600FF41C1 /* JSONCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONCache.h; sourceTree = "<group>"; };
61786179
E3305FB020B0F78700CEB82B /* InByVariant.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InByVariant.cpp; sourceTree = "<group>"; };
61796180
E3305FB120B0F78800CEB82B /* InByVariant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InByVariant.h; sourceTree = "<group>"; };
@@ -9432,6 +9433,7 @@
94329433
BC22A3980E16E14800AF21C8 /* JSObject.cpp */,
94339434
BC22A3990E16E14800AF21C8 /* JSObject.h */,
94349435
0F93275E1C21EF7F00CF6564 /* JSObjectInlines.h */,
9436+
DD9F6F27F5F64D0882634574 /* JSONCache.cpp */,
94359437
E32FEA2B27448F3600FF41C1 /* JSONCache.h */,
94369438
E32FEA2A27448F3600FF41C1 /* JSONCacheInlines.h */,
94379439
A7F9935E0FD7325100A0B2D0 /* JSONObject.cpp */,

‎Source/JavaScriptCore/Sources.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,7 @@ runtime/JSModuleLoader.cpp
948948
runtime/JSModuleNamespaceObject.cpp
949949
runtime/JSModuleRecord.cpp
950950
runtime/JSNativeStdFunction.cpp
951+
runtime/JSONCache.cpp
951952
runtime/JSONObject.cpp
952953
runtime/JSObject.cpp
953954
runtime/JSOrderedHashTable.cpp
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright (C) 2026 Apple Inc. All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions
6+
* are met:
7+
* 1. Redistributions of source code must retain the above copyright
8+
* notice, this list of conditions and the following disclaimer.
9+
* 2. Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
*
13+
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15+
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17+
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23+
* THE POSSIBILITY OF SUCH DAMAGE.
24+
*/
25+
26+
#include "config.h"
27+
#include "JSONCache.h"
28+
29+
#include "JSONCacheInlines.h"
30+
#include <wtf/StdLibExtras.h>
31+
32+
namespace JSC {
33+
34+
std::span<const Latin1Character> JSONCache::recordableName(Structure* from, Structure* to, PropertyOffset offset)
35+
{
36+
ASSERT_UNUSED(from, !from->isDictionary());
37+
ASSERT(to->transitionKind() == TransitionKind::PropertyAddition);
38+
ASSERT(!to->transitionPropertyAttributes());
39+
ASSERT(to->previousID() == from);
40+
ASSERT_UNUSED(offset, to->transitionOffset() == offset);
41+
SUPPRESS_UNCOUNTED_LOCAL UniquedStringImpl* name = to->transitionPropertyName();
42+
if (!name->is8Bit() || name->length() > maxNameLength)
43+
return { };
44+
// Matching the raw source text is only equivalent to lexing the key when the key needs no escaping.
45+
auto characters = name->span8();
46+
for (auto character : characters) {
47+
if (character < ' ' || character == '"' || character == '\\')
48+
return { };
49+
}
50+
return characters;
51+
}
52+
53+
template<unsigned size>
54+
template<unsigned ways>
55+
void JSONCache::NameTable<size>::insert(unsigned set, Structure* from, Structure* to, PropertyOffset offset, const NameText& text, unsigned textLength)
56+
{
57+
for (unsigned way = ways - 1; way; --way) {
58+
entries[set + way] = entries[set + way - 1];
59+
texts[set + way] = texts[set + way - 1];
60+
}
61+
auto& entry = entries[set];
62+
entry.from.setWithoutWriteBarrier(from);
63+
entry.to.setWithoutWriteBarrier(to);
64+
entry.offset = offset;
65+
entry.textLength = textLength;
66+
entry.toNameIndex = nameIndex(StructureID::encode(to));
67+
texts[set] = text;
68+
}
69+
70+
unsigned JSONCache::makeText(NameText& text, std::span<const Latin1Character> name)
71+
{
72+
text.fill(0);
73+
text[0] = '"';
74+
WTF::copyElements(std::span { text }.subspan(1, name.size()), name);
75+
text[name.size() + 1] = '"';
76+
text[name.size() + 2] = ':';
77+
return name.size() + 3;
78+
}
79+
80+
uint32_t JSONCache::textPrefix(std::span<const Latin1Character> name)
81+
{
82+
std::array<Latin1Character, sizeof(uint32_t) + 2> bytes { };
83+
auto head = name.first(std::min<size_t>(name.size(), sizeof(uint32_t) - 1));
84+
bytes[0] = '"';
85+
WTF::copyElements(std::span { bytes }.subspan(1, head.size()), head);
86+
bytes[head.size() + 1] = '"';
87+
bytes[head.size() + 2] = ':';
88+
return WTF::unalignedLoad<uint32_t>(bytes.data());
89+
}
90+
91+
void JSONCache::addPrefixedName(Structure* from, Structure* to, PropertyOffset offset)
92+
{
93+
SUPPRESS_UNCOUNTED_LOCAL UniquedStringImpl* transitionName = to->transitionPropertyName();
94+
if (!transitionName->is8Bit() || transitionName->length() > maxNameLength)
95+
return;
96+
uint32_t prefix = textPrefix(transitionName->span8());
97+
StructureID toID = StructureID::encode(to);
98+
unsigned set = prefixedNameIndex(StructureID::encode(from), prefix);
99+
for (unsigned way = 0; way < prefixedNameWays; ++way) {
100+
if (m_prefixedNames.entries[set + way].to.value() == toID)
101+
return;
102+
}
103+
auto name = recordableName(from, to, offset);
104+
if (name.empty())
105+
return;
106+
NameText text;
107+
unsigned textLength = makeText(text, name);
108+
ASSERT(WTF::unalignedLoad<uint32_t>(text.data()) == prefix);
109+
m_prefixedNames.insert<prefixedNameWays>(set, from, to, offset, text, textLength);
110+
}
111+
112+
void JSONCache::addName(Structure* from, Structure* to, PropertyOffset offset)
113+
{
114+
StructureID fromID = StructureID::encode(from);
115+
unsigned set = nameIndex(fromID);
116+
if (m_names.entries[set].from.value() == fromID)
117+
return;
118+
auto name = recordableName(from, to, offset);
119+
if (name.empty())
120+
return;
121+
NameText text;
122+
unsigned textLength = makeText(text, name);
123+
m_names.insert<1>(set, from, to, offset, text, textLength);
124+
}
125+
126+
} // namespace JSC

‎Source/JavaScriptCore/runtime/JSONCache.h‎

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
#pragma once
2727

28+
#include <JavaScriptCore/PropertyOffset.h>
2829
#include <JavaScriptCore/WriteBarrier.h>
2930
#include <array>
3031
#include <span>
@@ -39,11 +40,12 @@ class JSString;
3940
class Structure;
4041
class VM;
4142

42-
// Caches for JSON.parse: atoms and JSStrings for short keys and values, and a map from (Structure, property
43+
// Caches for JSON.parse: atoms and JSStrings for short keys and values, a map from (Structure, property
4344
// name) to the Structure's existing property addition transition, so that a key seen before skips both the
44-
// atom lookup and the transition table lookup.
45+
// atom lookup and the transition table lookup, and name tables that let such a key be matched directly
46+
// against the source text.
4547
//
46-
// Transition entries are stored without write barriers and are not otherwise marked. While a parse is running,
48+
// Transition and name entries are stored without write barriers and are not otherwise marked. While a parse is running,
4749
// visitAggregate() marks every entry; otherwise the transitions are cleared at the end of every GC, before
4850
// anything is swept. Both decisions are made with the mutator stopped: the final marking fixpoint runs every
4951
// constraint, and the collector goes from it to the end phase without resuming the mutator, so the two see
@@ -53,14 +55,15 @@ class JSONCache {
5355
WTF_MAKE_TZONE_ALLOCATED(JSONCache);
5456
public:
5557
static constexpr auto maxStringLengthForCache = 27;
58+
static constexpr auto atomStringCapacity = 512;
5659
static constexpr auto stringCapacity = 512;
5760

58-
struct Slot {
61+
struct AtomStringSlot {
5962
char16_t m_buffer[maxStringLengthForCache] { };
6063
char16_t m_length { 0 };
6164
RefPtr<AtomStringImpl> m_impl;
6265
};
63-
static_assert(sizeof(Slot) <= 64);
66+
static_assert(sizeof(AtomStringSlot) <= 64);
6467

6568
static constexpr unsigned maxLongStringLength = 256;
6669
static constexpr unsigned longStringCapacityLog2 = 7;
@@ -75,6 +78,21 @@ class JSONCache {
7578
};
7679
static_assert(sizeof(TransitionEntry) == 8);
7780

81+
static constexpr unsigned nameEntrySize = 256;
82+
83+
// A Structure's property addition transition, with the source text that names it: the quoted
84+
// key and the colon after it, as JSON.stringify writes them. The next object of the same shape can
85+
// then match its key with two vector comparisons against the source.
86+
struct NameEntry {
87+
WriteBarrierStructureID from;
88+
WriteBarrierStructureID to;
89+
PropertyOffset offset;
90+
uint8_t textLength;
91+
uint16_t toNameIndex;
92+
};
93+
static_assert(sizeof(NameEntry) == 16);
94+
static_assert(nameEntrySize <= std::numeric_limits<uint16_t>::max());
95+
7896
class ParsingScope {
7997
WTF_MAKE_NONCOPYABLE(ParsingScope);
8098
public:
@@ -109,9 +127,20 @@ class JSONCache {
109127
template<typename CharacterType> ALWAYS_INLINE Structure* getTransition(Structure* from, std::span<const CharacterType> name);
110128
template<typename CharacterType> ALWAYS_INLINE void addTransition(Structure* from, Structure* to, std::span<const CharacterType> name);
111129

130+
static ALWAYS_INLINE unsigned nameIndex(StructureID);
131+
// Returns the entry whose text the source starts with, for a transition from the Structure whose
132+
// nameIndex() is index. The caller consumes entry->textLength characters of the source.
133+
ALWAYS_INLINE const NameEntry* findName(StructureID from, unsigned index, std::span<const Latin1Character> source) const LIFETIME_BOUND;
134+
135+
// addName() serves Structures with a single transition. addPrefixedName() serves the others: the first
136+
// four bytes of the key's source text are part of its index, so that a Structure with several transitions
137+
// can have an entry for each of them.
138+
void addName(Structure* from, Structure* to, PropertyOffset);
139+
void addPrefixedName(Structure* from, Structure* to, PropertyOffset);
140+
112141
ALWAYS_INLINE void clearStrings()
113142
{
114-
m_strings.fill({ });
143+
m_atomStrings.fill({ });
115144
clearJSStrings();
116145
}
117146

@@ -129,20 +158,32 @@ class JSONCache {
129158
return;
130159
m_primaryTransitions.fill({ });
131160
m_secondaryTransitions.fill({ });
161+
m_names.entries.fill({ });
162+
m_prefixedNames.entries.fill({ });
132163
}
133164

134165
private:
135-
ALWAYS_INLINE unsigned stringIndex(char16_t firstCharacter, char16_t lastCharacter, char16_t length)
166+
// A new entry replaces the one in its slot, or in the 2-way prefixed table demotes way 0 to way 1,
167+
// because consecutive objects usually have the same shape, so only recent entries matter.
168+
static constexpr unsigned prefixedNameEntrySize = 128;
169+
static constexpr unsigned prefixedNameWays = 2;
170+
171+
static constexpr unsigned maxNameTextLength = 32;
172+
static constexpr unsigned maxNameLength = maxNameTextLength - 3;
173+
using NameText = std::array<Latin1Character, maxNameTextLength>;
174+
using NameSource = std::span<const Latin1Character, maxNameTextLength>;
175+
176+
ALWAYS_INLINE unsigned atomStringIndex(char16_t firstCharacter, char16_t lastCharacter, char16_t length)
136177
{
137178
unsigned hash = (firstCharacter << 6) ^ ((lastCharacter << 14) ^ firstCharacter);
138179
hash += (hash >> 14) + (length << 14);
139180
hash ^= hash << 14;
140-
return (hash + (hash >> 6)) % stringCapacity;
181+
return (hash + (hash >> 6)) % atomStringCapacity;
141182
}
142183

143-
ALWAYS_INLINE Slot& stringSlot(char16_t firstCharacter, char16_t lastCharacter, char16_t length)
184+
ALWAYS_INLINE AtomStringSlot& atomStringSlot(char16_t firstCharacter, char16_t lastCharacter, char16_t length)
144185
{
145-
return m_strings[stringIndex(firstCharacter, lastCharacter, length)];
186+
return m_atomStrings[atomStringIndex(firstCharacter, lastCharacter, length)];
146187
}
147188

148189
template<typename CharacterType>
@@ -153,12 +194,30 @@ class JSONCache {
153194
static ALWAYS_INLINE unsigned primaryTransitionIndex(uint64_t key);
154195
static ALWAYS_INLINE unsigned secondaryTransitionIndex(uint64_t key);
155196
template<typename CharacterType> static ALWAYS_INLINE Structure* transitionIfMatches(const TransitionEntry&, StructureID from, std::span<const CharacterType> name);
197+
static ALWAYS_INLINE unsigned prefixedNameIndex(StructureID, uint32_t prefix);
198+
static std::span<const Latin1Character> recordableName(Structure* from, Structure* to, PropertyOffset);
199+
static unsigned makeText(NameText&, std::span<const Latin1Character> name);
200+
static uint32_t textPrefix(std::span<const Latin1Character> name);
201+
static ALWAYS_INLINE bool textMatches(const NameText&, unsigned length, NameSource);
202+
203+
// Texts are kept apart from the entries, which a parse reads for every key, so that the entries
204+
// occupy fewer cache lines. A text is only meaningful while its entry has a source Structure.
205+
template<unsigned size>
206+
struct NameTable {
207+
ALWAYS_INLINE const NameEntry* match(unsigned index, StructureID from, std::span<const Latin1Character> source) const LIFETIME_BOUND;
208+
template<unsigned ways> void insert(unsigned set, Structure* from, Structure* to, PropertyOffset, const NameText&, unsigned textLength);
209+
210+
std::array<NameEntry, size> entries { };
211+
std::array<NameText, size> texts { };
212+
};
156213

157-
std::array<Slot, stringCapacity> m_strings { };
214+
std::array<AtomStringSlot, stringCapacity> m_atomStrings { };
158215
std::array<JSString*, stringCapacity> m_jsStrings { };
159216
std::array<JSString*, longStringCapacity> m_longJSStrings { };
160217
std::array<TransitionEntry, primaryTransitionCapacity> m_primaryTransitions { };
161218
std::array<TransitionEntry, secondaryTransitionCapacity> m_secondaryTransitions { };
219+
NameTable<nameEntrySize> m_names { };
220+
NameTable<prefixedNameEntrySize> m_prefixedNames { };
162221
bool m_isParsing { false };
163222
};
164223

0 commit comments

Comments
 (0)