From 360bc568cb520230083ea4c377117595a9c85caa Mon Sep 17 00:00:00 2001 From: "Daniel Von Fange (K)" Date: Tue, 12 May 2026 12:42:13 +0000 Subject: [PATCH] Using memory loads for large constant creation --- docs/internals/optimizer.rst | 11 ++++ docs/using-the-compiler.rst | 4 ++ libevmasm/Assembly.cpp | 4 +- libevmasm/Assembly.h | 1 + libevmasm/CMakeLists.txt | 2 + libevmasm/ConstantOptimiser.cpp | 50 +++++++++++++++---- libevmasm/ConstantOptimiser.h | 18 +++++++ libevmasm/MemoryMasking.cpp | 49 ++++++++++++++++++ libevmasm/MemoryMasking.h | 47 +++++++++++++++++ libsolidity/codegen/Compiler.cpp | 1 + libsolidity/codegen/CompilerContext.cpp | 8 ++- libsolidity/codegen/CompilerContext.h | 5 ++ libsolidity/codegen/CompilerUtils.cpp | 17 ++++++- libsolidity/codegen/CompilerUtils.h | 5 ++ libsolidity/codegen/ContractCompiler.cpp | 1 + .../codegen/ir/IRGenerationContext.cpp | 17 ++++--- libsolidity/codegen/ir/IRGenerationContext.h | 7 ++- libsolidity/codegen/ir/IRGenerator.cpp | 16 ++++-- libsolidity/codegen/ir/IRGenerator.h | 3 +- libsolidity/interface/CompilerStack.cpp | 5 +- libsolidity/interface/OptimiserSettings.h | 14 ++++++ libsolidity/interface/StandardCompiler.cpp | 24 +++++++-- libyul/ObjectOptimizer.cpp | 2 + libyul/ObjectOptimizer.h | 1 + libyul/YulStack.cpp | 3 +- libyul/YulStack.h | 7 ++- libyul/backends/evm/ConstantOptimiser.cpp | 30 +++++++++-- libyul/backends/evm/ConstantOptimiser.h | 15 ++++-- libyul/optimiser/Suite.cpp | 3 +- libyul/optimiser/Suite.h | 1 + solc/CommandLineParser.cpp | 3 ++ test/libevmasm/EVMAssemblyTest.cpp | 1 + test/libevmasm/Optimiser.cpp | 36 +++++++++++++ test/libsolidity/StandardCompiler.cpp | 4 +- test/libsolidity/gasTests/abiv2_optimised.sol | 12 ++--- .../gasTests/dispatch_large_optimised.sol | 48 +++++++++--------- .../gasTests/dispatch_medium_optimised.sol | 22 ++++---- .../gasTests/dispatch_small_optimised.sol | 12 ++--- test/libsolidity/gasTests/exp_optimized.sol | 12 ++--- test/libsolidity/gasTests/storage_costs.sol | 12 ++--- .../sizeLimits/bytecode_too_large.sol | 2 +- .../bytecode_too_large_abiencoder_v1.sol | 2 +- test/libyul/YulOptimizerTestCommon.cpp | 10 +++- .../full_mask_memory_masks.yul | 13 +++++ test/tools/fuzzer_common.cpp | 1 + 45 files changed, 457 insertions(+), 104 deletions(-) create mode 100644 libevmasm/MemoryMasking.cpp create mode 100644 libevmasm/MemoryMasking.h create mode 100644 test/libyul/yulOptimizerTests/constantOptimiserWithMemoryMasks/full_mask_memory_masks.yul diff --git a/docs/internals/optimizer.rst b/docs/internals/optimizer.rst index 8c737371c058..4dad1e0d51d2 100644 --- a/docs/internals/optimizer.rst +++ b/docs/internals/optimizer.rst @@ -204,6 +204,17 @@ simplifies to this: data[7] = 9; return 1; +Constant Optimizer +------------------ + +The opcode-based constant optimizer can replace large literal constants by shorter code that +computes the same value. For byte-aligned, right-aligned masks of ``0xff`` bytes that would +otherwise be emitted literally or computed, the compiler may reserve a memory area containing a +zero word followed by an all-ones word and materialize the mask with ``MLOAD``. This behavior is +controlled by the Standard JSON ``memoryMasks`` optimizer detail and defaults to enabled when +either the constant optimizer or the Yul optimizer is enabled and the optimizer ``runs`` value is at +most ``200``. + Simple Inlining --------------- diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index fcc8da248727..86463bd604df 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -330,6 +330,10 @@ Input Description // Tries to find better representations of literal numbers and strings, that satisfy the // size/cost trade-off determined by the 'runs' setting. "constantOptimizer": false, + // Memory mask constants. Optional. Default: true when either constant or Yul optimizer is enabled and 'runs' <= 200. + // Uses a reserved memory region to materialize selected mask constants that the constant + // optimizer would otherwise compute. + "memoryMasks": false, // Unchecked loop increment (codegen-based). Optional. Default: true. // Use unchecked arithmetic when incrementing the counter of 'for' loops under certain circumstances. // NOTE: Always runs (even with optimization disabled) unless explicitly turned off here. diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index ef6c0bec9cce..c3b7bd52d1d4 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -994,6 +994,7 @@ std::map const& Assembly::optimiseInternal( isCreation(), isCreation() ? 1 : _settings.expectedExecutionsPerDeployment, m_evmVersion, + _settings.useMemoryMasks && !isCreation(), *this ); @@ -1862,13 +1863,14 @@ Assembly const* Assembly::subAssemblyById(SubAssemblyID const _subId) const Assembly::OptimiserSettings Assembly::OptimiserSettings::translateSettings(frontend::OptimiserSettings const& _settings) { // Constructing it this way so that we notice changes in the fields. - OptimiserSettings asmSettings{false, false, false, false, false, false, 0}; + OptimiserSettings asmSettings{false, false, false, false, false, false, false, 0}; asmSettings.runInliner = _settings.runInliner; asmSettings.runJumpdestRemover = _settings.runJumpdestRemover; asmSettings.runPeephole = _settings.runPeephole; asmSettings.runDeduplicate = _settings.runDeduplicate; asmSettings.runCSE = _settings.runCSE; asmSettings.runConstantOptimiser = _settings.runConstantOptimiser; + asmSettings.useMemoryMasks = _settings.useMemoryMasks(); asmSettings.expectedExecutionsPerDeployment = _settings.expectedExecutionsPerDeployment; return asmSettings; } diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index f931c438e38a..f2c20d5ba64e 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -171,6 +171,7 @@ class Assembly bool runDeduplicate = false; bool runCSE = false; bool runConstantOptimiser = false; + bool useMemoryMasks = false; /// This specifies an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage. size_t expectedExecutionsPerDeployment = frontend::OptimiserSettings{}.expectedExecutionsPerDeployment; diff --git a/libevmasm/CMakeLists.txt b/libevmasm/CMakeLists.txt index 55ae30e7c182..30d8a3f599c2 100644 --- a/libevmasm/CMakeLists.txt +++ b/libevmasm/CMakeLists.txt @@ -35,6 +35,8 @@ set(sources KnownState.h LinkerObject.cpp LinkerObject.h + MemoryMasking.cpp + MemoryMasking.h PathGasMeter.cpp PathGasMeter.h PeepholeOptimiser.cpp diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp index 7cf00fc9ed41..bca516c64175 100644 --- a/libevmasm/ConstantOptimiser.cpp +++ b/libevmasm/ConstantOptimiser.cpp @@ -23,6 +23,7 @@ #include #include #include +#include using namespace solidity; using namespace solidity::evmasm; @@ -31,6 +32,7 @@ unsigned ConstantOptimisationMethod::optimiseConstants( bool _isCreation, size_t _runs, langutil::EVMVersion _evmVersion, + bool _useMemoryMasks, Assembly& _assembly ) { @@ -53,27 +55,38 @@ unsigned ConstantOptimisationMethod::optimiseConstants( Params params; params.multiplicity = it.second; params.isCreation = _isCreation; + params.useMemoryMasks = _useMemoryMasks; params.runs = _runs; params.evmVersion = _evmVersion; LiteralMethod lit(params, item.data()); bigint literalGas = lit.gasNeeded(); - CodeCopyMethod copy(params, item.data()); - bigint copyGas = copy.gasNeeded(); - ComputeMethod compute(params, item.data()); - bigint computeGas = compute.gasNeeded(); AssemblyItems replacement; - if (copyGas < literalGas && copyGas < computeGas) + if (params.useMemoryMasks) { - replacement = copy.execute(_assembly); - optimisations++; + MemoryLoadMethod memoryLoad(params, item.data()); + if (memoryLoad.valid() && memoryLoad.gasNeeded() < literalGas) + replacement = memoryLoad.execute(_assembly); } - else if (computeGas < literalGas && computeGas <= copyGas) + if (replacement.empty()) { - replacement = compute.execute(_assembly); - optimisations++; + CodeCopyMethod copy(params, item.data()); + bigint copyGas = copy.gasNeeded(); + ComputeMethod compute(params, item.data()); + bigint computeGas = compute.gasNeeded(); + if (copyGas < literalGas && copyGas < computeGas) + { + replacement = copy.execute(_assembly); + } + else if (computeGas < literalGas && computeGas <= copyGas) + { + replacement = compute.execute(_assembly); + } } if (!replacement.empty()) + { + optimisations++; pendingReplacements[item.data()] = replacement; + } } if (!pendingReplacements.empty()) replaceConstants(_items, pendingReplacements); @@ -389,3 +402,20 @@ bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine) const 0 ); } + +MemoryLoadMethod::MemoryLoadMethod(Params const& _params, u256 const& _value): + ConstantOptimisationMethod(_params, _value) +{ + if (std::optional offset = MemoryMasking::offsetForRightAlignedOnes(_value)) + m_routine = AssemblyItems{u256(*offset), Instruction::MLOAD}; +} + +bigint MemoryLoadMethod::gasNeeded() const +{ + solAssert(valid(), ""); + return combineGas( + simpleRunGas(m_routine, m_params.evmVersion), + bytesRequired(m_routine, m_params.evmVersion) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas), + 0 + ); +} diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h index 4952d7985d43..b82cdc1f9f9a 100644 --- a/libevmasm/ConstantOptimiser.h +++ b/libevmasm/ConstantOptimiser.h @@ -51,6 +51,7 @@ class ConstantOptimisationMethod bool _isCreation, size_t _runs, langutil::EVMVersion _evmVersion, + bool _useMemoryMasks, Assembly& _assembly ); @@ -60,6 +61,7 @@ class ConstantOptimisationMethod struct Params { bool isCreation; ///< Whether this is called during contract creation or runtime. + bool useMemoryMasks; ///< Whether the reserved memory mask region may be used. size_t runs; ///< Estimated number of calls per opcode oven the lifetime of the contract. size_t multiplicity; ///< Number of times the constant appears in the code. langutil::EVMVersion evmVersion; ///< Version of the EVM @@ -150,4 +152,20 @@ class ComputeMethod: public ConstantOptimisationMethod AssemblyItems m_routine; }; +/** + * Method that loads byte-aligned right-aligned masks from the reserved memory mask region. + */ +class MemoryLoadMethod: public ConstantOptimisationMethod +{ +public: + MemoryLoadMethod(Params const& _params, u256 const& _value); + + bool valid() const { return !m_routine.empty(); } + bigint gasNeeded() const override; + AssemblyItems execute(Assembly&) const override { return m_routine; } + +private: + AssemblyItems m_routine; +}; + } diff --git a/libevmasm/MemoryMasking.cpp b/libevmasm/MemoryMasking.cpp new file mode 100644 index 000000000000..62201eff1735 --- /dev/null +++ b/libevmasm/MemoryMasking.cpp @@ -0,0 +1,49 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +using namespace solidity; +using namespace solidity::evmasm; + +std::optional MemoryMasking::offsetForRightAlignedOnes(u256 const& _value) +{ + u256 mask = 0; + for (size_t bytes = 1; bytes < maskSize; ++bytes) + { + mask <<= 8; + mask |= 0xff; + if (bytes > 2 && _value == mask) + return zeroPointer + bytes; + } + return std::nullopt; +} + +std::optional MemoryMasking::constantForOffset(size_t _offset) +{ + if (_offset < zeroPointer || _offset > maskPointer) + return std::nullopt; + + u256 value = 0; + for (size_t bytes = 0; bytes < _offset - zeroPointer; ++bytes) + { + value <<= 8; + value |= 0xff; + } + return value; +} diff --git a/libevmasm/MemoryMasking.h b/libevmasm/MemoryMasking.h new file mode 100644 index 000000000000..730fc7612b88 --- /dev/null +++ b/libevmasm/MemoryMasking.h @@ -0,0 +1,47 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Helpers for the memory region used to materialize common mask constants. + */ + +#pragma once + +#include + +#include +#include + +namespace solidity::evmasm +{ + +struct MemoryMasking +{ + static size_t constexpr zeroPointer = 0x60; + static size_t constexpr maskPointer = 0x80; + static size_t constexpr maskSize = 32; + static size_t constexpr memoryStart = maskPointer + maskSize; + + /// @returns the offset in the zero/ones memory region whose MLOAD result is @a _value, + /// if @a _value is a right-aligned byte mask wider than two bytes. + static std::optional offsetForRightAlignedOnes(u256 const& _value); + + /// @returns the constant produced by MLOADing at @a _offset in the zero/ones memory region. + static std::optional constantForOffset(size_t _offset); +}; + +} diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 3e3f1499694b..c77edfd0a31c 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -56,6 +56,7 @@ void Compiler::compileContract( // This might modify m_runtimeContext because it can access runtime functions at // creation time. OptimiserSettings creationSettings{m_optimiserSettings}; + creationSettings.enableMemoryMasks = false; // The creation code will be executed at most once, so we modify the optimizer // settings accordingly. creationSettings.expectedExecutionsPerDeployment = 1; diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 82f5ea3dc2de..edcdcd36c223 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -75,11 +75,16 @@ void CompilerContext::addImmutable(VariableDeclaration const& _variable) solAssert(_variable.immutable(), "Attempted to register a non-immutable variable as immutable."); solUnimplementedAssert(_variable.annotation().type->isValueType(), "Only immutable variables of value type are supported."); solAssert(m_runtimeContext, "Attempted to register an immutable variable for runtime code generation."); - m_immutableVariables[&_variable] = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory; + m_immutableVariables[&_variable] = generalPurposeMemoryStart() + *m_reservedMemory; solAssert(_variable.annotation().type->memoryHeadSize() == 32, "Memory writes might overlap."); *m_reservedMemory += _variable.annotation().type->memoryHeadSize(); } +size_t CompilerContext::generalPurposeMemoryStart() const +{ + return CompilerUtils::generalPurposeMemoryStartFor(m_useMemoryMasks); +} + size_t CompilerContext::immutableMemoryOffset(VariableDeclaration const& _variable) const { solAssert(m_immutableVariables.count(&_variable), "Memory offset of unknown immutable queried."); @@ -550,6 +555,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, OptimiserSettings const& _optimiserSettings.yulOptimiserSteps, _optimiserSettings.yulOptimiserCleanupSteps, isCreation? std::nullopt : std::make_optional(_optimiserSettings.expectedExecutionsPerDeployment), + _optimiserSettings.useMemoryMasks(), _externalIdentifiers ); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 72f61113013a..6ecf32c53d49 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -80,6 +80,10 @@ class CompilerContext langutil::EVMVersion const& evmVersion() const { return m_evmVersion; } + void setUseMemoryMasks(bool _value) { m_useMemoryMasks = _value; } + bool useMemoryMasks() const { return m_useMemoryMasks; } + size_t generalPurposeMemoryStart() const; + void setUseABICoderV2(bool _value) { m_useABICoderV2 = _value; } bool useABICoderV2() const { return m_useABICoderV2; } @@ -352,6 +356,7 @@ class CompilerContext /// Version of the EVM to compile against. langutil::EVMVersion m_evmVersion; RevertStrings const m_revertStrings; + bool m_useMemoryMasks = false; bool m_useABICoderV2 = false; /// Other already compiled contracts to be used in contract creation calls. std::map> m_otherCompilers; diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index c7c219cb238e..ac6ecc74f16e 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -45,17 +46,29 @@ using solidity::toCompactHexWithPrefix; unsigned const CompilerUtils::dataStartOffset = 4; size_t const CompilerUtils::freeMemoryPointer = 64; size_t const CompilerUtils::zeroPointer = CompilerUtils::freeMemoryPointer + 32; +size_t const CompilerUtils::maskMemoryPointer = evmasm::MemoryMasking::maskPointer; size_t const CompilerUtils::generalPurposeMemoryStart = CompilerUtils::zeroPointer + 32; +size_t const CompilerUtils::generalPurposeMemoryStartWithMemoryMasks = evmasm::MemoryMasking::memoryStart; static_assert(CompilerUtils::freeMemoryPointer >= 64, "Free memory pointer must not overlap with scratch area."); static_assert(CompilerUtils::zeroPointer >= CompilerUtils::freeMemoryPointer + 32, "Zero pointer must not overlap with free memory pointer."); static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPointer + 32, "General purpose memory must not overlap with zero area."); +static_assert(CompilerUtils::maskMemoryPointer >= CompilerUtils::generalPurposeMemoryStart, "Mask pointer must not overlap with zero area."); +static_assert(CompilerUtils::generalPurposeMemoryStartWithMemoryMasks >= CompilerUtils::maskMemoryPointer + 32, "General purpose memory must not overlap with mask area."); + +size_t CompilerUtils::generalPurposeMemoryStartFor(bool _useMemoryMasks) +{ + return _useMemoryMasks ? generalPurposeMemoryStartWithMemoryMasks : generalPurposeMemoryStart; +} void CompilerUtils::initialiseFreeMemoryPointer() { size_t reservedMemory = m_context.reservedMemory(); - solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63); - m_context << (u256(generalPurposeMemoryStart) + reservedMemory); + size_t const memoryStart = m_context.generalPurposeMemoryStart(); + solAssert(bigint(memoryStart) + bigint(reservedMemory) < bigint(1) << 63); + if (m_context.useMemoryMasks()) + m_context << u256(0) << Instruction::NOT << u256(maskMemoryPointer) << Instruction::MSTORE; + m_context << (u256(memoryStart) + reservedMemory); storeFreeMemoryPointer(); } diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index f51f8c3cd888..767d15d88910 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -311,8 +311,13 @@ class CompilerUtils static size_t const freeMemoryPointer; /// Position of the memory slot that is always zero. static size_t const zeroPointer; + /// Position of the memory slot containing all ones when memory masks are enabled. + static size_t const maskMemoryPointer; /// Starting offset for memory available to the user (aka the contract). static size_t const generalPurposeMemoryStart; + /// Starting offset for memory available to the user when memory masks are enabled. + static size_t const generalPurposeMemoryStartWithMemoryMasks; + static size_t generalPurposeMemoryStartFor(bool _useMemoryMasks); private: /// Appends code that cleans higher-order bits for integer types. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 64b4e427d3d9..87b449ed0c19 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -128,6 +128,7 @@ void ContractCompiler::initializeContext( std::map> const& _otherCompilers ) { + m_context.setUseMemoryMasks(m_optimiserSettings.useMemoryMasks()); m_context.setUseABICoderV2(*_contract.sourceUnit().annotation().useABICoderV2); m_context.setOtherCompilers(_otherCompilers); m_context.setMostDerivedContract(_contract); diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index f33461b632a7..3ab9c1e0cc3f 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -94,7 +94,7 @@ void IRGenerationContext::registerImmutableVariable(VariableDeclaration const& _ "Only immutable variables of value type are supported." ); solAssert(m_reservedMemory.has_value(), "Reserved memory has already been reset."); - m_immutableVariables[&_variable] = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory; + m_immutableVariables[&_variable] = generalPurposeMemoryStart() + *m_reservedMemory; solAssert(_variable.annotation().type->memoryHeadSize() == 32, "Memory writes might overlap."); *m_reservedMemory += _variable.annotation().type->memoryHeadSize(); } @@ -111,8 +111,8 @@ size_t IRGenerationContext::immutableMemoryOffset(VariableDeclaration const& _va size_t IRGenerationContext::immutableMemoryOffsetRelative(VariableDeclaration const& _variable) const { auto const absoluteOffset = immutableMemoryOffset(_variable); - solAssert(absoluteOffset >= CompilerUtils::generalPurposeMemoryStart); - return absoluteOffset - CompilerUtils::generalPurposeMemoryStart; + solAssert(absoluteOffset >= generalPurposeMemoryStart()); + return absoluteOffset - generalPurposeMemoryStart(); } size_t IRGenerationContext::reservedMemorySize() const @@ -126,7 +126,7 @@ void IRGenerationContext::registerLibraryAddressImmutable() solAssert(m_executionContext != ExecutionContext::Deployed); solAssert(m_reservedMemory.has_value(), "Reserved memory has already been reset."); solAssert(!m_libraryAddressImmutableOffset.has_value()); - m_libraryAddressImmutableOffset = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory; + m_libraryAddressImmutableOffset = generalPurposeMemoryStart() + *m_reservedMemory; *m_reservedMemory += 32; } @@ -139,8 +139,13 @@ size_t IRGenerationContext::libraryAddressImmutableOffset() const size_t IRGenerationContext::libraryAddressImmutableOffsetRelative() const { solAssert(m_libraryAddressImmutableOffset.has_value()); - solAssert(m_libraryAddressImmutableOffset >= CompilerUtils::generalPurposeMemoryStart); - return *m_libraryAddressImmutableOffset - CompilerUtils::generalPurposeMemoryStart; + solAssert(m_libraryAddressImmutableOffset >= generalPurposeMemoryStart()); + return *m_libraryAddressImmutableOffset - generalPurposeMemoryStart(); +} + +size_t IRGenerationContext::generalPurposeMemoryStart() const +{ + return CompilerUtils::generalPurposeMemoryStartFor(m_useMemoryMasks); } size_t IRGenerationContext::reservedMemory() diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index 1a995734d28d..a16f4debee3e 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -63,12 +63,14 @@ class IRGenerationContext RevertStrings _revertStrings, std::map _sourceIndices, langutil::DebugInfoSelection const& _debugInfoSelection, - langutil::CharStreamProvider const* _soliditySourceProvider + langutil::CharStreamProvider const* _soliditySourceProvider, + bool _useMemoryMasks ): m_evmVersion(_evmVersion), m_eofVersion(_eofVersion), m_executionContext(_executionContext), m_revertStrings(_revertStrings), + m_useMemoryMasks(_useMemoryMasks), m_sourceIndices(std::move(_sourceIndices)), m_debugInfoSelection(_debugInfoSelection), m_soliditySourceProvider(_soliditySourceProvider) @@ -113,6 +115,8 @@ class IRGenerationContext /// to after the area used for immutables. size_t reservedMemory(); size_t reservedMemorySize() const; + size_t generalPurposeMemoryStart() const; + bool useMemoryMasks() const { return m_useMemoryMasks; } void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset); bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); } @@ -184,6 +188,7 @@ class IRGenerationContext std::optional m_eofVersion; ExecutionContext m_executionContext; RevertStrings m_revertStrings; + bool m_useMemoryMasks = false; std::map m_sourceIndices; std::set m_usedSourceNames; ContractDefinition const* m_mostDerivedContract = nullptr; diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 27f7bedce225..70f25415657d 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -1033,7 +1033,7 @@ std::string IRGenerator::deployCode(ContractDefinition const& _contract) if (eof) { t("library", _contract.isLibrary()); - t("auxDataStart", std::to_string(CompilerUtils::generalPurposeMemoryStart)); + t("auxDataStart", std::to_string(m_context.generalPurposeMemoryStart())); solAssert(m_context.reservedMemorySize() <= 0xFFFF, "Reserved memory size exceeded maximum allowed EOF data section size."); t("auxDataSize", std::to_string(m_context.reservedMemorySize())); } @@ -1134,7 +1134,7 @@ std::string IRGenerator::memoryInit(bool _useMemoryGuard) // This function should be called at the beginning of the EVM call frame // and thus can assume all memory to be zero, including the contents of // the "zero memory area" (the position CompilerUtils::zeroPointer points to). - return + std::string memoryInit = Whiskers{ _useMemoryGuard ? "mstore(, memoryguard())" : @@ -1143,8 +1143,15 @@ std::string IRGenerator::memoryInit(bool _useMemoryGuard) ("memPtr", std::to_string(CompilerUtils::freeMemoryPointer)) ( "freeMemoryStart", - std::to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory()) + std::to_string(m_context.generalPurposeMemoryStart() + m_context.reservedMemory()) ).render(); + + if (!m_context.useMemoryMasks()) + return memoryInit; + + return Whiskers{"mstore(, not(0))\n" + memoryInit} + ("maskPtr", std::to_string(CompilerUtils::maskMemoryPointer)) + .render(); } void IRGenerator::resetContext(ContractDefinition const& _contract, ExecutionContext _context) @@ -1168,7 +1175,8 @@ void IRGenerator::resetContext(ContractDefinition const& _contract, ExecutionCon m_context.revertStrings(), m_context.sourceIndices(), m_context.debugInfoSelection(), - m_context.soliditySourceProvider() + m_context.soliditySourceProvider(), + _context != ExecutionContext::Creation && m_optimiserSettings.useMemoryMasks() ); m_context = std::move(newContext); diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index 60d5d25cd026..94a8380c3617 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -64,7 +64,8 @@ class IRGenerator _revertStrings, std::move(_sourceIndices), _debugInfoSelection, - _soliditySourceProvider + _soliditySourceProvider, + false ), m_utils(_evmVersion, _eofVersion, m_context.revertStrings(), m_context.functionCollector()), m_optimiserSettings(_optimiserSettings) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 03eede8f3611..b5237ba16665 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -860,7 +860,8 @@ YulStack CompilerStack::loadGeneratedIR(std::string const& _ir) const m_optimiserSettings, m_debugInfoSelection, this, // _soliditySourceProvider - m_objectOptimizer + m_objectOptimizer, + true ); bool yulAnalysisSuccessful = stack.parseAndAnalyze("", _ir); solAssert( @@ -1809,6 +1810,8 @@ std::string CompilerStack::createMetadata(Contract const& _contract, bool _forIR details["deduplicate"] = m_optimiserSettings.runDeduplicate; details["cse"] = m_optimiserSettings.runCSE; details["constantOptimizer"] = m_optimiserSettings.runConstantOptimiser; + if (m_optimiserSettings.enableMemoryMasks.has_value()) + details["memoryMasks"] = m_optimiserSettings.useMemoryMasks(); details["simpleCounterForLoopUncheckedIncrement"] = m_optimiserSettings.simpleCounterForLoopUncheckedIncrement; details["yul"] = m_optimiserSettings.runYulOptimiser; if (m_optimiserSettings.runYulOptimiser) diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index eec1e42b0d8c..08aa65a1b8d4 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -26,6 +26,7 @@ #include #include +#include #include namespace solidity::frontend @@ -114,6 +115,16 @@ struct OptimiserSettings bool operator==(OptimiserSettings const& _other) const = default; bool operator!=(OptimiserSettings const& _other) const = default; + bool useMemoryMasks() const + { + return enableMemoryMasks.value_or(defaultUseMemoryMasks()); + } + + bool defaultUseMemoryMasks() const + { + return (runConstantOptimiser || runYulOptimiser) && expectedExecutionsPerDeployment <= 200; + } + /// Move literals to the right of commutative binary operators during code generation. /// This helps exploiting associativity. bool runOrderLiterals = false; @@ -130,6 +141,9 @@ struct OptimiserSettings /// Constant optimizer, which tries to find better representations that satisfy the given /// size/cost-trade-off. bool runConstantOptimiser = false; + /// Use the reserved memory mask region to materialize common mask constants. + /// The default is selected from the effective optimiser settings and optimize-runs value. + std::optional enableMemoryMasks = std::nullopt; /// Allow unchecked arithmetic when incrementing the counter of certain kinds of 'for' loop bool simpleCounterForLoopUncheckedIncrement = false; /// Perform more efficient stack allocation for variables during code generation from Yul to bytecode. diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 0314abc58c21..fb1fba0c5fca 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -503,7 +503,7 @@ std::optional checkOptimizerKeys(Json const& _input) std::optional checkOptimizerDetailsKeys(Json const& _input) { - static std::set keys{"peephole", "inliner", "jumpdestRemover", "orderLiterals", "deduplicate", "cse", "constantOptimizer", "yul", "yulDetails", "simpleCounterForLoopUncheckedIncrement"}; + static std::set keys{"peephole", "inliner", "jumpdestRemover", "orderLiterals", "deduplicate", "cse", "constantOptimizer", "memoryMasks", "yul", "yulDetails", "simpleCounterForLoopUncheckedIncrement"}; return checkKeys(_input, keys, "settings.optimizer.details"); } @@ -518,6 +518,17 @@ std::optional checkOptimizerDetail(Json const& _details, std::string const return {}; } +std::optional checkOptimizerDetail(Json const& _details, std::string const& _name, std::optional& _setting) +{ + if (_details.contains(_name)) + { + if (!_details[_name].is_boolean()) + return formatFatalError(Error::Type::JSONError, "\"settings.optimizer.details." + _name + "\" must be Boolean"); + _setting = _details[_name].get(); + } + return {}; +} + std::optional checkOptimizerDetailSteps(Json const& _details, std::string const& _name, std::string& _optimiserSetting, std::string& _cleanupSetting, bool _runYulOptimizer) { if (_details.contains(_name)) @@ -626,6 +637,11 @@ std::variant parseOptimizerSettings(std::string_view co return *result; OptimiserSettings settings = _language == "EVMAssembly" ? OptimiserSettings::none() : OptimiserSettings::minimal(); + auto finalizeSettings = [&]() { + if (_language == "EVMAssembly") + settings.enableMemoryMasks = false; + return std::move(settings); + }; if (_jsonInput.contains("enabled")) { @@ -663,6 +679,8 @@ std::variant parseOptimizerSettings(std::string_view co return *error; if (auto error = checkOptimizerDetail(details, "constantOptimizer", settings.runConstantOptimiser)) return *error; + if (auto error = checkOptimizerDetail(details, "memoryMasks", settings.enableMemoryMasks)) + return *error; if (auto error = checkOptimizerDetail(details, "yul", settings.runYulOptimiser)) return *error; if (auto error = checkOptimizerDetail(details, "simpleCounterForLoopUncheckedIncrement", settings.simpleCounterForLoopUncheckedIncrement)) @@ -676,7 +694,7 @@ std::variant parseOptimizerSettings(std::string_view co return formatFatalError(Error::Type::JSONError, "Only optimizerSteps can be set in yulDetails when Yul optimizer is disabled."); if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps, settings.yulOptimiserCleanupSteps, settings.runYulOptimiser)) return *error; - return {std::move(settings)}; + return {finalizeSettings()}; } if (auto result = checkKeys(details["yulDetails"], {"stackAllocation", "optimizerSteps"}, "settings.optimizer.details.yulDetails")) @@ -687,7 +705,7 @@ std::variant parseOptimizerSettings(std::string_view co return *error; } } - return {std::move(settings)}; + return {finalizeSettings()}; } } diff --git a/libyul/ObjectOptimizer.cpp b/libyul/ObjectOptimizer.cpp index e0a0ddb24a3e..141c7a22a879 100644 --- a/libyul/ObjectOptimizer.cpp +++ b/libyul/ObjectOptimizer.cpp @@ -83,6 +83,7 @@ void ObjectOptimizer::optimize(Object& _object, Settings const& _settings, bool _settings.yulOptimiserSteps, _settings.yulOptimiserCleanupSteps, _isCreation ? std::nullopt : std::make_optional(_settings.expectedExecutionsPerDeployment), + _settings.useMemoryMasks, {} ); @@ -142,6 +143,7 @@ std::optional ObjectOptimizer::calculateCacheKey( rawKey += keccak256(_debugData.formatUseSrcComment()).asBytes(); static_assert(static_cast(static_cast(2)) == 1); rawKey += FixedHash<1>(static_cast(_settings.optimizeStackAllocation)).asBytes(); + rawKey += FixedHash<1>(static_cast(_settings.useMemoryMasks)).asBytes(); rawKey += h256(u256(_settings.expectedExecutionsPerDeployment)).asBytes(); rawKey += FixedHash<1>(static_cast(_isCreation)).asBytes(); rawKey += keccak256(_settings.evmVersion.name()).asBytes(); diff --git a/libyul/ObjectOptimizer.h b/libyul/ObjectOptimizer.h index 2cd9d7f98378..104368c2cc50 100644 --- a/libyul/ObjectOptimizer.h +++ b/libyul/ObjectOptimizer.h @@ -54,6 +54,7 @@ class ObjectOptimizer std::string yulOptimiserSteps; std::string yulOptimiserCleanupSteps; size_t expectedExecutionsPerDeployment; + bool useMemoryMasks; }; /// Recursively optimizes a Yul object with given settings, reusing cached ASTs where possible diff --git a/libyul/YulStack.cpp b/libyul/YulStack.cpp index 0633836d134d..8939412d6bfb 100644 --- a/libyul/YulStack.cpp +++ b/libyul/YulStack.cpp @@ -136,7 +136,8 @@ void YulStack::optimize() optimizeStackAllocation, yulOptimiserSteps, yulOptimiserCleanupSteps, - m_optimiserSettings.expectedExecutionsPerDeployment + m_optimiserSettings.expectedExecutionsPerDeployment, + m_hasMemoryMaskPrologue && m_optimiserSettings.useMemoryMasks() } ); diff --git a/libyul/YulStack.h b/libyul/YulStack.h index 66cd56c067fd..8c06092ec1f0 100644 --- a/libyul/YulStack.h +++ b/libyul/YulStack.h @@ -91,7 +91,8 @@ class YulStack: public langutil::CharStreamProvider solidity::frontend::OptimiserSettings _optimiserSettings, langutil::DebugInfoSelection const& _debugInfoSelection, langutil::CharStreamProvider const* _soliditySourceProvider = nullptr, - std::shared_ptr _objectOptimizer = nullptr + std::shared_ptr _objectOptimizer = nullptr, + bool _hasMemoryMaskPrologue = false ): m_evmVersion(_evmVersion), m_eofVersion(_eofVersion), @@ -99,7 +100,8 @@ class YulStack: public langutil::CharStreamProvider m_debugInfoSelection(_debugInfoSelection), m_soliditySourceProvider(_soliditySourceProvider), m_errorReporter(m_errors), - m_objectOptimizer(_objectOptimizer ? std::move(_objectOptimizer) : std::make_shared()) + m_objectOptimizer(_objectOptimizer ? std::move(_objectOptimizer) : std::make_shared()), + m_hasMemoryMaskPrologue(_hasMemoryMaskPrologue) {} /// @returns the char stream used during parsing @@ -188,6 +190,7 @@ class YulStack: public langutil::CharStreamProvider langutil::ErrorReporter m_errorReporter; std::shared_ptr m_objectOptimizer; + bool m_hasMemoryMaskPrologue = false; }; } diff --git a/libyul/backends/evm/ConstantOptimiser.cpp b/libyul/backends/evm/ConstantOptimiser.cpp index aa744415bf9d..267e970ce1ae 100644 --- a/libyul/backends/evm/ConstantOptimiser.cpp +++ b/libyul/backends/evm/ConstantOptimiser.cpp @@ -26,6 +26,8 @@ #include #include +#include + #include #include @@ -66,6 +68,11 @@ struct MiniEVMInterpreter return args.at(0) > 255 ? 0 : (args.at(1) << unsigned(args.at(0))); case evmasm::Instruction::NOT: return ~args.at(0); + case evmasm::Instruction::MLOAD: + { + std::optional value = evmasm::MemoryMasking::constantForOffset(args.at(0).convert_to()); + return *value; + } default: yulAssert(false, "Invalid operation generated in constant optimizer."); } @@ -99,7 +106,7 @@ void ConstantOptimiser::visit(Expression& _e) if ( Expression const* repr = - RepresentationFinder(m_dialect, m_meter, debugDataOf(_e), m_cache) + RepresentationFinder(m_dialect, m_meter, debugDataOf(_e), m_cache, m_useMemoryMasks) .tryFindRepresentation(literal.value.value()) ) _e = ASTCopier{}.translate(*repr); @@ -116,8 +123,15 @@ Expression const* RepresentationFinder::tryFindRepresentation(u256 const& _value Representation const& repr = findRepresentation(_value); if (std::holds_alternative(*repr.expression)) return nullptr; - else - return repr.expression.get(); + if (m_useMemoryMasks) + { + if (std::optional memoryRepr = memoryMaskRepresentation(_value)) + { + if (memoryRepr->cost <= repr.cost) + return (m_cache[_value] = std::move(*memoryRepr)).expression.get(); + } + } + return repr.expression.get(); } Representation const& RepresentationFinder::findRepresentation(u256 const& _value) @@ -223,6 +237,16 @@ Representation RepresentationFinder::represent( return repr; } +std::optional RepresentationFinder::memoryMaskRepresentation(u256 const& _value) const +{ + std::optional offset = evmasm::MemoryMasking::offsetForRightAlignedOnes(_value); + if (!offset) + return std::nullopt; + std::optional mload = m_dialect.memoryLoadFunctionHandle(); + yulAssert(mload, ""); + return represent(*mload, represent(u256(*offset))); +} + Representation RepresentationFinder::min(Representation _a, Representation _b) { if (_a.cost <= _b.cost) diff --git a/libyul/backends/evm/ConstantOptimiser.h b/libyul/backends/evm/ConstantOptimiser.h index 9c25450bf96f..64beada32e3d 100644 --- a/libyul/backends/evm/ConstantOptimiser.h +++ b/libyul/backends/evm/ConstantOptimiser.h @@ -34,6 +34,7 @@ #include #include #include +#include namespace solidity::yul { @@ -48,9 +49,10 @@ class GasMeter; class ConstantOptimiser: public ASTModifier { public: - ConstantOptimiser(EVMDialect const& _dialect, GasMeter const& _meter): + ConstantOptimiser(EVMDialect const& _dialect, GasMeter const& _meter, bool _useMemoryMasks = false): m_dialect(_dialect), - m_meter(_meter) + m_meter(_meter), + m_useMemoryMasks(_useMemoryMasks) {} void visit(Expression& _e) override; @@ -64,6 +66,7 @@ class ConstantOptimiser: public ASTModifier private: EVMDialect const& m_dialect; GasMeter const& m_meter; + bool m_useMemoryMasks = false; std::map m_cache; }; @@ -75,12 +78,14 @@ class RepresentationFinder EVMDialect const& _dialect, GasMeter const& _meter, langutil::DebugData::ConstPtr _debugData, - std::map& _cache + std::map& _cache, + bool _useMemoryMasks ): m_dialect(_dialect), m_meter(_meter), m_debugData(std::move(_debugData)), - m_cache(_cache) + m_cache(_cache), + m_useMemoryMasks(_useMemoryMasks) {} /// @returns a cheaper representation for the number than its representation @@ -95,6 +100,7 @@ class RepresentationFinder Representation represent(u256 const& _value) const; Representation represent(BuiltinHandle const& _instruction, Representation const& _arg) const; Representation represent(BuiltinHandle const& _instruction, Representation const& _arg1, Representation const& _arg2) const; + std::optional memoryMaskRepresentation(u256 const& _value) const; Representation min(Representation _a, Representation _b); @@ -104,6 +110,7 @@ class RepresentationFinder /// Counter for the complexity of optimization, will stop when it reaches zero. size_t m_maxSteps = 10000; std::map& m_cache; + bool m_useMemoryMasks = false; }; } diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 17698d74e2a0..381a0f85698b 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -95,6 +95,7 @@ void OptimiserSuite::run( std::string_view _optimisationSequence, std::string_view _optimisationCleanupSequence, std::optional _expectedExecutionsPerDeployment, + bool _useMemoryMasks, std::set const& _externallyUsedIdentifiers ) { @@ -159,7 +160,7 @@ void OptimiserSuite::run( yulAssert(_meter, ""); { PROFILER_PROBE("ConstantOptimiser", probe); - ConstantOptimiser{*evmDialect, *_meter}(astRoot); + ConstantOptimiser{*evmDialect, *_meter, _useMemoryMasks}(astRoot); } if (usesOptimizedCodeGenerator) { diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index b0c3314089b7..85c34b18daa0 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -69,6 +69,7 @@ class OptimiserSuite std::string_view _optimisationSequence, std::string_view _optimisationCleanupSequence, std::optional _expectedExecutionsPerDeployment, + bool _useMemoryMasks, std::set const& _externallyUsedIdentifiers = {} ); diff --git a/solc/CommandLineParser.cpp b/solc/CommandLineParser.cpp index f29faedecbdb..6c6077aabcc6 100644 --- a/solc/CommandLineParser.cpp +++ b/solc/CommandLineParser.cpp @@ -287,6 +287,9 @@ OptimiserSettings CommandLineOptions::optimiserSettings() const solAssert(settings.yulOptimiserCleanupSteps == OptimiserSettings::DefaultYulOptimiserCleanupSteps); } + if (input.mode == InputMode::EVMAssemblerJSON) + settings.enableMemoryMasks = false; + return settings; } diff --git a/test/libevmasm/EVMAssemblyTest.cpp b/test/libevmasm/EVMAssemblyTest.cpp index 3bc43b188294..8a7438222195 100644 --- a/test/libevmasm/EVMAssemblyTest.cpp +++ b/test/libevmasm/EVMAssemblyTest.cpp @@ -78,6 +78,7 @@ EVMAssemblyTest::EVMAssemblyTest(std::string const& _filename): "none" ); m_optimizerSettings = Assembly::OptimiserSettings::translateSettings(OptimiserSettings::preset(optimizationPreset)); + m_optimizerSettings.useMemoryMasks = false; size_t defaultExpectedExecutionsPerDeployment = m_optimizerSettings.expectedExecutionsPerDeployment; m_optimizerSettings.expectedExecutionsPerDeployment = m_reader.sizetSetting( "optimizer.expectedExecutionsPerDeployment", diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 2c93751830eb..b2249460a638 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -1506,6 +1507,41 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal) ); } +BOOST_AUTO_TEST_CASE(constant_optimiser_memory_masks_do_not_load_full_mask) +{ + u256 const fullMask = ~u256(0); + u256 const thirtyOneByteMask = (u256(1) << 248) - 1; + + Assembly assembly{CommonOptions::get().evmVersion(), false, std::nullopt, {}}; + assembly.append(fullMask); + assembly.append(fullMask); + assembly.append(thirtyOneByteMask); + + BOOST_REQUIRE(ConstantOptimisationMethod::optimiseConstants( + false, + OptimiserSettings{}.expectedExecutionsPerDeployment, + CommonOptions::get().evmVersion(), + true, + assembly + ) > 0); + + AssemblyItems const& items = assembly.codeSections().at(0).items; + BOOST_CHECK(ranges::any_of(items, [](AssemblyItem const& _item) { + return _item == AssemblyItem{Instruction::MLOAD}; + })); + for (auto item = items.begin(); item != items.end(); ++item) + if (*item == AssemblyItem{Instruction::MLOAD}) + { + BOOST_REQUIRE(item != items.begin()); + auto const& offset = *std::prev(item); + BOOST_CHECK(!(offset.type() == Push && offset.data() == 0x80)); + } + BOOST_CHECK_EQUAL(count(items.begin(), items.end(), AssemblyItem{Instruction::NOT}), 2); + BOOST_CHECK(!ranges::any_of(items, [fullMask](AssemblyItem const& _item) { + return _item.type() == Push && _item.data() == fullMask; + })); +} + BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies, *boost::unit_test::precondition(nonEOF())) { // This tests that tags from subassemblies are not removed diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index c13704cc061a..5aa81b73e8f3 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -1317,6 +1317,7 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) "jumpdestRemover" : true, "orderLiterals" : false, "peephole" : true, + "memoryMasks": false, "yul": true, "inliner": true } } @@ -1345,6 +1346,7 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) BOOST_CHECK(optimizer["details"]["jumpdestRemover"].get() == true); BOOST_CHECK(optimizer["details"]["orderLiterals"].get() == false); BOOST_CHECK(optimizer["details"]["peephole"].get() == true); + BOOST_CHECK(optimizer["details"]["memoryMasks"].get() == false); BOOST_CHECK(optimizer["details"]["yul"].get() == true); BOOST_CHECK(optimizer["details"]["yulDetails"].is_object()); // BOOST_CHECK( @@ -1356,7 +1358,7 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) optimizer["details"]["yulDetails"]["optimizerSteps"].get() == OptimiserSettings::DefaultYulOptimiserSteps + ":"s + OptimiserSettings::DefaultYulOptimiserCleanupSteps ); - BOOST_CHECK_EQUAL(optimizer["details"].size(), 10); + BOOST_CHECK_EQUAL(optimizer["details"].size(), 11); BOOST_CHECK(optimizer["runs"].get() == 600); } diff --git a/test/libsolidity/gasTests/abiv2_optimised.sol b/test/libsolidity/gasTests/abiv2_optimised.sol index e23a7bff2562..8181aa414447 100644 --- a/test/libsolidity/gasTests/abiv2_optimised.sol +++ b/test/libsolidity/gasTests/abiv2_optimised.sol @@ -19,13 +19,13 @@ contract C { // optimize-yul: true // ---- // creation: -// codeDepositCost: 618200 -// executionCost: 649 -// totalCost: 618849 +// codeDepositCost: 598200 +// executionCost: 630 +// totalCost: 598830 // external: -// a(): 2283 -// b(uint256): 4649 -// f1(uint256): 304 +// a(): 2297 +// b(uint256): 4663 +// f1(uint256): 318 // f2(uint256[],string[],uint16,address): infinite // f3(uint16[],string[],uint16,address): infinite // f4(uint32[],string[12],bytes[2][],address): infinite diff --git a/test/libsolidity/gasTests/dispatch_large_optimised.sol b/test/libsolidity/gasTests/dispatch_large_optimised.sol index 5ab62e892285..bc8d2cd92418 100644 --- a/test/libsolidity/gasTests/dispatch_large_optimised.sol +++ b/test/libsolidity/gasTests/dispatch_large_optimised.sol @@ -29,29 +29,29 @@ contract Large { // optimize-runs: 2 // ---- // creation: -// codeDepositCost: 224600 +// codeDepositCost: 225600 // executionCost: 267 -// totalCost: 224867 +// totalCost: 225867 // external: -// a(): 2281 -// b(uint256): 4934 -// f0(uint256): 363 -// f1(uint256): 47002 -// f2(uint256): 24967 -// f3(uint256): 25055 -// f4(uint256): 25033 -// f5(uint256): 25011 -// f6(uint256): 24923 -// f7(uint256): 24703 -// f8(uint256): 24835 -// f9(uint256): 24857 -// g0(uint256): 603 -// g1(uint256): 46714 -// g2(uint256): 24701 -// g3(uint256): 24789 -// g4(uint256): 24767 -// g5(uint256): 24855 -// g6(uint256): 24635 -// g7(uint256): 24745 -// g8(uint256): 24723 -// g9(uint256): 24569 +// a(): 2295 +// b(uint256): 4948 +// f0(uint256): 377 +// f1(uint256): 47016 +// f2(uint256): 24981 +// f3(uint256): 25069 +// f4(uint256): 25047 +// f5(uint256): 25025 +// f6(uint256): 24937 +// f7(uint256): 24717 +// f8(uint256): 24849 +// f9(uint256): 24871 +// g0(uint256): 617 +// g1(uint256): 46728 +// g2(uint256): 24715 +// g3(uint256): 24803 +// g4(uint256): 24781 +// g5(uint256): 24869 +// g6(uint256): 24649 +// g7(uint256): 24759 +// g8(uint256): 24737 +// g9(uint256): 24583 diff --git a/test/libsolidity/gasTests/dispatch_medium_optimised.sol b/test/libsolidity/gasTests/dispatch_medium_optimised.sol index 2ea33955e9c8..774c56f17945 100644 --- a/test/libsolidity/gasTests/dispatch_medium_optimised.sol +++ b/test/libsolidity/gasTests/dispatch_medium_optimised.sol @@ -17,16 +17,16 @@ contract Medium { // optimize-runs: 2 // ---- // creation: -// codeDepositCost: 126000 +// codeDepositCost: 127000 // executionCost: 169 -// totalCost: 126169 +// totalCost: 127169 // external: -// a(): 2281 -// b(uint256): 4692 -// f1(uint256): 46782 -// f2(uint256): 24725 -// f3(uint256): 24769 -// g0(uint256): 361 -// g7(uint256): 24635 -// g8(uint256): 24613 -// g9(uint256): 24569 +// a(): 2295 +// b(uint256): 4706 +// f1(uint256): 46796 +// f2(uint256): 24739 +// f3(uint256): 24783 +// g0(uint256): 375 +// g7(uint256): 24649 +// g8(uint256): 24627 +// g9(uint256): 24583 diff --git a/test/libsolidity/gasTests/dispatch_small_optimised.sol b/test/libsolidity/gasTests/dispatch_small_optimised.sol index b59bdafc2780..dec54323eb9f 100644 --- a/test/libsolidity/gasTests/dispatch_small_optimised.sol +++ b/test/libsolidity/gasTests/dispatch_small_optimised.sol @@ -11,11 +11,11 @@ contract Small { // bytecodeFormat: legacy // ---- // creation: -// codeDepositCost: 58200 +// codeDepositCost: 59200 // executionCost: 109 -// totalCost: 58309 +// totalCost: 59309 // external: -// fallback: 117 -// a(): 2259 -// b(uint256): 4582 -// f1(uint256): 46716 +// fallback: 134 +// a(): 2273 +// b(uint256): 4596 +// f1(uint256): 46730 diff --git a/test/libsolidity/gasTests/exp_optimized.sol b/test/libsolidity/gasTests/exp_optimized.sol index fae9a24fd757..417f05ba06b4 100644 --- a/test/libsolidity/gasTests/exp_optimized.sol +++ b/test/libsolidity/gasTests/exp_optimized.sol @@ -21,11 +21,11 @@ contract C { // optimize-yul: true // ---- // creation: -// codeDepositCost: 35800 +// codeDepositCost: 36800 // executionCost: 85 -// totalCost: 35885 +// totalCost: 36885 // external: -// exp_neg_one(uint256): 1914 -// exp_one(uint256): 1868 -// exp_two(uint256): 1846 -// exp_zero(uint256): 1889 +// exp_neg_one(uint256): 1928 +// exp_one(uint256): 1882 +// exp_two(uint256): 1860 +// exp_zero(uint256): 1903 diff --git a/test/libsolidity/gasTests/storage_costs.sol b/test/libsolidity/gasTests/storage_costs.sol index 871c15eab388..acadf9b6f3ed 100644 --- a/test/libsolidity/gasTests/storage_costs.sol +++ b/test/libsolidity/gasTests/storage_costs.sol @@ -17,10 +17,10 @@ contract C { // bytecodeFormat: legacy // ---- // creation: -// codeDepositCost: 25600 -// executionCost: 73 -// totalCost: 25673 +// codeDepositCost: 26600 +// executionCost: 79 +// totalCost: 26679 // external: -// readX(): 2288 -// resetX(): 5114 -// setX(uint256): 22309 +// readX(): 2302 +// resetX(): 5131 +// setX(uint256): 22326 diff --git a/test/libsolidity/syntaxTests/sizeLimits/bytecode_too_large.sol b/test/libsolidity/syntaxTests/sizeLimits/bytecode_too_large.sol index 5ac74bba7cb7..8f7696c4d82d 100644 --- a/test/libsolidity/syntaxTests/sizeLimits/bytecode_too_large.sol +++ b/test/libsolidity/syntaxTests/sizeLimits/bytecode_too_large.sol @@ -10,4 +10,4 @@ contract test { // EVMVersion: >=cancun // bytecodeFormat: legacy // ---- -// Warning 5574: (21-27154): Contract code size is 27164 bytes and exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on Mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries. +// Warning 5574: (21-27154): Contract code size is 27169 bytes and exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on Mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries. diff --git a/test/libsolidity/syntaxTests/sizeLimits/bytecode_too_large_abiencoder_v1.sol b/test/libsolidity/syntaxTests/sizeLimits/bytecode_too_large_abiencoder_v1.sol index 599267274a50..fe07f5fb963e 100644 --- a/test/libsolidity/syntaxTests/sizeLimits/bytecode_too_large_abiencoder_v1.sol +++ b/test/libsolidity/syntaxTests/sizeLimits/bytecode_too_large_abiencoder_v1.sol @@ -11,4 +11,4 @@ contract test { // bytecodeFormat: legacy // ---- // Warning 9511: (0-19): ABI coder v1 is deprecated and scheduled for removal. Use ABI coder v2 instead. -// Warning 5574: (21-27154): Contract code size is 27205 bytes and exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on Mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries. +// Warning 5574: (21-27154): Contract code size is 27210 bytes and exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on Mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries. diff --git a/test/libyul/YulOptimizerTestCommon.cpp b/test/libyul/YulOptimizerTestCommon.cpp index cd54318c19ef..4c6c74b8081a 100644 --- a/test/libyul/YulOptimizerTestCommon.cpp +++ b/test/libyul/YulOptimizerTestCommon.cpp @@ -108,6 +108,13 @@ YulOptimizerTestCommon::YulOptimizerTestCommon(std::shared_ptr _ob ConstantOptimiser{dynamic_cast(*m_object->dialect()), meter}(block); return block; }}, + {"constantOptimiserWithMemoryMasks", [&]() { + auto block = std::get(ASTCopier{}(m_object->code()->root())); + updateContext(block); + GasMeter meter(dynamic_cast(*m_object->dialect()), false, 200); + ConstantOptimiser{dynamic_cast(*m_object->dialect()), meter, true}(block); + return block; + }}, {"varDeclInitializer", [&]() { auto block = std::get(ASTCopier{}(m_object->code()->root())); updateContext(block); @@ -420,7 +427,8 @@ YulOptimizerTestCommon::YulOptimizerTestCommon(std::shared_ptr _ob true, frontend::OptimiserSettings::DefaultYulOptimiserSteps, frontend::OptimiserSettings::DefaultYulOptimiserCleanupSteps, - frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment + frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment, + false ); return std::get(ASTCopier{}(m_optimizedObject->code()->root())); }}, diff --git a/test/libyul/yulOptimizerTests/constantOptimiserWithMemoryMasks/full_mask_memory_masks.yul b/test/libyul/yulOptimizerTests/constantOptimiserWithMemoryMasks/full_mask_memory_masks.yul new file mode 100644 index 000000000000..8cfc2863b719 --- /dev/null +++ b/test/libyul/yulOptimizerTests/constantOptimiserWithMemoryMasks/full_mask_memory_masks.yul @@ -0,0 +1,13 @@ +{ + let x := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + let y := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +} +// ==== +// EVMVersion: >=constantinople +// ---- +// step: constantOptimiserWithMemoryMasks +// +// { +// let x := not(0) +// let y := mload(127) +// } diff --git a/test/tools/fuzzer_common.cpp b/test/tools/fuzzer_common.cpp index 8cc30c199507..406e9a294445 100644 --- a/test/tools/fuzzer_common.cpp +++ b/test/tools/fuzzer_common.cpp @@ -193,6 +193,7 @@ void FuzzerUtil::testConstantOptimizer(std::string const& _input, bool _quiet) isCreation, runs, langutil::EVMVersion{}, + false, tmp ); }