/* 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 #include #include #include #include #include #include #include #include #include using namespace std; using namespace solidity; using namespace solidity::yul; namespace { vector generateMemoryStore( Dialect const& _dialect, langutil::SourceLocation const& _loc, YulString _mpos, Expression _value ) { BuiltinFunction const* memoryStoreFunction = _dialect.memoryStoreFunction(_dialect.defaultType); yulAssert(memoryStoreFunction, ""); vector result; result.emplace_back(ExpressionStatement{_loc, FunctionCall{ _loc, Identifier{_loc, memoryStoreFunction->name}, { Literal{_loc, LiteralKind::Number, _mpos, {}}, std::move(_value) } }}); return result; } FunctionCall generateMemoryLoad(Dialect const& _dialect, langutil::SourceLocation const& _loc, YulString _mpos) { BuiltinFunction const* memoryLoadFunction = _dialect.memoryLoadFunction(_dialect.defaultType); yulAssert(memoryLoadFunction, ""); return FunctionCall{ _loc, Identifier{_loc, memoryLoadFunction->name}, { Literal{ _loc, LiteralKind::Number, _mpos, {} } } }; } } void StackToMemoryMover::run( OptimiserStepContext& _context, u256 _reservedMemory, map const& _memorySlots, uint64_t _numRequiredSlots, Block& _block ) { if (!_numRequiredSlots) { yulAssert(_memorySlots.empty(), ""); return; } auto const* evmDialect = dynamic_cast(&_context.dialect); yulAssert( evmDialect && evmDialect->providesObjectAccess(), "StackToMemoryMover can only be run on objects using the EVMDialect with object access." ); VariableMemoryOffsetTracker memoryOffsetTracker(_reservedMemory, _memorySlots, _numRequiredSlots); ForLoopConditionIntoBody::run(_context, _block); ExpressionSplitter::run(_context, _block); FunctionCallRewriter functionCallRewriter(_context, memoryOffsetTracker, FunctionDefinitionCollector::run(_block)); functionCallRewriter(_block); VariableDeclarationAndAssignmentMover declarationAndAssignmentMover(_context, memoryOffsetTracker); declarationAndAssignmentMover(_block); ExpressionJoiner::runUntilStabilized(_context, _block); ForLoopConditionOutOfBody::run(_context, _block); FunctionDefinitionRewriter functionDefinitionRewriter(_context, memoryOffsetTracker); functionDefinitionRewriter(_block); IdentifierMover identifierMover(_context, memoryOffsetTracker); identifierMover(_block); } void StackToMemoryMover::VariableDeclarationAndAssignmentMover::operator()(Block& _block) { using OptionalStatements = std::optional>; auto containsVariableNeedingEscalation = [&](auto const& _variables) { return util::contains_if(_variables, [&](auto const& var) { return m_memoryOffsetTracker(var.name); }); }; auto rewriteAssignmentOrVariableDeclaration = [&]( langutil::SourceLocation const& _loc, auto const& _variables, std::unique_ptr _value ) -> std::vector { VariableDeclaration tempDecl{_loc, {}, std::move(_value)}; vector memoryAssignments; vector variableAssignments; for (auto& var: _variables) { YulString tempVarName = m_context.dispenser.newName(var.name); tempDecl.variables.emplace_back(TypedName{var.location, tempVarName, {}}); if (optional offset = m_memoryOffsetTracker(var.name)) memoryAssignments += generateMemoryStore( m_context.dialect, _loc, *offset, Identifier{_loc, tempVarName} ); else if constexpr (std::is_same_v, Identifier>) variableAssignments.emplace_back(Assignment{ _loc, { Identifier{var.location, var.name} }, make_unique(Identifier{_loc, tempVarName}) }); else variableAssignments.emplace_back(VariableDeclaration{ _loc, {std::move(var)}, make_unique(Identifier{_loc, tempVarName}) }); } 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); return result; }; util::iterateReplacing( _block.statements, [&](Statement& _statement) { auto defaultVisit = [&]() { ASTModifier::visit(_statement); return OptionalStatements{}; }; return std::visit(util::GenericVisitor{ [&](Assignment& _assignment) -> OptionalStatements { if (!containsVariableNeedingEscalation(_assignment.variableNames)) return defaultVisit(); visit(*_assignment.value); return {rewriteAssignmentOrVariableDeclaration( _assignment.location, _assignment.variableNames, std::move(_assignment.value) )}; }, [&](VariableDeclaration& _varDecl) -> OptionalStatements { if (!containsVariableNeedingEscalation(_varDecl.variables)) return defaultVisit(); if (_varDecl.value) visit(*_varDecl.value); return {rewriteAssignmentOrVariableDeclaration( _varDecl.location, _varDecl.variables, std::move(_varDecl.value) )}; }, [&](auto&) { return defaultVisit(); } }, _statement); }); } void StackToMemoryMover::IdentifierMover::visit(Expression& _expression) { if (Identifier* identifier = std::get_if(&_expression)) if (optional offset = m_memoryOffsetTracker(identifier->name)) { _expression = generateMemoryLoad(m_context.dialect, identifier->location, *offset); return; } ASTModifier::visit(_expression); } optional StackToMemoryMover::VariableMemoryOffsetTracker::operator()(YulString _variable) const { if (m_memorySlots.count(_variable)) { uint64_t slot = m_memorySlots.at(_variable); yulAssert(slot < m_numRequiredSlots, ""); return YulString{util::toCompactHexWithPrefix(m_reservedMemory + 32 * (m_numRequiredSlots - slot - 1))}; } else return std::nullopt; } void StackToMemoryMover::FunctionDefinitionRewriter::operator()(FunctionDefinition& _functionDefinition) { vector returnVariableMemoryInits; TypedNameList parameters; for (TypedName& parameter: _functionDefinition.parameters) if (!m_memoryOffsetTracker(parameter.name)) parameters.emplace_back(std::move(parameter)); _functionDefinition.parameters = std::move(parameters); TypedNameList returnVariables; for (TypedName& returnVariable: _functionDefinition.returnVariables) if (auto slot = m_memoryOffsetTracker(returnVariable.name)) returnVariableMemoryInits += generateMemoryStore( m_context.dialect, returnVariable.location, *slot, Literal{returnVariable.location, LiteralKind::Number, "0"_yulstring, {}} ); else returnVariables.emplace_back(std::move(returnVariable)); _functionDefinition.returnVariables = std::move(returnVariables); if (!returnVariableMemoryInits.empty()) { returnVariableMemoryInits += std::move(_functionDefinition.body.statements); _functionDefinition.body.statements = std::move(returnVariableMemoryInits); } } StackToMemoryMover::FunctionCallRewriter::FunctionCallRewriter( OptimiserStepContext& _context, VariableMemoryOffsetTracker const& _memoryOffsetTracker, std::map const& _functionDefinitions ): m_context(_context), m_memoryOffsetTracker(_memoryOffsetTracker) { for (auto [functionName, functionDefinition]: _functionDefinitions) { FunctionArguments& functionArguments = m_functionArguments[functionName]; for (TypedName const& parameter: functionDefinition->parameters) functionArguments.parameters.push_back(parameter.name); for (TypedName const& returnVariable: functionDefinition->returnVariables) functionArguments.returnVariables.push_back(returnVariable.name); } } void StackToMemoryMover::FunctionCallRewriter::operator()(FunctionCall& _functionCall) { if (!m_functionArguments.count(_functionCall.functionName.name)) { // If it is not in the list of user-defined functions, it should be a builtin. yulAssert(m_context.dialect.builtin(_functionCall.functionName.name), ""); ASTModifier::operator()(_functionCall); return; } yulAssert(m_slotsForCurrentReturns.empty(), ""); FunctionArguments const& functionArguments = m_functionArguments.at(_functionCall.functionName.name); yulAssert(_functionCall.arguments.size() == functionArguments.parameters.size(), ""); vector arguments; for (size_t i = 0; i < functionArguments.parameters.size(); ++i) { yulAssert( holds_alternative(_functionCall.arguments[i]) || holds_alternative(_functionCall.arguments[i]), "Expected fully split expressions." ); if (auto slot = m_memoryOffsetTracker(functionArguments.parameters[i])) { auto loc = locationOf(_functionCall.arguments[i]); m_statementsToPrefix += generateMemoryStore( m_context.dialect, loc, *slot, std::move(_functionCall.arguments[i]) ); } else arguments.emplace_back(_functionCall.arguments[i]); } for (YulString returnVariable: functionArguments.returnVariables) if (auto slot = m_memoryOffsetTracker(returnVariable)) m_slotsForCurrentReturns.emplace_back(*slot); else m_slotsForCurrentReturns.emplace_back(std::nullopt); _functionCall.arguments = std::move(arguments); } void StackToMemoryMover::FunctionCallRewriter::operator()(Block& _block) { util::iterateReplacing(_block.statements, [&](Statement& _statement) -> std::optional> { yulAssert(m_statementsToPrefix.empty(), ""); vector statementsToSuffix; auto handleVariableDeclarationOrAssignment = [&](auto& _stmt, auto& _variableList) { visit(*_stmt.value); if (!m_slotsForCurrentReturns.empty()) { std::decay_t variableList; yulAssert(_variableList.size() == m_slotsForCurrentReturns.size(), ""); for (size_t i = 0; i < m_slotsForCurrentReturns.size(); ++i) if (auto slot = m_slotsForCurrentReturns[i]) { auto loc = _variableList[i].location; statementsToSuffix.emplace_back(std::decay_t{ loc, {std::move(_variableList[i])}, std::make_unique(generateMemoryLoad( m_context.dialect, loc, *slot )) }); } else variableList.emplace_back(move(_variableList[i])); if (variableList.empty()) _statement = ExpressionStatement{_stmt.location, std::move(*_stmt.value)}; else _variableList = move(variableList); m_slotsForCurrentReturns.clear(); } }; if (Assignment* _assignment = std::get_if(&_statement)) handleVariableDeclarationOrAssignment(*_assignment, _assignment->variableNames); else if (VariableDeclaration* _variableDeclaration = std::get_if(&_statement)) { if (_variableDeclaration->value) handleVariableDeclarationOrAssignment(*_variableDeclaration, _variableDeclaration->variables); } else visit(_statement); if (m_statementsToPrefix.empty() && statementsToSuffix.empty()) return {}; vector result; result.swap(m_statementsToPrefix); result.emplace_back(std::move(_statement)); result += std::move(statementsToSuffix); statementsToSuffix.clear(); return optional>(move(result)); }); }