Skip to content

Commit

Permalink
[JSC] Optimize Array#indexOf in DFG / FTL
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=246225
rdar://100907119

Reviewed by Justin Michaud and Alexey Shvayka.

This patch optimizes JetStream2/babylon-wtb up to 10%.

1. We were giving up optimization of ArrayIndexOf once we start getting BadType on the searching parameter.
   But let's not do it and instead keep working with a bit more generic version since parameter type speculation
   only offers a bit of performance win. So, using generic one still makes sense in terms of optimization if
   type speculation failed before.
2. Add JSString::equalInline and use it in ArrayIndexOf DFG operation when we know the search parameter is JSString.
3. Add WTF::find64 optimized path for Object search in ArrayIndexOf DFG operation.

* Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleIntrinsicCall):
* Source/JavaScriptCore/dfg/DFGOperations.cpp:
(JSC::DFG::arrayIndexOfString):
(JSC::DFG::JSC_DEFINE_JIT_OPERATION):
* Source/JavaScriptCore/runtime/ArrayPrototype.cpp:
(JSC::argumentClampedIndexFromStartOrEnd):
* Source/JavaScriptCore/runtime/JSString.cpp:
(JSC::JSString::equalSlowCase const):
* Source/JavaScriptCore/runtime/JSString.h:
* Source/JavaScriptCore/runtime/JSStringInlines.h:
(JSC::JSString::equalInline const):

Canonical link: https://commits.webkit.org/255296@main
  • Loading branch information
Constellation committed Oct 7, 2022
1 parent ea3d0e6 commit 6b2c1f4
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 21 deletions.
7 changes: 5 additions & 2 deletions Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Expand Up @@ -2604,8 +2604,11 @@ bool ByteCodeParser::handleIntrinsicCall(Node* callee, Operand result, Intrinsic

if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadIndexingType)
|| m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadConstantCache)
|| m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCache)
|| m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType))
|| m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCache))
return false;

// index parameter's BadType is critical. But the other ones can be relaxed, so not giving up optimization.
if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType) && argumentCountIncludingThis > 2)
return false;

ArrayMode arrayMode = getArrayMode(Array::Read);
Expand Down
29 changes: 25 additions & 4 deletions Source/JavaScriptCore/dfg/DFGOperations.cpp
Expand Up @@ -3122,11 +3122,9 @@ JSC_DEFINE_JIT_OPERATION(operationNumberIsInteger, size_t, (JSGlobalObject* glob
return NumberConstructor::isIntegerImpl(JSValue::decode(value));
}

JSC_DEFINE_JIT_OPERATION(operationArrayIndexOfString, UCPUStrictInt32, (JSGlobalObject* globalObject, Butterfly* butterfly, JSString* searchElement, int32_t index))
static ALWAYS_INLINE UCPUStrictInt32 arrayIndexOfString(JSGlobalObject* globalObject, Butterfly* butterfly, JSString* searchElement, int32_t index)
{
VM& vm = globalObject->vm();
CallFrame* callFrame = DECLARE_CALL_FRAME(vm);
JITOperationPrologueCallFrameTracer tracer(vm, callFrame);
auto scope = DECLARE_THROW_SCOPE(vm);

int32_t length = butterfly->publicLength();
Expand All @@ -3138,7 +3136,7 @@ JSC_DEFINE_JIT_OPERATION(operationArrayIndexOfString, UCPUStrictInt32, (JSGlobal
auto* string = asString(value);
if (string == searchElement)
return toUCPUStrictInt32(index);
if (string->equal(globalObject, searchElement)) {
if (string->equalInline(globalObject, searchElement)) {
scope.assertNoExceptionExceptTermination();
return toUCPUStrictInt32(index);
}
Expand All @@ -3147,6 +3145,15 @@ JSC_DEFINE_JIT_OPERATION(operationArrayIndexOfString, UCPUStrictInt32, (JSGlobal
return toUCPUStrictInt32(-1);
}

JSC_DEFINE_JIT_OPERATION(operationArrayIndexOfString, UCPUStrictInt32, (JSGlobalObject* globalObject, Butterfly* butterfly, JSString* searchElement, int32_t index))
{
VM& vm = globalObject->vm();
CallFrame* callFrame = DECLARE_CALL_FRAME(vm);
JITOperationPrologueCallFrameTracer tracer(vm, callFrame);

return arrayIndexOfString(globalObject, butterfly, searchElement, index);
}

JSC_DEFINE_JIT_OPERATION(operationArrayIndexOfValueInt32OrContiguous, UCPUStrictInt32, (JSGlobalObject* globalObject, Butterfly* butterfly, EncodedJSValue encodedValue, int32_t index))
{
VM& vm = globalObject->vm();
Expand All @@ -3156,8 +3163,22 @@ JSC_DEFINE_JIT_OPERATION(operationArrayIndexOfValueInt32OrContiguous, UCPUStrict

JSValue searchElement = JSValue::decode(encodedValue);

if (searchElement.isString())
RELEASE_AND_RETURN(scope, arrayIndexOfString(globalObject, butterfly, asString(searchElement), index));

int32_t length = butterfly->publicLength();
auto data = butterfly->contiguous().data();

if (index >= length)
return toUCPUStrictInt32(-1);

if (searchElement.isObject()) {
auto* result = bitwise_cast<const WriteBarrier<Unknown>*>(WTF::find64(bitwise_cast<const uint64_t*>(data + index), encodedValue, length - index));
if (result)
return toUCPUStrictInt32(result - data);
return toUCPUStrictInt32(-1);
}

for (; index < length; ++index) {
JSValue value = data[index].get();
if (!value)
Expand Down
10 changes: 10 additions & 0 deletions Source/JavaScriptCore/runtime/ArrayPrototype.cpp
Expand Up @@ -201,6 +201,16 @@ static inline uint64_t argumentClampedIndexFromStartOrEnd(JSGlobalObject* global
if (value.isUndefined())
return undefinedValue;

if (LIKELY(value.isInt32())) {
int64_t indexInt64 = value.asInt32();
if (indexInt64 < 0) {
indexInt64 += length;
return indexInt64 < 0 ? 0 : static_cast<uint64_t>(indexInt64);
}
uint64_t indexUInt64 = static_cast<uint64_t>(indexInt64);
return std::min(indexUInt64, length);
}

double indexDouble = value.toIntegerOrInfinity(globalObject);
if (indexDouble < 0) {
indexDouble += length;
Expand Down
16 changes: 1 addition & 15 deletions Source/JavaScriptCore/runtime/JSString.cpp
Expand Up @@ -86,21 +86,7 @@ void JSString::dumpToStream(const JSCell* cell, PrintStream& out)

bool JSString::equalSlowCase(JSGlobalObject* globalObject, JSString* other) const
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);

unsigned length = this->length();
if (length != other->length())
return false;

auto str1 = unsafeView(globalObject);
RETURN_IF_EXCEPTION(scope, false);
auto str2 = other->unsafeView(globalObject);
RETURN_IF_EXCEPTION(scope, false);

ensureStillAliveHere(this);
ensureStillAliveHere(other);
return WTF::equal(str1, str2, length);
return equalInline(globalObject, other);
}

size_t JSString::estimatedSize(JSCell* cell, VM& vm)
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/runtime/JSString.h
Expand Up @@ -203,6 +203,7 @@ class JSString : public JSCell {

StringViewWithUnderlyingString viewWithUnderlyingString(JSGlobalObject*) const;

ALWAYS_INLINE bool equalInline(JSGlobalObject*, JSString* other) const;
inline bool equal(JSGlobalObject*, JSString* other) const;
const String& value(JSGlobalObject*) const;
inline const String& tryGetValue(bool allocationAllowed = true) const;
Expand Down
19 changes: 19 additions & 0 deletions Source/JavaScriptCore/runtime/JSStringInlines.h
Expand Up @@ -44,6 +44,25 @@ bool JSString::equal(JSGlobalObject* globalObject, JSString* other) const
return WTF::equal(*valueInternal().impl(), *other->valueInternal().impl());
}

ALWAYS_INLINE bool JSString::equalInline(JSGlobalObject* globalObject, JSString* other) const
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);

unsigned length = this->length();
if (length != other->length())
return false;

auto str1 = unsafeView(globalObject);
RETURN_IF_EXCEPTION(scope, false);
auto str2 = other->unsafeView(globalObject);
RETURN_IF_EXCEPTION(scope, false);

ensureStillAliveHere(this);
ensureStillAliveHere(other);
return WTF::equal(str1, str2, length);
}

template<typename StringType>
inline JSValue jsMakeNontrivialString(VM& vm, StringType&& string)
{
Expand Down

0 comments on commit 6b2c1f4

Please sign in to comment.