Skip to content

Mask generator optimizations #15935

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

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Language Features:
Compiler Features:
* NatSpec: Capture Natspec documentation of `enum` values in the AST.

* Constant Optimizer: Compute masks using shifts when optimizing for size; use an ``--optimizer-runs`` value less than 200 for maximum size reduction.

Bugfixes:
* SMTChecker: Do not consider loop conditions as constant-condition verification target as this could cause incorrect reports and internal compiler errors.
Expand Down
42 changes: 41 additions & 1 deletion libevmasm/ConstantOptimiser.cpp
Copy link
Member

@cameel cameel Mar 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekpyron Not strictly related to the PR, but while looking at it I noticed that ConstantOptimisationMethod::simpleRunGas() does not account for the new AssemblyItem types we introduced for EOF.

Especially DUPN and SWAPN. Though fortunately it should not have any real consequences because we are not using them in constant optimizer yet (heads up @rodiazet).

Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,47 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
if (_value < 0x10000)
// Very small value, not worth computing
return AssemblyItems{_value};
else if (numberEncodingSize(~_value) < numberEncodingSize(_value))

// check for masks first
unsigned lowZeros = 0;
unsigned highOnes = 0;
for (; ((_value >> lowZeros) & 1) == 0 && lowZeros < 256; lowZeros++) {}
for (; ((_value >> (lowZeros + highOnes)) & 1) == 1 && highOnes < 256; highOnes++) {}
if (m_params.evmVersion.hasBitwiseShifting() && highOnes > 32 &&
((_value >> (lowZeros + highOnes)) == 0) &&
((lowZeros + highOnes < 256) || lowZeros > 16))
{
// this is a big enough mask to use zero negation
AssemblyItems newRoutine = AssemblyItems{u256(0), Instruction::NOT};
if ((highOnes + lowZeros) != 256)
newRoutine += AssemblyItems{u256(256 - highOnes), Instruction::SHR};
if (lowZeros > 0)
newRoutine += AssemblyItems{u256(lowZeros), Instruction::SHL};
return newRoutine;
}
// check powers of 10
u256 divBy10 = _value;
unsigned pow10 = 0;
while (divBy10 > 0 && divBy10 % 10 == 0)
{
divBy10 = divBy10 / 10;
pow10++;
}
// 10^9 = 0x3b9aca00; requires 5 bytes with PUSH4; also 5 bytes with PUSH 5 PUSH 10 EXP
if (pow10 > 9 && divBy10 == 1)
{
// pure power of 10
return AssemblyItems{u256(pow10), u256(10), Instruction::EXP};
}
// 10^x * y can be encoded as: push x push 10 exp push y mul. That's about 7 bytes more than plain push
if (pow10 > 12)
{
AssemblyItems newRoutine = findRepresentation(divBy10);
newRoutine += AssemblyItems{u256(pow10), u256(10), Instruction::EXP, Instruction::MUL};
return newRoutine;
}
if (numberEncodingSize(~_value) < numberEncodingSize(_value) &&
(lowZeros+highOnes < 256 || highOnes > 16))
// Negated is shorter to represent
return findRepresentation(~_value) + AssemblyItems{Instruction::NOT};
else
Expand Down
4 changes: 2 additions & 2 deletions test/cmdlineTests/optimizer_BlockDeDuplicator/output
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ EVM assembly:
0x00
dup1
sload
not(sub(shl(0x40, 0x01), 0x01))
shl(0x40, not(0x00))
and
/* "input.sol":201:206 fun_x */
or(tag_0_7, shl(0x20, tag_2))
sub(shl(0x40, 0x01), 0x01)
shr(0xc0, not(0x00))
/* "input.sol":179:210 function() r = true ? fun_x : f */
and
or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ sub_0: assembly {
/* "input.sol":147:152 x = f */
dup1
sload
not(0xffffffffffffffff)
shl(0x40, not(0x00))
and
/* "input.sol":151:152 f */
tag_17
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ tag_1:
/* "input.sol":93:98 x = f */
dup1
sload
not(sub(shl(0x40, 0x01), 0x01))
shl(0x40, not(0x00))
and
/* "input.sol":97:98 f */
or(tag_0_12, shl(0x20, tag_4))
sub(shl(0x40, 0x01), 0x01)
shr(0xc0, not(0x00))
/* "input.sol":93:98 x = f */
and
or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
dup4
add
swap2
sub(shl(0x40, 0x01), 0x01)
shr(0xc0, not(0x00))
dup4
gt
dup5
Expand Down Expand Up @@ -104,7 +104,7 @@ sub_0: assembly {
jumpi(tag_26, callvalue)
jumpi(tag_26, slt(add(not(0x03), calldatasize), 0x00))
sload(0x00)
sub(shl(0xff, 0x01), 0x01)
shr(0x01, not(0x00))
dup2
eq
tag_14
Expand Down Expand Up @@ -350,7 +350,7 @@ sub_0: assembly {
dup4
add
swap2
sub(shl(0x40, 0x01), 0x01)
shr(0xc0, not(0x00))
dup4
gt
dup5
Expand Down Expand Up @@ -450,7 +450,7 @@ sub_0: assembly {
jumpi(tag_26, callvalue)
jumpi(tag_26, slt(add(not(0x03), calldatasize), 0x00))
sload(0x00)
sub(shl(0xff, 0x01), 0x01)
shr(0x01, not(0x00))
dup2
eq
tag_14
Expand Down
4 changes: 2 additions & 2 deletions test/cmdlineTests/viair_subobject_optimization/output
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ EVM assembly:
dup4
add
swap2
sub(shl(0x40, 0x01), 0x01)
shr(0xc0, not(0x00))
dup4
gt
dup5
Expand Down Expand Up @@ -223,7 +223,7 @@ sub_0: assembly {
dup4
add
swap2
sub(shl(0x40, 0x01), 0x01)
shr(0xc0, not(0x00))
dup4
gt
dup5
Expand Down
716 changes: 716 additions & 0 deletions test/libevmasm/evmAssemblyTests/constant_optimizer_runs_0.asmjson

Large diffs are not rendered by default.

716 changes: 716 additions & 0 deletions test/libevmasm/evmAssemblyTests/constant_optimizer_runs_200.asmjson

Large diffs are not rendered by default.

716 changes: 716 additions & 0 deletions test/libevmasm/evmAssemblyTests/constant_optimizer_runs_2M.asmjson

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// optimizer.constantOptimizer: true
// ----
// Assembly:
// sub(shl(0x40, 0x01), 0x01)
// Bytecode: 6001600160401b03
// Opcodes: PUSH1 0x1 PUSH1 0x1 PUSH1 0x40 SHL SUB
// SourceMappings: :::-:0;;;;
// shr(0xc0, not(0x00))
// Bytecode: 5f1960c01c
// Opcodes: PUSH0 NOT PUSH1 0xC0 SHR
// SourceMappings: :::-:0;;;
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
// stop
//
// sub_0: assembly {
// sub(shl(0x40, 0x01), 0x01)
// shr(0xc0, not(0x00))
// }
// Bytecode: 6003fe6001600160401b03
// Opcodes: PUSH1 0x3 INVALID PUSH1 0x1 PUSH1 0x1 PUSH1 0x40 SHL SUB
// Bytecode: 6003fe5f1960c01c
// Opcodes: PUSH1 0x3 INVALID PUSH0 NOT PUSH1 0xC0 SHR
// SourceMappings: :::-:0
6 changes: 3 additions & 3 deletions test/libsolidity/gasTests/abiv2_optimised.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ contract C {
// optimize-yul: true
// ----
// creation:
// codeDepositCost: 618200
// executionCost: 649
// totalCost: 618849
// codeDepositCost: 605600
// executionCost: 636
// totalCost: 606236
// external:
// a(): 2283
// b(uint256): 4649
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ contract C {
}
// ----
// f(bytes): 0x20, 0x80, 0x21, 0x40, 0x7, "abcdefg" -> 0x21, 0x40, 0x7, "abcdefg"
// gas irOptimized: 135499
// gas irOptimized: 135563
// gas legacy: 137095
// gas legacyOptimized: 135823
// gas legacyOptimized: 135879
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ contract C {
}
// ----
// f() -> 0x20, 0x8, 0x40, 0x3, 0x9, 0xa, 0xb
// gas irOptimized: 203167
// gas irOptimized: 203255
// gas legacy: 206263
// gas legacyOptimized: 203172
// gas legacyOptimized: 203220
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ contract C {
// EVMVersion: >homestead
// ----
// test_bytes() ->
// gas irOptimized: 314884
// gas irOptimized: 302904
// gas legacy: 305816
// gas legacyOptimized: 253573
// gas legacyOptimized: 244887
// test_uint256() ->
// gas irOptimized: 448346
// gas irOptimized: 428338
// gas legacy: 421304
// gas legacyOptimized: 351544
// gas legacyOptimized: 337032
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ contract C {
// EVMVersion: >homestead
// ----
// test_bytes() ->
// gas irOptimized: 314884
// gas irOptimized: 302904
// gas legacy: 305816
// gas legacyOptimized: 253573
// gas legacyOptimized: 244887
// test_uint256() ->
// gas irOptimized: 448346
// gas irOptimized: 428338
// gas legacy: 421304
// gas legacyOptimized: 351544
// gas legacyOptimized: 337032
4 changes: 2 additions & 2 deletions test/libsolidity/semanticTests/abiEncoderV2/abi_encode_v2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ contract C {
// f2() -> 0x20, 0xa0, 0x1, 0x60, 0x2, 0x3, "abc"
// f3() -> 0x20, 0xa0, 0x1, 0x60, 0x2, 0x3, "abc"
// f4() -> 0x20, 0x160, 0x1, 0x80, 0xc0, 0x2, 0x3, "abc", 0x7, 0x40, 0x2, 0x2, 0x3
// gas irOptimized: 111816
// gas irOptimized: 111807
// gas legacy: 113890
// gas legacyOptimized: 111658
// gas legacyOptimized: 111649
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ contract C is B {
}
// ----
// test() -> 77
// gas irOptimized: 55117
// gas irOptimized code: 56800
// gas irOptimized: 55177
// gas irOptimized code: 54400
// gas legacy: 57266
// gas legacy code: 94600
// gas legacyOptimized: 55195
// gas legacyOptimized: 55191
// gas legacyOptimized code: 55000
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ contract C {
// f(uint256[][1]): 32, 32, 0 -> true
// f(uint256[][1]): 32, 32, 1, 42 -> true
// f(uint256[][1]): 32, 32, 8, 421, 422, 423, 424, 425, 426, 427, 428 -> true
// gas irOptimized: 120978
// gas irOptimized: 115310
// gas legacy: 101568
// gas legacyOptimized: 119092
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ contract C {
// f_which(uint256[],uint256[2],uint256): 0x40, 1, 2, 1, 5, 6 -> 0x20, 0x40, 5, 2
// f_which(uint256[],uint256[2],uint256): 0x40, 1, 2, 1 -> FAILURE
// f_storage(uint256[],uint256[2]): 0x20, 1, 2 -> 0x20, 0x60, 0x20, 1, 2
// gas irOptimized: 111409
// gas irOptimized: 111441
// gas legacy: 112707
// gas legacyOptimized: 111845
// gas legacyOptimized: 111861
// f_storage(uint256[],uint256[2]): 0x40, 1, 2, 5, 6 -> 0x20, 0x80, 0x20, 2, 5, 6
// f_storage(uint256[],uint256[2]): 0x40, 1, 2, 5 -> FAILURE
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ contract C {
// EVMVersion: >homestead
// ----
// h(uint256[2][]): 0x20, 3, 123, 124, 223, 224, 323, 324 -> 32, 256, 0x20, 3, 123, 124, 223, 224, 323, 324
// gas irOptimized: 180080
// gas irOptimized: 180104
// gas legacy: 184233
// gas legacyOptimized: 180856
// i(uint256[2][2]): 123, 124, 223, 224 -> 32, 128, 123, 124, 223, 224
// gas irOptimized: 112031
// gas irOptimized: 112039
// gas legacy: 115091
// gas legacyOptimized: 112657
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ contract Test {
}
// ----
// set(uint24[3][]): 0x20, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12 -> 0x06
// gas irOptimized: 185216
// gas irOptimized: 185288
// gas legacy: 211054
// gas legacyOptimized: 206077
// gas legacyOptimized: 206149
// data(uint256,uint256): 0x02, 0x02 -> 0x09
// data(uint256,uint256): 0x05, 0x01 -> 0x11
// data(uint256,uint256): 0x06, 0x00 -> FAILURE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ contract c {
// test() -> 0
// gas irOptimized: 122717
// gas legacy: 147108
// gas legacyOptimized: 144200
// gas legacyOptimized: 143790
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ contract C {
}
// ----
// constructor(): 1, 2, 3 ->
// gas irOptimized: 124991
// gas irOptimized: 124887
// gas irOptimized code: 14800
// gas legacy: 134317
// gas legacy code: 46200
// gas legacyOptimized: 127166
// gas legacyOptimized: 127114
// gas legacyOptimized code: 23400
// a(uint256): 0 -> 1
// a(uint256): 1 -> 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ contract C {
}
// ----
// f() -> true
// gas irOptimized: 122541
// gas irOptimized: 122810
// gas legacy: 124643
// gas legacyOptimized: 122801
// gas legacyOptimized: 123108
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ contract c {
}
// ----
// test() -> 0x01000000000000000000000000000000000000000000000000, 0x02000000000000000000000000000000000000000000000000, 0x03000000000000000000000000000000000000000000000000, 0x04000000000000000000000000000000000000000000000000, 0x05000000000000000000000000000000000000000000000000
// gas irOptimized: 208415
// gas irOptimized: 208657
// gas legacy: 220711
// gas legacyOptimized: 220097
// gas legacyOptimized: 220354
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ contract c {
}
// ----
// test() -> 5, 4
// gas irOptimized: 205667
// gas irOptimized: 205763
// gas legacy: 213863
// gas legacyOptimized: 212901
// gas legacyOptimized: 212981
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ contract c {
}
// ----
// test() -> 3, 4
// gas irOptimized: 169669
// gas irOptimized: 169614
// gas legacy: 175415
// gas legacyOptimized: 172533
// gas legacyOptimized: 172474
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ contract c {
// ----
// setData1(uint256,uint256,uint256): 10, 5, 4 ->
// copyStorageStorage() ->
// gas irOptimized: 111350
// gas irOptimized: 111358
// gas legacy: 109272
// gas legacyOptimized: 109262
// getData2(uint256): 5 -> 10, 4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ contract c {
}
// ----
// test() -> 4, 5
// gas irOptimized: 190628
// gas irOptimized: 190621
// gas legacy: 190828
// gas legacyOptimized: 189657
// storageEmpty -> 1
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ contract c {
}
// ----
// test() -> 0xffffffff, 0x0000000000000000000000000a00090008000700060005000400030002000100, 0x0000000000000000000000000000000000000000000000000000000000000000
// gas irOptimized: 100496
// gas irOptimized: 100492
// gas legacy: 158109
// gas legacyOptimized: 141019
// gas legacyOptimized: 140731
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ contract c {
}
// ----
// test() -> 0x01000000000000000000000000000000000000000000000000, 0x02000000000000000000000000000000000000000000000000, 0x03000000000000000000000000000000000000000000000000, 0x04000000000000000000000000000000000000000000000000, 0x0
// gas irOptimized: 273543
// gas irOptimized: 273515
// gas legacy: 282601
// gas legacyOptimized: 281510
// gas legacyOptimized: 281424
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ contract c {
}
// ----
// test() -> 0x01000000000000000000000000000000000000000000000000, 0x02000000000000000000000000000000000000000000000000, 0x03000000000000000000000000000000000000000000000000, 0x04000000000000000000000000000000000000000000000000, 0x00
// gas irOptimized: 232995
// gas irOptimized: 233107
// gas legacy: 235694
// gas legacyOptimized: 235193
// gas legacyOptimized: 235102
Loading