Normative: A more precise Array.prototype.sort - #1585
Conversation
| <emu-alg> | ||
| 1. If _x_ and _y_ are both *undefined*, return *+0*. | ||
| 1. If _x_ is *undefined*, return 1. | ||
| 1. If _y_ is *undefined*, return -1. |
There was a problem hiding this comment.
undefineds are never part of items, and thus, x and y can never be undefined.
|
To make this easier to follow, Simon left inline comments on the diff. We've also prepared a slide deck that walks through the proposed algorithm in a more visual way, which I'll present at the upcoming TC39 meeting in July. One question people might have is: to which extent does this PR match implementation reality? We mentioned that V8 implements
|
ljharb
left a comment
There was a problem hiding this comment.
I’d be very interested in seeing the toString step included as well, to reduce variance further.
jmdyck
left a comment
There was a problem hiding this comment.
Since %TypedArray%.prototype.sort is defined roughly as a diff against Array.prototype.sort, it may need collateral changes.
For instance, this sentence would no longer apply?:
The implementation-defined sort order condition for exotic objects is not applied by %TypedArray%
.prototype.sort.
Also, its reference to "the entry steps in Array.prototype.sort" would be a big vaguer, since those steps no longer constitute a distinct <emu-alg> of their own. We could just leave it as is and trust that people will figure it out, or change it to something like "the first three steps in Array.prototype.sort", but then that's less robust to future change (e.g. if A.p.sort were to insert some Asserts at the start).
|
I'd like to see more investigation of the behavior of existing engines in edge cases. Off the top of my head:
I am sure there are more cases to consider, too. |
SpiderMonkey always first collects all elements, stores them in a temporary object, and then performs the search in that temporary object:
The elements in the sorted object are then written back with [[Set]] semantics and holes are deleted through [[Delete]]: |
I implemented some of these here. All tests were run using Accessor on the array itself ( Accessors on the arrays prototype object with a hole ( non-writeable or non-configurable elements on the array or its prototype
Accessors mutate an element
Accessors delete an element
Accessor add/remove elements
Accessors modify
0-length and 1-length arrays ( Sort on a proxy with a backing array Sort an array with a proxy prototype The result is not that surprising given that v8 implements basically the same approach now as spidermonkey. Please note that in the |
|
Thanks for the more thorough tests! It looks like JavaScriptCore disagrees with the proposed spec more often than it agrees, so this normative change mostly amounts to a request that JSC change their implementation of |
@kmiller68 @msaboff During the last meeting I had the impression you were willing to change the JSC implementation. Is this still the case? (We could also discuss this during the meeting, but if this turns out to be uncontroversial, then we could all save some valuable meeting time.) |
|
I've filed https://bugzilla.mozilla.org/show_bug.cgi?id=1583528 for the necessary SpiderMonkey changes. |
|
Per the recent TC39 meeting, this patch now has consensus. Here's what still needs to happen before it can be merged:
|
|
I’ve filed https://bugs.webkit.org/show_bug.cgi?id=202582 to track the necessary JSC changes. I’ll also update this thread with the results of my investigation. |
|
Here are a few bugs:
That invariant is now broken.
That invariant is now also broken due to the refactoring of SortCompare to take out the handling of
It's not clear what "compare" means here, but this is in the SortCompare section, so the presumable interpretation is "SortCompare". In that case, this is no longer valid due to the |
Could you please elaborate here? I don't see how the invariant is broken.
We can revert the refactoring of upfront |
Co-authored-by: Simon Zünd <szuend@chromium.org> Co-authored-by: Mathias Bynens <mathias@qiwi.be>
|
Thanks @szuend and @mathiasbynens for making this happen! |
`Array.prototype.sort` was already made stable in ECMAScript 2019 (see two paragraphs above). The change in tc39#1585 made `Array.prototype.sort` more precise, reducing the amount of cases that result in an implementation-defined sort order.
`Array.prototype.sort` was already made stable in ECMAScript 2019 (see two paragraphs above). The change in tc39#1585 made `Array.prototype.sort` more precise, reducing the amount of cases that result in an implementation-defined sort order.
`Array.prototype.sort` was already made stable in ECMAScript 2019 (see two paragraphs above). The change in #1585 made `Array.prototype.sort` more precise, reducing the amount of cases that result in an implementation-defined sort order.
… r=tcampbell <tc39/ecma262#1585> changed `Array.prototype.sort` to first collect all elements in a temporary list. And because `TypedArray.prototype.sort` follows `Array.prototype.sort`, the same copying has to happen here. Depends on D143288 Differential Revision: https://phabricator.services.mozilla.com/D143289
… r=tcampbell <tc39/ecma262#1585> changed `Array.prototype.sort` to first collect all elements in a temporary list. And because `TypedArray.prototype.sort` follows `Array.prototype.sort`, the same copying has to happen here. Depends on D143288 Differential Revision: https://phabricator.services.mozilla.com/D143289
… r=tcampbell <tc39/ecma262#1585> changed `Array.prototype.sort` to first collect all elements in a temporary list. And because `TypedArray.prototype.sort` follows `Array.prototype.sort`, the same copying has to happen here. Depends on D143288 Differential Revision: https://phabricator.services.mozilla.com/D143289
… r=tcampbell <tc39/ecma262#1585> changed `Array.prototype.sort` to first collect all elements in a temporary list. And because `TypedArray.prototype.sort` follows `Array.prototype.sort`, the same copying has to happen here. Depends on D143288 Differential Revision: https://phabricator.services.mozilla.com/D143289
… r=arai This ensures our native Array.prototype.sort implementation matches the proposed semantics in <tc39/ecma262#1585>. (Our self-hosted implementation was already deleting the properties in ascending order.) Differential Revision: https://phabricator.services.mozilla.com/D52350
… r=tcampbell <tc39/ecma262#1585> changed `Array.prototype.sort` to first collect all elements in a temporary list. And because `TypedArray.prototype.sort` follows `Array.prototype.sort`, the same copying has to happen here. Depends on D143288 Differential Revision: https://phabricator.services.mozilla.com/D143289
… r=tcampbell <tc39/ecma262#1585> changed `Array.prototype.sort` to first collect all elements in a temporary list. And because `TypedArray.prototype.sort` follows `Array.prototype.sort`, the same copying has to happen here. Depends on D143288 Differential Revision: https://phabricator.services.mozilla.com/D143289
tl;dr This PR intends to nail down some parts of
Array.prototype.sortto reduce the amount of cases that result in an implementation-defined sort order.Overview
The
Array.prototype.sortprocedure that this PR proposes, can be summarized as follows:undefinedvalues in the range of[0, [[length]])into a temporary list using[[Get]](let the length of this list ben).undefineds are counted and not added to this temporary list (let this count bem).nsorted values using[[Set]].mundefineds.[[Delete]]on integer-indexed properties in the range of[n + m, [[length]])as holes are moved to the end of the sorting range.Advantages
The main advantage is that the following cases no longer result in an implementation-defined sort order:
3whenObject.prototype[3] = 42)[[Get]],[[Set]]behavior for integer-indexed properties inside the sorting range is not the "ordinary" implementation (i.e. a getter or setter or a proxy).The reason for this is that
[[Get]],[[Set]]and[[Delete]]are now called in a well-defined order, regardless of the chosen sorting algorithm of any engine. (Previously, non-extensible objects and non-configurable/non-writable properties could result in exceptions at any point in time during sorting, depending on the implementation.)Additionally,
ToStringoperations or comparison functions that throw now leave the object as-is (modulo side-effects and changes caused by[[Get]]).Web compatibility
We believe this change to be web-compatible, given that it has been (for the most part) shipping since Chrome 74, with the remaining bits shipping since Chrome 76.
Starting with V8 v7.4 (Chrome 74), V8 copies non-
undefinedvalues into a temporary list for sorting, as described in this PR. However, compacting all values at the start of the sorting range (removing holes) still happened on the object itself and was observable.As of V8 v7.6 (Chrome version 76.0.3806.0) V8's implementation of
Array.prototype.sortfully behaves as described by this PR.Memory concerns
Implementers might be concerned about the added
O(n)memory requirements ofArray.prototype.sort. The first version in V8 that appliedArray.prototype.sortto a temporary copy landed with Chrome 74. Worried about the impact on memory consumption (especially on mobile), we analysed memory usage of some 1000 websites and did not see any change in peak memory consumption.Variants
This is only a first initial draft of how
Array.prototype.sortcould be specified in a more precise manner. A few variations come to mind:undefineds, they could be collected as well and passed toSortCompare. This would requireSortCompareto be left as-is and handleundefineds appropriately.undefinedcomparison function, the initial collection phase could callToStringdirectly. This means the temporary list would contain pairs consisting of the original value and the result ofToStringfor the respective value. This would further reduce the cases of implementation-defined sort order.Ref. #302.
@mathiasbynens @ajklein