Skip to content

Commit c19ac1f

Browse files
authored
✨ countZeroBytes for bytes (#1414)
1 parent b423060 commit c19ac1f

File tree

3 files changed

+80
-6
lines changed

3 files changed

+80
-6
lines changed

docs/utils/libbit.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,27 @@ function countZeroBytes(uint256 x) internal pure returns (uint256 c)
6262
Returns the number of zero bytes in `x`.
6363
To get the number of non-zero bytes, simply do `32 - countZeroBytes(x)`.
6464

65+
### countZeroBytes(bytes)
66+
67+
```solidity
68+
function countZeroBytes(bytes memory s) internal pure returns (uint256 c)
69+
```
70+
71+
Returns the number of zero bytes in `s`.
72+
To get the number of non-zero bytes, simply do `s.length - countZeroBytes(s)`.
73+
74+
### countZeroBytesCalldata(bytes)
75+
76+
```solidity
77+
function countZeroBytesCalldata(bytes calldata s)
78+
internal
79+
pure
80+
returns (uint256 c)
81+
```
82+
83+
Returns the number of zero bytes in `s`.
84+
To get the number of non-zero bytes, simply do `s.length - countZeroBytes(s)`.
85+
6586
### isPo2(uint256)
6687

6788
```solidity

src/utils/LibBit.sol

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,47 @@ library LibBit {
8686
function countZeroBytes(uint256 x) internal pure returns (uint256 c) {
8787
/// @solidity memory-safe-assembly
8888
assembly {
89-
c := 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
90-
c := not(or(or(add(and(x, c), c), x), c)) // `.each(b => b == 0x00 ? 0x80 : 0x00)`.
91-
c := shr(7, c)
92-
c := mul(0x0101010101010101010101010101010101010101010101010101010101010101, c)
93-
c := byte(0, c)
89+
let m := 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
90+
c := byte(0, mul(shr(7, not(m)), shr(7, not(or(or(add(and(x, m), m), x), m)))))
91+
}
92+
}
93+
94+
/// @dev Returns the number of zero bytes in `s`.
95+
/// To get the number of non-zero bytes, simply do `s.length - countZeroBytes(s)`.
96+
function countZeroBytes(bytes memory s) internal pure returns (uint256 c) {
97+
/// @solidity memory-safe-assembly
98+
assembly {
99+
function czb(x_) -> _c {
100+
let _m := 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
101+
_c := shr(7, not(or(or(add(and(x_, _m), _m), x_), _m)))
102+
_c := byte(0, mul(shr(7, not(_m)), _c))
103+
}
104+
let n := mload(s)
105+
let l := shl(5, shr(5, n))
106+
s := add(s, 0x20)
107+
for { let i } xor(i, l) { i := add(i, 0x20) } { c := add(czb(mload(add(s, i))), c) }
108+
if lt(l, n) { c := add(czb(or(shr(shl(3, sub(n, l)), not(0)), mload(add(s, l)))), c) }
109+
}
110+
}
111+
112+
/// @dev Returns the number of zero bytes in `s`.
113+
/// To get the number of non-zero bytes, simply do `s.length - countZeroBytes(s)`.
114+
function countZeroBytesCalldata(bytes calldata s) internal pure returns (uint256 c) {
115+
/// @solidity memory-safe-assembly
116+
assembly {
117+
function czb(x_) -> _c {
118+
let _m := 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
119+
_c := shr(7, not(or(or(add(and(x_, _m), _m), x_), _m)))
120+
_c := byte(0, mul(shr(7, not(_m)), _c))
121+
}
122+
let l := shl(5, shr(5, s.length))
123+
for { let i } xor(i, l) { i := add(i, 0x20) } {
124+
c := add(czb(calldataload(add(s.offset, i))), c)
125+
}
126+
if lt(l, s.length) {
127+
let m := shr(shl(3, sub(s.length, l)), not(0))
128+
c := add(czb(or(m, calldataload(add(s.offset, l)))), c)
129+
}
94130
}
95131
}
96132

test/LibBit.t.sol

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,24 @@ contract LibBitTest is SoladyTest {
290290
function _countZeroBytesOriginal(uint256 x) internal pure returns (uint256 c) {
291291
unchecked {
292292
for (uint256 i; i < 32; ++i) {
293-
c += ((x & (0xff << (i * 8))) == 0 ? 1 : 0);
293+
c += (x & (0xff << (i * 8))) == 0 ? 1 : 0;
294+
}
295+
return c;
296+
}
297+
}
298+
299+
function testCountZeroBytesDifferential(bytes memory s) public {
300+
assertEq(LibBit.countZeroBytes(s), _countZeroBytesOriginal(s));
301+
}
302+
303+
function testCountZeroBytesCalldataDifferential(bytes calldata s) public {
304+
assertEq(LibBit.countZeroBytesCalldata(s), _countZeroBytesOriginal(s));
305+
}
306+
307+
function _countZeroBytesOriginal(bytes memory s) internal pure returns (uint256 c) {
308+
unchecked {
309+
for (uint256 i; i < s.length; ++i) {
310+
c += uint8(s[i]) == 0 ? 1 : 0;
294311
}
295312
return c;
296313
}

0 commit comments

Comments
 (0)