diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index 2a90ee9b2..1150da659 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -388,6 +389,14 @@ bool DataFlowAnalyzer::inScope(YulString _variableName) const return false; } +optional DataFlowAnalyzer::valueOfIdentifier(YulString const& _name) +{ + if (m_value.count(_name)) + if (Literal const* literal = get_if(m_value.at(_name).value)) + return valueOfLiteral(*literal); + return nullopt; +} + std::optional> DataFlowAnalyzer::isSimpleStore( StoreLoadLocation _location, ExpressionStatement const& _statement @@ -412,4 +421,3 @@ std::optional DataFlowAnalyzer::isSimpleLoad( return key->name; return {}; } - diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 7cbbe63b7..deaa71f89 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -29,6 +29,8 @@ #include // Needed for m_zero below. #include +#include + #include #include @@ -134,6 +136,9 @@ protected: /// Returns true iff the variable is in scope. bool inScope(YulString _variableName) const; + /// Returns the literal value of the identifier, if it exists. + std::optional valueOfIdentifier(YulString const& _name); + enum class StoreLoadLocation { Memory = 0, Storage = 1, diff --git a/libyul/optimiser/LoadResolver.cpp b/libyul/optimiser/LoadResolver.cpp index 99d3f4ffc..f73cd48bc 100644 --- a/libyul/optimiser/LoadResolver.cpp +++ b/libyul/optimiser/LoadResolver.cpp @@ -23,13 +23,23 @@ #include #include +#include #include #include #include #include +#include + +#include +#include +#include + +#include using namespace std; using namespace solidity; +using namespace solidity::util; +using namespace solidity::evmasm; using namespace solidity::yul; void LoadResolver::run(OptimiserStepContext& _context, Block& _ast) @@ -38,7 +48,8 @@ void LoadResolver::run(OptimiserStepContext& _context, Block& _ast) LoadResolver{ _context.dialect, SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)), - !containsMSize + !containsMSize, + _context.expectedExecutionsPerDeployment }(_ast); } @@ -47,12 +58,17 @@ void LoadResolver::visit(Expression& _e) DataFlowAnalyzer::visit(_e); if (FunctionCall const* funCall = std::get_if(&_e)) + { for (auto location: { StoreLoadLocation::Memory, StoreLoadLocation::Storage }) if (funCall->functionName.name == m_loadFunctionName[static_cast(location)]) { tryResolve(_e, location, funCall->arguments); break; } + + if (funCall->functionName.name == m_dialect.hashFunction({})) + tryEvaluateKeccak(_e, funCall->arguments); + } } void LoadResolver::tryResolve( @@ -76,3 +92,59 @@ void LoadResolver::tryResolve( if (inScope(*value)) _e = Identifier{locationOf(_e), *value}; } + +void LoadResolver::tryEvaluateKeccak( + Expression& _e, + std::vector const& _arguments +) +{ + // The costs are only correct for hashes of 32 bytes or 1 word (when rounded up). + GasMeter gasMeter{ + dynamic_cast(m_dialect), + !m_expectedExecutionsPerDeployment, + m_expectedExecutionsPerDeployment ? *m_expectedExecutionsPerDeployment : 1 + }; + + bigint costOfKeccak = gasMeter.costs(_e); + bigint costOfLiteral = gasMeter.costs( + Literal{ + {}, + LiteralKind::Number, + // a dummy 256-bit number to represent the Keccak256 hash. + YulString{numeric_limits::max().str()}, + {} + } + ); + + // We skip if there are no net gas savings. + // Note that for default `m_runs = 200`, the values are + // `costOfLiteral = 7200` and `costOfKeccak = 9000` for runtime context. + // For creation context: `costOfLiteral = 531` and `costOfKeccak = 90`. + if (costOfLiteral > costOfKeccak) + return; + + yulAssert(_arguments.size() == 2, ""); + Identifier const* memoryKey = std::get_if(&_arguments.at(0)); + Identifier const* length = std::get_if(&_arguments.at(1)); + + if (!memoryKey || !length) + return; + + auto memoryValue = util::valueOrNullptr(m_memory, memoryKey->name); + if (memoryValue && inScope(*memoryValue)) + { + optional memoryContent = valueOfIdentifier(*memoryValue); + optional byteLength = valueOfIdentifier(length->name); + if (memoryContent && byteLength && *byteLength <= 32) + { + bytes contentAsBytes = toBigEndian(*memoryContent); + contentAsBytes.resize(static_cast(*byteLength)); + _e = Literal{ + locationOf(_e), + LiteralKind::Number, + YulString{u256(keccak256(contentAsBytes)).str()}, + m_dialect.defaultType + }; + } + } +} diff --git a/libyul/optimiser/LoadResolver.h b/libyul/optimiser/LoadResolver.h index a376ab059..e1758bf6b 100644 --- a/libyul/optimiser/LoadResolver.h +++ b/libyul/optimiser/LoadResolver.h @@ -32,6 +32,9 @@ namespace solidity::yul * Optimisation stage that replaces expressions of type ``sload(x)`` and ``mload(x)`` by the value * currently stored in storage resp. memory, if known. * + * Also evaluates simple ``keccak256(a, c)`` when the value at memory location `a` is known and `c` + * is a constant `<= 32`. + * * Works best if the code is in SSA form. * * Prerequisite: Disambiguator, ForLoopInitRewriter. @@ -47,10 +50,12 @@ private: LoadResolver( Dialect const& _dialect, std::map _functionSideEffects, - bool _optimizeMLoad + bool _optimizeMLoad, + std::optional _expectedExecutionsPerDeployment ): DataFlowAnalyzer(_dialect, std::move(_functionSideEffects)), - m_optimizeMLoad(_optimizeMLoad) + m_optimizeMLoad(_optimizeMLoad), + m_expectedExecutionsPerDeployment(std::move(_expectedExecutionsPerDeployment)) {} protected: @@ -63,7 +68,16 @@ protected: std::vector const& _arguments ); + /// Evaluates simple ``keccak256(a, c)`` when the value at memory location ``a`` is known and + /// `c` is a constant `<= 32`. + void tryEvaluateKeccak( + Expression& _e, + std::vector const& _arguments + ); + bool m_optimizeMLoad = false; + /// The --optimize-runs parameter. Value `nullopt` represents creation code. + std::optional m_expectedExecutionsPerDeployment; }; }