Christian Reitwiessner
Ethereum Foundation
@ethchris github.com/chriseth chris@ethereum.org
https://chriseth.github.io/notes/talks/yul_devcon5/
"Less Gas, More Fun: Optimising Smart Contracts through Yul"
function alloc(size) -> p {
p := mload(0x40)
mstore(p, add(p, size))
}
solc --ir
solc --strict-assembly --optimize
contract ERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
mapping (address => uint256) private _balances;
uint256 private _totalSupply;
constructor() public {
_mint(msg.sender, 20);
}
...
function _mint(address account, uint256 value) internal {
require(account != address(0), "ERC20: mint to the zero address");
// The additions here will revert on overflow.
_totalSupply = _totalSupply + value;
_balances[account] = _balances[account] + value;
emit Transfer(address(0), account, value);
}
...
}
mstore(64, 128) if callvalue() { revert(0, 0) } if iszero(caller()) { mstore(128, 0x08c379a0000000000000000000...) mstore(132, 32) mstore(164, 31) mstore(196, "ERC20: mint to the zero address") revert(128, 100) } // _totalSupply = _totalSupply + value sstore(0x2, checked_add_t_uint256(sload(0x2), 20)) // _balances[account] = _balances[account] + value sstore( mapping_index_access_t_mapping...(0x0, caller()), checked_add_t_uint256( sload(mapping_index_access_t_mapping...(0x0, caller())), 20)) // emit Transfer(address(0), account, value) let _1 := mload(64) log3(_1, sub(abi_encode_tuple_t_uint256__to_t_uint256__fromStack(_1, 0x14), _1), 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0, caller()) let _2 := datasize("ERC20_396_deployed") codecopy(0x0, dataoffset("ERC20_396_deployed"), _2) return(0x0, _2) ...
So still work to do, but on the right track!
solc --ewasm
-> Genericity of Yul is a big win, we can re-use almost all optimizer components.
mstore(0, gaslimit())
let _1, _2, _3, _4 := gaslimit()
mstore(0, 0, 0, 0, _1, _2, _3, _4)
function gaslimit() -> z1, z2, z3, z4 {
z4 := eth.getBlockGasLimit()
}
function mstore(x1, x2, x3, x4, y1, y2, y3, y4) {
let pos := u256_to_i32ptr(x1, x2, x3, x4)
i64.store(pos, endian_swap(x1))
i64.store(i64.add(pos, 8), endian_swap(x2))
i64.store(i64.add(pos, 16), endian_swap(x3))
i64.store(i64.add(pos, 24), endian_swap(x4))
}
function endian_swap(x) -> y {
...
}
function u256_to_i32ptr(x1, x2, x3, x4) -> v {
...
}
mstore(0, 0, 0, 0, 0, 0, 0, eth.getBlockGasLimit())
function mstore(x1, x2, x3, x4, y1, y2, y3, y4) {
let pos := u256_to_i32ptr(x1, x2, x3, x4)
i64.store(pos, endian_swap(x1))
i64.store(i64.add(pos, 8), endian_swap(x2))
i64.store(i64.add(pos, 16), endian_swap(x3))
i64.store(i64.add(pos, 24), endian_swap(x4))
}
function endian_swap(x) -> y {
...
}
function u256_to_i32ptr(x1, x2, x3, x4) -> v {
...
}
i64.store(0, 0)
i64.store(64, 0)
i64.store(128, 0)
i64.store(192, 0)
i64.store(256, 0)
i64.store(320, 0)
i64.store(384, 0)
i64.store(448, endian_swap(eth.getBlockGasLimit()))
function endian_swap(x) -> y {
...
}
Transform any
if c { stop() }
to
if c { stop() }
c := 0
.
Simple transformation and trivially correct. No need to search for this information in complicated data structure.
Downside: Cannot use this for if gt(c, 3) { stop() }
Optimizer mostly language-agnostic, can be configured with different built-ins (also works on ewasm-flavoured yul).