diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index a02b3d592..8d71c04a2 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -141,6 +141,8 @@ add_library(yul optimiser/LoopInvariantCodeMotion.h optimiser/MainFunction.cpp optimiser/MainFunction.h + optimiser/MemoryLoadResolver.cpp + optimiser/MemoryLoadResolver.h optimiser/Metrics.cpp optimiser/Metrics.h optimiser/NameCollector.cpp diff --git a/libyul/optimiser/MemoryLoadResolver.cpp b/libyul/optimiser/MemoryLoadResolver.cpp new file mode 100644 index 000000000..d50879503 --- /dev/null +++ b/libyul/optimiser/MemoryLoadResolver.cpp @@ -0,0 +1,344 @@ +/* + 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 "libyul/optimiser/CallGraphGenerator.h" +#include "libyul/optimiser/OptimiserStep.h" +#include "libyul/optimiser/SSAValueTracker.h" +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +using namespace std; +using namespace solidity; +using namespace solidity::yul; +using namespace solidity::util; +using namespace solidity::smtutil; + +void MemoryLoadResolver::run(OptimiserStepContext& _context, Block& _ast) +{ + MemoryLoadResolver{ + _context.dialect, + SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)), + SSAValueTracker::ssaVariables(_ast), + MSizeFinder::containsMSize(_context.dialect, _ast) + }(_ast); +} + +void MemoryLoadResolver::visit(Expression& _e) +{ + Scoper::visit(_e); + + // We skip the replacing if there is msize. TODO do we need this, and can we do it earlier? + if (m_containsMsize) + return; + + // Check and replace the value of mload(x), if possible + if (FunctionCall const* functionCall = get_if(&_e)) + if (functionCall->functionName.name == m_dialect.memoryLoadFunction(YulString{})->name) + + if (Identifier const* identifier = get_if(&functionCall->arguments.front())) + if (m_memory.count(identifier->name)) + { + auto& value = m_memory.at(identifier->name); + if (inScope(value)) + _e = Identifier{locationOf(_e), value}; + } +} + +void MemoryLoadResolver::operator()(VariableDeclaration& _v) +{ + // Otherwise, we simply assign a free variable to it. + Scoper::operator()(_v); + + if ( + _v.variables.size() == 1 && + _v.value && + m_ssaVariables.count(_v.variables.front().name) + ) + { + YulString& variableName = _v.variables.front().name; + bool const inserted = m_variables.insert({ + variableName, + m_solver->newVariable("yul_" + variableName.str(), defaultSort()) + }).second; + yulAssert(inserted, ""); + m_solver->addAssertion(m_variables.at(variableName) == encodeExpression(*_v.value)); + } + else + { + for(auto const& v: _v.variables) + { + YulString const& variableName = v.name; + bool const inserted = m_variables.insert({ + variableName, + m_solver->newVariable("yul_" + variableName.str(), defaultSort()) + }).second; + yulAssert(inserted, ""); + // TODO improve this by adding assertions directly, without the restricted variable. + m_solver->addAssertion(m_variables.at(variableName) == newRestrictedVariable()); + } + } +} + +void MemoryLoadResolver::operator()(FunctionCall& _f) +{ + Scoper::operator()(_f); + + SideEffectsCollector sideEffects{m_dialect, _f, &m_functionSideEffects}; + + if (sideEffects.invalidatesMemory()) + { + set keysToErase; + for ([[maybe_unused]] auto const& [key, value]: m_memory) + if (invalidatesMemoryLocation(key, _f)) + m_memory.erase(key); // TODO iterator invalidation? + } + + if (auto memoryMapping = isSimpleMStore(_f)) + // TODO can the key be already present in the m_memory? + m_memory[memoryMapping->first] = memoryMapping->second; + +} + +std::optional> MemoryLoadResolver::isSimpleMStore( + FunctionCall const& _functionCall +) const +{ + if (_functionCall.functionName.name == m_dialect.memoryStoreFunction({})->name) + if (Identifier const* key = std::get_if(&_functionCall.arguments.front())) + if (Identifier const* value = std::get_if(&_functionCall.arguments.back())) + if (m_ssaVariables.count(key->name) && m_ssaVariables.count(value->name)) + return make_pair(key->name, value->name); + return {}; +} + +smtutil::Expression MemoryLoadResolver::encodeExpression(yul::Expression const& _expression) +{ + return std::visit(GenericVisitor{ + [&](FunctionCall const& _functionCall) + { + if (auto const* dialect = dynamic_cast(&m_dialect)) + if (auto const* builtin = dialect->builtin(_functionCall.functionName.name)) + if (builtin->instruction) + return encodeEVMBuiltin(*builtin->instruction, _functionCall.arguments); + return newRestrictedVariable(); + }, + [&](Identifier const& _identifier) + { + if (// TODO do we need to check if it's an SSAVariable? + m_ssaVariables.count(_identifier.name) && + m_variables.count(_identifier.name) + ) + return m_variables.at(_identifier.name); + else + return newRestrictedVariable(); + + }, + [&](Literal const& _literal) + { + return literalValue(_literal); + } + }, _expression); +} + +smtutil::Expression MemoryLoadResolver::encodeEVMBuiltin( + evmasm::Instruction _instruction, + vector const& _arguments +) +{ + vector arguments = applyMap( + _arguments, + [this](yul::Expression const& _expr) { return encodeExpression(_expr); } + ); + switch (_instruction) + { + // TODO during the encoding for Real, change the wrapping. + case evmasm::Instruction::ADD: + return wrap(arguments.at(0) + arguments.at(1)); + + // Restrictions from EIP-1985 + case evmasm::Instruction::CALLDATASIZE: + case evmasm::Instruction::CODESIZE: + case evmasm::Instruction::EXTCODESIZE: + case evmasm::Instruction::MSIZE: + case evmasm::Instruction::RETURNDATASIZE: + return newRestrictedVariable(bigint(1) << 32); + break; + default: + break; + } + return newRestrictedVariable(); +} + +smtutil::Expression MemoryLoadResolver::newVariable() +{ + return m_solver->newVariable(uniqueName(), defaultSort()); +} + +smtutil::Expression MemoryLoadResolver::newRestrictedVariable(bigint _maxValue) +{ + smtutil::Expression var = newVariable(); + m_solver->addAssertion(0 <= var && var < smtutil::Expression(_maxValue)); + return var; +} + +string MemoryLoadResolver::uniqueName() +{ + return "expr_" + to_string(m_varCounter++); +} + +shared_ptr MemoryLoadResolver::defaultSort() const +{ + // TODO Change into realsort + return SortProvider::intSort(); +} + + +smtutil::Expression MemoryLoadResolver::constantValue(size_t _value) const +{ + return _value; +} + +smtutil::Expression MemoryLoadResolver::literalValue(Literal const& _literal) const +{ + return smtutil::Expression(valueOfLiteral(_literal)); +} + + +smtutil::Expression MemoryLoadResolver::wrap(smtutil::Expression _value) +{ + smtutil::Expression rest = newRestrictedVariable(); + smtutil::Expression multiplier = newVariable(); + m_solver->addAssertion(_value == multiplier * smtutil::Expression(bigint(1) << 256) + rest); + return rest; +} + +// TODO does it have to be _expr +bool MemoryLoadResolver::invalidatesMemoryLocation(YulString const& _name, Expression const& _expression) +{ + if (!holds_alternative(_expression)) + return true; + + FunctionCall const& functionCall = get(_expression); + + auto addMemoryConstraints = [&]( + evmasm::Instruction _instruction, + smtutil::Expression _memoryLocation, + vector const& _arguments + ) + { + vector arguments = applyMap( + _arguments, + [this](yul::Expression const& _expr) { return encodeExpression(_expr); } + ); + + switch (_instruction) + { + + case evmasm::Instruction::CALLDATACOPY: + case evmasm::Instruction::CODECOPY: + case evmasm::Instruction::RETURNDATACOPY: + yulAssert(arguments.size() == 3, ""); + m_solver->addAssertion(arguments.at(0) <= _memoryLocation); + m_solver->addAssertion(_memoryLocation < arguments.at(0) + arguments.at(2)); + break; + + case evmasm::Instruction::EXTCODECOPY: + yulAssert(arguments.size() == 4, ""); + m_solver->addAssertion(arguments.at(1) <= _memoryLocation); + m_solver->addAssertion(_memoryLocation < arguments.at(1) + arguments.at(3)); + break; + + // TODO Should mstore and mstore8 be dealt with separately? + case evmasm::Instruction::MSTORE: + yulAssert(arguments.size() == 2, ""); + m_solver->addAssertion(arguments.at(0) <= _memoryLocation); + m_solver->addAssertion(_memoryLocation < arguments.at(0) + constantValue(32)); + break; + + case evmasm::Instruction::MSTORE8: + yulAssert(arguments.size() == 2, ""); + m_solver->addAssertion(arguments.at(0) <= _memoryLocation); + m_solver->addAssertion(_memoryLocation < arguments.at(0) + constantValue(1)); + break; + + case evmasm::Instruction::CALL: + case evmasm::Instruction::CALLCODE: + yulAssert(arguments.size() == 7, ""); + m_solver->addAssertion(arguments.at(5) <= _memoryLocation); + m_solver->addAssertion(_memoryLocation < arguments.at(5) + arguments.at(6)); + break; + + case evmasm::Instruction::STATICCALL: + case evmasm::Instruction::DELEGATECALL: + yulAssert(arguments.size() == 6, ""); + m_solver->addAssertion(arguments.at(4) <= _memoryLocation); + m_solver->addAssertion(_memoryLocation < arguments.at(4) + arguments.at(5)); + break; + + default: + ; + } + }; + + if (auto dialect = dynamic_cast(&m_dialect)) + if (auto builtin = dialect->builtin(functionCall.functionName.name)) + if (builtin->instruction) + { + // TODO at this point, the constraints should be satisfiable. + // ADD an assert about it? + m_solver->push(); + addMemoryConstraints( + *builtin->instruction, + m_variables.at(_name), + functionCall.arguments + ); + + CheckResult result1 = m_solver->check({}).first; + m_solver->pop(); + + m_solver->push(); + addMemoryConstraints( + *builtin->instruction, + m_variables.at(_name) + constantValue(31), + functionCall.arguments + ); + + CheckResult result2 = m_solver->check({}).first; + m_solver->pop(); + + if ( + (result1 == CheckResult::UNSATISFIABLE) && + (result2 == CheckResult::UNSATISFIABLE) + ) + return false; + + } + + return true; +} diff --git a/libyul/optimiser/MemoryLoadResolver.h b/libyul/optimiser/MemoryLoadResolver.h new file mode 100644 index 000000000..cde764cd8 --- /dev/null +++ b/libyul/optimiser/MemoryLoadResolver.h @@ -0,0 +1,118 @@ +/* + 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 +#pragma once + + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include + +namespace solidity::yul +{ + +class MemoryLoadResolver: public Scoper +{ + +public: + static constexpr char const* name{"MemoryLoadResolver"}; + static void run(OptimiserStepContext& _context, Block& _ast); + + using ASTModifier::operator(); + void operator()(VariableDeclaration& _v) override; + void operator()(FunctionCall& _f) override; + + using ASTModifier::visit; + void visit(Expression& _e) override; + +private: + MemoryLoadResolver( + Dialect const& _dialect, + std::map _functionSideEffects, + std::set _ssaVariables, + bool _containsMsize + ) : + m_functionSideEffects(std::move(_functionSideEffects)), + m_ssaVariables(std::move(_ssaVariables)), + m_containsMsize(_containsMsize), + m_dialect(_dialect), + m_solver(std::make_unique()) + {} + + /// The mapping for memory + std::map m_memory; + + /// Side-effects of user-defined functions. Worst-case side-effects are assumed + /// if this is not provided or the function is not found. + std::map const m_functionSideEffects; + + /// A set of all SSA variables in the AST + std::set const m_ssaVariables; + + // TODO is this needed? loadresolver seems to use it + bool m_containsMsize = true; + + Dialect const& m_dialect; + + /// Checks if a function call is identical to a mstore(x, y), where x and y are SSA variables. + std::optional> isSimpleMStore( + FunctionCall const& _functionCall + ) const; + + + smtutil::Expression encodeExpression( + Expression const& _expression + ); + + virtual smtutil::Expression encodeEVMBuiltin( + evmasm::Instruction _instruction, + std::vector const& _arguments + ); + + smtutil::Expression newVariable(); + virtual smtutil::Expression newRestrictedVariable(bigint _maxValue = (bigint(1) << 256)); + std::string uniqueName(); + + virtual std::shared_ptr defaultSort() const; + virtual smtutil::Expression wrap(smtutil::Expression _value); + + virtual smtutil::Expression constantValue(size_t _value) const; + virtual smtutil::Expression literalValue(Literal const& _literal) const; + + bool invalidatesMemoryLocation(YulString const& _name, Expression const& _expression); + + std::unique_ptr m_solver; + std::map m_variables; + + size_t m_varCounter = 0; + + // TODO have a variable for function side effects +}; + +} diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 3f59e89e6..3fcefe9a4 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -195,6 +196,7 @@ map> const& OptimiserSuite::allSteps() LiteralRematerialiser, LoadResolver, LoopInvariantCodeMotion, + MemoryLoadResolver, RedundantAssignEliminator, ReasoningBasedSimplifier, Rematerialiser, @@ -234,6 +236,7 @@ map const& OptimiserSuite::stepNameToAbbreviationMap() {LiteralRematerialiser::name, 'T'}, {LoadResolver::name, 'L'}, {LoopInvariantCodeMotion::name, 'M'}, + {MemoryLoadResolver::name, 'y'}, {ReasoningBasedSimplifier::name, 'R'}, {RedundantAssignEliminator::name, 'r'}, {Rematerialiser::name, 'm'},