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 3 commits into
base: develop
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -4,6 +4,8 @@ Language Features:

Compiler Features:
* ethdebug: Experimental support for instructions and source locations under EOF.
* Constant Optimizer: Compute masks using shifts when optimizing for size; use an ``--optimizer-runs`` value less than 200 for maximum size reduction.


Bugfixes:

@@ -13,7 +15,6 @@ Compiler Features:
* EVM: Set default EVM Version to `prague`.
* NatSpec: Capture Natspec documentation of `enum` values in the AST.


Bugfixes:
* SMTChecker: Do not consider loop conditions as constant-condition verification target as this could cause incorrect reports and internal compiler errors.
* SMTChecker: Fix incorrect analysis when only a subset of contracts is selected with `--model-checker-contracts`.
36 changes: 35 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
@@ -253,7 +253,41 @@ 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
// high ones low zeros
// |----------||--------------|
// 0x000000000000000000000000000000000000ffffffffffff0000000000000000
unsigned lowZeros = 0;
unsigned highOnes = 0;
while (((_value >> lowZeros) & 1) == 0 && lowZeros < 256)
++lowZeros;
while (((_value >> (lowZeros + highOnes)) & 1) == 1 && highOnes < 256)
++highOnes;
if (
m_params.evmVersion.hasBitwiseShifting() &&
highOnes > 32 && // push would be more efficient otherwise
((_value >> (lowZeros + highOnes)) == 0) && // this is a pure mask
((lowZeros + highOnes < 256) || lowZeros > 16) // otherwise negation is more effective
)
{
// 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;
}
// pure negation can sometimes produce bad results
// example: 0xff00000000000000000000000000000000000000000000000000000000000000
// 0xff at the most significant byte of u256
// without the extra condition: not(sub(shl(0xf8, 0x01), 0x01))
// the extra condition turns that into: shl(0xf8, 0xff)
if (
numberEncodingSize(~_value) < numberEncodingSize(_value) &&
(lowZeros+highOnes < 256 || highOnes > 16)
)
// Negated is shorter to represent
return findRepresentation(~_value) + AssemblyItems{Instruction::NOT};
else
4 changes: 2 additions & 2 deletions test/cmdlineTests/optimizer_BlockDeDuplicator/output
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@
dup4
add
swap2
sub(shl(0x40, 0x01), 0x01)
shr(0xc0, not(0x00))
dup4
gt
dup5
@@ -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
@@ -350,7 +350,7 @@ sub_0: assembly {
dup4
add
swap2
sub(shl(0x40, 0x01), 0x01)
shr(0xc0, not(0x00))
dup4
gt
dup5
@@ -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
4 changes: 2 additions & 2 deletions test/cmdlineTests/viair_subobject_optimization/output
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ EVM assembly:
dup4
add
swap2
sub(shl(0x40, 0x01), 0x01)
shr(0xc0, not(0x00))
dup4
gt
dup5
@@ -223,7 +223,7 @@ sub_0: assembly {
dup4
add
swap2
sub(shl(0x40, 0x01), 0x01)
shr(0xc0, not(0x00))
dup4
gt
dup5
Loading