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
Replace runtime checks with compile-time checks for div and mod operations on constant denominator #13206
Comments
|
Hi. I'd like to attempt implementing this. I'm still in the process of tracing how the optimizer is implemented. |
|
@timweri I wanted to give you some hints but after looking into it I'm not sure if there's actually anything to do here. And if there is, I'd rate this as medium difficulty because jumping into the optimizer is definitely much more effort than the trivial tasks we usually rate as easy. @ZumZoom Have you tried the new IR-based pipeline (i.e. the ExampleLet's take parts of your example and compare the compiler output using both pipelines.
uint256 constant _MODULE = 17;
contract C {
function f(uint256 a) external pure returns(uint256) {
return a / _MODULE;
}
}
uint256 constant _MODULE = 17;
contract C {
function f(uint256 a) external pure returns(uint256) {
unchecked {
return a / _MODULE;
}
}
}IR pipelinediff --unified \
<(solc checked.sol --optimize --ir-optimized --debug-info none) \
<(solc unchecked.sol --optimize --ir-optimized --debug-info none)Here you get code that's pretty much identical. Only slight differences in naming that should not affect bytecode (and cost): @@ -1,18 +1,18 @@
Optimized IR:
-/// @use-src 0:"checked.sol"
-object "C_16" {
+/// @use-src 0:"unchecked.sol"
+object "C_17" {
code {
{
let _1 := memoryguard(0x80)
mstore(64, _1)
if callvalue() { revert(0, 0) }
- let _2 := datasize("C_16_deployed")
- codecopy(_1, dataoffset("C_16_deployed"), _2)
+ let _2 := datasize("C_17_deployed")
+ codecopy(_1, dataoffset("C_17_deployed"), _2)
return(_1, _2)
}
}
- /// @use-src 0:"checked.sol"
- object "C_16_deployed" {
+ /// @use-src 0:"unchecked.sol"
+ object "C_17_deployed" {
code {
{
let _1 := memoryguard(0x80)
@@ -30,7 +30,7 @@
revert(0, 0)
}
}Legacy pipelinediff --unified \
<(solc checked.sol --optimize --asm --debug-info none) \
<(solc unchecked.sol --optimize --asm --debug-info none)Via the legacy pipeline the unchecked version is clearly shorter: -======= checked.sol:C =======
+======= unchecked.sol:C =======
EVM assembly:
mstore(0x40, 0x80)
callvalue
@@ -52,8 +52,11 @@
tag_6
jump // in
tag_5:
- tag_7
- jump // in
+ 0x11
+ swap1
+ div
+ swap1
+ jump
tag_4:
mload(0x40)
swap1
@@ -67,19 +70,6 @@
sub
swap1
return
- tag_7:
- 0x00
- tag_11
- 0x11
- dup4
- tag_12
- jump // in
- tag_11:
- swap3
- swap2
- pop
- pop
- jump // out
tag_6:
0x00
0x20
@@ -88,40 +78,19 @@
sub
slt
iszero
- tag_15
+ tag_16
jumpi
0x00
dup1
revert
- tag_15:
+ tag_16:
pop
calldataload
swap2
swap1
pop
jump // out
- tag_12:
- 0x00
- dup3
- tag_18
- jumpi
- 0x4e487b71
- 0xe0
- shl
- 0x00
- mstore
- 0x12
- 0x04
- mstore
- 0x24
- 0x00
- revert
- tag_18:
- pop
- div
- swap1
- jump // out |
When doing division and modulo operation with constant or immutable denominator it is possible to only do compile-time denominator check and skip runtime checks.
Consider following contracts:
Even though at compile-time it is known that there is no case when division by zero will happen, generated code for checked case is more complicated than for unchecked case.
Corresponding gas costs of each function:
The text was updated successfully, but these errors were encountered: