-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
Implement checked exponentiation. #9479
Conversation
Would be nice to know the gas costs of this. |
The inner loop itself takes about 144 gas per iteration. For an exp that uses the full 8 iterations this is about 1200 gas per exp operation. |
In comparison, the opcode itself costs 10 gas. |
If the LHS of the operation is a Literal, can we write a version where the compiler computes the max-value of the exponent: I tried to look at all the non-trivial examples (i.e., not |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -0,0 +1,11 @@ | |||
contract C { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't this test added in some other PR? Also, why not also via Yul?
{ | ||
solUnimplementedAssert(!_type.isSigned(), ""); | ||
solAssert(!_exponentType.isSigned(), ""); | ||
string functionName = "checked_exp_" + _type.identifier(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does _exponentType
need to be in the function name identifier as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
solAssert(!dynamic_cast<IntegerType const&>(_exponentType).isSigned(), ""); | ||
|
||
m_context << Instruction::EXP; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra blank
@hrkrshnn @leonardoalt you are right, we should certainly specialize this for literals. it might even make sense to add some conditional to check for the values of non-literals. |
3e20bc0
to
4a4014c
Compare
After the "optimization for small bases" commit, most of the tests will now not test the loop anymore... |
@leonardoalt can we prove this code correct? |
All of the examples in @hrkrshnn 's list, the bases were either 2, 10 or 256, so I added optimized version for those bases. |
with a few adjustments I think so |
// b**e < 2**256 <=> e * log(b) < 256 * log(2) <=> e < 256 * log(2) / log(b) | ||
if or( | ||
and(lt(base, 11), lt(exponent, 78)), | ||
and(lt(base, 257), lt(exponent, 33)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and(lt(base, 257), lt(exponent, 33)), | |
and(lt(base, 257), lt(exponent, 32)), |
Because 256**32 = 2**256 = UINT256_MAX + 1
// small base optimizations | ||
if eq(base, 2) { | ||
if gt(exponent, 255) { revert(0, 0) } | ||
power := exp(2, exponent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be converted to shl
by the optimizer right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can check, but manly, shl
does not exist on all platforms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it seems we do not have such a rule. I will add it separately.
33d175e
to
1405ed6
Compare
I just realized that functions containing |
87ae687
to
12d0d6a
Compare
base := <cleanupFunction>(base) | ||
exponent := <exponentCleanupFunction>(exponent) | ||
|
||
// avoiding `leave` to allow this function to be inlined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the function too lengthy to be inlined?
Why do we need checked exponentiation for the yul IR, but not for the old codegen? |
This function is tricky. We do not necessarily want it to be inlined, but we want information about the size of the base to be taken into account in the function. Currently, the optimizer can only do this through inlining. Using In the end of course, we should write the code such that it is best readable and improve the optimizer instead, because Solidity code will also very likely use If a function does not contain a |
Reverted to the version that uses |
// 0**0 == 1 | ||
switch exponent | ||
case 0 { power := 1 leave } | ||
case 1 { power := base leave } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ouch, Yul looks weird with these statements on the same line :)
5ac5d66
to
e97d00c
Compare
if and(exponent, 1) | ||
{ | ||
// no check needed for positive power, because base >= power | ||
if and(slt(power, 0), slt(power, sdiv(min, base))) { revert(0, 0) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was not able to find a test that does not revert if I comment out this line - maybe the check about base above is sufficient. Maybe someone can prove this superfluous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's redundant because
- max < |min|
- |power| < |base| everywhere in the loop (except base = +1 or -1)
- at this point, |base * base| <= max.
- therefore |power * base| < |base * base| <= max < |min|. So there is no underflow.
I'm now confident this is a version that can be merged, so please review! The version that optimizes constants and small values will be done in another PR. |
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) | ||
("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) | |
("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) | |
("maxValue", toCompactHexWithPrefix(_type.max())) | |
("minValue", toCompactHexWithPrefix(_type.min())) |
if and(exponent, 1) | ||
{ | ||
// no check needed for positive power, because base >= power | ||
if and(slt(power, 0), slt(power, sdiv(min, base))) { revert(0, 0) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's redundant because
- max < |min|
- |power| < |base| everywhere in the loop (except base = +1 or -1)
- at this point, |base * base| <= max.
- therefore |power * base| < |base * base| <= max < |min|. So there is no underflow.
// f(int256,uint256): -2, 1 -> -2 | ||
// f(int256,uint256): -2, 2 -> 4 | ||
// f(int256,uint256): -7, 63 -> -174251498233690814305510551794710260107945042018748343 | ||
// f(int256,uint256): -128, 2 -> 0x4000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add this:
// g(int256,uint256): -1, 115792089237316195423570985008687907853269984665640564039457584007913129639935 -> -1
A test case that loops 255 times.
// overflow check for base * base | ||
if gt(base, div(max, base)) { revert(0, 0) } | ||
if and(exponent, 1) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hrkrshnn simplified your proof a little - can you check, please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Fixes #9110.
Fixes #8853.