Skip to content

Commit 4855c7a

Browse files
committed
[JSC] Use simde in JSON
https://bugs.webkit.org/show_bug.cgi?id=273838 rdar://127688057 Reviewed by Keith Miller. This patch implements following two things via simde. 1. Fast scanning of strings in JSON.parse. 2. Fast scanning and copying strings in JSON.stringify. * Source/JavaScriptCore/runtime/JSONObject.cpp: (JSC::FastStringifier<CharType>::append): * Source/JavaScriptCore/runtime/LiteralParser.cpp: (JSC::LiteralParser<CharType>::Lexer::lexString): * Source/WTF/wtf/text/StringCommon.h: Canonical link: https://commits.webkit.org/278494@main
1 parent 98754fd commit 4855c7a

File tree

3 files changed

+207
-38
lines changed

3 files changed

+207
-38
lines changed

Source/JavaScriptCore/runtime/JSONObject.cpp

Lines changed: 110 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include <charconv>
4242
#include <wtf/text/EscapedFormsForJSON.h>
4343
#include <wtf/text/StringBuilder.h>
44+
#include <wtf/text/StringCommon.h>
4445

4546
// Turn this on to log information about fastStringify usage, with a focus on why it failed.
4647
#define FAST_STRINGIFY_LOG_USAGE 0
@@ -1059,6 +1060,102 @@ void FastStringifier<CharType>::append(JSValue value)
10591060
recordFailure("String::tryGetValue"_s);
10601061
return;
10611062
}
1063+
1064+
auto charactersCopySameType = [&](auto span, auto* cursor) ALWAYS_INLINE_LAMBDA {
1065+
#if CPU(ARM64) || CPU(X86_64)
1066+
constexpr size_t stride = 16 / sizeof(CharType);
1067+
if (span.size() >= stride) {
1068+
using UnsignedType = std::make_unsigned_t<CharType>;
1069+
using BulkType = decltype(WTF::loadBulk(static_cast<const UnsignedType*>(nullptr)));
1070+
constexpr auto quoteMask = WTF::splatBulk(static_cast<UnsignedType>('"'));
1071+
constexpr auto escapeMask = WTF::splatBulk(static_cast<UnsignedType>('\\'));
1072+
constexpr auto controlMask = WTF::splatBulk(static_cast<UnsignedType>(' '));
1073+
const auto* ptr = span.data();
1074+
const auto* end = ptr + span.size();
1075+
auto* cursorEnd = cursor + span.size();
1076+
BulkType accumulated { };
1077+
for (; ptr + (stride - 1) < end; ptr += stride, cursor += stride) {
1078+
auto input = WTF::loadBulk(bitwise_cast<const UnsignedType*>(ptr));
1079+
WTF::storeBulk(input, bitwise_cast<UnsignedType*>(cursor));
1080+
auto quotes = WTF::equalBulk(input, quoteMask);
1081+
auto escapes = WTF::equalBulk(input, escapeMask);
1082+
auto controls = WTF::lessThanBulk(input, controlMask);
1083+
accumulated = WTF::mergeBulk(accumulated, WTF::mergeBulk(quotes, WTF::mergeBulk(escapes, controls)));
1084+
if constexpr (sizeof(CharType) != 1) {
1085+
constexpr auto surrogateMask = WTF::splatBulk(static_cast<UnsignedType>(0xf800));
1086+
constexpr auto surrogateCheckMask = WTF::splatBulk(static_cast<UnsignedType>(0xd800));
1087+
accumulated = WTF::mergeBulk(accumulated, WTF::equalBulk(simde_vandq_u16(input, surrogateMask), surrogateCheckMask));
1088+
}
1089+
}
1090+
if (ptr < end) {
1091+
auto input = WTF::loadBulk(bitwise_cast<const UnsignedType*>(end - stride));
1092+
WTF::storeBulk(input, bitwise_cast<UnsignedType*>(cursorEnd - stride));
1093+
auto quotes = WTF::equalBulk(input, quoteMask);
1094+
auto escapes = WTF::equalBulk(input, escapeMask);
1095+
auto controls = WTF::lessThanBulk(input, controlMask);
1096+
accumulated = WTF::mergeBulk(accumulated, WTF::mergeBulk(quotes, WTF::mergeBulk(escapes, controls)));
1097+
if constexpr (sizeof(CharType) != 1) {
1098+
constexpr auto surrogateMask = WTF::splatBulk(static_cast<UnsignedType>(0xf800));
1099+
constexpr auto surrogateCheckMask = WTF::splatBulk(static_cast<UnsignedType>(0xd800));
1100+
accumulated = WTF::mergeBulk(accumulated, WTF::equalBulk(simde_vandq_u16(input, surrogateMask), surrogateCheckMask));
1101+
}
1102+
}
1103+
return WTF::isNonZeroBulk(accumulated);
1104+
}
1105+
#endif
1106+
for (auto character : span) {
1107+
if constexpr (sizeof(CharType) != 1) {
1108+
if (UNLIKELY(U16_IS_SURROGATE(character)))
1109+
return true;
1110+
}
1111+
if (UNLIKELY(character <= 0xff && WTF::escapedFormsForJSON[character]))
1112+
return true;
1113+
*cursor++ = character;
1114+
}
1115+
return false;
1116+
};
1117+
1118+
auto charactersCopyUpconvert = [&](std::span<const LChar> span, UChar* cursor) ALWAYS_INLINE_LAMBDA {
1119+
#if CPU(ARM64) || CPU(X86_64)
1120+
constexpr size_t stride = 16 / sizeof(LChar);
1121+
if (span.size() >= stride) {
1122+
using UnsignedType = std::make_unsigned_t<LChar>;
1123+
using BulkType = decltype(WTF::loadBulk(static_cast<const UnsignedType*>(nullptr)));
1124+
constexpr auto quoteMask = WTF::splatBulk(static_cast<UnsignedType>('"'));
1125+
constexpr auto escapeMask = WTF::splatBulk(static_cast<UnsignedType>('\\'));
1126+
constexpr auto controlMask = WTF::splatBulk(static_cast<UnsignedType>(' '));
1127+
constexpr auto zeros = WTF::splatBulk(static_cast<UnsignedType>(0));
1128+
const auto* ptr = span.data();
1129+
const auto* end = ptr + span.size();
1130+
auto* cursorEnd = cursor + span.size();
1131+
BulkType accumulated { };
1132+
for (; ptr + (stride - 1) < end; ptr += stride, cursor += stride) {
1133+
auto input = WTF::loadBulk(bitwise_cast<const UnsignedType*>(ptr));
1134+
simde_vst2q_u8(bitwise_cast<UnsignedType*>(cursor), (simde_uint8x16x2_t { input, zeros }));
1135+
auto quotes = WTF::equalBulk(input, quoteMask);
1136+
auto escapes = WTF::equalBulk(input, escapeMask);
1137+
auto controls = WTF::lessThanBulk(input, controlMask);
1138+
accumulated = WTF::mergeBulk(accumulated, WTF::mergeBulk(quotes, WTF::mergeBulk(escapes, controls)));
1139+
}
1140+
if (ptr < end) {
1141+
auto input = WTF::loadBulk(bitwise_cast<const UnsignedType*>(end - stride));
1142+
simde_vst2q_u8(bitwise_cast<UnsignedType*>(cursorEnd - stride), (simde_uint8x16x2_t { input, zeros }));
1143+
auto quotes = WTF::equalBulk(input, quoteMask);
1144+
auto escapes = WTF::equalBulk(input, escapeMask);
1145+
auto controls = WTF::lessThanBulk(input, controlMask);
1146+
accumulated = WTF::mergeBulk(accumulated, WTF::mergeBulk(quotes, WTF::mergeBulk(escapes, controls)));
1147+
}
1148+
return WTF::isNonZeroBulk(accumulated);
1149+
}
1150+
#endif
1151+
for (auto character : span) {
1152+
if (UNLIKELY(WTF::escapedFormsForJSON[character]))
1153+
return true;
1154+
*cursor++ = character;
1155+
}
1156+
return false;
1157+
};
1158+
10621159
if constexpr (sizeof(CharType) == 1) {
10631160
if (UNLIKELY(!string.is8Bit())) {
10641161
m_retryWith16BitFastStringifier = m_length < (m_capacity / 2);
@@ -1070,47 +1167,32 @@ void FastStringifier<CharType>::append(JSValue value)
10701167
recordBufferFull();
10711168
return;
10721169
}
1073-
auto* cursor = m_buffer + m_length;
1074-
*cursor++ = '"';
1075-
for (auto character : string.span8()) {
1076-
if (UNLIKELY(WTF::escapedFormsForJSON[character])) {
1077-
recordFailure("string character needs escaping"_s);
1078-
return;
1079-
}
1080-
*cursor++ = character;
1170+
m_buffer[m_length] = '"';
1171+
if (UNLIKELY(charactersCopySameType(string.span8(), m_buffer + m_length + 1))) {
1172+
recordFailure("string character needs escaping"_s);
1173+
return;
10811174
}
1082-
*cursor = '"';
1175+
m_buffer[m_length + 1 + stringLength] = '"';
10831176
m_length += 1 + stringLength + 1;
10841177
} else {
10851178
auto stringLength = string.length();
10861179
if (UNLIKELY(!hasRemainingCapacity(1 + stringLength + 1))) {
10871180
recordBufferFull();
10881181
return;
10891182
}
1090-
auto* cursor = m_buffer + m_length;
1091-
*cursor++ = '"';
1183+
m_buffer[m_length] = '"';
10921184
if (string.is8Bit()) {
1093-
for (auto character : string.span8()) {
1094-
if (UNLIKELY(WTF::escapedFormsForJSON[character])) {
1095-
recordFailure("string character needs escaping"_s);
1096-
return;
1097-
}
1098-
*cursor++ = character;
1185+
if (UNLIKELY(charactersCopyUpconvert(string.span8(), m_buffer + m_length + 1))) {
1186+
recordFailure("string character needs escaping"_s);
1187+
return;
10991188
}
11001189
} else {
1101-
for (auto character : string.span16()) {
1102-
if (UNLIKELY(U16_IS_SURROGATE(character))) {
1103-
recordFailure("string character is surrogate"_s);
1104-
return;
1105-
}
1106-
if (UNLIKELY(character <= 0xff && WTF::escapedFormsForJSON[character])) {
1107-
recordFailure("string character needs escaping"_s);
1108-
return;
1109-
}
1110-
*cursor++ = character;
1190+
if (UNLIKELY(charactersCopySameType(string.span16(), m_buffer + m_length + 1))) {
1191+
recordFailure("string character needs escaping or surrogate pair handling"_s);
1192+
return;
11111193
}
11121194
}
1113-
*cursor = '"';
1195+
m_buffer[m_length + 1 + stringLength] = '"';
11141196
m_length += 1 + stringLength + 1;
11151197
}
11161198
return;

Source/JavaScriptCore/runtime/LiteralParser.cpp

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -867,8 +867,43 @@ ALWAYS_INLINE TokenType LiteralParser<CharType>::Lexer::lexString(LiteralParserT
867867
while (m_ptr < m_end && isSafeStringCharacterForIdentifier<SafeStringCharacterSet::Strict>(*m_ptr, '"'))
868868
++m_ptr;
869869
} else {
870-
while (m_ptr < m_end && isSafeStringCharacter<SafeStringCharacterSet::Strict>(*m_ptr, '"'))
871-
++m_ptr;
870+
([&]() ALWAYS_INLINE_LAMBDA {
871+
#if CPU(ARM64) || CPU(X86_64)
872+
constexpr size_t stride = 16 / sizeof(CharType);
873+
using UnsignedType = std::make_unsigned_t<CharType>;
874+
if (static_cast<size_t>(m_end - m_ptr) >= stride) {
875+
constexpr auto quoteMask = WTF::splatBulk(static_cast<UnsignedType>('"'));
876+
constexpr auto escapeMask = WTF::splatBulk(static_cast<UnsignedType>('\\'));
877+
constexpr auto controlMask = WTF::splatBulk(static_cast<UnsignedType>(' '));
878+
for (; m_ptr + (stride - 1) < m_end; m_ptr += stride) {
879+
auto input = WTF::loadBulk(bitwise_cast<const UnsignedType*>(m_ptr));
880+
auto quotes = WTF::equalBulk(input, quoteMask);
881+
auto escapes = WTF::equalBulk(input, escapeMask);
882+
auto controls = WTF::lessThanBulk(input, controlMask);
883+
auto mask = WTF::mergeBulk(quotes, WTF::mergeBulk(escapes, controls));
884+
if (WTF::isNonZeroBulk(mask)) {
885+
m_ptr += WTF::findFirstNonZeroIndexBulk(mask);
886+
return;
887+
}
888+
}
889+
if (m_ptr < m_end) {
890+
auto input = WTF::loadBulk(bitwise_cast<const UnsignedType*>(m_end - stride));
891+
auto quotes = WTF::equalBulk(input, quoteMask);
892+
auto escapes = WTF::equalBulk(input, escapeMask);
893+
auto controls = WTF::lessThanBulk(input, controlMask);
894+
auto mask = WTF::mergeBulk(quotes, WTF::mergeBulk(escapes, controls));
895+
if (WTF::isNonZeroBulk(mask)) {
896+
m_ptr = m_end - stride + WTF::findFirstNonZeroIndexBulk(mask);
897+
return;
898+
}
899+
m_ptr = m_end;
900+
}
901+
return;
902+
}
903+
#endif
904+
while (m_ptr < m_end && isSafeStringCharacter<SafeStringCharacterSet::Strict>(*m_ptr, '"'))
905+
++m_ptr;
906+
}());
872907
}
873908
} else {
874909
while (m_ptr < m_end && isSafeStringCharacter<SafeStringCharacterSet::Sloppy>(*m_ptr, terminator))

Source/WTF/wtf/text/StringCommon.h

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,7 +1210,17 @@ inline void copyElements(LChar* __restrict destination, const UChar* __restrict
12101210
copyElements(bitwise_cast<uint8_t*>(destination), bitwise_cast<const uint16_t*>(source), length);
12111211
}
12121212

1213-
#if CPU(ARM64)
1213+
#if CPU(ARM64) || CPU(X86_64)
1214+
1215+
constexpr simde_uint8x16_t splatBulk(uint8_t code)
1216+
{
1217+
return simde_uint8x16_t { code, code, code, code, code, code, code, code, code, code, code, code, code, code, code, code };
1218+
}
1219+
1220+
constexpr simde_uint16x8_t splatBulk(uint16_t code)
1221+
{
1222+
return simde_uint16x8_t { code, code, code, code, code, code, code, code };
1223+
}
12141224

12151225
ALWAYS_INLINE simde_uint8x16_t loadBulk(const uint8_t* ptr)
12161226
{
@@ -1222,6 +1232,16 @@ ALWAYS_INLINE simde_uint16x8_t loadBulk(const uint16_t* ptr)
12221232
return simde_vld1q_u16(ptr);
12231233
}
12241234

1235+
ALWAYS_INLINE void storeBulk(simde_uint8x16_t value, uint8_t* ptr)
1236+
{
1237+
return simde_vst1q_u8(ptr, value);
1238+
}
1239+
1240+
ALWAYS_INLINE void storeBulk(simde_uint16x8_t value, uint16_t* ptr)
1241+
{
1242+
return simde_vst1q_u16(ptr, value);
1243+
}
1244+
12251245
ALWAYS_INLINE simde_uint8x16_t mergeBulk(simde_uint8x16_t accumulated, simde_uint8x16_t input)
12261246
{
12271247
return simde_vorrq_u8(accumulated, input);
@@ -1242,24 +1262,56 @@ ALWAYS_INLINE bool isNonZeroBulk(simde_uint16x8_t accumulated)
12421262
return simde_vmaxvq_u16(accumulated);
12431263
}
12441264

1265+
ALWAYS_INLINE uint8_t findFirstNonZeroIndexBulk(simde_uint8x16_t value)
1266+
{
1267+
constexpr simde_uint8x16_t indexMask { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
1268+
return simde_vminvq_u8(simde_vornq_u8(indexMask, value));
1269+
}
1270+
1271+
ALWAYS_INLINE uint8_t findFirstNonZeroIndexBulk(simde_uint16x8_t value)
1272+
{
1273+
constexpr simde_uint16x8_t indexMask { 0, 1, 2, 3, 4, 5, 6, 7 };
1274+
return simde_vminvq_u16(simde_vornq_u16(indexMask, value));
1275+
}
1276+
12451277
template<LChar character, LChar... characters>
1246-
ALWAYS_INLINE simde_uint8x16_t compareBulk(simde_uint8x16_t input)
1278+
ALWAYS_INLINE simde_uint8x16_t equalBulk(simde_uint8x16_t input)
12471279
{
12481280
auto result = simde_vceqq_u8(input, simde_vmovq_n_u8(character));
12491281
if constexpr (!sizeof...(characters))
12501282
return result;
12511283
else
1252-
return mergeBulk(result, compareBulk<characters...>(input));
1284+
return mergeBulk(result, equalBulk<characters...>(input));
12531285
}
12541286

12551287
template<UChar character, UChar... characters>
1256-
ALWAYS_INLINE simde_uint16x8_t compareBulk(simde_uint16x8_t input)
1288+
ALWAYS_INLINE simde_uint16x8_t equalBulk(simde_uint16x8_t input)
12571289
{
12581290
auto result = simde_vceqq_u16(input, simde_vmovq_n_u16(character));
12591291
if constexpr (!sizeof...(characters))
12601292
return result;
12611293
else
1262-
return mergeBulk(result, compareBulk<characters...>(input));
1294+
return mergeBulk(result, equalBulk<characters...>(input));
1295+
}
1296+
1297+
ALWAYS_INLINE simde_uint8x16_t equalBulk(simde_uint8x16_t lhs, simde_uint8x16_t rhs)
1298+
{
1299+
return simde_vceqq_u8(lhs, rhs);
1300+
}
1301+
1302+
ALWAYS_INLINE simde_uint16x8_t equalBulk(simde_uint16x8_t lhs, simde_uint16x8_t rhs)
1303+
{
1304+
return simde_vceqq_u16(lhs, rhs);
1305+
}
1306+
1307+
ALWAYS_INLINE simde_uint8x16_t lessThanBulk(simde_uint8x16_t lhs, simde_uint8x16_t rhs)
1308+
{
1309+
return simde_vcltq_u8(lhs, rhs);
1310+
}
1311+
1312+
ALWAYS_INLINE simde_uint16x8_t lessThanBulk(simde_uint16x8_t lhs, simde_uint16x8_t rhs)
1313+
{
1314+
return simde_vcltq_u16(lhs, rhs);
12631315
}
12641316

12651317
#endif
@@ -1277,18 +1329,18 @@ ALWAYS_INLINE bool charactersContain(std::span<const CharacterType> span)
12771329
auto* data = span.data();
12781330
size_t length = span.size();
12791331

1280-
#if CPU(ARM64)
1332+
#if CPU(ARM64) || CPU(X86_64)
12811333
constexpr size_t stride = 16 / sizeof(CharacterType);
12821334
using UnsignedType = std::make_unsigned_t<CharacterType>;
12831335
using BulkType = decltype(loadBulk(static_cast<const UnsignedType*>(nullptr)));
12841336
if (length >= stride) {
12851337
size_t index = 0;
12861338
BulkType accumulated { };
12871339
for (; index + (stride - 1) < length; index += stride)
1288-
accumulated = mergeBulk(accumulated, compareBulk<characters...>(loadBulk(bitwise_cast<const UnsignedType*>(data + index))));
1340+
accumulated = mergeBulk(accumulated, equalBulk<characters...>(loadBulk(bitwise_cast<const UnsignedType*>(data + index))));
12891341

12901342
if (index < length)
1291-
accumulated = mergeBulk(accumulated, compareBulk<characters...>(loadBulk(bitwise_cast<const UnsignedType*>(data + length - stride))));
1343+
accumulated = mergeBulk(accumulated, equalBulk<characters...>(loadBulk(bitwise_cast<const UnsignedType*>(data + length - stride))));
12921344

12931345
return isNonZeroBulk(accumulated);
12941346
}

0 commit comments

Comments
 (0)