mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-20 15:27:32 +09:00
Optimize string operations with SSE2
This commit is contained in:
parent
70b4a79405
commit
b7c0ebfcd4
6 changed files with 268 additions and 18 deletions
21
js/src/jit-test/tests/latin1/sse2-search.js
Normal file
21
js/src/jit-test/tests/latin1/sse2-search.js
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Exercise vector-sized spans, scalar tails, and mixed character encodings.
|
||||||
|
for (var length of [0, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65]) {
|
||||||
|
for (var needle of ["\0", "\x7f", "\x80", "\xff", "\u0100", "\ud800", "\uffff"]) {
|
||||||
|
for (var position = 0; position <= length; ++position) {
|
||||||
|
var text = "a".repeat(position) + needle + "a".repeat(length - position);
|
||||||
|
for (var start of [0, position, position + 1, text.length]) {
|
||||||
|
var expected = start <= position ? position : -1;
|
||||||
|
assertEq(text.indexOf(needle, start), expected);
|
||||||
|
assertEq(text.includes(needle, start), expected !== -1);
|
||||||
|
}
|
||||||
|
assertEq(text.indexOf(needle + "b"), -1);
|
||||||
|
assertEq(text.indexOf(needle + "a"), position < length ? position : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var latin1 = "\0".repeat(length) + "\xff";
|
||||||
|
assertEq(latin1.indexOf("\u0100"), -1); // Must not narrow to NUL on x86.
|
||||||
|
assertEq(latin1.indexOf("\uffff"), -1); // Must not narrow to 0xff on x86.
|
||||||
|
var wide = "\u0100" + latin1;
|
||||||
|
assertEq(wide.indexOf("\xff"), length + 1);
|
||||||
|
assertEq(wide.indexOf(latin1), 1);
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ UNIFIED_SOURCES += [
|
||||||
'testBug604087.cpp',
|
'testBug604087.cpp',
|
||||||
'testCallArgs.cpp',
|
'testCallArgs.cpp',
|
||||||
'testCallNonGenericMethodOnProxy.cpp',
|
'testCallNonGenericMethodOnProxy.cpp',
|
||||||
|
'testCharacterOperations.cpp',
|
||||||
'testChromeBuffer.cpp',
|
'testChromeBuffer.cpp',
|
||||||
'testClassGetter.cpp',
|
'testClassGetter.cpp',
|
||||||
'testCloneScript.cpp',
|
'testCloneScript.cpp',
|
||||||
|
|
|
||||||
145
js/src/jsapi-tests/testCharacterOperations.cpp
Normal file
145
js/src/jsapi-tests/testCharacterOperations.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
/* 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
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "vm/CharacterOperations.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#ifndef JS_CHARACTER_OPERATIONS_STANDALONE
|
||||||
|
# include "jsapi-tests/tests.h"
|
||||||
|
# include "jswin.h"
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
# include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename CharT>
|
||||||
|
static bool
|
||||||
|
CheckCharacterSearch()
|
||||||
|
{
|
||||||
|
CharT buffer[128];
|
||||||
|
const uint32_t needles[] = {0, 0x7f, 0x80, 0xff, 0x100, 0xd800, 0xdc00, 0xffff};
|
||||||
|
for (uint32_t value : needles) {
|
||||||
|
if (sizeof(CharT) == 1 && value > 0xff)
|
||||||
|
continue;
|
||||||
|
const CharT needle = CharT(value);
|
||||||
|
const CharT other = CharT(value ^ 1);
|
||||||
|
for (size_t offset = 0; offset < 16 / sizeof(CharT); ++offset) {
|
||||||
|
CharT* text = buffer + offset;
|
||||||
|
for (size_t length = 0; length <= 96; ++length) {
|
||||||
|
for (size_t i = 0; i <= length; ++i)
|
||||||
|
text[i] = other;
|
||||||
|
// A match just outside the span must never be returned.
|
||||||
|
text[length] = needle;
|
||||||
|
if (js::FindCharacter(text, length, needle))
|
||||||
|
return false;
|
||||||
|
for (size_t position = 0; position < length; ++position) {
|
||||||
|
text[position] = needle;
|
||||||
|
text[length - 1] = needle;
|
||||||
|
if (js::FindCharacter(text, length, needle) != text + position)
|
||||||
|
return false;
|
||||||
|
text[position] = other;
|
||||||
|
text[length - 1] = other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
CheckLatin1Detection()
|
||||||
|
{
|
||||||
|
char16_t buffer[128];
|
||||||
|
const char16_t nonLatin1[] = {0x100, 0x8000, 0xd800, 0xdc00, 0xffff};
|
||||||
|
for (size_t offset = 0; offset < 8; ++offset) {
|
||||||
|
char16_t* text = buffer + offset;
|
||||||
|
for (size_t length = 0; length <= 96; ++length) {
|
||||||
|
for (size_t i = 0; i < length; ++i)
|
||||||
|
text[i] = (i & 1) ? 0xff : 0x80;
|
||||||
|
text[length] = 0xffff;
|
||||||
|
if (!js::CharactersFitInLatin1(text, length))
|
||||||
|
return false;
|
||||||
|
for (char16_t invalid : nonLatin1) {
|
||||||
|
for (size_t position = 0; position < length; ++position) {
|
||||||
|
const char16_t saved = text[position];
|
||||||
|
text[position] = invalid;
|
||||||
|
if (js::CharactersFitInLatin1(text, length))
|
||||||
|
return false;
|
||||||
|
text[position] = saved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
static bool
|
||||||
|
CheckCharacterPageBoundary()
|
||||||
|
{
|
||||||
|
SYSTEM_INFO info;
|
||||||
|
GetSystemInfo(&info);
|
||||||
|
char* pages = static_cast<char*>(VirtualAlloc(nullptr, 2 * info.dwPageSize,
|
||||||
|
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||||
|
if (!pages)
|
||||||
|
return false;
|
||||||
|
DWORD oldProtection;
|
||||||
|
char* end = pages + info.dwPageSize;
|
||||||
|
bool ok = !!VirtualProtect(end, info.dwPageSize, PAGE_NOACCESS, &oldProtection);
|
||||||
|
if (ok) {
|
||||||
|
for (size_t length = 0; length <= 64; ++length) {
|
||||||
|
char* bytes = end - length;
|
||||||
|
for (size_t i = 0; i < length; ++i)
|
||||||
|
bytes[i] = 'a';
|
||||||
|
ok = ok && !js::FindCharacter(bytes, length, 'b');
|
||||||
|
if (length) {
|
||||||
|
bytes[length - 1] = 'b';
|
||||||
|
ok = ok && js::FindCharacter(bytes, length, 'b') == bytes + length - 1;
|
||||||
|
}
|
||||||
|
char16_t* wide = reinterpret_cast<char16_t*>(end) - length;
|
||||||
|
for (size_t i = 0; i < length; ++i)
|
||||||
|
wide[i] = 0xff;
|
||||||
|
ok = ok && !js::FindCharacter(wide, length, char16_t(0x100));
|
||||||
|
ok = ok && js::CharactersFitInLatin1(wide, length);
|
||||||
|
if (length) {
|
||||||
|
wide[length - 1] = 0x100;
|
||||||
|
ok = ok && js::FindCharacter(wide, length, char16_t(0x100)) == wide + length - 1;
|
||||||
|
ok = ok && !js::CharactersFitInLatin1(wide, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VirtualFree(pages, 0, MEM_RELEASE);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool
|
||||||
|
CheckCharacterOperations()
|
||||||
|
{
|
||||||
|
return CheckCharacterSearch<char>() && CheckCharacterSearch<unsigned char>() &&
|
||||||
|
CheckCharacterSearch<char16_t>() && CheckLatin1Detection()
|
||||||
|
#ifdef _WIN32
|
||||||
|
&& CheckCharacterPageBoundary()
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow testing these native helpers without rebuilding/linking SpiderMonkey.
|
||||||
|
#ifdef JS_CHARACTER_OPERATIONS_STANDALONE
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
if (!CheckCharacterOperations()) {
|
||||||
|
fprintf(stderr, "Character operation regression test failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
BEGIN_TEST(testCharacterOperations)
|
||||||
|
{
|
||||||
|
CHECK(CheckCharacterOperations());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
END_TEST(testCharacterOperations)
|
||||||
|
#endif
|
||||||
|
|
@ -38,6 +38,7 @@
|
||||||
#include "js/UniquePtr.h"
|
#include "js/UniquePtr.h"
|
||||||
#include "unicode/uchar.h"
|
#include "unicode/uchar.h"
|
||||||
#include "unicode/unorm2.h"
|
#include "unicode/unorm2.h"
|
||||||
|
#include "vm/CharacterOperations.h"
|
||||||
#include "vm/GlobalObject.h"
|
#include "vm/GlobalObject.h"
|
||||||
#include "vm/Interpreter.h"
|
#include "vm/Interpreter.h"
|
||||||
#include "vm/Opcodes.h"
|
#include "vm/Opcodes.h"
|
||||||
|
|
@ -1640,6 +1641,10 @@ FirstCharMatcherUnrolled(const TextChar* text, uint32_t n, const PatChar pat)
|
||||||
static const char*
|
static const char*
|
||||||
FirstCharMatcher8bit(const char* text, uint32_t n, const char pat)
|
FirstCharMatcher8bit(const char* text, uint32_t n, const char pat)
|
||||||
{
|
{
|
||||||
|
#ifdef JS_HAS_SSE2_CHARACTER_OPERATIONS
|
||||||
|
if (n >= 16)
|
||||||
|
return FindCharacter(text, n, pat);
|
||||||
|
#endif
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
return FirstCharMatcherUnrolled<char, char>(text, n, pat);
|
return FirstCharMatcherUnrolled<char, char>(text, n, pat);
|
||||||
#else
|
#else
|
||||||
|
|
@ -1650,6 +1655,10 @@ FirstCharMatcher8bit(const char* text, uint32_t n, const char pat)
|
||||||
static const char16_t*
|
static const char16_t*
|
||||||
FirstCharMatcher16bit(const char16_t* text, uint32_t n, const char16_t pat)
|
FirstCharMatcher16bit(const char16_t* text, uint32_t n, const char16_t pat)
|
||||||
{
|
{
|
||||||
|
#ifdef JS_HAS_SSE2_CHARACTER_OPERATIONS
|
||||||
|
if (n >= 8)
|
||||||
|
return FindCharacter(text, n, pat);
|
||||||
|
#endif
|
||||||
#if defined(XP_DARWIN) || defined(XP_WIN)
|
#if defined(XP_DARWIN) || defined(XP_WIN)
|
||||||
/*
|
/*
|
||||||
* Performance of memchr is horrible in OSX. Windows is better,
|
* Performance of memchr is horrible in OSX. Windows is better,
|
||||||
|
|
@ -1734,18 +1743,14 @@ StringMatch(const TextChar* text, uint32_t textLen, const PatChar* pat, uint32_t
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
#if defined(__i386__) || defined(_M_IX86) || defined(__i386)
|
#if defined(__i386__) || defined(_M_IX86) || defined(__i386)
|
||||||
/*
|
// Avoid the generic substring matcher for a single character on x86.
|
||||||
* Given enough registers, the unrolled loop below is faster than the
|
// FindCharacter uses SSE2 where available, including mixed encodings.
|
||||||
* following loop. 32-bit x86 does not have enough registers.
|
|
||||||
*/
|
|
||||||
if (patLen == 1) {
|
if (patLen == 1) {
|
||||||
const PatChar p0 = *pat;
|
// A two-byte needle cannot match Latin1 text if it exceeds 0xff.
|
||||||
const TextChar* end = text + textLen;
|
if (sizeof(TextChar) == 1 && uint32_t(*pat) > 0xff)
|
||||||
for (const TextChar* c = text; c != end; ++c) {
|
|
||||||
if (*c == p0)
|
|
||||||
return c - text;
|
|
||||||
}
|
|
||||||
return -1;
|
return -1;
|
||||||
|
const TextChar* match = FindCharacter(text, textLen, TextChar(*pat));
|
||||||
|
return match ? int(match - text) : -1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
81
js/src/vm/CharacterOperations.h
Normal file
81
js/src/vm/CharacterOperations.h
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
/* 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
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef vm_CharacterOperations_h
|
||||||
|
#define vm_CharacterOperations_h
|
||||||
|
|
||||||
|
#include "mozilla/MathAlgorithms.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Use intrinsics only when SSE2 is part of the compiler's target baseline.
|
||||||
|
// Builds for other architectures (or pre-SSE2 x86) retain scalar operations.
|
||||||
|
#if defined(__SSE2__) || defined(_M_X64) || \
|
||||||
|
(defined(_M_IX86_FP) && _M_IX86_FP >= 2)
|
||||||
|
# define JS_HAS_SSE2_CHARACTER_OPERATIONS
|
||||||
|
# include <emmintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace js {
|
||||||
|
|
||||||
|
template <typename CharT>
|
||||||
|
inline const CharT*
|
||||||
|
FindCharacter(const CharT* chars, size_t length, CharT match)
|
||||||
|
{
|
||||||
|
static_assert(sizeof(CharT) == 1 || sizeof(CharT) == 2, "character width");
|
||||||
|
#ifdef JS_HAS_SSE2_CHARACTER_OPERATIONS
|
||||||
|
const size_t lanes = 16 / sizeof(CharT);
|
||||||
|
if (length >= lanes) {
|
||||||
|
const __m128i needle = sizeof(CharT) == 1
|
||||||
|
? _mm_set1_epi8(static_cast<char>(match))
|
||||||
|
: _mm_set1_epi16(static_cast<short>(match));
|
||||||
|
do {
|
||||||
|
// Never read beyond the supplied span, even at a page boundary.
|
||||||
|
const __m128i block = _mm_loadu_si128(reinterpret_cast<const __m128i*>(chars));
|
||||||
|
const __m128i equal = sizeof(CharT) == 1
|
||||||
|
? _mm_cmpeq_epi8(block, needle)
|
||||||
|
: _mm_cmpeq_epi16(block, needle);
|
||||||
|
const uint32_t mask = static_cast<uint32_t>(_mm_movemask_epi8(equal));
|
||||||
|
if (mask)
|
||||||
|
return chars + mozilla::CountTrailingZeroes32(mask) / sizeof(CharT);
|
||||||
|
chars += lanes;
|
||||||
|
length -= lanes;
|
||||||
|
} while (length >= lanes);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (; length; --length, ++chars) {
|
||||||
|
if (*chars == match)
|
||||||
|
return chars;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
CharactersFitInLatin1(const char16_t* chars, size_t length)
|
||||||
|
{
|
||||||
|
#ifdef JS_HAS_SSE2_CHARACTER_OPERATIONS
|
||||||
|
if (length >= 8) {
|
||||||
|
const __m128i highBytes = _mm_set1_epi16(static_cast<short>(0xff00));
|
||||||
|
const __m128i zero = _mm_setzero_si128();
|
||||||
|
do {
|
||||||
|
const __m128i block = _mm_loadu_si128(reinterpret_cast<const __m128i*>(chars));
|
||||||
|
const __m128i fits = _mm_cmpeq_epi16(_mm_and_si128(block, highBytes), zero);
|
||||||
|
if (_mm_movemask_epi8(fits) != 0xffff)
|
||||||
|
return false;
|
||||||
|
chars += 8;
|
||||||
|
length -= 8;
|
||||||
|
} while (length >= 8);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (; length; --length, ++chars) {
|
||||||
|
if (*chars > 0xff)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace js
|
||||||
|
|
||||||
|
#endif // vm_CharacterOperations_h
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
#include "gc/Marking.h"
|
#include "gc/Marking.h"
|
||||||
#include "js/UbiNode.h"
|
#include "js/UbiNode.h"
|
||||||
|
#include "vm/CharacterOperations.h"
|
||||||
#include "vm/SPSProfiler.h"
|
#include "vm/SPSProfiler.h"
|
||||||
|
|
||||||
#include "jscntxtinlines.h"
|
#include "jscntxtinlines.h"
|
||||||
|
|
@ -1147,12 +1148,8 @@ js::NewDependentString(JSContext* cx, JSString* baseArg, size_t start, size_t le
|
||||||
static bool
|
static bool
|
||||||
CanStoreCharsAsLatin1(const char16_t* s, size_t length)
|
CanStoreCharsAsLatin1(const char16_t* s, size_t length)
|
||||||
{
|
{
|
||||||
for (const char16_t* end = s + length; s < end; ++s) {
|
static_assert(JSString::MAX_LATIN1_CHAR == 0xff, "Latin1 character range");
|
||||||
if (*s > JSString::MAX_LATIN1_CHAR)
|
return CharactersFitInLatin1(s, length);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue