diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index ddbe4054b..71c7d7d1c 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -1032,7 +1032,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/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 86202e378..9ea63a3a2 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -26,8 +26,8 @@ #include #include #include +#include #include - #include #include @@ -185,9 +185,12 @@ map createBuiltins(langutil::EVMVersion _evmVe FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext&, - function _visitExpression + function ) { - visitArguments(_assembly, _call, _visitExpression); + yulAssert(_call.arguments.size() == 1, ""); + Literal const* literal = get_if(&_call.arguments.front()); + yulAssert(literal, ""); + _assembly.appendConstant(valueOfLiteral(*literal)); }) ); diff --git a/libyul/backends/evm/StackHelpers.h b/libyul/backends/evm/StackHelpers.h index 32e6119c4..2979b1e56 100644 --- a/libyul/backends/evm/StackHelpers.h +++ b/libyul/backends/evm/StackHelpers.h @@ -141,6 +141,7 @@ private: [&](size_t _offset) { return _ops.sourceIsSame(sourceOffset, _offset); } )) continue; + // Bring up the target slot that would otherwise become unreachable. for (size_t targetOffset: ranges::views::iota(0u, _ops.targetSize())) if (!_ops.targetIsArbitrary(targetOffset) && _ops.isCompatible(sourceOffset, targetOffset)) diff --git a/libyul/backends/evm/StackLayoutGenerator.cpp b/libyul/backends/evm/StackLayoutGenerator.cpp index d674300ea..b43c10dea 100644 --- a/libyul/backends/evm/StackLayoutGenerator.cpp +++ b/libyul/backends/evm/StackLayoutGenerator.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include using namespace solidity; @@ -47,17 +48,53 @@ using namespace std; StackLayout StackLayoutGenerator::run(CFG const& _cfg) { StackLayout stackLayout; - StackLayoutGenerator{stackLayout, {}}.processEntryPoint(*_cfg.entry); + { + StackLayoutGenerator generator{stackLayout}; + generator.processEntryPoint(*_cfg.entry); + } for (auto& functionInfo: _cfg.functionInfo | ranges::views::values) - StackLayoutGenerator{stackLayout, functionInfo.returnVariables}.processEntryPoint(*functionInfo.entry); + { + StackLayoutGenerator generator{stackLayout}; + generator.processEntryPoint(*functionInfo.entry); + } return stackLayout; } +map> StackLayoutGenerator::reportStackTooDeep(CFG const& _cfg) +{ + map> stackTooDeepErrors; + stackTooDeepErrors[YulString{}] = reportStackTooDeep(_cfg, YulString{}); + for (auto const& function: _cfg.functions) + { + auto errors = reportStackTooDeep(_cfg, function->name); + if (!errors.empty()) + stackTooDeepErrors[function->name] = errors; + } + return stackTooDeepErrors; +} +vector StackLayoutGenerator::reportStackTooDeep(CFG const& _cfg, YulString _functionName) +{ + StackLayout stackLayout; + CFG::FunctionInfo const* functionInfo = nullptr; + if (!_functionName.empty()) + { + for (auto&& [function, info]: _cfg.functionInfo) + if (info.function.name.str() == _functionName.str()) + { + functionInfo = &info; + break; + } + yulAssert(functionInfo, "Function not found."); + } -StackLayoutGenerator::StackLayoutGenerator(StackLayout& _layout, vector _currentFunctionReturnVariables): - m_layout(_layout), - m_currentFunctionReturnVariables(move(_currentFunctionReturnVariables)) + StackLayoutGenerator generator{stackLayout}; + CFG::BasicBlock const* entry = functionInfo ? functionInfo->entry : _cfg.entry; + generator.processEntryPoint(*entry); + return generator.reportStackTooDeep(*entry); +} + +StackLayoutGenerator::StackLayoutGenerator(StackLayout& _layout): m_layout(_layout) { } @@ -481,8 +518,10 @@ Stack StackLayoutGenerator::combineStack(Stack const& _stack1, Stack const& _sta Stack stack2Tail = _stack2 | ranges::views::drop(commonPrefix.size()) | ranges::to; if (stack1Tail.empty()) + // TODO: check if compress stack is actually good here. return commonPrefix + compressStack(stack2Tail); if (stack2Tail.empty()) + // TODO: check if compress stack is actually good here. return commonPrefix + compressStack(stack1Tail); Stack candidate; @@ -492,6 +531,7 @@ Stack StackLayoutGenerator::combineStack(Stack const& _stack1, Stack const& _sta for (auto slot: stack2Tail) if (!util::findOffset(candidate, slot)) candidate.emplace_back(slot); + // TODO: check if compressing here is actually good. cxx20::erase_if(candidate, [](StackSlot const& slot) { return holds_alternative(slot) || holds_alternative(slot); }); @@ -550,6 +590,91 @@ Stack StackLayoutGenerator::combineStack(Stack const& _stack1, Stack const& _sta return commonPrefix + bestCandidate; } +namespace +{ +vector findStackTooDeep(Stack const& _source, Stack const& _target) +{ + Stack currentStack = _source; + vector stackTooDeepErrors; + auto getVariableChoices = [](auto&& range) { + set result; + for (auto const& slot: range) + if (auto const* variableSlot = get_if(&slot)) + result.insert(variableSlot->variable.get().name); + return result; + }; + ::createStackLayout(currentStack, _target, [&](unsigned _i) { + if (_i > 16) + stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{ + _i - 16, + getVariableChoices(currentStack | ranges::views::take_last(_i + 1)) + }); + }, [&](StackSlot const& _slot) { + if (canBeFreelyGenerated(_slot)) + return; + if ( + auto depth = util::findOffset(currentStack | ranges::views::reverse, _slot); + depth && *depth >= 16 + ) + stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{ + *depth - 15, + getVariableChoices(currentStack | ranges::views::take_last(*depth + 1)) + }); + }, [&]() {}); + return stackTooDeepErrors; + +} +} + +vector StackLayoutGenerator::reportStackTooDeep(CFG::BasicBlock const& _entry) +{ + vector stackTooDeepErrors; + util::BreadthFirstSearch breadthFirstSearch{{&_entry}}; + breadthFirstSearch.run([&](CFG::BasicBlock const* _block, auto _addChild) { + Stack stack; + stack = m_layout.blockInfos.at(_block).entryLayout; + + for (auto const& operation: _block->operations) + { + Stack& operationEntry = m_layout.operationEntryLayout.at(&operation); + + stackTooDeepErrors += findStackTooDeep(stack, operationEntry); + stack = operationEntry; + for (size_t i = 0; i < operation.input.size(); i++) + stack.pop_back(); + stack += operation.output; + } + // Do not create attempt to create the exit layout here, since the code generator will directly move to the + // target entry layout. + + std::visit(util::GenericVisitor{ + [&](CFG::BasicBlock::MainExit const&) {}, + [&](CFG::BasicBlock::Jump const& _jump) + { + Stack const& targetLayout = m_layout.blockInfos.at(_jump.target).entryLayout; + stackTooDeepErrors += findStackTooDeep(stack, targetLayout); + + if (!_jump.backwards) + _addChild(_jump.target); + }, + [&](CFG::BasicBlock::ConditionalJump const& _conditionalJump) + { + for (Stack const& targetLayout: { + m_layout.blockInfos.at(_conditionalJump.zero).entryLayout, + m_layout.blockInfos.at(_conditionalJump.nonZero).entryLayout + }) + stackTooDeepErrors += findStackTooDeep(stack, targetLayout); + + _addChild(_conditionalJump.zero); + _addChild(_conditionalJump.nonZero); + }, + [&](CFG::BasicBlock::FunctionReturn const&) {}, + [&](CFG::BasicBlock::Terminated const&) {}, + }, _block->exit); + }); + return stackTooDeepErrors; +} + Stack StackLayoutGenerator::compressStack(Stack _stack) { optional firstDupOffset; diff --git a/libyul/backends/evm/StackLayoutGenerator.h b/libyul/backends/evm/StackLayoutGenerator.h index 908ef60b5..2cd2d66ab 100644 --- a/libyul/backends/evm/StackLayoutGenerator.h +++ b/libyul/backends/evm/StackLayoutGenerator.h @@ -47,10 +47,20 @@ struct StackLayout class StackLayoutGenerator { public: + struct StackTooDeep + { + /// Number of slots that need to be saved. + size_t deficit = 0; + /// Set of variables, eliminating which would decrease the stack deficit. + std::set variableChoices; + }; + static StackLayout run(CFG const& _cfg); + static std::map> reportStackTooDeep(CFG const& _cfg); + static std::vector reportStackTooDeep(CFG const& _cfg, YulString _functionName); private: - StackLayoutGenerator(StackLayout& _context, std::vector _currentFunctionReturnVariables); + StackLayoutGenerator(StackLayout& _context); /// @returns the optimal entry stack layout, s.t. @a _operation can be applied to it and /// the result can be transformed to @a _exitStack with minimal stack shuffling. @@ -86,13 +96,16 @@ private: /// stack shuffling when starting from the returned layout. static Stack combineStack(Stack const& _stack1, Stack const& _stack2); + /// Walks through the CFG and reports and stack too deep errors that would occur when generating code for it + /// without countermeasures. + std::vector reportStackTooDeep(CFG::BasicBlock const& _entry); + /// @returns a copy of @a _stack stripped of all duplicates and slots that can be freely generated. /// Attempts to create a layout that requires a minimal amount of operations to reconstruct the original /// stack @a _stack. static Stack compressStack(Stack _stack); StackLayout& m_layout; - std::vector const m_currentFunctionReturnVariables; }; } diff --git a/libyul/optimiser/Rematerialiser.cpp b/libyul/optimiser/Rematerialiser.cpp index e5fec6b62..f1597a7f2 100644 --- a/libyul/optimiser/Rematerialiser.cpp +++ b/libyul/optimiser/Rematerialiser.cpp @@ -32,39 +32,44 @@ using namespace std; using namespace solidity; using namespace solidity::yul; -void Rematerialiser::run(Dialect const& _dialect, Block& _ast, set _varsToAlwaysRematerialize) +void Rematerialiser::run(Dialect const& _dialect, Block& _ast, set _varsToAlwaysRematerialize, bool _onlySelectedVariables) { - Rematerialiser{_dialect, _ast, std::move(_varsToAlwaysRematerialize)}(_ast); + Rematerialiser{_dialect, _ast, std::move(_varsToAlwaysRematerialize), _onlySelectedVariables}(_ast); } void Rematerialiser::run( Dialect const& _dialect, FunctionDefinition& _function, - set _varsToAlwaysRematerialize + set _varsToAlwaysRematerialize, + bool _onlySelectedVariables ) { - Rematerialiser{_dialect, _function, std::move(_varsToAlwaysRematerialize)}(_function); + Rematerialiser{_dialect, _function, std::move(_varsToAlwaysRematerialize), _onlySelectedVariables}(_function); } Rematerialiser::Rematerialiser( Dialect const& _dialect, Block& _ast, - set _varsToAlwaysRematerialize + set _varsToAlwaysRematerialize, + bool _onlySelectedVariables ): DataFlowAnalyzer(_dialect), m_referenceCounts(ReferencesCounter::countReferences(_ast)), - m_varsToAlwaysRematerialize(std::move(_varsToAlwaysRematerialize)) + m_varsToAlwaysRematerialize(std::move(_varsToAlwaysRematerialize)), + m_onlySelectedVariables(_onlySelectedVariables) { } Rematerialiser::Rematerialiser( Dialect const& _dialect, FunctionDefinition& _function, - set _varsToAlwaysRematerialize + set _varsToAlwaysRematerialize, + bool _onlySelectedVariables ): DataFlowAnalyzer(_dialect), m_referenceCounts(ReferencesCounter::countReferences(_function)), - m_varsToAlwaysRematerialize(std::move(_varsToAlwaysRematerialize)) + m_varsToAlwaysRematerialize(std::move(_varsToAlwaysRematerialize)), + m_onlySelectedVariables(_onlySelectedVariables) { } @@ -81,10 +86,13 @@ void Rematerialiser::visit(Expression& _e) size_t refs = m_referenceCounts[name]; size_t cost = CodeCost::codeCost(m_dialect, *value.value); if ( - (refs <= 1 && value.loopDepth == m_loopDepth) || - cost == 0 || - (refs <= 5 && cost <= 1 && m_loopDepth == 0) || - m_varsToAlwaysRematerialize.count(name) + ( + !m_onlySelectedVariables && ( + (refs <= 1 && value.loopDepth == m_loopDepth) || + cost == 0 || + (refs <= 5 && cost <= 1 && m_loopDepth == 0) + ) + ) || m_varsToAlwaysRematerialize.count(name) ) { assertThrow(m_referenceCounts[name] > 0, OptimizerException, ""); diff --git a/libyul/optimiser/Rematerialiser.h b/libyul/optimiser/Rematerialiser.h index b7e98a6b8..d1ebb1595 100644 --- a/libyul/optimiser/Rematerialiser.h +++ b/libyul/optimiser/Rematerialiser.h @@ -50,24 +50,28 @@ public: static void run( Dialect const& _dialect, Block& _ast, - std::set _varsToAlwaysRematerialize = {} + std::set _varsToAlwaysRematerialize = {}, + bool _onlySelectedVariables = false ); static void run( Dialect const& _dialect, FunctionDefinition& _function, - std::set _varsToAlwaysRematerialize = {} + std::set _varsToAlwaysRematerialize = {}, + bool _onlySelectedVariables = false ); protected: Rematerialiser( Dialect const& _dialect, Block& _ast, - std::set _varsToAlwaysRematerialize = {} + std::set _varsToAlwaysRematerialize = {}, + bool _onlySelectedVariables = false ); Rematerialiser( Dialect const& _dialect, FunctionDefinition& _function, - std::set _varsToAlwaysRematerialize = {} + std::set _varsToAlwaysRematerialize = {}, + bool _onlySelectedVariables = false ); using DataFlowAnalyzer::operator(); @@ -77,6 +81,7 @@ protected: std::map m_referenceCounts; std::set m_varsToAlwaysRematerialize; + bool m_onlySelectedVariables = false; }; /** 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()