From 4229369180d2ba0001aa1d4254eb188703d59ff8 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 12 Aug 2021 17:17:21 +0200 Subject: [PATCH] Compatibility with StackCompressor and StackLimitEvader. --- libsolidity/codegen/ir/IRGenerator.cpp | 3 - libyul/optimiser/StackCompressor.cpp | 130 ++++++++++++++++++++----- libyul/optimiser/StackLimitEvader.cpp | 43 ++++++++ libyul/optimiser/StackLimitEvader.h | 20 ++++ libyul/optimiser/Suite.cpp | 38 +++++--- 5 files changed, 193 insertions(+), 41 deletions(-) diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index b17b33c03..c2bb0c3cf 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -1069,9 +1069,6 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) 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; // 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 a57d9738b..2deef5234 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -27,6 +27,13 @@ #include #include +#include +#include +#include + +#include +#include + #include #include @@ -162,6 +169,50 @@ 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, candidatesWithCost]: selector.candidates()) + for (auto candidate: candidatesWithCost) + candidates[get<0>(candidate)] = cost; + + set varsToEliminate; + + // TODO: this currently ignores the fact that variables may reference other variables we want to eliminate. + for (auto const& unreachable: _unreachables) + { + map> 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)) + if (!util::contains(suitableCandidates[*cost], varName)) + suitableCandidates[*cost].emplace_back(varName); + } + for (auto candidatesByCost: suitableCandidates) + { + for (auto candidate: candidatesByCost.second) + if (neededSlots--) + varsToEliminate.emplace(candidate); + else + break; + if (!neededSlots) + break; + } + } + Rematerialiser::run(_dialect, _block, std::move(varsToEliminate), true); + UnusedPruner::runUntilStabilised(_dialect, _block, _allowMSizeOptimization); +} + } bool StackCompressor::run( @@ -176,39 +227,66 @@ 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().canOverchargeGasForCall() && + evmDialect->providesObjectAccess(); 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 1c7903b1f..b77bd4f38 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().canOverchargeGasForCall()) + { + 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 5525e17d2..bd81f7c0b 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -95,6 +95,12 @@ void OptimiserSuite::run( set const& _externallyUsedIdentifiers ) { + EVMDialect const* evmDialect = dynamic_cast(&_dialect); + bool usesOptimizedCodeGenerator = + _optimizeStackAllocation && + evmDialect && + evmDialect->evmVersion().canOverchargeGasForCall() && + evmDialect->providesObjectAccess(); set reservedIdentifiers = _externallyUsedIdentifiers; reservedIdentifiers += _dialect.fixedFunctionNames(); @@ -121,24 +127,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)) {