diff --git a/Changelog.md b/Changelog.md index 3eae69c9d..d59e1f777 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,9 @@ Language Features: Compiler Features: + * Yul EVM Code Transform: Do not reuse stack slots that immediately become unreachable. + * Yul EVM Code Transform: Also pop unused argument slots for functions without return variables (under the same restrictions as for functions with return variables). + * Yul Optimizer: Move function arguments and return variables to memory with the experimental Stack Limit Evader (which is not enabled by default). Bugfixes: diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 18a96049d..59fb6d468 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -129,6 +129,8 @@ add_library(yul optimiser/FullInliner.h optimiser/FunctionCallFinder.cpp optimiser/FunctionCallFinder.h + optimiser/FunctionDefinitionCollector.cpp + optimiser/FunctionDefinitionCollector.h optimiser/FunctionGrouper.cpp optimiser/FunctionGrouper.h optimiser/FunctionHoister.cpp diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index e4ba54b2a..d5530c52a 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -182,16 +182,24 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl) else m_variablesScheduledForDeletion.insert(&var); } - else if (m_unusedStackSlots.empty()) - atTopOfStack = false; else { - auto slot = static_cast(*m_unusedStackSlots.begin()); - m_unusedStackSlots.erase(m_unusedStackSlots.begin()); - m_context->variableStackHeights[&var] = slot; - if (size_t heightDiff = variableHeightDiff(var, varName, true)) - m_assembly.appendInstruction(evmasm::swapInstruction(static_cast(heightDiff - 1))); - m_assembly.appendInstruction(evmasm::Instruction::POP); + bool foundUnusedSlot = false; + for (auto it = m_unusedStackSlots.begin(); it != m_unusedStackSlots.end(); ++it) + { + if (m_assembly.stackHeight() - *it > 17) + continue; + foundUnusedSlot = true; + auto slot = static_cast(*it); + m_unusedStackSlots.erase(it); + m_context->variableStackHeights[&var] = slot; + if (size_t heightDiff = variableHeightDiff(var, varName, true)) + m_assembly.appendInstruction(evmasm::swapInstruction(static_cast(heightDiff - 1))); + m_assembly.appendInstruction(evmasm::Instruction::POP); + break; + } + if (!foundUnusedSlot) + atTopOfStack = false; } } } @@ -404,7 +412,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function) subTransform.deleteVariable(var); } - if (!m_allowStackOpt || _function.returnVariables.empty()) + if (!m_allowStackOpt) subTransform.setupReturnVariablesAndFunctionExit(); subTransform(_function.body); @@ -594,6 +602,7 @@ void CodeTransform::visitExpression(Expression const& _expression) void CodeTransform::setupReturnVariablesAndFunctionExit() { + yulAssert(isInsideFunction(), ""); yulAssert(!returnVariablesAndFunctionExitAreSetup(), ""); yulAssert(m_scope, ""); @@ -656,7 +665,8 @@ void CodeTransform::visitStatements(vector const& _statements) { freeUnusedVariables(); if ( - !m_delayedReturnVariables.empty() && + isInsideFunction() && + !returnVariablesAndFunctionExitAreSetup() && statementNeedsReturnVariableSetup(statement, m_delayedReturnVariables) ) setupReturnVariablesAndFunctionExit(); diff --git a/libyul/backends/evm/EVMCodeTransform.h b/libyul/backends/evm/EVMCodeTransform.h index a76638850..894a6d077 100644 --- a/libyul/backends/evm/EVMCodeTransform.h +++ b/libyul/backends/evm/EVMCodeTransform.h @@ -181,6 +181,10 @@ private: { return m_functionExitStackHeight.has_value(); } + bool isInsideFunction() const + { + return m_functionExitLabel.has_value(); + } AbstractAssembly& m_assembly; AsmAnalysisInfo& m_info; diff --git a/libyul/optimiser/FunctionDefinitionCollector.cpp b/libyul/optimiser/FunctionDefinitionCollector.cpp new file mode 100644 index 000000000..70f2a268d --- /dev/null +++ b/libyul/optimiser/FunctionDefinitionCollector.cpp @@ -0,0 +1,36 @@ +/* + 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 . +*/ + +#include +#include + +using namespace std; +using namespace solidity; +using namespace solidity::yul; + +map FunctionDefinitionCollector::run(Block& _block) +{ + FunctionDefinitionCollector functionDefinitionCollector; + functionDefinitionCollector(_block); + return functionDefinitionCollector.m_functionDefinitions; +} + +void FunctionDefinitionCollector::operator()(FunctionDefinition const& _functionDefinition) +{ + m_functionDefinitions[_functionDefinition.name] = &_functionDefinition; + ASTWalker::operator()(_functionDefinition); +} diff --git a/libyul/optimiser/FunctionDefinitionCollector.h b/libyul/optimiser/FunctionDefinitionCollector.h new file mode 100644 index 000000000..c97f20d67 --- /dev/null +++ b/libyul/optimiser/FunctionDefinitionCollector.h @@ -0,0 +1,44 @@ +/* + 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 . +*/ +/** + * AST walker that finds all function definitions and stores them into a map indexed by the function names. + */ +#pragma once + +#include + +#include + +namespace solidity::yul +{ + +/** + * AST walker that finds all function definitions and stores them into a map indexed by the function names. + * + * Prerequisite: Disambiguator + */ +class FunctionDefinitionCollector: ASTWalker +{ +public: + static std::map run(Block& _block); +private: + using ASTWalker::operator(); + void operator()(FunctionDefinition const& _functionDefinition) override; + std::map m_functionDefinitions; +}; + +} diff --git a/libyul/optimiser/StackLimitEvader.cpp b/libyul/optimiser/StackLimitEvader.cpp index 31ba94937..8a632af53 100644 --- a/libyul/optimiser/StackLimitEvader.cpp +++ b/libyul/optimiser/StackLimitEvader.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,9 @@ #include #include +#include +#include + using namespace std; using namespace solidity; using namespace solidity::yul; @@ -62,25 +66,42 @@ struct MemoryOffsetAllocator for (YulString child: callGraph.at(_function)) requiredSlots = std::max(run(child), requiredSlots); - if (unreachableVariables.count(_function)) + if (auto const* unreachables = util::valueOrNullptr(unreachableVariables, _function)) { - yulAssert(!slotAllocations.count(_function), ""); - for (YulString variable: unreachableVariables.at(_function)) - if (variable.empty()) - { - // TODO: Too many function arguments or return parameters. - } - else + if (FunctionDefinition const* functionDefinition = util::valueOrDefault(functionDefinitions, _function, nullptr, util::allow_copy)) + if ( + size_t totalArgCount = functionDefinition->returnVariables.size() + functionDefinition->parameters.size(); + totalArgCount > 16 + ) + for (TypedName const& var: ranges::concat_view( + functionDefinition->parameters, + functionDefinition->returnVariables + ) | ranges::views::take(totalArgCount - 16)) + slotAllocations[var.name] = requiredSlots++; + + // Assign slots for all variables that become unreachable in the function body, if the above did not + // assign a slot for them already. + for (YulString variable: *unreachables) + // The empty case is a function with too many arguments or return values, + // which was already handled above. + if (!variable.empty() && !slotAllocations.count(variable)) slotAllocations[variable] = requiredSlots++; } return slotsRequiredForFunction[_function] = requiredSlots; } + /// Maps function names to the set of unreachable variables in that function. + /// An empty variable name means that the function has too many arguments or return variables. map> const& unreachableVariables; + /// The graph of immediate function calls of all functions. map> const& callGraph; + /// Maps the name of each user-defined function to its definition. + map const& functionDefinitions; + /// Maps variable names to the memory slot the respective variable is assigned. map slotAllocations{}; + /// Maps function names to the number of memory slots the respective function requires. map slotsRequiredForFunction{}; }; @@ -116,6 +137,8 @@ void StackLimitEvader::run( // Make sure all calls to ``memoryguard`` we found have the same value as argument (otherwise, abort). u256 reservedMemory = literalArgumentValue(*memoryGuardCalls.front()); + yulAssert(reservedMemory < u256(1) << 32 - 1, ""); + for (FunctionCall const* memoryGuardCall: memoryGuardCalls) if (reservedMemory != literalArgumentValue(*memoryGuardCall)) return; @@ -127,12 +150,14 @@ void StackLimitEvader::run( if (_unreachableVariables.count(function)) return; - MemoryOffsetAllocator memoryOffsetAllocator{_unreachableVariables, callGraph.functionCalls}; + map functionDefinitions = FunctionDefinitionCollector::run(*_object.code); + + MemoryOffsetAllocator memoryOffsetAllocator{_unreachableVariables, callGraph.functionCalls, functionDefinitions}; uint64_t requiredSlots = memoryOffsetAllocator.run(); + yulAssert(requiredSlots < (uint64_t(1) << 32) - 1, ""); StackToMemoryMover::run(_context, reservedMemory, memoryOffsetAllocator.slotAllocations, requiredSlots, *_object.code); - yulAssert(requiredSlots < std::numeric_limits::max() / 32, ""); reservedMemory += 32 * requiredSlots; for (FunctionCall* memoryGuardCall: FunctionCallFinder::run(*_object.code, "memoryguard"_yulstring)) { diff --git a/libyul/optimiser/StackToMemoryMover.cpp b/libyul/optimiser/StackToMemoryMover.cpp index 3e2d72cad..87976e116 100644 --- a/libyul/optimiser/StackToMemoryMover.cpp +++ b/libyul/optimiser/StackToMemoryMover.cpp @@ -15,13 +15,19 @@ along with solidity. If not, see . */ #include +#include #include #include #include #include -#include + +#include +#include +#include +#include +#include using namespace std; using namespace solidity; @@ -44,7 +50,7 @@ vector generateMemoryStore( Identifier{_debugData, memoryStoreFunction->name}, { Literal{_debugData, LiteralKind::Number, _mpos, {}}, - std::move(_value) + move(_value) } }}); return result; @@ -77,17 +83,30 @@ void StackToMemoryMover::run( ) { VariableMemoryOffsetTracker memoryOffsetTracker(_reservedMemory, _memorySlots, _numRequiredSlots); - StackToMemoryMover stackToMemoryMover(_context, memoryOffsetTracker); + StackToMemoryMover stackToMemoryMover( + _context, + memoryOffsetTracker, + util::applyMap( + FunctionDefinitionCollector::run(_block), + util::mapTuple([](YulString _name, FunctionDefinition const* _funDef) { + return make_pair(_name, _funDef->returnVariables); + }), + map{} + ) + ); stackToMemoryMover(_block); + _block.statements += move(stackToMemoryMover.m_newFunctionDefinitions); } StackToMemoryMover::StackToMemoryMover( OptimiserStepContext& _context, - VariableMemoryOffsetTracker const& _memoryOffsetTracker + VariableMemoryOffsetTracker const& _memoryOffsetTracker, + map _functionReturnVariables ): m_context(_context), m_memoryOffsetTracker(_memoryOffsetTracker), -m_nameDispenser(_context.dispenser) +m_nameDispenser(_context.dispenser), +m_functionReturnVariables(move(_functionReturnVariables)) { auto const* evmDialect = dynamic_cast(&_context.dialect); yulAssert( @@ -98,89 +117,186 @@ m_nameDispenser(_context.dispenser) void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition) { - for (TypedName const& param: _functionDefinition.parameters + _functionDefinition.returnVariables) - if (m_memoryOffsetTracker(param.name)) - { - // TODO: we cannot handle function parameters yet. - return; - } + // It is important to first visit the function body, so that it doesn't replace the memory inits for + // variable arguments we might generate below. ASTModifier::operator()(_functionDefinition); + + vector memoryVariableInits; + + // All function parameters with a memory slot are moved at the beginning of the function body. + for (TypedName const& param: _functionDefinition.parameters) + if (auto slot = m_memoryOffsetTracker(param.name)) + memoryVariableInits += generateMemoryStore( + m_context.dialect, + param.debugData, + *slot, + Identifier{param.debugData, param.name} + ); + + // All memory return variables have to be initialized to zero in memory. + for (TypedName const& returnVariable: _functionDefinition.returnVariables) + if (auto slot = m_memoryOffsetTracker(returnVariable.name)) + memoryVariableInits += generateMemoryStore( + m_context.dialect, + returnVariable.debugData, + *slot, + Literal{returnVariable.debugData, LiteralKind::Number, "0"_yulstring, {}} + ); + + // Special case of a function with a single return argument that needs to move to memory. + if (_functionDefinition.returnVariables.size() == 1 && m_memoryOffsetTracker(_functionDefinition.returnVariables.front().name)) + { + TypedNameList stackParameters = _functionDefinition.parameters | ranges::views::filter( + not_fn(m_memoryOffsetTracker) + ) | ranges::to; + // Generate new function without return variable and with only the non-moved parameters. + YulString newFunctionName = m_context.dispenser.newName(_functionDefinition.name); + m_newFunctionDefinitions.emplace_back(FunctionDefinition{ + _functionDefinition.debugData, + newFunctionName, + stackParameters, + {}, + move(_functionDefinition.body) + }); + // Generate new names for the arguments to maintain disambiguation. + std::map newArgumentNames; + for (TypedName const& _var: stackParameters) + newArgumentNames[_var.name] = m_context.dispenser.newName(_var.name); + for (auto& parameter: _functionDefinition.parameters) + parameter.name = util::valueOrDefault(newArgumentNames, parameter.name, parameter.name); + // Replace original function by a call to the new function and an assignment to the return variable from memory. + _functionDefinition.body = Block{_functionDefinition.debugData, move(memoryVariableInits)}; + _functionDefinition.body.statements.emplace_back(ExpressionStatement{ + _functionDefinition.debugData, + FunctionCall{ + _functionDefinition.debugData, + Identifier{_functionDefinition.debugData, newFunctionName}, + stackParameters | ranges::views::transform([&](TypedName const& _arg) { + return Expression{Identifier{_arg.debugData, newArgumentNames.at(_arg.name)}}; + }) | ranges::to> + } + }); + _functionDefinition.body.statements.emplace_back(Assignment{ + _functionDefinition.debugData, + {Identifier{_functionDefinition.debugData, _functionDefinition.returnVariables.front().name}}, + make_unique(generateMemoryLoad( + m_context.dialect, + _functionDefinition.debugData, + *m_memoryOffsetTracker(_functionDefinition.returnVariables.front().name) + )) + }); + return; + } + + if (!memoryVariableInits.empty()) + _functionDefinition.body.statements = move(memoryVariableInits) + move(_functionDefinition.body.statements); + + _functionDefinition.returnVariables = _functionDefinition.returnVariables | ranges::views::filter( + not_fn(m_memoryOffsetTracker) + ) | ranges::to; } void StackToMemoryMover::operator()(Block& _block) { - using OptionalStatements = std::optional>; - auto rewriteAssignmentOrVariableDeclaration = [&]( + using OptionalStatements = optional>; + + auto rewriteAssignmentOrVariableDeclarationLeftHandSide = [this]( auto& _stmt, - auto const& _variables + auto& _lhsVars ) -> OptionalStatements { using StatementType = decay_t; + + auto debugData = _stmt.debugData; + if (_lhsVars.size() == 1) + { + if (optional offset = m_memoryOffsetTracker(_lhsVars.front().name)) + return generateMemoryStore( + m_context.dialect, + debugData, + *offset, + _stmt.value ? *move(_stmt.value) : Literal{debugData, LiteralKind::Number, "0"_yulstring, {}} + ); + else + return {}; + } + vector> rhsMemorySlots; if (_stmt.value) - visit(*_stmt.value); - bool leftHandSideNeedsMoving = util::contains_if(_variables, [&](auto const& var) { - return m_memoryOffsetTracker(var.name); - }); - if (!leftHandSideNeedsMoving) + { + FunctionCall const* functionCall = get_if(_stmt.value.get()); + yulAssert(functionCall, ""); + if (m_context.dialect.builtin(functionCall->functionName.name)) + rhsMemorySlots = vector>(_lhsVars.size(), nullopt); + else + rhsMemorySlots = + m_functionReturnVariables.at(functionCall->functionName.name) | + ranges::views::transform(m_memoryOffsetTracker) | + ranges::to>>; + } + else + rhsMemorySlots = vector>(_lhsVars.size(), nullopt); + + // Nothing to do, if the right-hand-side remains entirely on the stack and + // none of the variables in the left-hand-side are moved. + if ( + ranges::none_of(rhsMemorySlots, [](optional const& _slot) { return _slot.has_value(); }) && + !util::contains_if(_lhsVars, m_memoryOffsetTracker) + ) return {}; - if (_variables.size() == 1) - { - optional offset = m_memoryOffsetTracker(_variables.front().name); - yulAssert(offset, ""); - return generateMemoryStore( - m_context.dialect, - _stmt.debugData, - *offset, - _stmt.value ? *std::move(_stmt.value) : Literal{_stmt.debugData, LiteralKind::Number, "0"_yulstring, {}} - ); - } - - VariableDeclaration tempDecl{_stmt.debugData, {}, std::move(_stmt.value)}; vector memoryAssignments; vector variableAssignments; - for (auto& var: _variables) - { - YulString tempVarName = m_nameDispenser.newName(var.name); - tempDecl.variables.emplace_back(TypedName{var.debugData, tempVarName, {}}); + VariableDeclaration tempDecl{debugData, {}, move(_stmt.value)}; - if (optional offset = m_memoryOffsetTracker(var.name)) + yulAssert(rhsMemorySlots.size() == _lhsVars.size(), ""); + for (auto&& [lhsVar, rhsSlot]: ranges::views::zip(_lhsVars, rhsMemorySlots)) + { + unique_ptr rhs; + if (rhsSlot) + rhs = make_unique(generateMemoryLoad(m_context.dialect, debugData, *rhsSlot)); + else + { + YulString tempVarName = m_nameDispenser.newName(lhsVar.name); + tempDecl.variables.emplace_back(TypedName{lhsVar.debugData, tempVarName, {}}); + rhs = make_unique(Identifier{debugData, tempVarName}); + } + + if (optional offset = m_memoryOffsetTracker(lhsVar.name)) memoryAssignments += generateMemoryStore( m_context.dialect, _stmt.debugData, *offset, - Identifier{_stmt.debugData, tempVarName} + move(*rhs) ); else variableAssignments.emplace_back(StatementType{ - _stmt.debugData, - {move(var)}, - make_unique(Identifier{_stmt.debugData, tempVarName}) + debugData, + { move(lhsVar) }, + move(rhs) }); } - std::vector result; - result.emplace_back(std::move(tempDecl)); - std::reverse(memoryAssignments.begin(), memoryAssignments.end()); - result += std::move(memoryAssignments); - std::reverse(variableAssignments.begin(), variableAssignments.end()); - result += std::move(variableAssignments); + + vector result; + if (tempDecl.variables.empty()) + result.emplace_back(ExpressionStatement{debugData, *move(tempDecl.value)}); + else + result.emplace_back(move(tempDecl)); + reverse(memoryAssignments.begin(), memoryAssignments.end()); + result += move(memoryAssignments); + reverse(variableAssignments.begin(), variableAssignments.end()); + result += move(variableAssignments); return OptionalStatements{move(result)}; }; util::iterateReplacing( _block.statements, - [&](Statement& _statement) + [&](Statement& _statement) -> OptionalStatements { - return std::visit(util::GenericVisitor{ - [&](Assignment& _assignment) -> OptionalStatements - { - return rewriteAssignmentOrVariableDeclaration(_assignment, _assignment.variableNames); - }, - [&](VariableDeclaration& _varDecl) -> OptionalStatements - { - return rewriteAssignmentOrVariableDeclaration(_varDecl, _varDecl.variables); - }, - [&](auto& _stmt) -> OptionalStatements { (*this)(_stmt); return {}; } - }, _statement); + visit(_statement); + if (auto* assignment = get_if(&_statement)) + return rewriteAssignmentOrVariableDeclarationLeftHandSide(*assignment, assignment->variableNames); + else if (auto* varDecl = get_if(&_statement)) + return rewriteAssignmentOrVariableDeclarationLeftHandSide(*varDecl, varDecl->variables); + return {}; } ); } @@ -188,7 +304,7 @@ void StackToMemoryMover::operator()(Block& _block) void StackToMemoryMover::visit(Expression& _expression) { ASTModifier::visit(_expression); - if (Identifier* identifier = std::get_if(&_expression)) + if (Identifier* identifier = get_if(&_expression)) if (optional offset = m_memoryOffsetTracker(identifier->name)) _expression = generateMemoryLoad(m_context.dialect, identifier->debugData, *offset); } @@ -202,5 +318,15 @@ optional StackToMemoryMover::VariableMemoryOffsetTracker::operator()( return YulString{util::toCompactHexWithPrefix(m_reservedMemory + 32 * (m_numRequiredSlots - slot - 1))}; } else - return std::nullopt; + return nullopt; +} + +optional StackToMemoryMover::VariableMemoryOffsetTracker::operator()(TypedName const& _variable) const +{ + return (*this)(_variable.name); +} + +optional StackToMemoryMover::VariableMemoryOffsetTracker::operator()(Identifier const& _variable) const +{ + return (*this)(_variable.name); } diff --git a/libyul/optimiser/StackToMemoryMover.h b/libyul/optimiser/StackToMemoryMover.h index 2ec831946..cca9b0690 100644 --- a/libyul/optimiser/StackToMemoryMover.h +++ b/libyul/optimiser/StackToMemoryMover.h @@ -22,6 +22,9 @@ #include #include +#include + +#include #include namespace solidity::yul @@ -52,6 +55,16 @@ namespace solidity::yul * let c := _3 * let a := _1 * + * In case f has return parameters that are moved to memory, fewer variables are returned and the return values read + * from memory instead. Assume the third return parameter of f (i.e. c) has to be moved to memory: + * let a, b, c, d := f() + * then it is replaced by + * let _1, _2, _4 := f() + * mstore(, _4) + * mstore(, _2) + * let c := mload() + * let a := _1 + * * Assignments to single variables are replaced by mstore's: * If a is in the map, replace * a := expr @@ -70,8 +83,44 @@ namespace solidity::yul * * Replace all references to a variable ``a`` in the map by ``mload()``. * - * If a visited function has arguments or return parameters that are contained in the map, - * the entire function is skipped (no local variables in the function will be moved at all). + * Function arguments are moved at the beginning of a function body: + * If a1 is in the map, replace + * function f(a1, a2, ..., a17) + * { + * ... + * sstore(a1, a17) + * } + * by + * function f(a1, a2, ..., a17) + * { + * mstore(, a1) + * ... + * sstore(mload(, a17) + * } + * This relies on the code transform popping arguments that are no longer used, if they are on the stack top. + * + * Functions with only one return argument that has to be moved are encapsulated in a wrapper function as follows: + * Suppose b and r need to be moved in: + * function f(a, b) -> r + * { + * ...body of f... + * r := b + * ...body of f continued... + * } + * then replace by: + * function f(a, b) -> r + * { + * mstore(, b) + * mstore(, 0) + * f_1(a) + * r := mload() + * } + * function f_1(a) + * { + * ...body of f... + * mstore(, mload()) + * ...body of f continued... + * } * * Prerequisite: Disambiguator, ForLoopInitRewriter, FunctionHoister. */ @@ -98,6 +147,7 @@ public: void operator()(FunctionDefinition& _functionDefinition) override; void operator()(Block& _block) override; + using ASTModifier::visit; void visit(Expression& _expression) override; private: class VariableMemoryOffsetTracker @@ -113,20 +163,36 @@ private: /// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal /// or std::nullopt if the variable should not be moved. std::optional operator()(YulString _variable) const; + /// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal + /// or std::nullopt if the variable should not be moved. + std::optional operator()(TypedName const& _variable) const; + /// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal + /// or std::nullopt if the variable should not be moved. + std::optional operator()(Identifier const& _variable) const; + private: u256 m_reservedMemory; std::map const& m_memorySlots; uint64_t m_numRequiredSlots = 0; }; + struct FunctionMoveInfo + { + std::vector> returnVariableSlots; + }; StackToMemoryMover( OptimiserStepContext& _context, - VariableMemoryOffsetTracker const& _memoryOffsetTracker + VariableMemoryOffsetTracker const& _memoryOffsetTracker, + std::map> _functionReturnVariables ); OptimiserStepContext& m_context; VariableMemoryOffsetTracker const& m_memoryOffsetTracker; NameDispenser& m_nameDispenser; + /// Map from function names to the return variables of the function with that name. + std::map> m_functionReturnVariables; + /// List of functions generated while running this step that are to be appended to the code in the end. + std::list m_newFunctionDefinitions; }; } diff --git a/test/libyul/YulOptimizerTestCommon.cpp b/test/libyul/YulOptimizerTestCommon.cpp index c2e836e45..00aba3c79 100644 --- a/test/libyul/YulOptimizerTestCommon.cpp +++ b/test/libyul/YulOptimizerTestCommon.cpp @@ -345,6 +345,10 @@ YulOptimizerTestCommon::YulOptimizerTestCommon( { YulString originalFunctionName = m_currentFunction; m_currentFunction = _function.name; + for (TypedName const& _argument: _function.parameters) + visitVariableName(_argument.name); + for (TypedName const& _argument: _function.returnVariables) + visitVariableName(_argument.name); ASTWalker::operator()(_function); m_currentFunction = originalFunctionName; } diff --git a/test/libyul/evmCodeTransform/stackReuse/function_argument_reuse_without_retparams.yul b/test/libyul/evmCodeTransform/stackReuse/function_argument_reuse_without_retparams.yul new file mode 100644 index 000000000..d1f6b5bdc --- /dev/null +++ b/test/libyul/evmCodeTransform/stackReuse/function_argument_reuse_without_retparams.yul @@ -0,0 +1,29 @@ +{ + function f(x, y) { + mstore(0x80, x) + if calldataload(0) { sstore(y, y) } + } +} +// ==== +// stackOptimization: true +// ---- +// PUSH1 0x17 +// JUMP +// JUMPDEST +// DUP1 +// PUSH1 0x80 +// MSTORE +// POP +// PUSH1 0x0 +// CALLDATALOAD +// ISZERO +// PUSH1 0x13 +// JUMPI +// DUP1 +// DUP2 +// SSTORE +// JUMPDEST +// POP +// JUMPDEST +// JUMP +// JUMPDEST diff --git a/test/libyul/evmCodeTransform/stackReuse/reuse_too_deep_slot.yul b/test/libyul/evmCodeTransform/stackReuse/reuse_too_deep_slot.yul new file mode 100644 index 000000000..badcc0969 --- /dev/null +++ b/test/libyul/evmCodeTransform/stackReuse/reuse_too_deep_slot.yul @@ -0,0 +1,110 @@ +{ + let x := 7 + + let y1, y2, y3, y4, y5, y6, y7, y8, y9, y10, y11, y12, y13, y14, y15 := verbatim_0i_15o("\x60\x42") // the verbatim will show up as PUSH1 42 + + // last use of x - the slot of x will be marked as unused, but not popped, since it is not at the stack top + sstore(0,x) + + // If the slot of x is blindly reused, this will fail. + let z1, z2 := verbatim_0i_2o("\x60\x43") // will show up as PUSH1 43 + + // prevent the z's from being popped immediately after their declaration above. + mstore(1, z1) + mstore(1, z2) + + // use all y's to prevent them from being popped immediately after their declaration above + sstore(1, y1) + sstore(1, y2) + sstore(1, y3) + sstore(1, y4) + sstore(1, y5) + sstore(1, y6) + sstore(1, y7) + sstore(1, y8) + sstore(1, y9) + sstore(1, y10) + sstore(1, y11) + sstore(1, y12) + sstore(1, y13) + sstore(1, y14) + sstore(1, y15) +} +// ==== +// stackOptimization: true +// ---- +// PUSH1 0x7 +// PUSH1 0x42 +// DUP16 +// PUSH1 0x0 +// SSTORE +// PUSH1 0x43 +// DUP2 +// PUSH1 0x1 +// MSTORE +// DUP1 +// PUSH1 0x1 +// MSTORE +// POP +// POP +// DUP15 +// PUSH1 0x1 +// SSTORE +// DUP14 +// PUSH1 0x1 +// SSTORE +// DUP13 +// PUSH1 0x1 +// SSTORE +// DUP12 +// PUSH1 0x1 +// SSTORE +// DUP11 +// PUSH1 0x1 +// SSTORE +// DUP10 +// PUSH1 0x1 +// SSTORE +// DUP9 +// PUSH1 0x1 +// SSTORE +// DUP8 +// PUSH1 0x1 +// SSTORE +// DUP7 +// PUSH1 0x1 +// SSTORE +// DUP6 +// PUSH1 0x1 +// SSTORE +// DUP5 +// PUSH1 0x1 +// SSTORE +// DUP4 +// PUSH1 0x1 +// SSTORE +// DUP3 +// PUSH1 0x1 +// SSTORE +// DUP2 +// PUSH1 0x1 +// SSTORE +// DUP1 +// PUSH1 0x1 +// SSTORE +// POP +// POP +// POP +// POP +// POP +// POP +// POP +// POP +// POP +// POP +// POP +// POP +// POP +// POP +// POP +// POP diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul index 6ce2e34b4..0e5aa9e13 100644 --- a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul @@ -15,6 +15,9 @@ // mstore(0x00, 0) // sstore(0, mload(0x00)) // function h($hx) -> y -// { y := $hx } +// { +// mstore(0x20, $hx) +// y := mload(0x20) +// } // sstore(1, h(32)) // } diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/multi_variable_declaration_without_value.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/multi_variable_declaration_without_value.yul new file mode 100644 index 000000000..5650b580d --- /dev/null +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/multi_variable_declaration_without_value.yul @@ -0,0 +1,21 @@ +{ + mstore(0x40, memoryguard(0x60)) + { + let x, y + } + { + let z, $w + } +} +// ---- +// step: fakeStackLimitEvader +// +// { +// mstore(0x40, memoryguard(0x80)) +// { let x, y } +// { +// let z_1, $w_2 +// mstore(0x60, $w_2) +// let z := z_1 +// } +// } diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/return_leave.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/return_leave.yul new file mode 100644 index 000000000..3b43bd534 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/return_leave.yul @@ -0,0 +1,48 @@ +{ + { + mstore(0x40, memoryguard(0x80)) + let a1, a2 := f() + sstore(a1, a2) + } + function g(x) -> a, b { a := x b := 2 } + function f() -> $b1, $b2 { + if calldataload(0) { + $b1, $b2 := g(1) + leave + } + $b1, $b2 := g(2) + } + +} +// ---- +// step: fakeStackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0xc0)) +// f() +// let a2 := mload(0x80) +// let a1 := mload(0xa0) +// sstore(a1, a2) +// } +// function g(x) -> a, b +// { +// a := x +// b := 2 +// } +// function f() +// { +// mstore(0xa0, 0) +// mstore(0x80, 0) +// if calldataload(0) +// { +// let $b1_1, $b2_2 := g(1) +// mstore(0x80, $b2_2) +// mstore(0xa0, $b1_1) +// leave +// } +// let $b1_3, $b2_4 := g(2) +// mstore(0x80, $b2_4) +// mstore(0xa0, $b1_3) +// } +// } diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/return_one.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/return_one.yul new file mode 100644 index 000000000..50a9d6486 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/return_one.yul @@ -0,0 +1,38 @@ +{ + { + mstore(0x40, memoryguard(0x80)) + sstore(0, f()) + } + function f() -> $b1 { + if calldataload(0) { + $b1 := 0 + leave + } + $b1 := 1 + } + +} +// ---- +// step: fakeStackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0xa0)) +// sstore(0, f()) +// } +// function f() -> $b1 +// { +// mstore(0x80, 0) +// f_1() +// $b1 := mload(0x80) +// } +// function f_1() +// { +// if calldataload(0) +// { +// mstore(0x80, 0) +// leave +// } +// mstore(0x80, 1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/return_one_with_args.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/return_one_with_args.yul new file mode 100644 index 000000000..706a46c62 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/return_one_with_args.yul @@ -0,0 +1,38 @@ +{ + { + mstore(0x40, memoryguard(0x80)) + sstore(0, f(1,2,3)) + } + function f(a, b, c) -> $b1 { + if calldataload(add(sub(a,b),c)) { + $b1 := 0 + leave + } + $b1 := 1 + } + +} +// ---- +// step: fakeStackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0xa0)) +// sstore(0, f(1, 2, 3)) +// } +// function f(a_2, b_3, c_4) -> $b1 +// { +// mstore(0x80, 0) +// f_1(a_2, b_3, c_4) +// $b1 := mload(0x80) +// } +// function f_1(a, b, c) +// { +// if calldataload(add(sub(a, b), c)) +// { +// mstore(0x80, 0) +// leave +// } +// mstore(0x80, 1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/same_variable_in_lhs_and_rhs.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/same_variable_in_lhs_and_rhs.yul new file mode 100644 index 000000000..e6a9379df --- /dev/null +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/same_variable_in_lhs_and_rhs.yul @@ -0,0 +1,17 @@ +{ + function f(x) -> y { y := x } + mstore(0x40, memoryguard(0)) + + let $z := 42 + $z := f($z) +} +// ---- +// step: fakeStackLimitEvader +// +// { +// function f(x) -> y +// { y := x } +// mstore(0x40, memoryguard(0x20)) +// mstore(0x00, 42) +// mstore(0x00, f(mload(0x00))) +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul index 7bc4d2e80..27f070515 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul @@ -50,6 +50,7 @@ // } // function f(a1) -> v // { +// mstore(0x80, a1) // let a2 := calldataload(mul(2, 4)) // let a3 := calldataload(mul(3, 4)) // let a4 := calldataload(mul(4, 4)) @@ -66,7 +67,7 @@ // let a15 := calldataload(mul(15, 4)) // let a16 := calldataload(mul(16, 4)) // let a17 := calldataload(mul(17, 4)) -// sstore(0, a1) +// sstore(0, mload(0x80)) // sstore(mul(17, 4), a17) // sstore(mul(16, 4), a16) // sstore(mul(15, 4), a15) @@ -83,6 +84,6 @@ // sstore(mul(4, 4), a4) // sstore(mul(3, 4), a3) // sstore(mul(2, 4), a2) -// sstore(mul(1, 4), a1) +// sstore(mul(1, 4), mload(0x80)) // } // } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul index ce12229b6..9f5001c87 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul @@ -1,13 +1,12 @@ { { mstore(0x40, memoryguard(128)) - sstore(g(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16), f()) + sstore(g(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17), f()) } - function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16) -> v { - // Should be, but cannot yet be escalated. - v := b16 + function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17) -> v { + v := add(b16, b17) } - function f() -> v{ + function f() -> v { let a1 := calldataload(mul(1,4)) let a2 := calldataload(mul(2,4)) let a3 := calldataload(mul(3,4)) @@ -52,10 +51,10 @@ // { // { // mstore(0x40, memoryguard(0xa0)) -// sstore(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), f()) +// sstore(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), f()) // } -// function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16) -> v -// { v := b16 } +// function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17) -> v +// { v := add(b16, b17) } // function f() -> v_1 // { // mstore(0x80, calldataload(mul(1, 4))) diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_14.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_14.yul new file mode 100644 index 000000000..74c74e36f --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_14.yul @@ -0,0 +1,55 @@ +{ + { + mstore(0x40, memoryguard(128)) + sstore(g(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29), 0) + } + function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29) -> v { + sstore(0, b14) + sstore(1, b15) + sstore(2, b16) + sstore(3, b17) + sstore(4, b18) + sstore(5, b19) + sstore(6, b29) + v := add(b1,b29) + } + +} +// ---- +// step: stackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0x02c0)) +// sstore(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29), 0) +// } +// function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29) -> v +// { +// mstore(0x02a0, b1) +// mstore(0x0280, b2) +// mstore(0x0260, b3) +// mstore(0x0240, b4) +// mstore(0x0220, b5) +// mstore(0x0200, b6) +// mstore(0x01e0, b7) +// mstore(0x01c0, b8) +// mstore(0x01a0, b9) +// mstore(0x0180, b10) +// mstore(0x0160, b11) +// mstore(0x0140, b12) +// mstore(0x0120, b13) +// mstore(0x0100, b14) +// mstore(0xc0, b17) +// mstore(0xa0, b18) +// mstore(0x80, b19) +// mstore(0xe0, b29) +// sstore(0, mload(0x0100)) +// sstore(1, b15) +// sstore(2, b16) +// sstore(3, mload(0xc0)) +// sstore(4, mload(0xa0)) +// sstore(5, mload(0x80)) +// sstore(6, mload(0xe0)) +// v := add(mload(0x02a0), mload(0xe0)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_15.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_15.yul new file mode 100644 index 000000000..8df48f376 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_15.yul @@ -0,0 +1,61 @@ +{ + { + mstore(0x40, memoryguard(128)) + sstore(g(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29, 30), 0) + } + function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30) -> v { + sstore(0, b14) + sstore(1, b15) + sstore(2, b16) + sstore(3, b17) + sstore(4, b18) + sstore(5, b19) + sstore(6, b29) + sstore(7, b30) + v := b30 + sstore(b1, b30) + } + +} +// ---- +// step: stackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0x0300)) +// sstore(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30), 0) +// } +// function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30) -> v +// { +// mstore(0x02e0, b1) +// mstore(0x02c0, b2) +// mstore(0x02a0, b3) +// mstore(0x0280, b4) +// mstore(0x0260, b5) +// mstore(0x0240, b6) +// mstore(0x0220, b7) +// mstore(0x0200, b8) +// mstore(0x01e0, b9) +// mstore(0x01c0, b10) +// mstore(0x01a0, b11) +// mstore(0x0180, b12) +// mstore(0x0160, b13) +// mstore(0x0140, b14) +// mstore(0x0120, b15) +// mstore(0xc0, b17) +// mstore(0xa0, b18) +// mstore(0x80, b19) +// mstore(0xe0, b29) +// mstore(0x0100, b30) +// sstore(0, mload(0x0140)) +// sstore(1, mload(0x0120)) +// sstore(2, b16) +// sstore(3, mload(0xc0)) +// sstore(4, mload(0xa0)) +// sstore(5, mload(0x80)) +// sstore(6, mload(0xe0)) +// sstore(7, mload(0x0100)) +// v := mload(0x0100) +// sstore(mload(0x02e0), mload(0x0100)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_16.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_16.yul new file mode 100644 index 000000000..42e1aabc2 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_args_16.yul @@ -0,0 +1,63 @@ +{ + { + mstore(0x40, memoryguard(128)) + sstore(g(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31), 0) + } + function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31) -> v { + sstore(0, b14) + sstore(1, b15) + sstore(2, b16) + sstore(3, b17) + sstore(4, b18) + sstore(5, b19) + sstore(6, b29) + sstore(7, b30) + sstore(8, b31) + v := add(b1,b31) + } + +} +// ---- +// step: stackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0x0340)) +// sstore(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31), 0) +// } +// function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31) -> v +// { +// mstore(0x0320, b1) +// mstore(0x0300, b2) +// mstore(0x02e0, b3) +// mstore(0x02c0, b4) +// mstore(0x02a0, b5) +// mstore(0x0280, b6) +// mstore(0x0260, b7) +// mstore(0x0240, b8) +// mstore(0x0220, b9) +// mstore(0x0200, b10) +// mstore(0x01e0, b11) +// mstore(0x01c0, b12) +// mstore(0x01a0, b13) +// mstore(0x0180, b14) +// mstore(0x0160, b15) +// mstore(0x0140, b16) +// mstore(0xc0, b17) +// mstore(0xa0, b18) +// mstore(0x80, b19) +// mstore(0xe0, b29) +// mstore(0x0120, b30) +// mstore(0x0100, b31) +// sstore(0, mload(0x0180)) +// sstore(1, mload(0x0160)) +// sstore(2, mload(0x0140)) +// sstore(3, mload(0xc0)) +// sstore(4, mload(0xa0)) +// sstore(5, mload(0x80)) +// sstore(6, mload(0xe0)) +// sstore(7, mload(0x0120)) +// sstore(8, mload(0x0100)) +// v := add(mload(0x0320), mload(0x0100)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_15.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_15.yul new file mode 100644 index 000000000..b9d1a5571 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_15.yul @@ -0,0 +1,48 @@ +{ + { + mstore(0x40, memoryguard(128)) + let a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30) + sstore(0, 1) + a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30) + } + function g(b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30) -> b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 { + b1 := 1 + b2 := 2 + b15 := 15 + sstore(b16, b30) + } + +} +// ---- +// step: stackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0x0260)) +// let a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30) +// sstore(0, 1) +// a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30) +// } +// function g(b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30) -> b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 +// { +// mstore(0x0240, b16) +// mstore(0x0220, b17) +// mstore(0x0200, b18) +// mstore(0x01e0, b19) +// mstore(0x01c0, b20) +// mstore(0x01a0, b21) +// mstore(0x0180, b22) +// mstore(0x0160, b23) +// mstore(0x0140, b24) +// mstore(0x0120, b25) +// mstore(0x0100, b26) +// mstore(0xe0, b27) +// mstore(0xc0, b28) +// mstore(0xa0, b29) +// mstore(0x80, b30) +// b1 := 1 +// b2 := 2 +// b15 := 15 +// sstore(mload(0x0240), mload(0x80)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_16.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_16.yul new file mode 100644 index 000000000..40553083f --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/too_many_returns_16.yul @@ -0,0 +1,49 @@ +{ + { + mstore(0x40, memoryguard(128)) + let a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31) + sstore(0, 1) + a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31) + } + function g(b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31) -> b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 { + b1 := 1 + b2 := 2 + b15 := 15 + sstore(b16, b31) + } + +} +// ---- +// step: stackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0x0280)) +// let a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31) +// sstore(0, 1) +// a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 := g(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31) +// } +// function g(b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31) -> b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15 +// { +// mstore(0x0260, b16) +// mstore(0x0240, b17) +// mstore(0x0220, b18) +// mstore(0x0200, b19) +// mstore(0x01e0, b20) +// mstore(0x01c0, b21) +// mstore(0x01a0, b22) +// mstore(0x0180, b23) +// mstore(0x0160, b24) +// mstore(0x0140, b25) +// mstore(0x0120, b26) +// mstore(0x0100, b27) +// mstore(0xe0, b28) +// mstore(0xc0, b29) +// mstore(0xa0, b30) +// mstore(0x80, b31) +// b1 := 1 +// b2 := 2 +// b15 := 15 +// sstore(mload(0x0260), mload(0x80)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments.yul new file mode 100644 index 000000000..cb4d69269 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments.yul @@ -0,0 +1,55 @@ +{ + { + mstore(0x40, memoryguard(128)) + let a_1 := 1 + let a_2 := 2 + let a_3 := 3 + let a_4 := 4 + let a_5 := 5 + let a_6 := 6 + let a_7 := 7 + let a_8 := 8 + let a_9 := 9 + let a_10 := 10 + let a_11 := 11 + let a_12 := 12 + let a_13 := 13 + let a_14 := 14 + let a_15 := 15 + let a_16 := 16 + let a_17 := 17 + let a_18 := 18 + let a_19 := 19 + let a_20 := 20 + verbatim_20i_0o("test", a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8, a_9, a_10, a_11, a_12, a_13, a_14, a_15, a_16, a_17, a_18, a_19, a_20) + } +} +// ---- +// step: stackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0x0200)) +// mstore(0x80, 1) +// mstore(0xa0, 2) +// mstore(0xc0, 3) +// mstore(0xe0, 4) +// mstore(0x0100, 5) +// mstore(0x0120, 6) +// mstore(0x0140, 7) +// mstore(0x0160, 8) +// mstore(0x0180, 9) +// mstore(0x01c0, 10) +// mstore(0x01a0, 11) +// mstore(0x01e0, 12) +// let a_13 := 13 +// let a_14 := 14 +// let a_15 := 15 +// let a_16 := 16 +// let a_17 := 17 +// let a_18 := 18 +// let a_19 := 19 +// let a_20 := 20 +// verbatim_20i_0o("test", mload(0x80), mload(0xa0), mload(0xc0), mload(0xe0), mload(0x0100), mload(0x0120), mload(0x0140), mload(0x0160), mload(0x0180), mload(0x01c0), mload(0x01a0), mload(0x01e0), a_13, a_14, a_15, a_16, a_17, a_18, a_19, a_20) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments_and_returns.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments_and_returns.yul new file mode 100644 index 000000000..de023e919 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_arguments_and_returns.yul @@ -0,0 +1,116 @@ +{ + { + mstore(0x40, memoryguard(128)) + let a_1 := 1 + let a_2 := 2 + let a_3 := 3 + let a_4 := 4 + let a_5 := 5 + let a_6 := 6 + let a_7 := 7 + let a_8 := 8 + let a_9 := 9 + let a_10 := 10 + let a_11 := 11 + let a_12 := 12 + let a_13 := 13 + let a_14 := 14 + let a_15 := 15 + let a_16 := 16 + let a_17 := 17 + let a_18 := 18 + let a_19 := 19 + let a_20 := 20 + let b_1, b_2, b_3, b_4, b_5, b_6, b_7, b_8, b_9, b_10, b_11, b_12, b_13, b_14, b_15, b_16, b_17, b_18, b_19, b_20 := verbatim_20i_20o("test", a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8, a_9, a_10, a_11, a_12, a_13, a_14, a_15, a_16, a_17, a_18, a_19, a_20) + sstore(1, b_1) + sstore(2, b_2) + sstore(3, b_3) + sstore(4, b_4) + sstore(5, b_5) + sstore(6, b_6) + sstore(7, b_7) + sstore(8, b_8) + sstore(9, b_9) + sstore(10, b_10) + sstore(11, b_11) + sstore(12, b_12) + sstore(13, b_13) + sstore(14, b_14) + sstore(15, b_15) + sstore(16, b_16) + sstore(17, b_17) + sstore(18, b_18) + sstore(19, b_19) + sstore(20, b_20) + + } +} +// ---- +// step: stackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0x0280)) +// mstore(0x80, 1) +// mstore(0xa0, 2) +// mstore(0xc0, 3) +// mstore(0xe0, 4) +// mstore(0x0100, 5) +// mstore(0x0120, 6) +// mstore(0x0140, 7) +// mstore(0x0160, 8) +// mstore(0x0180, 9) +// mstore(0x0240, 10) +// mstore(0x0220, 11) +// mstore(0x0260, 12) +// let a_13 := 13 +// let a_14 := 14 +// let a_15 := 15 +// let a_16 := 16 +// let a_17 := 17 +// let a_18 := 18 +// let a_19 := 19 +// let a_20 := 20 +// let b_1_1, b_2_2, b_3_3, b_4_4, b_5_5, b_6_6, b_7_7, b_8_8, b_9_9, b_10_10, b_11_11, b_12_12, b_13_13, b_14_14, b_15_15, b_16_16, b_17_17, b_18_18, b_19_19, b_20_20 := verbatim_20i_20o("test", mload(0x80), mload(0xa0), mload(0xc0), mload(0xe0), mload(0x0100), mload(0x0120), mload(0x0140), mload(0x0160), mload(0x0180), mload(0x0240), mload(0x0220), mload(0x0260), a_13, a_14, a_15, a_16, a_17, a_18, a_19, a_20) +// mstore(0x01a0, b_4_4) +// mstore(0x01c0, b_3_3) +// mstore(0x01e0, b_2_2) +// mstore(0x0200, b_1_1) +// let b_20 := b_20_20 +// let b_19 := b_19_19 +// let b_18 := b_18_18 +// let b_17 := b_17_17 +// let b_16 := b_16_16 +// let b_15 := b_15_15 +// let b_14 := b_14_14 +// let b_13 := b_13_13 +// let b_12 := b_12_12 +// let b_11 := b_11_11 +// let b_10 := b_10_10 +// let b_9 := b_9_9 +// let b_8 := b_8_8 +// let b_7 := b_7_7 +// let b_6 := b_6_6 +// let b_5 := b_5_5 +// sstore(1, mload(0x0200)) +// sstore(2, mload(0x01e0)) +// sstore(3, mload(0x01c0)) +// sstore(4, mload(0x01a0)) +// sstore(5, b_5) +// sstore(6, b_6) +// sstore(7, b_7) +// sstore(8, b_8) +// sstore(9, b_9) +// sstore(10, b_10) +// sstore(11, b_11) +// sstore(12, b_12) +// sstore(13, b_13) +// sstore(14, b_14) +// sstore(15, b_15) +// sstore(16, b_16) +// sstore(17, b_17) +// sstore(18, b_18) +// sstore(19, b_19) +// sstore(20, b_20) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_returns.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_returns.yul new file mode 100644 index 000000000..47db17e21 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/verbatim_many_returns.yul @@ -0,0 +1,53 @@ +{ + { + mstore(0x40, memoryguard(128)) + let a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a13, a14, a15, a16, a17, a18 := verbatim_0i_16o("test") + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a13, a14, a15, a16, a17, a18 := verbatim_0i_16o("test") + sstore(a1, 10) + sstore(a18, 20) + } +} +// ---- +// step: stackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0xa0)) +// let a1_1, a2_2, a3_3, a4_4, a5_5, a6_6, a7_7, a8_8, a9_9, a10_10, a13_11, a14_12, a15_13, a16_14, a17_15, a18_16 := verbatim_0i_16o("test") +// mstore(0x80, a1_1) +// let a18 := a18_16 +// let a17 := a17_15 +// let a16 := a16_14 +// let a15 := a15_13 +// let a14 := a14_12 +// let a13 := a13_11 +// let a10 := a10_10 +// let a9 := a9_9 +// let a8 := a8_8 +// let a7 := a7_7 +// let a6 := a6_6 +// let a5 := a5_5 +// let a4 := a4_4 +// let a3 := a3_3 +// let a2 := a2_2 +// let a1_17, a2_18, a3_19, a4_20, a5_21, a6_22, a7_23, a8_24, a9_25, a10_26, a13_27, a14_28, a15_29, a16_30, a17_31, a18_32 := verbatim_0i_16o("test") +// mstore(0x80, a1_17) +// a18 := a18_32 +// a17 := a17_31 +// a16 := a16_30 +// a15 := a15_29 +// a14 := a14_28 +// a13 := a13_27 +// a10 := a10_26 +// a9 := a9_25 +// a8 := a8_24 +// a7 := a7_23 +// a6 := a6_22 +// a5 := a5_21 +// a4 := a4_20 +// a3 := a3_19 +// a2 := a2_18 +// sstore(mload(0x80), 10) +// sstore(a18, 20) +// } +// }