Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚡️ Branchless sqrt opt #317

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
34 changes: 17 additions & 17 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ AuthTest:testSetOwnerWithPermissiveAuthority(address,address) (runs: 256, μ: 13
Bytes32AddressLibTest:testFillLast12Bytes() (gas: 223)
Bytes32AddressLibTest:testFromLast20Bytes() (gas: 191)
CREATE3Test:testDeployERC20() (gas: 853111)
CREATE3Test:testDeployERC20(bytes32,string,string,uint8) (runs: 256, μ: 923845, ~: 921961)
CREATE3Test:testDeployERC20(bytes32,string,string,uint8) (runs: 256, μ: 924106, ~: 921961)
CREATE3Test:testFailDoubleDeployDifferentBytecode() (gas: 9079256848778914174)
CREATE3Test:testFailDoubleDeployDifferentBytecode(bytes32,bytes,bytes) (runs: 256, μ: 5062195514745832485, ~: 8937393460516727435)
CREATE3Test:testFailDoubleDeployDifferentBytecode(bytes32,bytes,bytes) (runs: 256, μ: 5097107207950975695, ~: 8937393460516727446)
CREATE3Test:testFailDoubleDeploySameBytecode() (gas: 9079256848778906218)
CREATE3Test:testFailDoubleDeploySameBytecode(bytes32,bytes) (runs: 256, μ: 5027837975401088877, ~: 8937393460516728677)
CREATE3Test:testFailDoubleDeploySameBytecode(bytes32,bytes) (runs: 256, μ: 5062749668606232091, ~: 8937393460516728689)
DSTestPlusTest:testBound() (gas: 14214)
DSTestPlusTest:testBound(uint256,uint256,uint256) (runs: 256, μ: 2787, ~: 2793)
DSTestPlusTest:testBrutalizeMemory() (gas: 823)
Expand Down Expand Up @@ -116,7 +116,7 @@ ERC1155Test:testSafeTransferFromToEOA(uint256,uint256,bytes,uint256,address,byte
ERC1155Test:testSafeTransferFromToERC1155Recipient() (gas: 739583)
ERC1155Test:testSafeTransferFromToERC1155Recipient(uint256,uint256,bytes,uint256,bytes) (runs: 256, μ: 769591, ~: 765729)
ERC20Invariants:invariantBalanceSum() (runs: 256, calls: 3840, reverts: 2388)
ERC20Test:invariantMetadata() (runs: 256, calls: 3840, reverts: 2606)
ERC20Test:invariantMetadata() (runs: 256, calls: 3840, reverts: 2560)
ERC20Test:testApprove() (gas: 31058)
ERC20Test:testApprove(address,uint256) (runs: 256, μ: 30424, ~: 31280)
ERC20Test:testBurn() (gas: 56970)
Expand All @@ -137,15 +137,15 @@ ERC20Test:testFailTransferFromInsufficientBalance(address,uint256,uint256) (runs
ERC20Test:testFailTransferInsufficientBalance() (gas: 52806)
ERC20Test:testFailTransferInsufficientBalance(address,uint256,uint256) (runs: 256, μ: 51720, ~: 55310)
ERC20Test:testInfiniteApproveTransferFrom() (gas: 89793)
ERC20Test:testMetadata(string,string,uint8) (runs: 256, μ: 870618, ~: 863277)
ERC20Test:testMetadata(string,string,uint8) (runs: 256, μ: 868512, ~: 863173)
ERC20Test:testMint() (gas: 53746)
ERC20Test:testMint(address,uint256) (runs: 256, μ: 52214, ~: 53925)
ERC20Test:testPermit() (gas: 63193)
ERC20Test:testPermit(uint248,address,uint256,uint256) (runs: 256, μ: 62584, ~: 63517)
ERC20Test:testTransfer() (gas: 60272)
ERC20Test:testTransfer(address,uint256) (runs: 256, μ: 58773, ~: 60484)
ERC20Test:testTransferFrom() (gas: 83777)
ERC20Test:testTransferFrom(address,uint256,uint256) (runs: 256, μ: 86464, ~: 92841)
ERC20Test:testTransferFrom(address,uint256,uint256) (runs: 256, μ: 86308, ~: 92841)
ERC4626Test:invariantMetadata() (runs: 256, calls: 3840, reverts: 2881)
ERC4626Test:testFailDepositWithNoApproval() (gas: 13357)
ERC4626Test:testFailDepositWithNotEnoughApproval() (gas: 86993)
Expand Down Expand Up @@ -240,17 +240,17 @@ ERC721Test:testTransferFromApproveAll() (gas: 92898)
ERC721Test:testTransferFromApproveAll(uint256,address) (runs: 256, μ: 93182, ~: 93182)
ERC721Test:testTransferFromSelf() (gas: 64776)
ERC721Test:testTransferFromSelf(uint256,address) (runs: 256, μ: 65061, ~: 65061)
FixedPointMathLibTest:testDifferentiallyFuzzSqrt(uint256) (runs: 256, μ: 13827, ~: 4181)
FixedPointMathLibTest:testDifferentiallyFuzzSqrt(uint256) (runs: 256, μ: 13819, ~: 4231)
FixedPointMathLibTest:testDivWadDown() (gas: 841)
FixedPointMathLibTest:testDivWadDown(uint256,uint256) (runs: 256, μ: 718, ~: 820)
FixedPointMathLibTest:testDivWadDown(uint256,uint256) (runs: 256, μ: 717, ~: 820)
FixedPointMathLibTest:testDivWadDownEdgeCases() (gas: 446)
FixedPointMathLibTest:testDivWadUp() (gas: 1003)
FixedPointMathLibTest:testDivWadUp(uint256,uint256) (runs: 256, μ: 809, ~: 972)
FixedPointMathLibTest:testDivWadUp(uint256,uint256) (runs: 256, μ: 807, ~: 972)
FixedPointMathLibTest:testDivWadUpEdgeCases() (gas: 462)
FixedPointMathLibTest:testFailDivWadDownOverflow(uint256,uint256) (runs: 256, μ: 443, ~: 419)
FixedPointMathLibTest:testFailDivWadDownOverflow(uint256,uint256) (runs: 256, μ: 444, ~: 419)
FixedPointMathLibTest:testFailDivWadDownZeroDenominator() (gas: 342)
FixedPointMathLibTest:testFailDivWadDownZeroDenominator(uint256) (runs: 256, μ: 397, ~: 397)
FixedPointMathLibTest:testFailDivWadUpOverflow(uint256,uint256) (runs: 256, μ: 398, ~: 374)
FixedPointMathLibTest:testFailDivWadUpOverflow(uint256,uint256) (runs: 256, μ: 399, ~: 374)
FixedPointMathLibTest:testFailDivWadUpZeroDenominator() (gas: 342)
FixedPointMathLibTest:testFailDivWadUpZeroDenominator(uint256) (runs: 256, μ: 396, ~: 396)
FixedPointMathLibTest:testFailMulDivDownOverflow(uint256,uint256,uint256) (runs: 256, μ: 437, ~: 414)
Expand All @@ -274,12 +274,12 @@ FixedPointMathLibTest:testMulWadUp() (gas: 981)
FixedPointMathLibTest:testMulWadUp(uint256,uint256) (runs: 256, μ: 835, ~: 1073)
FixedPointMathLibTest:testMulWadUpEdgeCases() (gas: 959)
FixedPointMathLibTest:testRPow() (gas: 2164)
FixedPointMathLibTest:testSqrt() (gas: 2580)
FixedPointMathLibTest:testSqrt(uint256) (runs: 256, μ: 997, ~: 1013)
FixedPointMathLibTest:testSqrtBack(uint256) (runs: 256, μ: 15210, ~: 340)
FixedPointMathLibTest:testSqrtBackHashed(uint256) (runs: 256, μ: 59040, ~: 59500)
FixedPointMathLibTest:testSqrtBackHashedSingle() (gas: 58937)
LibStringTest:testDifferentiallyFuzzToString(uint256,bytes) (runs: 256, μ: 20749, ~: 8925)
FixedPointMathLibTest:testSqrt() (gas: 600)
FixedPointMathLibTest:testSqrt(uint256) (runs: 256, μ: 990, ~: 1038)
FixedPointMathLibTest:testSqrtBack(uint256) (runs: 256, μ: 14665, ~: 340)
FixedPointMathLibTest:testSqrtBackHashed(uint256) (runs: 256, μ: 56528, ~: 56940)
FixedPointMathLibTest:testSqrtBackHashedSingle() (gas: 56448)
LibStringTest:testDifferentiallyFuzzToString(uint256,bytes) (runs: 256, μ: 21006, ~: 9531)
LibStringTest:testToString() (gas: 10047)
LibStringTest:testToStringDirty() (gas: 8123)
LibStringTest:testToStringOverwrite() (gas: 484)
Expand Down
2 changes: 1 addition & 1 deletion lib/ds-test
Submodule ds-test updated 2 files
+22 −21 demo/demo.sol
+4 −4 src/test.sol
31 changes: 11 additions & 20 deletions src/utils/FixedPointMathLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -172,24 +172,15 @@ library FixedPointMathLib {
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.

// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Compute r = log2(x / 2^16) & 0b11110000, where y = x / 2^r.
// We only need the upper 4 bits of the log2.
let r := shl(7, gt(x, 0xffffffffffffffffffffffffffffffffff))
r := or(r, shl(6, gt(shr(r, x), 0xffffffffffffffffff)))
r := or(r, shl(5, gt(shr(r, x), 0xffffffffff)))
r := or(r, shl(4, gt(shr(r, x), 0xffffff)))
// Compute a good start for z.
// Shifting 181 left by r / 2 makes z approximately sqrt(x).
z := shl(shr(1, r), z)

// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
Expand All @@ -206,8 +197,8 @@ library FixedPointMathLib {
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.

// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// There is no overflow risk here since y < 2^136 after the approximate log2 computation above.
z := shr(18, mul(z, add(shr(r, x), 65536))) // A mul() is saved from starting z at 181.

// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
Expand Down