Skip to content

Commit

Permalink
[JSC] Implement Array.prototype.includes in C++
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=287693

Reviewed by Yusuke Suzuki.

Currently, JSC’s `Array#includes` is implemented in JavaScript, whereas
the similar `Array#indexOf` is implemented in C++ and benefits from
DFG/FTL optimizations.

This patch reimplements `Array#includes` in C++ in the same way as
`Array#indexOf`, thereby enabling DFG/FTL support.

In most cases, the patch shows performance improvements:

```
                                                  TipOfTree                  Patched
array-prototype-includes-double                25.2368+-0.1405     ^     20.9887+-3.0246        ^ definitely 1.2024x faster
array-prototype-includes-int32-from-contiguous
                                               22.4340+-0.4231     ^     17.1624+-0.2591        ^ definitely 1.3072x faster
array-prototype-includes-string                43.4965+-0.6154     ^     30.8301+-0.7207        ^ definitely 1.4108x faster
array-prototype-includes-string-16             73.3458+-0.8209     ^     51.7629+-0.5639        ^ definitely 1.4170x faster
array-prototype-includes-double-from-contiguous
                                               73.3174+-0.9760     ^     42.4634+-1.0352        ^ definitely 1.7266x faster
array-prototype-includes-int32                205.9207+-0.2986     ^    107.2346+-0.4414        ^ definitely 1.9203x faster
array-prototype-includes-contiguous            27.8838+-2.6451     ^      9.7461+-0.1414        ^ definitely 2.8610x faster
array-prototype-includes-bigint                17.6066+-0.2040     ^     12.2816+-0.1275        ^ definitely 1.4336x faster
```

However, benchmarks that use constant strings experience a performance
regression. This is due to the loss of constant folding for the `===`
operator that was effective in the JavaScript implementation:

```
                                                  TipOfTree                  Patched
array-prototype-includes-string-const          17.8715+-0.0715     !     24.9579+-0.2986        ! definitely 1.3965x slower
array-prototype-includes-string-16-const       17.9026+-0.0907     !     50.5343+-0.6068        ! definitely 2.8227x slower
```

* Source/JavaScriptCore/builtins/ArrayPrototype.js:
(includes): Deleted.
* Source/JavaScriptCore/runtime/ArrayPrototype.cpp:
(JSC::ArrayPrototype::finishCreation):
(JSC::JSC_DEFINE_HOST_FUNCTION):
* Source/JavaScriptCore/runtime/CommonIdentifiers.h:
* Source/JavaScriptCore/runtime/JSCJSValueInlines.h:
(JSC::sameValueZero):

Canonical link: https://commits.webkit.org/290906@main
  • Loading branch information
sosukesuzuki committed Feb 23, 2025
1 parent 4a3cd84 commit febcbad
Show file tree
Hide file tree
Showing 49 changed files with 858 additions and 89 deletions.
9 changes: 9 additions & 0 deletions JSTests/microbenchmarks/array-prototype-includes-bigint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
for (var i = 0; i < 1e5; ++i) {
[
0n,1n,2n,3n,4n,5n,6n,7n,8n,9n,10n,11n,12n,13n,14n,15n,
31n,30n,29n,28n,27n,26n,25n,24n,23n,22n,21n,20n,19n,
18n,17n,16n,32n,33n,34n,35n,36n,37n,38n,39n,40n,41n,
42n,43n,44n,45n,46n,47n,63n,62n,61n,60n,59n,58n,57n,
56n,55n,54n,53n,52n,51n,50n,49n,
].includes(38n);
}
21 changes: 21 additions & 0 deletions JSTests/microbenchmarks/array-prototype-includes-contiguous.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const search = { value: 36 };
const array = [
{ value: 0 }, { value: 1 }, { value: 2 }, { value: 3 }, { value: 4 },
{ value: 5 }, { value: 6 }, { value: 7 }, { value: 8 }, { value: 9 },
{ value: 10 }, { value: 11 }, { value: 12 }, { value: 13 }, { value: 14 },
{ value: 15 }, { value: 31 }, { value: 30 }, { value: 29 }, { value: 28 },
{ value: 27 }, { value: 26 }, { value: 25 }, { value: 24 }, { value: 23 },
{ value: 22 }, { value: 21 }, { value: 20 }, { value: 19 }, { value: 18 },
{ value: 17 }, { value: 16 }, { value: 32 }, { value: 33 }, { value: 34 },
{ value: 35 }, search, { value: 37 }, { value: 38 }, { value: 39 },
{ value: 40 }, { value: 41 }, { value: 42 }, { value: 43 }, { value: 44 },
{ value: 45 }, { value: 46 }, { value: 47 }, { value: 63 }, { value: 62 },
{ value: 61 }, { value: 60 }, { value: 59 }, { value: 58 }, { value: 57 },
{ value: 56 }, { value: 55 }, { value: 54 }, { value: 53 }, { value: 52 },
{ value: 51 }, { value: 50 }, { value: 49 },
];

for (var i = 0; i < 1e6; ++i) {
array.includes(search);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const array = [
{ value: 0 }, { value: 1 }, { value: 2 }, { value: 3 }, { value: 4 },
{ value: 5 }, { value: 6 }, { value: 7 }, { value: 8 }, { value: 9 },
{ value: 10 }, { value: 11 }, { value: 12 }, { value: 13 }, { value: 14 },
{ value: 15 }, { value: 31 }, { value: 30 }, { value: 29 }, { value: 28 },
{ value: 27 }, { value: 26 }, { value: 25 }, { value: 24 }, { value: 23 },
{ value: 22 }, { value: 21 }, { value: 20 }, { value: 19 }, { value: 18 },
{ value: 17 }, { value: 16 }, { value: 32 }, { value: 33 }, { value: 34 },
{ value: 35 }, 3.6, { value: 37 }, { value: 38 }, { value: 39 },
{ value: 40 }, { value: 41 }, { value: 42 }, { value: 43 }, { value: 44 },
{ value: 45 }, { value: 46 }, { value: 47 }, { value: 63 }, { value: 62 },
{ value: 61 }, { value: 60 }, { value: 59 }, { value: 58 }, { value: 57 },
{ value: 56 }, { value: 55 }, { value: 54 }, { value: 53 }, { value: 52 },
{ value: 51 }, { value: 50 }, { value: 49 },
];

for (var i = 0; i < 1e6; ++i) {
array.includes(3.6);
}

9 changes: 9 additions & 0 deletions JSTests/microbenchmarks/array-prototype-includes-double.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
for (var i = 0; i < 1e6; ++i) {
[
0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 8.2, 9.2, 10.2, 11.2, 12.2, 13.2, 14.2, 15.2,
31.2, 30.2, 29.2, 28.2, 27.2, 26.2, 25.2, 24.2, 23.2, 22.2, 21.2, 20.2, 19.2, 18.2,
17.2, 16.2, 32.2, 33.2, 34.2, 35.2, 36.2, 37.2, 38.2, 39.2, 40.2, 41.2, 42.2, 43.2,
44.2, 45.2, 46.2, 47.2, 63.2, 62.2, 61.2, 60.2, 59.2, 58.2, 57.2, 56.2, 55.2, 54.2,
53.2, 52.2, 51.2, 50.2, 49.2,
].includes(38.2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const array = [
{ value: 0 }, { value: 1 }, { value: 2 }, { value: 3 }, { value: 4 },
{ value: 5 }, { value: 6 }, { value: 7 }, { value: 8 }, { value: 9 },
{ value: 10 }, { value: 11 }, { value: 12 }, { value: 13 }, { value: 14 },
{ value: 15 }, { value: 31 }, { value: 30 }, { value: 29 }, { value: 28 },
{ value: 27 }, { value: 26 }, { value: 25 }, { value: 24 }, { value: 23 },
{ value: 22 }, { value: 21 }, { value: 20 }, { value: 19 }, { value: 18 },
{ value: 17 }, { value: 16 }, { value: 32 }, { value: 33 }, { value: 34 },
{ value: 35 }, 3, { value: 37 }, { value: 38 }, { value: 39 },
{ value: 40 }, { value: 41 }, { value: 42 }, { value: 43 }, { value: 44 },
{ value: 45 }, { value: 46 }, { value: 47 }, { value: 63 }, { value: 62 },
{ value: 61 }, { value: 60 }, { value: 59 }, { value: 58 }, { value: 57 },
{ value: 56 }, { value: 55 }, { value: 54 }, { value: 53 }, { value: 52 },
{ value: 51 }, { value: 50 }, { value: 49 },
];

for (var i = 0; i < 1e6; ++i) {
array.includes(3);
}

11 changes: 11 additions & 0 deletions JSTests/microbenchmarks/array-prototype-includes-int32.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function test(array, searchElement) {
return array.includes(searchElement);
}
noInline(test);

var array = new Array(1024);
for (var i = 0; i < array.length; i++)
array[i] = i;

for (var i = 0; i < 1e6; ++i)
test(array, 512);
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
function test(array, value) {
return array.includes(value);
}
noInline(test);

const array = [
"あいうえおかきくけこさしすせそたちつてとなにぬねの",
"かきくけこさしすせそたちつてとなにぬねのはひふへほ",
"さしすせそたちつてとなにぬねのはひふへほまみむめも",
"たちつてとなにぬねのはひふへほまみむめもやゆよらり",
"なにぬねのはひふへほまみむめもやゆよらりるれろわを",
"はひふへほまみむめもやゆよらりるれろわをんあいうえ",
"まみむめもやゆよらりるれろわをんあいうえおかきくけ",
"やゆよらりるれろわをんあいうえおかきくけこさしすせ",
"らりるれろわをんあいうえおかきくけこさしすせそたち",
"わをんあいうえおかきくけこさしすせそたちつてとなに",
"んあいうえおかきくけこさしすせそたちつてとなにぬね",
"ひふへほまみむめもやゆよらりるれろわをんあいうえお",
"ふへほまみむめもやゆよらりるれろわをんあいうえおか",
"へほまみむめもやゆよらりるれろわをんあいうえおかき",
"ほまみむめもやゆよらりるれろわをんあいうえおかきく",
"みむめもやゆよらりるれろわをんあいうえおかきくけこ",
"むめもやゆよらりるれろわをんあいうえおかきくけこさ",
"めもやゆよらりるれろわをんあいうえおかきくけこさし",
"もやゆよらりるれろわをんあいうえおかきくけこさしす",
"ゆよらりるれろわをんあいうえおかきくけこさしすせそ",
"よらりるれろわをんあいうえおかきくけこさしすせそた",
"らりるれろわをんあいうえおかきくけこさしすせそたち",
"りるれろわをんあいうえおかきくけこさしすせそたちつ",
"るれろわをんあいうえおかきくけこさしすせそたちつて",
"れろわをんあいうえおかきくけこさしすせそたちつてと",
"ろわをんあいうえおかきくけこさしすせそたちつてとな",
];

for (var i = 0; i < 1e6; ++i)
test(array, "もやゆよらりるれろわをんあいうえおかきくけこさしす");
36 changes: 36 additions & 0 deletions JSTests/microbenchmarks/array-prototype-includes-string-16.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
function test(array, value) {
return array.includes(value);
}
noInline(test);

const array = [
"あいうえおかきくけこさしすせそたちつてとなにぬねの",
"かきくけこさしすせそたちつてとなにぬねのはひふへほ",
"さしすせそたちつてとなにぬねのはひふへほまみむめも",
"たちつてとなにぬねのはひふへほまみむめもやゆよらり",
"なにぬねのはひふへほまみむめもやゆよらりるれろわを",
"はひふへほまみむめもやゆよらりるれろわをんあいうえ",
"まみむめもやゆよらりるれろわをんあいうえおかきくけ",
"やゆよらりるれろわをんあいうえおかきくけこさしすせ",
"らりるれろわをんあいうえおかきくけこさしすせそたち",
"わをんあいうえおかきくけこさしすせそたちつてとなに",
"んあいうえおかきくけこさしすせそたちつてとなにぬね",
"ひふへほまみむめもやゆよらりるれろわをんあいうえお",
"ふへほまみむめもやゆよらりるれろわをんあいうえおか",
"へほまみむめもやゆよらりるれろわをんあいうえおかき",
"ほまみむめもやゆよらりるれろわをんあいうえおかきく",
"みむめもやゆよらりるれろわをんあいうえおかきくけこ",
"むめもやゆよらりるれろわをんあいうえおかきくけこさ",
"めもやゆよらりるれろわをんあいうえおかきくけこさし",
"もやゆよらりるれろわをんあいうえおかきくけこさしす",
"ゆよらりるれろわをんあいうえおかきくけこさしすせそ",
"よらりるれろわをんあいうえおかきくけこさしすせそた",
"らりるれろわをんあいうえおかきくけこさしすせそたち",
"りるれろわをんあいうえおかきくけこさしすせそたちつ",
"るれろわをんあいうえおかきくけこさしすせそたちつて",
"れろわをんあいうえおかきくけこさしすせそたちつてと",
"ろわをんあいうえおかきくけこさしすせそたちつてとな",
];

for (var i = 0; i < 1e6; ++i)
test(array, "もやゆよらりるれろわをん" + "あいうえおかきくけこさしす");
35 changes: 35 additions & 0 deletions JSTests/microbenchmarks/array-prototype-includes-string-const.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
function test(array, index) {
return array.includes(index);
}
noInline(test);

const array = [
"dEXt0TxZQQQasd999!@#$%d^&",
"AAZZ!!@@**CC77zzxx1122d33",
"HelloWorldHeldloWorlddABC",
"abcABC123!@#xfyzXYZ!$d%+=",
"Zyx9!Zyx9!Zyx9!Zyx9!!d???",
"LoremIpsum1234!@#$LordemI",
"QQQQQQQQQQqqqqqqqqqq-----",
"&&&&1111%%%%2222@@@@33f33",
"TestStringasdVariousChars",
"RandomASCII~d~~~====?????",
"^^^^#####^^^f^^+++++.....",
"AZaz09!?AZaz09!?AZaz09!?%",
"Foobar##1122Foobar##1122F",
"9876543210!@d#$%^&*()_+=-",
"OneTwo3FourFive6Seven8Nin",
"Zwxy000111Zwxy000111Zwxy0",
"Spark!!!Spark???Spark%%%S",
"ShortAndSweet123??!!Short",
"BenchmarkCaseHere12345!!?",
"MultiLineString###000xxx@",
"ABCDEFGHabcdfefgh1234!!!!",
"111122223333d4444!!!!####",
"EndlessVariety?!@#$%^^^^^",
"PqrstUVWX9876pqrstUVWX987",
"MixItUpWith5omeWe!rdCHARS",
"FinallyZzYyXx!@#$4321)(*&",
];
for (var i = 0; i < 1e6; ++i)
test(array, "BenchmarkCaseHere12345!!?");
35 changes: 35 additions & 0 deletions JSTests/microbenchmarks/array-prototype-includes-string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
function test(array, index) {
return array.includes(index);
}
noInline(test);

const array = [
"dEXt0TxZQQQasd999!@#$%d^&",
"AAZZ!!@@**CC77zzxx1122d33",
"HelloWorldHeldloWorlddABC",
"abcABC123!@#xfyzXYZ!$d%+=",
"Zyx9!Zyx9!Zyx9!Zyx9!!d???",
"LoremIpsum1234!@#$LordemI",
"QQQQQQQQQQqqqqqqqqqq-----",
"&&&&1111%%%%2222@@@@33f33",
"TestStringasdVariousChars",
"RandomASCII~d~~~====?????",
"^^^^#####^^^f^^+++++.....",
"AZaz09!?AZaz09!?AZaz09!?%",
"Foobar##1122Foobar##1122F",
"9876543210!@d#$%^&*()_+=-",
"OneTwo3FourFive6Seven8Nin",
"Zwxy000111Zwxy000111Zwxy0",
"Spark!!!Spark???Spark%%%S",
"ShortAndSweet123??!!Short",
"BenchmarkCaseHere12345!!?",
"MultiLineString###000xxx@",
"ABCDEFGHabcdfefgh1234!!!!",
"111122223333d4444!!!!####",
"EndlessVariety?!@#$%^^^^^",
"PqrstUVWX9876pqrstUVWX987",
"MixItUpWith5omeWe!rdCHARS",
"FinallyZzYyXx!@#$4321)(*&",
];
for (var i = 0; i < 1e6; ++i)
test(array, "Benchmark" + "CaseHere12345!!?");
10 changes: 10 additions & 0 deletions JSTests/stress/array-prototype-includes-contiguous-nan.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

const array = [{}, {}, NaN, {}, {}];

for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(NaN), true);
}
10 changes: 10 additions & 0 deletions JSTests/stress/array-prototype-includes-double-nan.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

const array = [1.2, 2.4, NaN, 4.4, 5.4];

for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(NaN), true);
}
13 changes: 13 additions & 0 deletions JSTests/stress/array-prototype-includes-doublerepuse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

function test(array, searchElement, expected) {
for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(searchElement), expected);
}
}

test([1, 2, 3.4, 4, 5, 6, 6, 4], 3.4, true);
test([1, 2, 3.4, 4, 5, 6, 6, 4], 4.5, false);
10 changes: 10 additions & 0 deletions JSTests/stress/array-prototype-includes-hole-contiguous.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

const array = [{}, {}, {}, , {}];

for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(undefined), true);
}
10 changes: 10 additions & 0 deletions JSTests/stress/array-prototype-includes-hole-double.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

const array = [1.4, 2.4, , 4.4, 5.5];

for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(undefined), true);
}
10 changes: 10 additions & 0 deletions JSTests/stress/array-prototype-includes-hole-int32.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

const array = [1, 2, , 4, 5];

for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(undefined), true);
}
10 changes: 10 additions & 0 deletions JSTests/stress/array-prototype-includes-int32-nan.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

const array = [1, 2, NaN, 4, 5];

for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(NaN), true);
}
13 changes: 13 additions & 0 deletions JSTests/stress/array-prototype-includes-int32use.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

function test(array, searchElement, expected) {
for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(searchElement), expected);
}
}

test([1, 2, 3, 4, 5, 6, 6, 4], 5, true);
test([1, 2, 3, 4, 5, 6, 6, 4], 32, false);
14 changes: 14 additions & 0 deletions JSTests/stress/array-prototype-includes-objectuse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

function test(array, searchElement, expected) {
for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(searchElement), expected);
}
}

const obj = {};
test([1, 2, 3.4, obj, 5, 6, 6, 4], obj, true);
test([1, 2, 3.4, {}, 5, 6, 6, 4], obj, false);
16 changes: 16 additions & 0 deletions JSTests/stress/array-prototype-includes-otheruse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

function test(array, searchElement, expected) {
for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(searchElement), expected);
}
}

test([1, 2, 3.4, null, 5, 6, 6, 4], null, true);
test([1, 2, 3.4, undefined, 5, 6, 6, 4], undefined, true);
test([1, 2, 3.4, null, 5, 6, 6, 4], undefined, false);
test([1, 2, 3.4, undefined, 5, 6, 6, 4], null, false);

13 changes: 13 additions & 0 deletions JSTests/stress/array-prototype-includes-stringuse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

function test(array, searchElement, expected) {
for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(searchElement), expected);
}
}

test([1, 2, 3.4, "foo", 5, 6, 6, 4], "foo", true);
test([1, 2, 3.4, "foo", 5, 6, 6, 4], "bar", false);
14 changes: 14 additions & 0 deletions JSTests/stress/array-prototype-includes-symboluse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function sameValue(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

function test(array, searchElement, expected) {
for (let i = 0; i < testLoopCount; i++) {
sameValue(array.includes(searchElement), expected);
}
}

const sym = Symbol("foo");
test([1, 2, 3.4, sym, 5, 6, 6, 4], sym, true);
test([1, 2, 3.4, Symbol("bar"), 5, 6, 6, 4], sym, false);
Loading

0 comments on commit febcbad

Please # to comment.