Skip to content
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
3 changes: 2 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
* Assembler: Fix not using a fixed-width type for IDs being assigned to subassemblies nested more than one level away, resulting in inconsistent `--asm-json` output between target architectures.
Expand All @@ -15,7 +17,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`.
Expand Down
36 changes: 35 additions & 1 deletion libevmasm/ConstantOptimiser.cpp
Copy link
Collaborator

@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,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
Expand Down
29 changes: 29 additions & 0 deletions libyul/backends/evm/ConstantOptimiser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ struct MiniEVMInterpreter
return exp256(args.at(0), args.at(1));
case evmasm::Instruction::SHL:
return args.at(0) > 255 ? 0 : (args.at(1) << unsigned(args.at(0)));
case evmasm::Instruction::SHR:
return args.at(1) >> unsigned(args.at(0));
case evmasm::Instruction::NOT:
return ~args.at(0);
default:
Expand Down Expand Up @@ -135,6 +137,33 @@ Representation const& RepresentationFinder::findRepresentation(u256 const& _valu

Representation routine = represent(_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_dialect.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
Representation newRoutine = represent(*auxHandles.not_, represent(0));
if ((highOnes + lowZeros) != 256)
newRoutine = represent(*auxHandles.shr, represent(256 - highOnes), newRoutine);
if (lowZeros > 0)
newRoutine = represent(*auxHandles.shl, represent(lowZeros), newRoutine);
routine = min(std::move(routine), std::move(newRoutine));
}


if (numberEncodingSize(~_value) < numberEncodingSize(_value))
// Negated is shorter to represent
routine = min(std::move(routine), represent(*auxHandles.not_, findRepresentation(~_value)));
Expand Down
1 change: 1 addition & 0 deletions libyul/backends/evm/EVMDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, std::optional<uint8_t>
m_auxiliaryBuiltinHandles.mul = EVMDialect::findBuiltin("mul");
m_auxiliaryBuiltinHandles.not_ = EVMDialect::findBuiltin("not");
m_auxiliaryBuiltinHandles.shl = EVMDialect::findBuiltin("shl");
m_auxiliaryBuiltinHandles.shr = EVMDialect::findBuiltin("shr");
m_auxiliaryBuiltinHandles.sub = EVMDialect::findBuiltin("sub");
}

Expand Down
1 change: 1 addition & 0 deletions libyul/backends/evm/EVMDialect.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class EVMDialect: public Dialect
std::optional<BuiltinHandle> mul;
std::optional<BuiltinHandle> not_;
std::optional<BuiltinHandle> shl;
std::optional<BuiltinHandle> shr;
std::optional<BuiltinHandle> sub;
};
/// Constructor, should only be used internally. Use the factory functions below.
Expand Down
8 changes: 4 additions & 4 deletions test/cmdlineTests/ir_subobject_order/output
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ object "C_33" {
if callvalue() { revert(0, 0) }
let _2 := datasize("A_13")
let _3 := add(_1, _2)
if or(gt(_3, sub(shl(64, 1), 1)), lt(_3, _1))
if or(gt(_3, shr(192, not(0))), lt(_3, _1))
{
mstore(0, shl(224, 0x4e487b71))
mstore(4, 0x41)
Expand All @@ -44,11 +44,11 @@ object "C_33" {
returndatacopy(pos, 0, returndatasize())
revert(pos, returndatasize())
}
sstore(0, or(and(sload(0), not(sub(shl(160, 1), 1))), and(expr_address, sub(shl(160, 1), 1))))
sstore(0, or(and(sload(0), shl(160, not(0))), and(expr_address, shr(96, not(0)))))
let _4 := mload(64)
let _5 := datasize("B_7")
let _6 := add(_4, _5)
if or(gt(_6, sub(shl(64, 1), 1)), lt(_6, _4))
if or(gt(_6, shr(192, not(0))), lt(_6, _4))
{
mstore(0, shl(224, 0x4e487b71))
mstore(4, 0x41)
Expand All @@ -62,7 +62,7 @@ object "C_33" {
returndatacopy(pos_1, 0, returndatasize())
revert(pos_1, returndatasize())
}
sstore(0x01, or(and(sload(0x01), not(sub(shl(160, 1), 1))), and(expr_address_1, sub(shl(160, 1), 1))))
sstore(0x01, or(and(sload(0x01), shl(160, not(0))), and(expr_address_1, shr(96, not(0)))))
let _7 := mload(64)
let _8 := datasize("C_33_deployed")
codecopy(_7, dataoffset("C_33_deployed"), _8)
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
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ object \"C_54\" {
let programSize := datasize(\"C_54\")
let argSize := sub(codesize(), programSize)
let newFreePtr := add(_1, and(add(argSize, 31), not(31)))
if or(gt(newFreePtr, sub(shl(64, 1), 1)), lt(newFreePtr, _1))
if or(gt(newFreePtr, shr(192, not(0))), lt(newFreePtr, _1))
{
mstore(/** @src -1:-1:-1 */ 0, /** @src 0:79:510 \"contract C...\" */ shl(224, 0x4e487b71))
mstore(4, 0x41)
Expand Down Expand Up @@ -685,7 +685,7 @@ object \"C_54\" {
if callvalue() { revert(0, 0) }
if slt(add(calldatasize(), not(3)), 0) { revert(0, 0) }
let _3 := sload(0)
if eq(_3, sub(shl(255, 1), 1))
if eq(_3, shr(1, not(0)))
{
mstore(0, shl(224, 0x4e487b71))
mstore(4, 0x11)
Expand Down Expand Up @@ -1457,7 +1457,7 @@ object \"D_72\" {
let programSize := datasize(\"D_72\")
let argSize := sub(codesize(), programSize)
let newFreePtr := add(_1, and(add(argSize, 31), not(31)))
if or(gt(newFreePtr, sub(shl(64, 1), 1)), lt(newFreePtr, _1))
if or(gt(newFreePtr, shr(192, not(0))), lt(newFreePtr, _1))
{
mstore(/** @src -1:-1:-1 */ 0, /** @src 1:91:181 \"contract D is C(3)...\" */ shl(224, 0x4e487b71))
mstore(4, 0x41)
Expand Down Expand Up @@ -1529,7 +1529,7 @@ object \"D_72\" {
if callvalue() { revert(0, 0) }
if slt(add(calldatasize(), not(3)), 0) { revert(0, 0) }
let _3 := sload(0)
if eq(_3, sub(shl(255, 1), 1))
if eq(_3, shr(1, not(0)))
{
mstore(0, shl(224, 0x4e487b71))
mstore(4, 0x11)
Expand Down
Loading