Skip to content

Tracking issue: solidity optimizer story #1

Open
@moh-eulith

Description

@moh-eulith

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:

  1. Mask generation
    ethereum/solidity@develop...moh-eulith:solidity:28_a_mask_generation
    a)
    15 instances of PUSH1 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 of PUSH1 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)

  1. 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

  2. push dup swap
    ethereum/solidity@develop...moh-eulith:solidity:28_c_push_dup_swap
    58 instance of PUSH[123456789] 0x[0-9A-F]* DUP[2-9] SWAP1
    PUSH DUP[2-9] SWAP1 -> DUP[x-1] PUSH (save 1 byte)

  3. 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)

  4. 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

  5. Double jumps
    85 instances of JUMPDEST PUSH[12] 0x[0-9A-F]* JUMP
    this one is tricky (not peephole). it's typically preceded with JUMP or JUMPI
    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 between PUSH and PUSH_TAG
    then if the preceding statement is JUMP or STOP or RETURN, the whole thing can be removed (82 instances)
    if the preceding statement is JUMPI, only JUMPDEST can be removed

    After a discussion with @cameel, it became clear this can be implemented.
    ethereum/solidity@develop...moh-eulith:solidity:trampoline


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions