diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 5affb1c08..5f8c29fc1 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -1033,7 +1033,7 @@ string IRGenerator::memoryInit(bool _useMemoryGuard) { // TODO: Remove once we have made sure it is safe, i.e. after "Yul memory objects lite". // Also restore the tests removed in the commit that adds this comment. - _useMemoryGuard = false; + // _useMemoryGuard = false; // 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). diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index 5e9202530..bbbbd169e 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -27,6 +27,13 @@ #include #include +#include +#include +#include + +#include +#include + #include #include @@ -150,6 +157,40 @@ void eliminateVariables( UnusedPruner::runUntilStabilised(_dialect, _node, _allowMSizeOptimization); } +void eliminateVariables( + Dialect const& _dialect, + Block& _block, + vector const& _unreachables, + bool _allowMSizeOptimization +) +{ + RematCandidateSelector selector{_dialect}; + selector(_block); + std::map candidates; + for (auto [cost, name, dependencies]: selector.candidates()) + candidates[name] = cost; + + set varsToEliminate; + + for (auto const& unreachable: _unreachables) + { + set> suitableCandidates; + size_t neededSlots = unreachable.deficit; + for (auto varName: unreachable.variableChoices) + { + if (varsToEliminate.count(varName)) + --neededSlots; + else if (size_t* cost = util::valueOrNullptr(candidates, varName)) + suitableCandidates.insert(std::make_tuple(*cost, varName)); + } + + for (auto candidate: suitableCandidates | ranges::views::take(neededSlots)) + varsToEliminate.emplace(get<1>(candidate)); + } + Rematerialiser::run(_dialect, _block, std::move(varsToEliminate), true); + UnusedPruner::runUntilStabilised(_dialect, _block, _allowMSizeOptimization); +} + } bool StackCompressor::run( @@ -164,39 +205,63 @@ bool StackCompressor::run( _object.code->statements.size() > 0 && holds_alternative(_object.code->statements.at(0)), "Need to run the function grouper before the stack compressor." ); + bool usesOptimizedCodeGenerator = false; + if (auto evmDialect = dynamic_cast(&_dialect)) + usesOptimizedCodeGenerator = _optimizeStackAllocation && evmDialect->evmVersion() > langutil::EVMVersion::homestead(); bool allowMSizeOptimzation = !MSizeFinder::containsMSize(_dialect, *_object.code); - for (size_t iterations = 0; iterations < _maxIterations; iterations++) + if (usesOptimizedCodeGenerator) { - map stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit; - if (stackSurplus.empty()) - return true; - - if (stackSurplus.count(YulString{})) - { - yulAssert(stackSurplus.at({}) > 0, "Invalid surplus value."); - eliminateVariables( - _dialect, - std::get(_object.code->statements.at(0)), - static_cast(stackSurplus.at({})), - allowMSizeOptimzation - ); - } - + yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object); + unique_ptr cfg = ControlFlowGraphBuilder::build(analysisInfo, _dialect, *_object.code); + Block& mainBlock = std::get(_object.code->statements.at(0)); + if ( + auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, YulString{}); + !stackTooDeepErrors.empty() + ) + eliminateVariables(_dialect, mainBlock, stackTooDeepErrors, allowMSizeOptimzation); for (size_t i = 1; i < _object.code->statements.size(); ++i) { auto& fun = std::get(_object.code->statements[i]); - if (!stackSurplus.count(fun.name)) - continue; - - yulAssert(stackSurplus.at(fun.name) > 0, "Invalid surplus value."); - eliminateVariables( - _dialect, - fun, - static_cast(stackSurplus.at(fun.name)), - allowMSizeOptimzation - ); + if ( + auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, fun.name); + !stackTooDeepErrors.empty() + ) + eliminateVariables(_dialect, fun.body, stackTooDeepErrors, allowMSizeOptimzation); } } + else + for (size_t iterations = 0; iterations < _maxIterations; iterations++) + { + map stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit; + if (stackSurplus.empty()) + return true; + + if (stackSurplus.count(YulString{})) + { + yulAssert(stackSurplus.at({}) > 0, "Invalid surplus value."); + eliminateVariables( + _dialect, + std::get(_object.code->statements.at(0)), + static_cast(stackSurplus.at({})), + allowMSizeOptimzation + ); + } + + for (size_t i = 1; i < _object.code->statements.size(); ++i) + { + auto& fun = std::get(_object.code->statements[i]); + if (!stackSurplus.count(fun.name)) + continue; + + yulAssert(stackSurplus.at(fun.name) > 0, "Invalid surplus value."); + eliminateVariables( + _dialect, + fun, + static_cast(stackSurplus.at(fun.name)), + allowMSizeOptimzation + ); + } + } return false; } diff --git a/libyul/optimiser/StackLimitEvader.cpp b/libyul/optimiser/StackLimitEvader.cpp index 8a632af53..5cf1cd57f 100644 --- a/libyul/optimiser/StackLimitEvader.cpp +++ b/libyul/optimiser/StackLimitEvader.cpp @@ -21,14 +21,18 @@ #include #include #include +#include #include +#include #include +#include #include #include #include #include #include +#include #include #include @@ -114,6 +118,45 @@ u256 literalArgumentValue(FunctionCall const& _call) } } +void StackLimitEvader::run( + OptimiserStepContext& _context, + Object& _object +) +{ + auto const* evmDialect = dynamic_cast(&_context.dialect); + yulAssert( + evmDialect && evmDialect->providesObjectAccess(), + "StackLimitEvader can only be run on objects using the EVMDialect with object access." + ); + if (evmDialect && evmDialect->evmVersion() > langutil::EVMVersion::homestead()) + { + yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(*evmDialect, _object); + unique_ptr cfg = ControlFlowGraphBuilder::build(analysisInfo, *evmDialect, *_object.code); + run(_context, _object, StackLayoutGenerator::reportStackTooDeep(*cfg)); + } + else + run(_context, _object, CompilabilityChecker{ + _context.dialect, + _object, + true + }.unreachableVariables); + +} + +void StackLimitEvader::run( + OptimiserStepContext& _context, + Object& _object, + map> const& _stackTooDeepErrors +) +{ + map> unreachableVariables; + for (auto&& [function, stackTooDeepErrors]: _stackTooDeepErrors) + // TODO: choose wisely. + for (auto const& stackTooDeepError: stackTooDeepErrors) + unreachableVariables[function] += stackTooDeepError.variableChoices | ranges::views::take(stackTooDeepError.deficit) | ranges::to>; + run(_context, _object, unreachableVariables); +} + void StackLimitEvader::run( OptimiserStepContext& _context, Object& _object, diff --git a/libyul/optimiser/StackLimitEvader.h b/libyul/optimiser/StackLimitEvader.h index 4fc351f73..ae2d31c37 100644 --- a/libyul/optimiser/StackLimitEvader.h +++ b/libyul/optimiser/StackLimitEvader.h @@ -22,6 +22,7 @@ #pragma once #include +#include namespace solidity::yul { @@ -61,6 +62,25 @@ public: Object& _object, std::map> const& _unreachableVariables ); + /// @a _stackTooDeepErrors can be determined by the StackLayoutGenerator. + /// Can only be run on the EVM dialect with objects. + /// Abort and do nothing, if no ``memoryguard`` call or several ``memoryguard`` calls + /// with non-matching arguments are found, or if any of the @a _stackTooDeepErrors + /// are contained in a recursive function. + static void run( + OptimiserStepContext& _context, + Object& _object, + std::map> const& _stackTooDeepErrors + ); + /// Determines stack too deep errors using the appropriate code generation backend. + /// Can only be run on the EVM dialect with objects. + /// Abort and do nothing, if no ``memoryguard`` call or several ``memoryguard`` calls + /// with non-matching arguments are found, or if any of the unreachable variables + /// are contained in a recursive function. + static void run( + OptimiserStepContext& _context, + Object& _object + ); }; } diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 480a542a3..ee5e6e620 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -92,6 +92,11 @@ void OptimiserSuite::run( set const& _externallyUsedIdentifiers ) { + EVMDialect const* evmDialect = dynamic_cast(&_dialect); + bool usesOptimizedCodeGenerator = + _optimizeStackAllocation && + evmDialect && + evmDialect->evmVersion() > langutil::EVMVersion::homestead(); set reservedIdentifiers = _externallyUsedIdentifiers; reservedIdentifiers += _dialect.fixedFunctionNames(); @@ -118,24 +123,32 @@ void OptimiserSuite::run( // We ignore the return value because we will get a much better error // message once we perform code generation. - StackCompressor::run( - _dialect, - _object, - _optimizeStackAllocation, - stackCompressorMaxIterations - ); + if (!usesOptimizedCodeGenerator) + StackCompressor::run( + _dialect, + _object, + _optimizeStackAllocation, + stackCompressorMaxIterations + ); suite.runSequence("fDnTOc g", ast); - if (EVMDialect const* dialect = dynamic_cast(&_dialect)) + if (evmDialect) { yulAssert(_meter, ""); - ConstantOptimiser{*dialect, *_meter}(ast); - if (dialect->providesObjectAccess() && _optimizeStackAllocation) - StackLimitEvader::run(suite.m_context, _object, CompilabilityChecker{ + ConstantOptimiser{*evmDialect, *_meter}(ast); + if (usesOptimizedCodeGenerator) + { + StackCompressor::run( _dialect, _object, - _optimizeStackAllocation - }.unreachableVariables); + _optimizeStackAllocation, + stackCompressorMaxIterations + ); + if (evmDialect->providesObjectAccess()) + StackLimitEvader::run(suite.m_context, _object); + } + else if (evmDialect->providesObjectAccess() && _optimizeStackAllocation) + StackLimitEvader::run(suite.m_context, _object); } else if (dynamic_cast(&_dialect)) { diff --git a/test/libyul/StackShufflingTest.cpp b/test/libyul/StackShufflingTest.cpp index f33134318..bfeaf3f0c 100644 --- a/test/libyul/StackShufflingTest.cpp +++ b/test/libyul/StackShufflingTest.cpp @@ -29,6 +29,25 @@ using namespace std; namespace solidity::yul::test { +namespace +{ +bool triggersStackTooDeep(Stack _source, Stack const& _target) +{ + bool result = false; + createStackLayout(_source, _target, [&](unsigned _i) { + if (_i > 16) + result = true; + }, [&](StackSlot const& _slot) { + if (canBeFreelyGenerated(_slot)) + return; + if (auto depth = util::findOffset(_source | ranges::views::reverse, _slot); depth && *depth >= 16) + if (*depth >= 16) + result = true; + }, [&]() {}); + return result; +} +} + BOOST_AUTO_TEST_SUITE(StackHelpers) BOOST_AUTO_TEST_CASE(avoid_deep_dup) @@ -74,8 +93,7 @@ BOOST_AUTO_TEST_CASE(avoid_deep_dup) VariableSlot{variableContainer[14]}, // While "optimal", bringing this slot up first will make the next unreachable. VariableSlot{variableContainer[0]} }}; - auto unreachable = OptimizedEVMCodeTransform::tryCreateStackLayout(from, to, {}); - BOOST_CHECK(unreachable.empty()); + BOOST_CHECK(!triggersStackTooDeep(from, to)); } BOOST_AUTO_TEST_SUITE_END()