Title: [267514] trunk
Revision
267514
Author
[email protected]
Date
2020-09-23 19:46:41 -0700 (Wed, 23 Sep 2020)

Log Message

Update Array.prototype.sort to be consistent with tightened spec
https://bugs.webkit.org/show_bug.cgi?id=202582

Reviewed by Yusuke Suzuki and Keith Miller.

JSTests:

Provided microbenchmarks test receivers that are half-sorted: 50% of their
items and item pairs (to accomodate merge sort) are at the right place.

Arrays of multiple sizes (8/24/64 items) are tested with both userland
and default comparator (to cover bucket sort).

* ChakraCore/test/Array/array_sort.baseline-jsc: Fix typo in error message.
* microbenchmarks/array-prototype-sort-large-array-comparator.js: Added.
* microbenchmarks/array-prototype-sort-large-array.js: Added.
* microbenchmarks/array-prototype-sort-medium-array-comparator.js: Added.
* microbenchmarks/array-prototype-sort-medium-array.js: Added.
* microbenchmarks/array-prototype-sort-small-array-comparator.js: Added.
* microbenchmarks/array-prototype-sort-small-array.js: Added.
* mozilla/js1_5/Array/regress-157652.js: Skip sorting sparse array of UINT_MAX size.
* stress/regress-188577.js: Replace sort() with unshift() and refactor.

Source/_javascript_Core:

This patch implements the spec change [1] that reduces amount of cases resulting
in an implementation-defined sort order, aligning JSC with V8 and SpiderMonkey.

To achieve this, we collect all existing non-undefined receiver elements to a
temporary array, sort it, and write back sorted items, followed by `undefined`
values and holes.

This change is proven to be web-compatible (shipping since Chrome 76) and neutral
on peak memory consumption in the wild.

Although we can unobservably detect sparse receivers, we can't avoid creating a
temporary array for common case since userland comparators may throw; string
sorting won't measurably benefit from this, only increasing code complexity.

This change uses @putByValDirect unless the spec requires [[Set]], avoids using
closure variables, and adds a few drive-by optimizations, resulting in ~22%
faster string sorting and 13% speed-up for userland comparators.
Dromaeo/jslib is neutral.

[1]: https://github.com/tc39/ecma262/pull/1585

* builtins/ArrayPrototype.js:
(sort.stringComparator):
Optimization #1: replace char-by-char comparison loop with > operator, aligning
JSC with V8 and SpiderMonkey. This semantically equivalent change alone is a ~15%
progression for string sort.

(sort.compact):
(sort.commit):
Optimization #2: copy large non-numeric arrays in a loop rather than @appendMemcpy.
Using the latter unconditionally regresses provided microbenchmarks.

(sort.merge):
Optimization #3: replace `typeof` check and negation with strict equality.

(sort.mergeSort):
Optimization #4: always return sorted array instead of copying, even if it's the buffer.
Tweak: create the buffer with correct length.

(sort.bucketSort):
Optimization #5: avoid emitting 2 extra get_by_val ops by saving bucket lookup to a variable.
Tweak: create new bucket via array literal.

(sort): Fix typo in error message.
(sort.compactSparse): Deleted.
(sort.compactSlow): Deleted.
(sort.comparatorSort): Deleted.
(sort.stringSort): Deleted.
* runtime/ObjectConstructor.cpp:
(JSC::ObjectConstructor::finishCreation):
Remove @Object.@getPrototypeOf as it's now unused and we have @getPrototypeOf intrinsic anyway.

LayoutTests:

While adding new LayoutTests for JS-only features is undesirable, it's a
quick-and-dirty way to import the tests [1] and fix the call count/order
of observable operations via debug() and text expectations.

The tests are imported into LayoutTests/js/dom instead of LayoutTests/js for
run-_javascript_core-tests to ignore them as they require array-sort-harness.js.

These files will be removed shortly in favor of thorough test262 coverage,
which is required for the proposal [2] to be merged.

[1]: https://gist.github.com/szuend/05ae15b4e1329b264ab4c9a1cda09242
[2]: https://github.com/tc39/ecma262/pull/1585

* TestExpectations: Mark a test as slow.
* js/dom/array-sort-*-expected.txt: Added.
* js/dom/array-sort-*.html: Added.
* js/dom/script-tests/array-sort-*.js: Added.
* js/resources/array-sort-harness.js: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChakraCore/test/Array/array_sort.baseline-jsc (267513 => 267514)


--- trunk/JSTests/ChakraCore/test/Array/array_sort.baseline-jsc	2020-09-24 02:34:03 UTC (rev 267513)
+++ trunk/JSTests/ChakraCore/test/Array/array_sort.baseline-jsc	2020-09-24 02:46:41 UTC (rev 267514)
@@ -7,5 +7,5 @@
 1,2,3
 10
 1,1.2,4,4.8,12
-TypeError: Array.prototype.sort requires the comparsion function be a function or undefined
+TypeError: Array.prototype.sort requires the comparator argument to be a function or undefined
 1,2,3

Modified: trunk/JSTests/ChangeLog (267513 => 267514)


--- trunk/JSTests/ChangeLog	2020-09-24 02:34:03 UTC (rev 267513)
+++ trunk/JSTests/ChangeLog	2020-09-24 02:46:41 UTC (rev 267514)
@@ -1,3 +1,26 @@
+2020-09-23  Alexey Shvayka  <[email protected]>
+
+        Update Array.prototype.sort to be consistent with tightened spec
+        https://bugs.webkit.org/show_bug.cgi?id=202582
+
+        Reviewed by Yusuke Suzuki and Keith Miller.
+
+        Provided microbenchmarks test receivers that are half-sorted: 50% of their
+        items and item pairs (to accomodate merge sort) are at the right place.
+
+        Arrays of multiple sizes (8/24/64 items) are tested with both userland
+        and default comparator (to cover bucket sort).
+
+        * ChakraCore/test/Array/array_sort.baseline-jsc: Fix typo in error message.
+        * microbenchmarks/array-prototype-sort-large-array-comparator.js: Added.
+        * microbenchmarks/array-prototype-sort-large-array.js: Added.
+        * microbenchmarks/array-prototype-sort-medium-array-comparator.js: Added.
+        * microbenchmarks/array-prototype-sort-medium-array.js: Added.
+        * microbenchmarks/array-prototype-sort-small-array-comparator.js: Added.
+        * microbenchmarks/array-prototype-sort-small-array.js: Added.
+        * mozilla/js1_5/Array/regress-157652.js: Skip sorting sparse array of UINT_MAX size.
+        * stress/regress-188577.js: Replace sort() with unshift() and refactor.
+
 2020-09-23  Yusuke Suzuki  <[email protected]>
 
         [JSC] Intl spec update: handle awkward rounding behavior

Added: trunk/JSTests/microbenchmarks/array-prototype-sort-large-array-comparator.js (0 => 267514)


--- trunk/JSTests/microbenchmarks/array-prototype-sort-large-array-comparator.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/array-prototype-sort-large-array-comparator.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,9 @@
+var comparator = (a, b) => a - b;
+
+for (var i = 0; i < 1e4; ++i) {
+    [
+        0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,31,30,29,28,27,26,25,24,23,22,21,20,19,
+        18,17,16,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,63,62,61,60,59,58,57,
+        56,55,54,53,52,51,50,49,48
+    ].sort(comparator);
+}

Added: trunk/JSTests/microbenchmarks/array-prototype-sort-large-array.js (0 => 267514)


--- trunk/JSTests/microbenchmarks/array-prototype-sort-large-array.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/array-prototype-sort-large-array.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,7 @@
+for (var i = 0; i < 1e4; ++i) {
+    [
+        "0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?","O","N","M","L","K","J","I",
+        "H","G","F","E","D","C","B","A","@","P","Q","R","S","T","U","V","W","X","Y","Z","[","\\","]",
+        "^","_","o","n","m","l","k","j","i","h","g","f","e","d","c","b","a","`"
+    ].sort();
+}

Added: trunk/JSTests/microbenchmarks/array-prototype-sort-medium-array-comparator.js (0 => 267514)


--- trunk/JSTests/microbenchmarks/array-prototype-sort-medium-array-comparator.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/array-prototype-sort-medium-array-comparator.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,5 @@
+var comparator = (a, b) => a - b;
+
+for (var i = 0; i < 1e5; ++i) {
+    [0,1,2,3,4,5,11,10,9,8,7,6,12,13,14,15,16,17,23,22,21,20,19,18].sort(comparator);
+}

Added: trunk/JSTests/microbenchmarks/array-prototype-sort-medium-array.js (0 => 267514)


--- trunk/JSTests/microbenchmarks/array-prototype-sort-medium-array.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/array-prototype-sort-medium-array.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,3 @@
+for (var i = 0; i < 1e5; ++i) {
+    ["A","B","C","D","E","F","L","K","J","I","H","G","M","N","O","P","Q","R","X","W","V","U","T","S"].sort();
+}

Added: trunk/JSTests/microbenchmarks/array-prototype-sort-small-array-comparator.js (0 => 267514)


--- trunk/JSTests/microbenchmarks/array-prototype-sort-small-array-comparator.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/array-prototype-sort-small-array-comparator.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,5 @@
+var comparator = (a, b) => a - b;
+
+for (var i = 0; i < 1e5; ++i) {
+    [0,1,2,3,7,6,5,4].sort(comparator);
+}

Added: trunk/JSTests/microbenchmarks/array-prototype-sort-small-array.js (0 => 267514)


--- trunk/JSTests/microbenchmarks/array-prototype-sort-small-array.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/array-prototype-sort-small-array.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,3 @@
+for (var i = 0; i < 1e5; ++i) {
+    ["A","B","C","D","H","G","F","E"].sort();
+}

Modified: trunk/JSTests/mozilla/js1_5/Array/regress-157652.js (267513 => 267514)


--- trunk/JSTests/mozilla/js1_5/Array/regress-157652.js	2020-09-24 02:34:03 UTC (rev 267513)
+++ trunk/JSTests/mozilla/js1_5/Array/regress-157652.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -117,9 +117,12 @@
 printStatus(summary);
 
 // JSC doesn't run out of memory, so we don't expect an abnormal exit code 
-//expectExitCode(3);
-var IN_RHINO = inRhino();
+// expectExitCode(3);
 
+// As of https://webkit.org/b/202582, JSC copies internal spare array to a
+// temporary buffer like Rhino, SpiderMonkey, and V8.
+// var IN_RHINO = inRhino();
+var IN_RHINO = true;
 if (!IN_RHINO)
 {
   var a1=Array(0xFFFFFFFF);

Modified: trunk/JSTests/stress/regress-188577.js (267513 => 267514)


--- trunk/JSTests/stress/regress-188577.js	2020-09-24 02:34:03 UTC (rev 267513)
+++ trunk/JSTests/stress/regress-188577.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -2,16 +2,11 @@
 
 var exception;
 try {
-    var i = 25000;
-    var args = [];
-    var v3;
-    while (i--)
-        args[i] = "a";
-    var argsList = args.join();
-    setter = Function(argsList, "");
-    Object.defineProperty(args, '0', {set: setter});
-    args.sort();
-
+    var args = new Array(25000).fill("a");
+    Object.defineProperty(args, "0", {
+        set: new Function(args.join(), ""),
+    });
+    args.unshift(1);
 } catch (e) {
     exception = e;
 }

Modified: trunk/LayoutTests/ChangeLog (267513 => 267514)


--- trunk/LayoutTests/ChangeLog	2020-09-24 02:34:03 UTC (rev 267513)
+++ trunk/LayoutTests/ChangeLog	2020-09-24 02:46:41 UTC (rev 267514)
@@ -1,3 +1,29 @@
+2020-09-23  Alexey Shvayka  <[email protected]>
+
+        Update Array.prototype.sort to be consistent with tightened spec
+        https://bugs.webkit.org/show_bug.cgi?id=202582
+
+        Reviewed by Yusuke Suzuki and Keith Miller.
+
+        While adding new LayoutTests for JS-only features is undesirable, it's a
+        quick-and-dirty way to import the tests [1] and fix the call count/order
+        of observable operations via debug() and text expectations.
+
+        The tests are imported into LayoutTests/js/dom instead of LayoutTests/js for
+        run-_javascript_core-tests to ignore them as they require array-sort-harness.js.
+
+        These files will be removed shortly in favor of thorough test262 coverage,
+        which is required for the proposal [2] to be merged.
+
+        [1]: https://gist.github.com/szuend/05ae15b4e1329b264ab4c9a1cda09242
+        [2]: https://github.com/tc39/ecma262/pull/1585
+
+        * TestExpectations: Mark a test as slow.
+        * js/dom/array-sort-*-expected.txt: Added.
+        * js/dom/array-sort-*.html: Added.
+        * js/dom/script-tests/array-sort-*.js: Added.
+        * js/resources/array-sort-harness.js: Added.
+
 2020-09-23  Yusuke Suzuki  <[email protected]>
 
         Unreviewed, we should put it under js/dom since it is not usable in JSC shell

Modified: trunk/LayoutTests/TestExpectations (267513 => 267514)


--- trunk/LayoutTests/TestExpectations	2020-09-24 02:34:03 UTC (rev 267513)
+++ trunk/LayoutTests/TestExpectations	2020-09-24 02:46:41 UTC (rev 267514)
@@ -1810,6 +1810,7 @@
 webkit.org/b/121452 [ Debug ] fast/frames/lots-of-iframes.html [ Slow ]
 webkit.org/b/135053 [ Debug ] html5lib/webkit-resumer.html [ Slow ]
 [ Debug ] js/dfg-double-vote-fuzz.html [ Slow ]
+[ Debug ] js/array-sort-small-sparse-array-with-large-length.html [ Slow ]
 [ Debug ] js/dom/string-replacement-outofmemory.html [ Slow ]
 [ Debug ] js/dfg-osr-entry-hoisted-clobbered-structure-check.html [ Slow ]
 [ Debug ] editing/selection/move-by-character-brute-force.html [ Slow ]

Added: trunk/LayoutTests/js/dom/array-sort-accessor-adds-two-elements-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-adds-two-elements-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-adds-two-elements-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,8 @@
+.sort(comparator):
+c,b,a,d,undefined,undefined,undefined,hole,foo,bar,foo,bar
+.sort():
+a,b,c,d,undefined,undefined,undefined,hole,foo,bar
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-accessor-adds-two-elements.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-adds-two-elements.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-adds-two-elements.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-accessor-decreases-length-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-decreases-length-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-decreases-length-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,8 @@
+.sort():
+b,c,undefined,undefined
+.sort(comparator):
+c,b,a,d,undefined,undefined,undefined
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-accessor-decreases-length.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-decreases-length.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-decreases-length.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-accessor-deletes-predecessor-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-deletes-predecessor-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-deletes-predecessor-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,8 @@
+.sort(comparator):
+c,b,a,d,undefined,undefined,undefined,hole
+.sort():
+a,hole,c,d,undefined,undefined,undefined,hole
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-accessor-deletes-predecessor.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-deletes-predecessor.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-deletes-predecessor.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-accessor-deletes-successor-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-deletes-successor-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-deletes-successor-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,8 @@
+.sort():
+a,c,d,hole,undefined,undefined,hole,hole
+.sort(comparator):
+c,b,a,d,undefined,undefined,undefined,hole
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-accessor-deletes-successor.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-deletes-successor.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-deletes-successor.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-accessor-increases-length-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-increases-length-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-increases-length-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,8 @@
+.sort(comparator):
+c,b,a,d,undefined,undefined,undefined,hole,hole,hole,hole,hole
+.sort():
+a,b,c,d,undefined,undefined,undefined,hole,hole,hole
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-accessor-increases-length.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-increases-length.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-increases-length.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-accessor-removes-two-elements-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-removes-two-elements-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-removes-two-elements-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,8 @@
+.sort():
+b,c,undefined,undefined
+.sort(comparator):
+c,b,a,d,undefined,undefined,undefined
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-accessor-removes-two-elements.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-removes-two-elements.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-removes-two-elements.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-accessor-sets-predecessor-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-sets-predecessor-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-sets-predecessor-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,8 @@
+.sort(comparator):
+c,b,a,d,undefined,undefined,undefined,hole
+.sort():
+a,foobar,c,d,undefined,undefined,undefined,hole
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-accessor-sets-predecessor.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-sets-predecessor.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-sets-predecessor.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-accessor-sets-successor-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-sets-successor-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-sets-successor-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,8 @@
+.sort():
+a,c,d,foobar,undefined,undefined,undefined,hole
+.sort(comparator):
+c,b,a,d,undefined,undefined,undefined,hole
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-accessor-sets-successor.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessor-sets-successor.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessor-sets-successor.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-accessors-on-array-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessors-on-array-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessors-on-array-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,12 @@
+.sort(comparator):
+get [2]
+get [3]
+set [2] with d
+set [3] with undefined
+get [2]
+get [3]
+c,a,d,undefined,undefined,undefined,undefined,hole
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-accessors-on-array.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessors-on-array.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessors-on-array.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-accessors-on-object-proto-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessors-on-object-proto-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessors-on-object-proto-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,8 @@
+.sort(comparator):
+get [2]
+set [2] with a
+c,b,hole,d,undefined,undefined,undefined,hole
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-accessors-on-object-proto.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-accessors-on-object-proto.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-accessors-on-object-proto.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-non-configurable-element-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-non-configurable-element-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-non-configurable-element-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,5 @@
+PASS array.sort() threw exception TypeError: Unable to delete property..
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-non-configurable-element.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-non-configurable-element.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-non-configurable-element.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-non-configurable-proto-hole-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-non-configurable-proto-hole-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-non-configurable-proto-hole-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,6 @@
+.sort():
+a,b,c,d,foo,undefined,undefined,hole
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-non-configurable-proto-hole.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-non-configurable-proto-hole.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-non-configurable-proto-hole.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-non-writeable-element-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-non-writeable-element-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-non-writeable-element-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,5 @@
+PASS array.sort((a, b) => a - b) threw exception TypeError: Attempted to assign to readonly property..
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-non-writeable-element.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-non-writeable-element.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-non-writeable-element.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-non-writeable-proto-hole-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-non-writeable-proto-hole-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-non-writeable-proto-hole-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,5 @@
+PASS array.sort() threw exception TypeError: Attempted to assign to readonly property..
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-non-writeable-proto-hole.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-non-writeable-proto-hole.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-non-writeable-proto-hole.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-proxy-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-proxy-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-proxy-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,28 @@
+.sort():
+get ['length']
+has ['0']
+get ['0']
+has ['1']
+get ['1']
+has ['2']
+has ['3']
+get ['3']
+has ['4']
+get ['4']
+has ['5']
+has ['6']
+get ['6']
+has ['7']
+get ['7']
+set ['0'] = a
+set ['1'] = b
+set ['2'] = c
+set ['3'] = d
+set ['4'] = undefined
+set ['5'] = undefined
+delete ['6']
+delete ['7']
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-proxy-proto-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-proxy-proto-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-proxy-proto-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,9 @@
+.sort(comparator):
+has ['2']
+has ['5']
+set ['2'] = a
+set ['5'] = undefined
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-proxy-proto.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-proxy-proto.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-proxy-proto.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-proxy.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-proxy.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-proxy.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/array-sort-short-arrays-expected.txt (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-short-arrays-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-short-arrays-expected.txt	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,12 @@
+.sort(comparator) 0-length array:
+get [0]
+undefined
+.sort() 1-length array:
+get [0]
+set [0] with bar
+get [0]
+bar,undefined
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/js/dom/array-sort-short-arrays.html (0 => 267514)


--- trunk/LayoutTests/js/dom/array-sort-short-arrays.html	                        (rev 0)
+++ trunk/LayoutTests/js/dom/array-sort-short-arrays.html	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-adds-two-elements.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-adds-two-elements.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-adds-two-elements.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,23 @@
+// author: Simon Zünd
+
+let array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { array.push('foo'); array.push('bar'); return this.foo; },
+  set(v) { this.foo = v; }
+});
+
+debug('.sort(comparator):');
+array.sort((a, b) => a - b);
+log(array);
+
+array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { return this.foo; },
+  set(v) {  array.push('foo'); array.push('bar'); this.foo = v; }
+});
+
+debug('.sort():');
+array.sort();
+log(array);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-decreases-length.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-decreases-length.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-decreases-length.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,23 @@
+// author: Simon Zünd
+
+let array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { array.length = array.length - 2; return this.foo; },
+  set(v) { this.foo = v; }
+});
+
+debug('.sort():');
+array.sort();
+log(array);
+
+array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { return this.foo; },
+  set(v) { array.length = array.length - 2; this.foo = v; }
+});
+
+debug('.sort(comparator):');
+array.sort((a, b) => a - b);
+log(array);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-deletes-predecessor.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-deletes-predecessor.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-deletes-predecessor.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,23 @@
+// author: Simon Zünd
+
+let array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { delete array[1]; return this.foo; },
+  set(v) { this.foo = v; }
+});
+
+debug('.sort(comparator):');
+array.sort((a, b) => a - b);
+log(array);
+
+array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { return this.foo; },
+  set(v) { delete array[1]; this.foo = v; }
+});
+
+debug('.sort():');
+array.sort();
+log(array);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-deletes-successor.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-deletes-successor.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-deletes-successor.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,23 @@
+// author: Simon Zünd
+
+let array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { delete array[3]; return this.foo; },
+  set(v) { this.foo = v; }
+});
+
+debug('.sort():');
+array.sort();
+log(array);
+
+array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { return this.foo; },
+  set(v) { delete array[3]; this.foo = v; }
+});
+
+debug('.sort(comparator):');
+array.sort((a, b) => a - b);
+log(array);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-increases-length.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-increases-length.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-increases-length.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,23 @@
+// author: Simon Zünd
+
+let array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { array.length = array.length + 2; return this.foo; },
+  set(v) { this.foo = v; }
+});
+
+debug('.sort(comparator):');
+array.sort((a, b) => a - b);
+log(array);
+
+array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { return this.foo; },
+  set(v) { array.length = array.length + 2; this.foo = v; }
+});
+
+debug('.sort():');
+array.sort();
+log(array);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-removes-two-elements.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-removes-two-elements.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-removes-two-elements.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,23 @@
+// author: Simon Zünd
+
+let array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { array.pop(); array.pop(); return this.foo; },
+  set(v) { this.foo = v; }
+});
+
+debug('.sort():');
+array.sort();
+log(array);
+
+array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { return this.foo; },
+  set(v) { array.pop(); array.pop(); this.foo = v; }
+});
+
+debug('.sort(comparator):');
+array.sort((a, b) => a - b);
+log(array);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-sets-predecessor.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-sets-predecessor.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-sets-predecessor.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,23 @@
+// author: Simon Zünd
+
+let array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { array[1] = 'foobar'; return this.foo; },
+  set(v) { this.foo = v; }
+});
+
+debug('.sort(comparator):');
+array.sort((a, b) => a - b);
+log(array);
+
+array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { return this.foo; },
+  set(v) { array[1] = 'foobar'; this.foo = v; }
+});
+
+debug('.sort():');
+array.sort();
+log(array);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-sets-successor.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-sets-successor.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-accessor-sets-successor.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,23 @@
+// author: Simon Zünd
+
+let array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { array[3] = 'foobar'; return this.foo; },
+  set(v) { this.foo = v; }
+});
+
+debug('.sort():');
+array.sort();
+log(array);
+
+array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { return this.foo; },
+  set(v) { array[3] = 'foobar'; this.foo = v; }
+});
+
+debug('.sort(comparator):');
+array.sort((a, b) => a - b);
+log(array);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-accessors-on-array.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-accessors-on-array.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-accessors-on-array.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,17 @@
+// author: Simon Zünd
+
+const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '2', {
+  get() { debug('get [2]'); return this.foo; },
+  set(v) { debug(`set [2] with ${v}`); this.foo = v; }
+});
+
+Object.defineProperty(array, '3', {
+  get() { debug('get [3]'); return this.bar; },
+  set(v) { debug(`set [3] with ${v}`); this.bar = v; }
+});
+
+debug('.sort(comparator):');
+array.sort((a, b) => a - b);
+log(array);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-accessors-on-object-proto.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-accessors-on-object-proto.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-accessors-on-object-proto.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,21 @@
+// author: Simon Zünd
+
+const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+const object = {};
+
+Object.defineProperty(object, '2', {
+  get() { debug('get [2]'); return this.foo; },
+  set(v) { debug(`set [2] with ${v}`); this.foo = v; }
+});
+
+Object.defineProperty(object, '3', {
+  get() { debug('get [3]'); return this.bar; },
+  set(v) { debug(`set [3] with ${v}`); this.bar = v; }
+});
+
+array.__proto__ = object;
+
+debug('.sort(comparator):');
+Array.prototype.sort.call(array, (a, b) => a - b);
+log(array);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-non-configurable-element.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-non-configurable-element.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-non-configurable-element.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,19 @@
+// author: Simon Zünd
+
+const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '6', {
+  value: 'a',
+  configurable: false,
+  enumerable: true,
+  writable: true,
+});
+
+Object.defineProperty(array, '7', {
+  value: 'd',
+  configurable: false,
+  enumerable: true,
+  writable: true,
+});
+
+shouldThrow('array.sort()', '"TypeError: Unable to delete property."');

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-non-configurable-proto-hole.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-non-configurable-proto-hole.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-non-configurable-proto-hole.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,14 @@
+// author: Simon Zünd
+
+Object.defineProperty(Object.prototype, '2', {
+  value: 'foo',
+  configurable: false,
+  enumerable: true,
+  writable: true,
+});
+
+const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+debug('.sort():');
+array.sort();
+log(array);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-non-writeable-element.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-non-writeable-element.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-non-writeable-element.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,19 @@
+// author: Simon Zünd
+
+const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+Object.defineProperty(array, '1', {
+  value: 'c',
+  configurable: true,
+  enumerable: true,
+  writable: false,
+});
+
+Object.defineProperty(array, '3', {
+  value: 'b',
+  configurable: true,
+  enumerable: true,
+  writable: false,
+});
+
+shouldThrow('array.sort((a, b) => a - b)', '"TypeError: Attempted to assign to readonly property."');

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-non-writeable-proto-hole.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-non-writeable-proto-hole.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-non-writeable-proto-hole.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,12 @@
+// author: Simon Zünd
+
+Object.defineProperty(Object.prototype, '2', {
+  value: 'foo',
+  configurable: true,
+  enumerable: true,
+  writable: false,
+});
+
+const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+shouldThrow('array.sort()', '"TypeError: Attempted to assign to readonly property."');

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-proxy-proto.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-proxy-proto.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-proxy-proto.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,31 @@
+// author: Simon Zünd
+
+const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+const proxy = new Proxy({}, {
+  get: (target, name) => {
+    debug("get ['" + name + "']");
+    return target[name];
+  },
+
+  set: (target, name, value) => {
+    debug("set ['" + name + "'] = " + value);
+    target[name] = value;
+    return true;
+  },
+
+  has: (target, name) => {
+    debug("has ['" + name + "']");
+    return name in target;
+  },
+
+  deleteProperty: (target, name) => {
+    debug("delete ['" + name + "']");
+    return delete target[name];
+  }
+});
+
+array.__proto__ = proxy;
+
+debug('.sort(comparator):');
+Array.prototype.sort.call(array, (a, b) => a - b);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-proxy.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-proxy.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-proxy.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,29 @@
+// author: Simon Zünd
+
+const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
+
+const proxy = new Proxy(array, {
+  get: (target, name) => {
+    debug("get ['" + name + "']");
+    return target[name];
+  },
+
+  set: (target, name, value) => {
+    debug("set ['" + name + "'] = " + value);
+    target[name] = value;
+    return true;
+  },
+
+  has: (target, name) => {
+    debug("has ['" + name + "']");
+    return name in target;
+  },
+
+  deleteProperty: (target, name) => {
+    debug("delete ['" + name + "']");
+    return delete target[name];
+  }
+});
+
+debug('.sort():');
+Array.prototype.sort.call(proxy);

Added: trunk/LayoutTests/js/dom/script-tests/array-sort-short-arrays.js (0 => 267514)


--- trunk/LayoutTests/js/dom/script-tests/array-sort-short-arrays.js	                        (rev 0)
+++ trunk/LayoutTests/js/dom/script-tests/array-sort-short-arrays.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,17 @@
+// author: Simon Zünd
+
+const array = [];
+
+Object.defineProperty(array, '0', {
+  get() { debug('get [0]'); return this.foo; },
+  set(v) { debug(`set [0] with ${v}`); this.foo = v; }
+});
+
+debug(".sort(comparator) 0-length array:");
+array.sort((a, b) => a - b);
+log(array);
+
+debug(".sort() 1-length array:");
+array.push('bar');
+array.sort();
+log(array);

Added: trunk/LayoutTests/js/resources/array-sort-harness.js (0 => 267514)


--- trunk/LayoutTests/js/resources/array-sort-harness.js	                        (rev 0)
+++ trunk/LayoutTests/js/resources/array-sort-harness.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -0,0 +1,12 @@
+function log(array) {
+  let buf = '';
+  for (let index = 0; index < array.length; index++) {
+    if (array.hasOwnProperty(index)) {
+      buf += String(array[index]);
+    } else {
+      buf += 'hole';
+    }
+    if (index < array.length - 1) buf += ',';
+  }
+  debug(buf);
+}

Modified: trunk/Source/_javascript_Core/ChangeLog (267513 => 267514)


--- trunk/Source/_javascript_Core/ChangeLog	2020-09-24 02:34:03 UTC (rev 267513)
+++ trunk/Source/_javascript_Core/ChangeLog	2020-09-24 02:46:41 UTC (rev 267514)
@@ -1,3 +1,62 @@
+2020-09-23  Alexey Shvayka  <[email protected]>
+
+        Update Array.prototype.sort to be consistent with tightened spec
+        https://bugs.webkit.org/show_bug.cgi?id=202582
+
+        Reviewed by Yusuke Suzuki and Keith Miller.
+
+        This patch implements the spec change [1] that reduces amount of cases resulting
+        in an implementation-defined sort order, aligning JSC with V8 and SpiderMonkey.
+
+        To achieve this, we collect all existing non-undefined receiver elements to a
+        temporary array, sort it, and write back sorted items, followed by `undefined`
+        values and holes.
+
+        This change is proven to be web-compatible (shipping since Chrome 76) and neutral
+        on peak memory consumption in the wild.
+
+        Although we can unobservably detect sparse receivers, we can't avoid creating a
+        temporary array for common case since userland comparators may throw; string
+        sorting won't measurably benefit from this, only increasing code complexity.
+
+        This change uses @putByValDirect unless the spec requires [[Set]], avoids using
+        closure variables, and adds a few drive-by optimizations, resulting in ~22%
+        faster string sorting and 13% speed-up for userland comparators.
+        Dromaeo/jslib is neutral.
+
+        [1]: https://github.com/tc39/ecma262/pull/1585
+
+        * builtins/ArrayPrototype.js:
+        (sort.stringComparator):
+        Optimization #1: replace char-by-char comparison loop with > operator, aligning
+        JSC with V8 and SpiderMonkey. This semantically equivalent change alone is a ~15%
+        progression for string sort.
+
+        (sort.compact):
+        (sort.commit):
+        Optimization #2: copy large non-numeric arrays in a loop rather than @appendMemcpy.
+        Using the latter unconditionally regresses provided microbenchmarks.
+
+        (sort.merge):
+        Optimization #3: replace `typeof` check and negation with strict equality.
+
+        (sort.mergeSort):
+        Optimization #4: always return sorted array instead of copying, even if it's the buffer.
+        Tweak: create the buffer with correct length.
+
+        (sort.bucketSort):
+        Optimization #5: avoid emitting 2 extra get_by_val ops by saving bucket lookup to a variable.
+        Tweak: create new bucket via array literal.
+
+        (sort): Fix typo in error message.
+        (sort.compactSparse): Deleted.
+        (sort.compactSlow): Deleted.
+        (sort.comparatorSort): Deleted.
+        (sort.stringSort): Deleted.
+        * runtime/ObjectConstructor.cpp:
+        (JSC::ObjectConstructor::finishCreation):
+        Remove @Object.@getPrototypeOf as it's now unused and we have @getPrototypeOf intrinsic anyway.
+
 2020-09-23  Yusuke Suzuki  <[email protected]>
 
         [JSC] Intl spec update: handle awkward rounding behavior

Modified: trunk/Source/_javascript_Core/builtins/ArrayPrototype.js (267513 => 267514)


--- trunk/Source/_javascript_Core/builtins/ArrayPrototype.js	2020-09-24 02:34:03 UTC (rev 267513)
+++ trunk/Source/_javascript_Core/builtins/ArrayPrototype.js	2020-09-24 02:46:41 UTC (rev 267514)
@@ -320,107 +320,56 @@
         var aString = a.string;
         var bString = b.string;
 
-        var aLength = aString.length;
-        var bLength = bString.length;
-        var length = min(aLength, bLength);
+        if (aString === bString)
+            return 0;
 
-        for (var i = 0; i < length; ++i) {
-            var aCharCode = aString.@charCodeAt(i);
-            var bCharCode = bString.@charCodeAt(i);
-
-            if (aCharCode == bCharCode)
-                continue;
-
-            return aCharCode - bCharCode;
-        }
-
-        return aLength - bLength;
+        return aString > bString ? 1 : -1;
     }
 
-    // Move undefineds and holes to the end of a sparse array. Result is [values..., undefineds..., holes...].
-    function compactSparse(array, dst, src, length)
+    function compact(receiver, receiverLength, compacted, isStringSort)
     {
-        var values = [ ];
-        var seen = { };
-        var valueCount = 0;
         var undefinedCount = 0;
+        var compactedIndex = 0;
 
-        // Clean up after the in-progress non-sparse compaction that failed.
-        for (var i = dst; i < src; ++i)
-            delete array[i];
-
-        for (var object = array; object; object = @Object.@getPrototypeOf(object)) {
-            var propertyNames = @Object.@getOwnPropertyNames(object);
-            for (var i = 0; i < propertyNames.length; ++i) {
-                var index = propertyNames[i];
-                if (index < length) { // Exclude non-numeric properties and properties past length.
-                    if (seen[index]) // Exclude duplicates.
-                        continue;
-                    seen[index] = 1;
-
-                    var value = array[index];
-                    delete array[index];
-
-                    if (value === @undefined) {
-                        ++undefinedCount;
-                        continue;
-                    }
-
-                    array[valueCount++] = value;
+        for (var i = 0; i < receiverLength; ++i) {
+            if (i in receiver) {
+                var value = receiver[i];
+                if (value === @undefined)
+                    ++undefinedCount;
+                else {
+                    @putByValDirect(compacted, compactedIndex, 
+                        isStringSort ? {string: @toString(value), value} : value);
+                    ++compactedIndex;
                 }
             }
         }
 
-        for (var i = valueCount; i < valueCount + undefinedCount; ++i)
-            array[i] = @undefined;
-
-        return valueCount;
+        return undefinedCount;
     }
 
-    function compactSlow(array, length)
+    // Move undefineds and holes to the end of an array. Result is [values..., undefineds..., holes...].
+    function commit(receiver, receiverLength, sorted, undefinedCount)
     {
-        var holeCount = 0;
+        @assert(@isJSArray(sorted));
+        var sortedLength = sorted.length;
+        @assert(sortedLength + undefinedCount <= receiverLength);
 
-        var dst = 0;
-        var src = ""
-        for (; src < length; ++src) {
-            if (!(src in array)) {
-                ++holeCount;
-                if (holeCount < 256)
-                    continue;
-                return compactSparse(array, dst, src, length);
-            }
-
-            var value = array[src];
-            if (value === @undefined)
-                continue;
-
-            array[dst++] = value;
+        var i = 0;
+        if (@isJSArray(receiver) && sortedLength >= 64 && typeof sorted[0] !== "number") { // heuristic
+            @appendMemcpy(receiver, sorted, 0);
+            i = sortedLength;
+        } else {
+            for (; i < sortedLength; ++i)
+                receiver[i] = sorted[i];
         }
 
-        var valueCount = dst;
-        var undefinedCount = length - valueCount - holeCount;
+        for (; i < sortedLength + undefinedCount; ++i)
+            receiver[i] = @undefined;
 
-        for (var i = valueCount; i < valueCount + undefinedCount; ++i)
-            array[i] = @undefined;
-
-        for (var i = valueCount + undefinedCount; i < length; ++i)
-            delete array[i];
-
-        return valueCount;
+        for (; i < receiverLength; ++i)
+            delete receiver[i];
     }
 
-    // Move undefineds and holes to the end of an array. Result is [values..., undefineds..., holes...].
-    function compact(array, length)
-    {
-        for (var i = 0; i < array.length; ++i) {
-            if (array[i] === @undefined)
-                return compactSlow(array, length);
-        }
-
-        return length;
-    }
-
     function merge(dst, src, srcIndex, srcEnd, width, comparator)
     {
         var left = srcIndex;
@@ -431,26 +380,30 @@
         for (var dstIndex = left; dstIndex < rightEnd; ++dstIndex) {
             if (right < rightEnd) {
                 if (left >= leftEnd) {
-                    dst[dstIndex] = src[right++];
+                    @putByValDirect(dst, dstIndex, src[right]);
+                    ++right;
                     continue;
                 }
 
+                // See https://bugs.webkit.org/show_bug.cgi?id=47825 on boolean special-casing
                 var comparisonResult = comparator(src[right], src[left]);
-                if ((typeof comparisonResult === "boolean" && !comparisonResult) || comparisonResult < 0) {
-                    dst[dstIndex] = src[right++];
+                if (comparisonResult === false || comparisonResult < 0) {
+                    @putByValDirect(dst, dstIndex, src[right]);
+                    ++right;
                     continue;
                 }
 
             }
 
-            dst[dstIndex] = src[left++];
+            @putByValDirect(dst, dstIndex, src[left]);
+            ++left;
         }
     }
 
-    function mergeSort(array, valueCount, comparator)
+    function mergeSort(array, comparator)
     {
-        var buffer = [ ];
-        buffer.length = valueCount;
+        var valueCount = array.length;
+        var buffer = @newArrayWithSize(valueCount);
 
         var dst = buffer;
         var src = ""
@@ -463,18 +416,17 @@
             dst = tmp;
         }
 
-        if (src != array) {
-            for(var i = 0; i < valueCount; i++)
-                array[i] = src[i];
-        }
+        return src;
     }
 
     function bucketSort(array, dst, bucket, depth)
     {
         if (bucket.length < 32 || depth > 32) {
-            mergeSort(bucket, bucket.length, stringComparator);
-            for (var i = 0; i < bucket.length; ++i)
-                array[dst++] = bucket[i].value;
+            var sorted = mergeSort(bucket, stringComparator);
+            for (var i = 0; i < sorted.length; ++i) {
+                @putByValDirect(array, dst, sorted[i].value);
+                ++dst;
+            }
             return dst;
         }
 
@@ -483,14 +435,17 @@
             var entry = bucket[i];
             var string = entry.string;
             if (string.length == depth) {
-                array[dst++] = entry.value;
+                @putByValDirect(array, dst, entry.value);
+                ++dst;
                 continue;
             }
 
             var c = string.@charCodeAt(depth);
-            if (!buckets[c])
-                buckets[c] = [ ];
-            buckets[c][buckets[c].length] = entry;
+            var cBucket = buckets[c];
+            if (cBucket)
+                @putByValDirect(cBucket, cBucket.length, entry);
+            else
+                @putByValDirect(buckets, c, [ entry ]);
         }
 
         for (var i = 0; i < buckets.length; ++i) {
@@ -502,42 +457,32 @@
         return dst;
     }
 
-    function comparatorSort(array, length, comparator)
-    {
-        var valueCount = compact(array, length);
-        mergeSort(array, valueCount, comparator);
-    }
+    var isStringSort = false;
+    if (comparator === @undefined)
+        isStringSort = true;
+    else if (!@isCallable(comparator))
+        @throwTypeError("Array.prototype.sort requires the comparator argument to be a function or undefined");
 
-    function stringSort(array, length)
-    {
-        var valueCount = compact(array, length);
+    var receiver = @toObject(this, "Array.prototype.sort requires that |this| not be null or undefined");
+    var receiverLength = @toLength(receiver.length);
 
-        var strings = @newArrayWithSize(valueCount);
-        for (var i = 0; i < valueCount; ++i)
-            strings[i] = { string: @toString(array[i]), value: array[i] };
+    // For compatibility with Firefox and Chrome, do nothing observable
+    // to the target array if it has 0 or 1 sortable properties.
+    if (receiverLength < 2)
+        return receiver;
 
-        bucketSort(array, 0, strings, 0);
-    }
+    var compacted = [ ];
+    var sorted = null;
+    var undefinedCount = compact(receiver, receiverLength, compacted, isStringSort);
 
-    var sortFunction;
-    if (@isCallable(comparator))
-        sortFunction = comparatorSort;
-    else if (comparator === @undefined)
-        sortFunction = stringSort;
-    else
-        @throwTypeError("Array.prototype.sort requires the comparsion function be a function or undefined");
+    if (isStringSort) {
+        sorted = @newArrayWithSize(compacted.length);
+        bucketSort(sorted, 0, compacted, 0);
+    } else
+        sorted = mergeSort(compacted, comparator);
 
-    var array = @toObject(this, "Array.prototype.sort requires that |this| not be null or undefined");
-
-    var length = @toLength(array.length);
-
-    // For compatibility with Firefox and Chrome, do nothing observable
-    // to the target array if it has 0 or 1 sortable properties.
-    if (length < 2)
-        return array;
-
-    sortFunction(array, length, comparator);
-    return array;
+    commit(receiver, receiverLength, sorted, undefinedCount);
+    return receiver;
 }
 
 @globalPrivate

Modified: trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp (267513 => 267514)


--- trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp	2020-09-24 02:34:03 UTC (rev 267513)
+++ trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp	2020-09-24 02:46:41 UTC (rev 267514)
@@ -99,7 +99,6 @@
 
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().createPrivateName(), objectConstructorCreate, static_cast<unsigned>(PropertyAttribute::DontEnum), 2);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().definePropertyPrivateName(), objectConstructorDefineProperty, static_cast<unsigned>(PropertyAttribute::DontEnum), 3);
-    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().getPrototypeOfPrivateName(), objectConstructorGetPrototypeOf, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().getOwnPropertyNamesPrivateName(), objectConstructorGetOwnPropertyNames, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to