Skip to content

Commit 325d888

Browse files
authored
⚡️ Optimize cdCompress and cdDecompress more (#1403)
1 parent d76362f commit 325d888

2 files changed

Lines changed: 204 additions & 34 deletions

File tree

src/utils/LibZip.sol

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -183,17 +183,23 @@ library LibZip {
183183
data := add(data, 1)
184184
let c := byte(31, mload(data))
185185
if iszero(c) {
186-
let z := 0
187186
for {} 1 {} {
188-
let r := 0x20
189-
let x := mload(add(data, r))
190-
if x { r := countLeadingZeroBytes(x) }
191-
r := min(min(sub(end, data), r), sub(0x7f, z))
187+
let x := mload(add(data, 0x20))
188+
if iszero(x) {
189+
let r := min(sub(end, data), 0x20)
190+
r := min(sub(0x7f, c), r)
191+
data := add(data, r)
192+
c := add(c, r)
193+
if iszero(gt(r, 0x1f)) { break }
194+
continue
195+
}
196+
let r := countLeadingZeroBytes(x)
197+
r := min(sub(end, data), r)
192198
data := add(data, r)
193-
z := add(z, r)
194-
if iszero(gt(r, 0x1f)) { break }
199+
c := add(c, r)
200+
break
195201
}
196-
mstore(o, shl(240, z))
202+
mstore(o, shl(240, c))
197203
o := add(o, 2)
198204
continue
199205
}
@@ -222,27 +228,47 @@ library LibZip {
222228
function cdDecompress(bytes memory data) internal pure returns (bytes memory result) {
223229
/// @solidity memory-safe-assembly
224230
assembly {
231+
function countLeadingZeroBytes(x_) -> _r {
232+
_r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x_))
233+
_r := or(_r, shl(6, lt(0xffffffffffffffff, shr(_r, x_))))
234+
_r := or(_r, shl(5, lt(0xffffffff, shr(_r, x_))))
235+
_r := or(_r, shl(4, lt(0xffff, shr(_r, x_))))
236+
_r := xor(31, or(shr(3, _r), lt(0xff, shr(_r, x_))))
237+
}
238+
function min(x_, y_) -> _z {
239+
_z := xor(x_, mul(xor(x_, y_), lt(y_, x_)))
240+
}
225241
if mload(data) {
226242
result := mload(0x40)
227-
let o := add(result, 0x20)
228243
let s := add(data, 4)
229244
let v := mload(s)
230-
let end := add(data, mload(data))
245+
let end := add(add(0x20, data), mload(data))
246+
let m := not(shl(7, div(not(iszero(end)), 255))) // `0x7f7f ...`.
247+
let o := add(result, 0x20)
231248
mstore(s, not(v)) // Bitwise negate the first 4 bytes.
232-
for {} lt(data, end) {} {
233-
data := add(data, 1)
234-
let c := byte(31, mload(data))
235-
if iszero(c) {
236-
data := add(data, 1)
237-
let d := byte(31, mload(data))
238-
// Fill with either 0xff or 0x00.
239-
mstore(o, not(0))
240-
if iszero(gt(d, 0x7f)) { calldatacopy(o, calldatasize(), add(d, 1)) }
241-
o := add(o, add(and(d, 0x7f), 1))
249+
for { let i := add(0x20, data) } 1 {} {
250+
let c := mload(i)
251+
if iszero(byte(0, c)) {
252+
c := add(byte(1, c), 1)
253+
if iszero(gt(c, 0x80)) {
254+
calldatacopy(o, calldatasize(), c) // Fill with 0x00.
255+
o := add(o, c)
256+
i := add(i, 2)
257+
if iszero(lt(i, end)) { break }
258+
continue
259+
}
260+
mstore(o, not(0)) // Fill with 0xff.
261+
o := add(o, sub(c, 0x80))
262+
i := add(i, 2)
263+
if iszero(lt(i, end)) { break }
242264
continue
243265
}
244-
mstore8(o, c)
245-
o := add(o, 1)
266+
mstore(o, c)
267+
c := not(or(or(add(and(c, m), m), c), m))
268+
c := add(iszero(c), countLeadingZeroBytes(c))
269+
o := add(min(sub(end, i), c), o)
270+
i := add(c, i)
271+
if iszero(lt(i, end)) { break }
246272
}
247273
mstore(s, v) // Restore the first 4 bytes.
248274
mstore(result, sub(o, add(result, 0x20))) // Store the length.

test/LibZip.t.sol

Lines changed: 156 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,29 @@ contract LibZipTest is SoladyTest {
3838
bytes internal constant _CD_COMPRESS_INPUT =
3939
hex"00000000000000000000000000000000000000000000000000000000000ae11c0000000000000000000000000000000000000000000000000000002b9cdca0ab0000000000000000000000000000000000003961790f8baa365051889e4c367d00000000000000000000000000000000000026d85539440bc844167ac0cc42320000000000000000000000000000000000000000000000007b55939986433925";
4040

41+
bytes internal constant _CD_COMPRESS_OUTPUT =
42+
hex"ffe3f51e1c001a2b9cdca0ab00113961790f8baa365051889e4c367d001126d85539440bc844167ac0cc423200177b55939986433925";
43+
44+
function testABCCdCompressAndDecompressGas() public {
45+
bytes memory data = abi.encode(_A, _B, _C);
46+
assertEq(LibZip.cdDecompress(LibZip.cdCompress(data)).length, data.length);
47+
}
48+
49+
function testABCCdCompressAndDecompressOriginalGas() public {
50+
bytes memory data = abi.encode(_A, _B, _C);
51+
assertEq(_cdDecompressOriginal(_cdCompressOriginal(data)).length, data.length);
52+
}
53+
54+
function testCdDecompressGas() public {
55+
bytes memory data = _CD_COMPRESS_OUTPUT;
56+
assertGt(LibZip.cdDecompress(data).length, data.length);
57+
}
58+
59+
function testCdDecompressOriginalGas() public {
60+
bytes memory data = _CD_COMPRESS_OUTPUT;
61+
assertGt(_cdDecompressOriginal(data).length, data.length);
62+
}
63+
4164
function testCdCompressGas() public {
4265
bytes memory data = _CD_COMPRESS_INPUT;
4366
assertLt(LibZip.cdCompress(data).length, data.length);
@@ -48,15 +71,15 @@ contract LibZipTest is SoladyTest {
4871
assertLt(_cdCompressOriginal(data).length, data.length);
4972
}
5073

51-
function testStoreABCWithCdCompressGas() public {
74+
function testABCStoreWithCdCompressGas() public {
5275
_bytesStorage.set(LibZip.cdCompress(abi.encode(_A, _B, _C)));
5376
}
5477

55-
function testStoreABCWithCdCompressOriginalGas() public {
78+
function testABCStoreWithCdCompressOriginalGas() public {
5679
_bytesStorage.set(_cdCompressOriginal(abi.encode(_A, _B, _C)));
5780
}
5881

59-
function testStoreABCWithFlzCompressGas() public {
82+
function testABCStoreWithFlzCompressGas() public {
6083
_bytesStorage.set(LibZip.flzCompress(abi.encode(_A, _B, _C)));
6184
}
6285

@@ -73,11 +96,36 @@ contract LibZipTest is SoladyTest {
7396
}
7497

7598
function testCdCompressDifferential(bytes32) public {
76-
testCdCompressDifferential(_randomCd());
99+
bytes memory data;
100+
if (_randomChance(8)) data = _randomCd();
101+
uint256 t = _randomUniform() % 4;
102+
for (uint256 i; i < t; ++i) {
103+
if (_randomChance(2)) data = abi.encodePacked(data, _random());
104+
if (_randomChance(2)) data = abi.encodePacked(data, new bytes(_random() & 0x3ff));
105+
if (_randomChance(2)) data = abi.encodePacked(data, _random());
106+
if (_randomChance(32)) data = abi.encodePacked(data, _randomCd());
107+
}
108+
testCdCompressDifferential(data);
77109
}
78110

79111
function testCdCompressDifferential(bytes memory data) public {
80-
assertEq(LibZip.cdCompress(data), _cdCompressOriginal(data));
112+
if (_randomChance(32)) _misalignFreeMemoryPointer();
113+
if (_randomChance(32)) _brutalizeMemory();
114+
bytes memory computed = LibZip.cdCompress(data);
115+
assertEq(computed, _cdCompressOriginal(data));
116+
}
117+
118+
function testCdDecompressDifferential(bytes32) public {
119+
bytes memory data = _randomCd();
120+
if (_randomChance(2)) {
121+
testCdDecompressDifferential(LibZip.cdCompress(data));
122+
} else {
123+
testCdDecompressDifferential(data);
124+
}
125+
}
126+
127+
function testCdDecompressDifferential(bytes memory data) public {
128+
assertEq(LibZip.cdDecompress(data), _cdDecompressOriginal(data));
81129
}
82130

83131
function _cdCompressOriginal(bytes memory data) internal pure returns (bytes memory result) {
@@ -121,6 +169,39 @@ contract LibZipTest is SoladyTest {
121169
}
122170
}
123171

172+
function _cdDecompressOriginal(bytes memory data) internal pure returns (bytes memory result) {
173+
/// @solidity memory-safe-assembly
174+
assembly {
175+
if mload(data) {
176+
result := mload(0x40)
177+
let o := add(result, 0x20)
178+
let s := add(data, 4)
179+
let v := mload(s)
180+
let end := add(data, mload(data))
181+
mstore(s, not(v)) // Bitwise negate the first 4 bytes.
182+
for {} lt(data, end) {} {
183+
data := add(data, 1)
184+
let c := byte(31, mload(data))
185+
if iszero(c) {
186+
data := add(data, 1)
187+
let d := byte(31, mload(data))
188+
// Fill with either 0xff or 0x00.
189+
mstore(o, not(0))
190+
if iszero(gt(d, 0x7f)) { calldatacopy(o, calldatasize(), add(d, 1)) }
191+
o := add(o, add(and(d, 0x7f), 1))
192+
continue
193+
}
194+
mstore8(o, c)
195+
o := add(o, 1)
196+
}
197+
mstore(s, v) // Restore the first 4 bytes.
198+
mstore(result, sub(o, add(result, 0x20))) // Store the length.
199+
mstore(o, 0) // Zeroize the slot after the string.
200+
mstore(0x40, add(o, 0x20)) // Allocate the memory.
201+
}
202+
}
203+
}
204+
124205
function testFlzCompressDecompress() public brutalizeMemory {
125206
assertEq(LibZip.flzCompress(""), "");
126207
assertEq(LibZip.flzDecompress(""), "");
@@ -211,7 +292,18 @@ contract LibZipTest is SoladyTest {
211292
function _randomCd() internal returns (bytes memory data) {
212293
uint256 n = _randomChance(8) ? _random() % 2048 : _random() % 256;
213294
data = new bytes(n);
214-
if (_randomChance(2)) {
295+
if (_randomChance(32)) {
296+
uint256 r = _randomUniform();
297+
/// @solidity memory-safe-assembly
298+
assembly {
299+
mstore(0x00, r)
300+
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
301+
mstore(0x20, xor("randomUniform", i))
302+
mstore(add(add(data, 0x20), i), keccak256(0x00, 0x40))
303+
}
304+
}
305+
}
306+
if (_randomChance(4)) {
215307
/// @solidity memory-safe-assembly
216308
assembly {
217309
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
@@ -224,16 +316,45 @@ contract LibZipTest is SoladyTest {
224316
/// @solidity memory-safe-assembly
225317
assembly {
226318
mstore(0x00, r)
319+
mstore(0x20, xor("mode", not(0)))
320+
let mode := and(1, keccak256(0x00, 0x40))
227321
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
228-
mstore(0x20, i)
229-
if and(1, keccak256(0x00, 0x40)) { mstore(add(add(data, 0x20), i), 0) }
322+
mstore(0x20, xor("mode", i))
323+
mode := xor(mode, iszero(and(keccak256(0x00, 0x40), 7)))
324+
mstore(add(add(data, 0x20), i), mul(iszero(mode), not(0)))
230325
}
231326
}
232327
}
233-
if (n != 0) {
234-
uint256 m = _random() % 8;
235-
for (uint256 j; j < m; ++j) {
236-
data[_random() % n] = bytes1(uint8(_random()));
328+
if (_randomChance(16)) {
329+
uint256 r = _randomUniform();
330+
/// @solidity memory-safe-assembly
331+
assembly {
332+
mstore(0x00, r)
333+
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
334+
mstore(0x20, xor("0", i))
335+
let p := keccak256(0x00, 0x40)
336+
if and(0x01, p) { mstore(add(add(data, 0x20), i), 0) }
337+
}
338+
}
339+
}
340+
if (_randomChance(16)) {
341+
uint256 r = _randomUniform();
342+
/// @solidity memory-safe-assembly
343+
assembly {
344+
mstore(0x00, r)
345+
for { let i := 0 } lt(i, n) { i := add(i, 0x20) } {
346+
mstore(0x20, xor("not(0)", i))
347+
let p := keccak256(0x00, 0x40)
348+
if and(0x10, p) { mstore(add(add(data, 0x20), i), not(0)) }
349+
}
350+
}
351+
}
352+
if (_randomChance(2)) {
353+
if (n != 0) {
354+
uint256 m = _random() % 8;
355+
for (uint256 j; j < m; ++j) {
356+
data[_random() % n] = bytes1(uint8(_random()));
357+
}
237358
}
238359
}
239360
}
@@ -393,4 +514,27 @@ contract LibZipTest is SoladyTest {
393514
}
394515
assertEq(a, b);
395516
}
517+
518+
function testCountLeadingNonZeroBytes(bytes32 s) public {
519+
uint256 expected;
520+
uint256 computed;
521+
/// @solidity memory-safe-assembly
522+
assembly {
523+
let n := 0
524+
for {} byte(n, s) { n := add(n, 1) } {} // Scan for '\0'.
525+
expected := n
526+
let m := 0x7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F
527+
let x := not(or(or(add(and(s, m), m), s), m))
528+
computed := 0x20
529+
if x {
530+
let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
531+
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
532+
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
533+
r := or(r, shl(4, lt(0xffff, shr(r, x))))
534+
r := xor(31, or(shr(3, r), lt(0xff, shr(r, x))))
535+
computed := r
536+
}
537+
}
538+
assertEq(computed, expected);
539+
}
396540
}

0 commit comments

Comments
 (0)