Description
I have a large contract that's compiled with --via-ir --optimize --optimize-runs 2
It's very close to going over the limit.
I looked at the generated opcodes and found several optimization opportunities.
I've implemented 1-5 in this repo and will do PR's against the main repo.
1 is implemented as a single change to the constant optimizer properly chooses between gas and size based
on command line parameters. This is single biggest size win (~945 bytes).
All five reduce the binary size of my large contract by 1149 bytes, which is significant.
ethereum/solidity@develop...moh-eulith:solidity:peep_combine
(6) is the only remaining large one, which I can't implement easily.
The following reduce both size and gas:
- Mask generation
ethereum/solidity@develop...moh-eulith:solidity:28_a_mask_generation
a)
15 instances ofPUSH1 0x1 PUSH1 0x1 PUSH1 0x[0-9A-F]* SHL SUB NOT
(sub case of the next one)
->PUSH0 NOT PUSH1 $bits SHL
(saves 4 bytes)
247 instances ofPUSH1 0x1 PUSH1 0x1 PUSH1 0x[0-9A-F]* SHL SUB
, saves 3*247 bytes
PUSH1 0x1 PUSH1 0x1 PUSH1 0x40 SHL SUB
: 8 bytes, 3 + 3 + 3 + 3 + 3 = 15 gas
->PUSH0 NOT PUSH1 0xC0 SHR
: 5 bytes, 2 + 3 + 3 + 3 = 11 gas
b) Shifted mask generation
53 instances of PUSH1 0x1 PUSH1 0x[0-9A-F]* SHL PUSH1 0x1 PUSH1 0x[0-9A-F]* SHL SUB
: saves 3*53
PUSH1 0x1 PUSH1 0x40 SHL PUSH1 0x1 PUSH1 0x80 SHL SUB
: 11 opcodes
-> PUSH0 NOT PUSH1 0xC0 SHR PUSH1 0x40 SHL
: 8 opcodes
The following reduce code size but increase gas cost:
This is implemented as part of the constant optimizer and properly chooses between gas and size based on command line params.
c)
42 instances of:
PUSH[56789] 0x[137F]F[F]*
(pushing with all 1's).
Length optimization (gas deopt): PUSH0 NOT PUSH1 (256 - $bits) SHR
(save 1 or more bytes)
-
triple swap
ethereum/solidity@develop...moh-eulith:solidity:28_b_triple_swap
40 instances of:
PUSH[1234] 0x[0-9ABCDEF]* SWAPN SWAP1 SWAPN
->SWAP[N-1] PUSH
(save 2 bytes)
no instances of dup swapN swap1 swapN -
push dup swap
ethereum/solidity@develop...moh-eulith:solidity:28_c_push_dup_swap
58 instance ofPUSH[123456789] 0x[0-9A-F]* DUP[2-9] SWAP1
PUSH DUP[2-9] SWAP1
->DUP[x-1] PUSH
(save 1 byte) -
push x, swapZ, pushy, swap1
ethereum/solidity@develop...moh-eulith:solidity:28_d_two_push_swap
12 instances of PUSH[123456789] 0x[0-9A-F]* SWAP[1-9] PUSH[123456789] 0x[0-9A-F]* SWAP1
push x, swapZ, pushy, swap1
this the same as: push y, push x, swap[Z+1]
1st push can also be dup:
dupX swapZ pushY swap1 -> pushY dup(X+1) swap(Z+1) -
push swap1 swap2 and (about 20 instances)
-> swap1 push and (because and is commutative)
ethereum/solidity@develop...moh-eulith:solidity:28_e_push_two_swap_comop
generalizes to: push swap1 swapN ComOp -> swap[N-1] push ComOp
no dup instances -
Double jumps
85 instances ofJUMPDEST PUSH[12] 0x[0-9A-F]* JUMP
this one is tricky (not peephole). it's typically preceded withJUMP
orJUMPI
All incoming jumps can be replaced with the push value (3 + 8 == 11 gas optimization)this is hard.
it's not clear to me if there are any dynamic jumps in the code (jump address is computed, not a constant)
The code seems to differentiate between push and push_tag, so maybe there are no dynamic jumps.
This also can't be done externally because by the time we have just opcodes, there is no difference betweenPUSH
andPUSH_TAG
then if the preceding statement isJUMP
orSTOP
orRETURN
, the whole thing can be removed (82 instances)
if the preceding statement isJUMPI
, onlyJUMPDEST
can be removed
After a discussion with @cameel, it became clear this can be implemented.
ethereum/solidity@develop...moh-eulith:solidity:trampoline