Skip to content

Commit

Permalink
⚡️ Optimize DynamicBufferLib (#570)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized authored Aug 31, 2023
1 parent 161481d commit 59377f1
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 38 deletions.
31 changes: 16 additions & 15 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ DateTimeLibTest:testNthWeekdayInMonthOfYearTimestamp() (gas: 12031)
DateTimeLibTest:testNthWeekdayInMonthOfYearTimestamp(uint256,uint256,uint256,uint256) (runs: 256, μ: 3440, ~: 3480)
DateTimeLibTest:testWeekday() (gas: 705)
DateTimeLibTest:test__codesize() (gas: 20051)
DynamicBufferLibTest:testDynamicBuffer(bytes[],uint256) (runs: 256, μ: 878694, ~: 766753)
DynamicBufferLibTest:testDynamicBufferChaining() (gas: 26013)
DynamicBufferLibTest:testDynamicBuffer(bytes[],uint256) (runs: 256, μ: 857036, ~: 762581)
DynamicBufferLibTest:testDynamicBuffer(uint256) (runs: 256, μ: 882828, ~: 739342)
DynamicBufferLibTest:testDynamicBufferChaining() (gas: 26188)
DynamicBufferLibTest:testJoinWithConcat() (gas: 31407)
DynamicBufferLibTest:testJoinWithDynamicBuffer() (gas: 11594)
DynamicBufferLibTest:test__codesize() (gas: 7460)
DynamicBufferLibTest:testJoinWithDynamicBuffer() (gas: 10784)
DynamicBufferLibTest:test__codesize() (gas: 9606)
ECDSATest:testBytes32ToEthSignedMessageHash() (gas: 381)
ECDSATest:testBytesToEthSignedMessageHash() (gas: 11588890)
ECDSATest:testBytesToEthSignedMessageHashEmpty() (gas: 556)
Expand Down Expand Up @@ -684,19 +685,19 @@ LibStringTest:testToStringZeroBrutalized() (gas: 597698)
LibStringTest:testToStringZeroRightPadded(uint256) (runs: 256, μ: 728906, ~: 597374)
LibStringTest:test__codesize() (gas: 39660)
LibZipTest:testCdCompress() (gas: 166411)
LibZipTest:testCdCompressDecompress(bytes) (runs: 256, μ: 743300, ~: 653289)
LibZipTest:testCdCompressDecompress(uint256) (runs: 256, μ: 765790, ~: 707642)
LibZipTest:testCdCompressDecompress(bytes) (runs: 256, μ: 760758, ~: 653751)
LibZipTest:testCdCompressDecompress(uint256) (runs: 256, μ: 765585, ~: 711125)
LibZipTest:testCdDecompressOnInvalidInput() (gas: 34920)
LibZipTest:testCdFallback() (gas: 5691909)
LibZipTest:testCdFallback(bytes,uint256) (runs: 256, μ: 1203692, ~: 1047639)
LibZipTest:testCdFallbackDecompressor(bytes) (runs: 256, μ: 121351, ~: 117938)
LibZipTest:testCdFallbackDecompressor(uint256) (runs: 256, μ: 167809, ~: 155501)
LibZipTest:testCdFallbackMaskTrick(uint256,uint256) (runs: 256, μ: 687, ~: 663)
LibZipTest:testDecompressWontRevert(bytes) (runs: 256, μ: 757965, ~: 630530)
LibZipTest:testFlzCompressDecompress() (gas: 2162211)
LibZipTest:testFlzCompressDecompress(bytes) (runs: 256, μ: 906973, ~: 690153)
LibZipTest:testFlzCompressDecompress2() (gas: 1014569)
LibZipTest:test__codesize() (gas: 21748)
LibZipTest:testCdFallback(bytes,uint256) (runs: 256, μ: 1216655, ~: 1049394)
LibZipTest:testCdFallbackDecompressor(bytes) (runs: 256, μ: 121211, ~: 116876)
LibZipTest:testCdFallbackDecompressor(uint256) (runs: 256, μ: 168662, ~: 153081)
LibZipTest:testCdFallbackMaskTrick(uint256,uint256) (runs: 256, μ: 688, ~: 663)
LibZipTest:testDecompressWontRevert(bytes) (runs: 256, μ: 735033, ~: 631295)
LibZipTest:testFlzCompressDecompress() (gas: 2162678)
LibZipTest:testFlzCompressDecompress(bytes) (runs: 256, μ: 872804, ~: 684040)
LibZipTest:testFlzCompressDecompress2() (gas: 1014958)
LibZipTest:test__codesize() (gas: 21757)
MerkleProofLibTest:testEmptyCalldataHelpers() (gas: 1086)
MerkleProofLibTest:testVerifyMultiProof(bool,bool,bool,bool,bytes32) (runs: 256, μ: 779659, ~: 630526)
MerkleProofLibTest:testVerifyMultiProofForHeightOneTree(bool,bool,bool,bool,bool,bool[]) (runs: 256, μ: 33431, ~: 32418)
Expand Down
89 changes: 67 additions & 22 deletions src/utils/DynamicBufferLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,40 @@ library DynamicBufferLib {
/* OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Reserves at least `minimum` amount of contiguous memory.
function reserve(DynamicBuffer memory buffer, uint256 minimum)
internal
pure
returns (DynamicBuffer memory result)
{
_deallocate(result);
bytes memory data;
bool grow;
/// @solidity memory-safe-assembly
assembly {
let n := mload(mload(buffer))
grow := gt(minimum, n)
if grow {
let i := 0x80
for {} lt(i, minimum) { i := shl(1, i) } {}
data := 0x00
mstore(data, sub(i, n))
}
}
if (grow) result = append(buffer, data);
}

/// @dev Appends `data` to `buffer`.
/// Returns the same buffer, so that it can be used for function chaining.
function append(DynamicBuffer memory buffer, bytes memory data)
internal
pure
returns (DynamicBuffer memory)
returns (DynamicBuffer memory result)
{
_deallocate(result);
/// @solidity memory-safe-assembly
assembly {
result := buffer
if mload(data) {
let w := not(0x1f)
let bufferData := mload(buffer)
Expand Down Expand Up @@ -79,6 +104,10 @@ library DynamicBufferLib {
mstore(add(bufferData, w), mul(prime, newCapacity))
break
}
if iszero(gt(data, 0x60)) {
mstore(data, 0)
newBufferDataLength := bufferDataLength
}
// Initialize `output` to the next empty position in `bufferData`.
let output := add(bufferData, bufferDataLength)
// Copy `data` one word at a time, backwards.
Expand All @@ -93,43 +122,45 @@ library DynamicBufferLib {
mstore(bufferData, newBufferDataLength)
}
}
return buffer;
}

/// @dev Appends `data0`, `data1` to `buffer`.
/// Returns the same buffer, so that it can be used for function chaining.
function append(DynamicBuffer memory buffer, bytes memory data0, bytes memory data1)
internal
pure
returns (DynamicBuffer memory)
returns (DynamicBuffer memory result)
{
return append(append(buffer, data0), data1);
_deallocate(result);
result = append(append(buffer, data0), data1);
}

/// @dev Appends `data0`, `data1`, `data2` to `buffer`.
/// @dev Appends `data0` .. `data2` to `buffer`.
/// Returns the same buffer, so that it can be used for function chaining.
function append(
DynamicBuffer memory buffer,
bytes memory data0,
bytes memory data1,
bytes memory data2
) internal pure returns (DynamicBuffer memory) {
return append(append(append(buffer, data0), data1), data2);
) internal pure returns (DynamicBuffer memory result) {
_deallocate(result);
result = append(append(append(buffer, data0), data1), data2);
}

/// @dev Appends `data0`, `data1`, `data2`, `data3` to `buffer`.
/// @dev Appends `data0` .. `data3` to `buffer`.
/// Returns the same buffer, so that it can be used for function chaining.
function append(
DynamicBuffer memory buffer,
bytes memory data0,
bytes memory data1,
bytes memory data2,
bytes memory data3
) internal pure returns (DynamicBuffer memory) {
return append(append(append(append(buffer, data0), data1), data2), data3);
) internal pure returns (DynamicBuffer memory result) {
_deallocate(result);
result = append(append(append(append(buffer, data0), data1), data2), data3);
}

/// @dev Appends `data0`, `data1`, `data2`, `data3`, `data4` to `buffer`.
/// @dev Appends `data0` .. `data4` to `buffer`.
/// Returns the same buffer, so that it can be used for function chaining.
function append(
DynamicBuffer memory buffer,
Expand All @@ -138,12 +169,12 @@ library DynamicBufferLib {
bytes memory data2,
bytes memory data3,
bytes memory data4
) internal pure returns (DynamicBuffer memory) {
append(append(append(append(buffer, data0), data1), data2), data3);
return append(buffer, data4);
) internal pure returns (DynamicBuffer memory result) {
_deallocate(result);
result = append(append(append(append(append(buffer, data0), data1), data2), data3), data4);
}

/// @dev Appends `data0`, `data1`, `data2`, `data3`, `data4`, `data5` to `buffer`.
/// @dev Appends `data0` .. `data5` to `buffer`.
/// Returns the same buffer, so that it can be used for function chaining.
function append(
DynamicBuffer memory buffer,
Expand All @@ -153,12 +184,13 @@ library DynamicBufferLib {
bytes memory data3,
bytes memory data4,
bytes memory data5
) internal pure returns (DynamicBuffer memory) {
append(append(append(append(buffer, data0), data1), data2), data3);
return append(append(buffer, data4), data5);
) internal pure returns (DynamicBuffer memory result) {
_deallocate(result);
result = append(append(append(buffer, data0), data1), data2);
result = append(append(append(result, data3), data4), data5);
}

/// @dev Appends `data0`, `data1`, `data2`, `data3`, `data4`, `data5`, `data6` to `buffer`.
/// @dev Appends `data0` .. `data6` to `buffer`.
/// Returns the same buffer, so that it can be used for function chaining.
function append(
DynamicBuffer memory buffer,
Expand All @@ -169,8 +201,21 @@ library DynamicBufferLib {
bytes memory data4,
bytes memory data5,
bytes memory data6
) internal pure returns (DynamicBuffer memory) {
append(append(append(append(buffer, data0), data1), data2), data3);
return append(append(append(buffer, data4), data5), data6);
) internal pure returns (DynamicBuffer memory result) {
_deallocate(result);
result = append(append(append(buffer, data0), data1), data2);
result = append(append(append(append(result, data3), data4), data5), data6);
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Helper for deallocating a automatically allocated `buffer` pointer.
function _deallocate(DynamicBuffer memory result) private pure {
/// @solidity memory-safe-assembly
assembly {
mstore(0x40, result) // Deallocate, as we have already allocated.
}
}
}
87 changes: 86 additions & 1 deletion test/DynamicBufferLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,93 @@ import {DynamicBufferLib} from "../src/utils/DynamicBufferLib.sol";
contract DynamicBufferLibTest is SoladyTest {
using DynamicBufferLib for DynamicBufferLib.DynamicBuffer;

function testDynamicBuffer(uint256) public brutalizeMemory {
unchecked {
if (_random() % 8 == 0) _misalignFreeMemoryPointer();
DynamicBufferLib.DynamicBuffer memory bufferA;
DynamicBufferLib.DynamicBuffer memory bufferB;
uint256 z = _bound(_random(), 32, 4096);
if (_random() % 8 == 0) bufferA.reserve(_random() % z);
if (_random() % 8 == 0) bufferB.reserve(_random() % z);
uint256 r = _random() % 3;
uint256 o = _bound(_random(), 0, 32);
uint256 n = _bound(_random(), 5, _random() % 8 == 0 ? 64 : 8);
z = z + z;

if (r == 0) {
for (uint256 i; i != n; ++i) {
if (_random() % 8 == 0) bufferA.reserve(_random() % z);
bufferA.append(_generateRandomBytes(i + o, i + z));
}
for (uint256 i; i != n; ++i) {
if (_random() % 8 == 0) bufferB.reserve(_random() % z);
bufferB.append(_generateRandomBytes(i + o, i + z));
}
} else if (r == 1) {
for (uint256 i; i != n; ++i) {
if (_random() % 8 == 0) bufferB.reserve(_random() % z);
bufferB.append(_generateRandomBytes(i + o, i + z));
}
for (uint256 i; i != n; ++i) {
if (_random() % 8 == 0) bufferA.reserve(_random() % z);
bufferA.append(_generateRandomBytes(i + o, i + z));
}
} else {
uint256 mode;
for (uint256 i; i != n; ++i) {
if (_random() % 8 == 0) mode ^= 1;
if (mode == 0) {
if (_random() % 8 == 0) bufferA.reserve(_random() % z);
bufferA.append(_generateRandomBytes(i + o, i + z));
if (_random() % 8 == 0) bufferB.reserve(_random() % z);
bufferB.append(_generateRandomBytes(i + o, i + z));
} else {
if (_random() % 8 == 0) bufferB.reserve(_random() % z);
bufferB.append(_generateRandomBytes(i + o, i + z));
if (_random() % 8 == 0) bufferA.reserve(_random() % z);
bufferA.append(_generateRandomBytes(i + o, i + z));
}
}
}

bytes memory expected;
for (uint256 i; i != n; ++i) {
expected = bytes.concat(expected, _generateRandomBytes(i + o, i + z));
}
assertEq(bufferA.data, expected);
assertEq(bufferB.data, expected);
}
}

function _generateRandomBytes(uint256 n, uint256 seed)
internal
pure
returns (bytes memory result)
{
/// @solidity memory-safe-assembly
assembly {
if n {
result := mload(0x40)
mstore(result, n)
mstore(0x00, seed)
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
mstore(0x20, i)
mstore(add(add(result, 0x20), i), keccak256(0x00, 0x40))
}
mstore(0x40, add(add(result, 0x20), n))
}
}
}

function testDynamicBuffer(bytes[] memory inputs, uint256 randomness) public brutalizeMemory {
_boundInputs(inputs);

_misalignFreeMemoryPointer();
if ((randomness >> 16) % 8 == 0) _misalignFreeMemoryPointer();
DynamicBufferLib.DynamicBuffer memory buffer;
if ((randomness >> 32) % 4 == 0) {
buffer.reserve((randomness >> 128) % 1024);
}

unchecked {
uint256 expectedLength;
uint256 start;
Expand All @@ -35,6 +117,9 @@ contract DynamicBufferLibTest is SoladyTest {
mstore(0x40, add(corruptCheckSlot, 0x20))
}
buffer.append(inputs[i]);
if ((randomness >> 48) % 8 == 0 && expectedLength != 0) {
buffer.reserve((randomness >> 160) % (expectedLength * 2));
}
assertEq(buffer.data.length, expectedLength);
_checkMemory(buffer.data);
bool isCorrupted;
Expand Down

0 comments on commit 59377f1

Please sign in to comment.