Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 33 additions & 17 deletions contracts/utils/Arrays.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,25 +114,41 @@ library Arrays {
*/
function _quickSort(uint256 begin, uint256 end, function(uint256, uint256) pure returns (bool) comp) private pure {
unchecked {
if (end - begin < 0x40) return;

// Use first element as pivot
uint256 pivot = _mload(begin);
// Position where the pivot should be at the end of the loop
uint256 pos = begin;

for (uint256 it = begin + 0x20; it < end; it += 0x20) {
if (comp(_mload(it), pivot)) {
// If the value stored at the iterator's position comes before the pivot, we increment the
// position of the pivot and move the value there.
pos += 0x20;
_swap(pos, it);
// Iterative quicksort with recursion on the smaller partition only.
// This avoids unbounded recursion depth in the worst case (e.g. already-sorted arrays).
while (end - begin >= 0x40) {
// Use first element as pivot
uint256 pivot = _mload(begin);
// Position where the pivot should be at the end of the loop
uint256 pos = begin;

for (uint256 it = begin + 0x20; it < end; it += 0x20) {
if (comp(_mload(it), pivot)) {
// If the value stored at the iterator's position comes before the pivot, we increment the
// position of the pivot and move the value there.
pos += 0x20;
_swap(pos, it);
}
}
}

_swap(begin, pos); // Swap pivot into place
_quickSort(begin, pos, comp); // Sort the left side of the pivot
_quickSort(pos + 0x20, end, comp); // Sort the right side of the pivot
_swap(begin, pos); // Swap pivot into place

// Recurse on the smaller side, then loop on the larger side.
uint256 leftBegin = begin;
uint256 leftEnd = pos;
uint256 rightBegin = pos + 0x20;
uint256 rightEnd = end;

if (leftEnd - leftBegin < rightEnd - rightBegin) {
_quickSort(leftBegin, leftEnd, comp);
begin = rightBegin;
end = rightEnd;
} else {
_quickSort(rightBegin, rightEnd, comp);
begin = leftBegin;
end = leftEnd;
}
}
}
}

Expand Down
50 changes: 33 additions & 17 deletions scripts/generate/templates/Arrays.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,41 @@ const quickSort = `\
*/
function _quickSort(uint256 begin, uint256 end, function(uint256, uint256) pure returns (bool) comp) private pure {
unchecked {
if (end - begin < 0x40) return;

// Use first element as pivot
uint256 pivot = _mload(begin);
// Position where the pivot should be at the end of the loop
uint256 pos = begin;

for (uint256 it = begin + 0x20; it < end; it += 0x20) {
if (comp(_mload(it), pivot)) {
// If the value stored at the iterator's position comes before the pivot, we increment the
// position of the pivot and move the value there.
pos += 0x20;
_swap(pos, it);
// Iterative quicksort with recursion on the smaller partition only.
// This avoids unbounded recursion depth in the worst case (e.g. already-sorted arrays).
while (end - begin >= 0x40) {
// Use first element as pivot
uint256 pivot = _mload(begin);
// Position where the pivot should be at the end of the loop
uint256 pos = begin;

for (uint256 it = begin + 0x20; it < end; it += 0x20) {
if (comp(_mload(it), pivot)) {
// If the value stored at the iterator's position comes before the pivot, we increment the
// position of the pivot and move the value there.
pos += 0x20;
_swap(pos, it);
}
}
}

_swap(begin, pos); // Swap pivot into place
_quickSort(begin, pos, comp); // Sort the left side of the pivot
_quickSort(pos + 0x20, end, comp); // Sort the right side of the pivot
_swap(begin, pos); // Swap pivot into place

// Recurse on the smaller side, then loop on the larger side.
uint256 leftBegin = begin;
uint256 leftEnd = pos;
uint256 rightBegin = pos + 0x20;
uint256 rightEnd = end;

if (leftEnd - leftBegin < rightEnd - rightBegin) {
_quickSort(leftBegin, leftEnd, comp);
begin = rightBegin;
end = rightEnd;
} else {
_quickSort(rightBegin, rightEnd, comp);
begin = leftBegin;
end = leftEnd;
}
}
}
}

Expand Down
7 changes: 6 additions & 1 deletion test/utils/Arrays.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,12 @@ describe('Arrays', function () {

if (isValueType) {
describe('sort', function () {
for (const length of [0, 1, 2, 8, 32, 128]) {
const lengths = [0, 1, 2, 8, 32, 128];
// Sorting larger arrays in the worst case (already reversed) used to overflow the EVM stack
// due to unbounded recursion depth in the internal quicksort implementation.
if (name === 'uint256') lengths.push(256);

for (const length of lengths) {
describe(`${name}[] of length ${length}`, function () {
beforeEach(async function () {
this.array = Array.from({ length }, generators[name]);
Expand Down
Loading