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

Optimize Math operations using branchless bool to uint translation. #4878

Merged
merged 10 commits into from
Feb 7, 2024
87 changes: 41 additions & 46 deletions contracts/utils/math/Math.sol
Original file line number Diff line number Diff line change
Expand Up @@ -391,57 +391,42 @@ library Math {
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256 result) {
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
uint256 isGt;
unchecked {
uint256 isGt;

assembly {
isGt := gt(value, 0xffffffffffffffffffffffffffffffff)
}
isGt = boolToUint(value > 0xffffffffffffffffffffffffffffffff);
value >>= isGt * 128;
result += isGt * 128;

assembly {
isGt := gt(value, 0xffffffffffffffff)
}
isGt = boolToUint(value > 0xffffffffffffffff);
value >>= isGt * 64;
result += isGt * 64;

assembly {
isGt := gt(value, 0xffffffff)
}
isGt = boolToUint(value > 0xffffffff);
value >>= isGt * 32;
result += isGt * 32;

assembly {
isGt := gt(value, 0xffff)
}
isGt = boolToUint(value > 0xffff);
value >>= isGt * 16;
result += isGt * 16;

assembly {
isGt := gt(value, 0xff)
}
isGt = boolToUint(value > 0xff);
value >>= isGt * 8;
result += isGt * 8;

assembly {
isGt := gt(value, 0xf)
}
isGt = boolToUint(value > 0xf);
value >>= isGt * 4;
result += isGt * 4;

assembly {
isGt := gt(value, 0x3)
}
isGt = boolToUint(value > 0x3);
value >>= isGt * 2;
result += isGt * 2;

assembly {
isGt := gt(value, 0x1)
}
isGt = boolToUint(value > 0x1);
result += isGt;
}
return result;
}

/**
Expand Down Expand Up @@ -512,26 +497,26 @@ library Math {
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
uint256 isGt;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
isGt = boolToUint(value > 0xffffffffffffffffffffffffffffffff);
value >>= isGt * 128;
result += isGt * 16;

isGt = boolToUint(value > 0xffffffffffffffff);
value >>= isGt * 64;
result += isGt * 8;

isGt = boolToUint(value > 0xffffffff);
value >>= isGt * 32;
result += isGt * 4;

isGt = boolToUint(value > 0xffff);
value >>= isGt * 16;
result += isGt * 2;

isGt = boolToUint(value > 0xff);
result += isGt;
}
return result;
}
Expand All @@ -553,4 +538,14 @@ library Math {
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}

/**
* @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
*/
function boolToUint(bool b) internal pure returns (uint256 u) {
/// @solidity memory-safe-assembly
assembly {
u := b
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks to me more of a fit into the SafeCast library with the function toUint(bool b) signature as it already requires a boolean as an argument.

Copy link
Contributor Author

@CodeSandwich CodeSandwich Feb 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm totally fine with this, but does it fit the other functions from SafeCast? IIUC the main goal of this library is to make dangerous operations and throw in case of an overflow. In toUint256(bool) there's no overflow possible, there's nothing "safe" about this SafeCast function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's right, I don't have the last word but I feel confident that SafeCast is a better fit than Math.

We might consider having these unconventional casts in a different library but that's a broader discussion.

}
10 changes: 10 additions & 0 deletions test/utils/math/Math.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,4 +512,14 @@ describe('Math', function () {
});
});
});

describe('boolToUint', function () {
it('boolToUint(false) should be 0', async function () {
expect(await this.mock.$boolToUint(false)).to.equal(0n);
});

it('boolToUint(true) should be 1', async function () {
expect(await this.mock.$boolToUint(true)).to.equal(1n);
});
});
});
Loading