mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			333 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			333 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| 	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 <http://www.gnu.org/licenses/>.
 | |
| */
 | |
| #include <libyul/optimiser/StackToMemoryMover.h>
 | |
| #include <libyul/optimiser/NameCollector.h>
 | |
| #include <libyul/optimiser/NameDispenser.h>
 | |
| #include <libyul/backends/evm/EVMDialect.h>
 | |
| 
 | |
| #include <libyul/AST.h>
 | |
| 
 | |
| #include <libsolutil/CommonData.h>
 | |
| 
 | |
| #include <range/v3/algorithm/none_of.hpp>
 | |
| #include <range/v3/view/filter.hpp>
 | |
| #include <range/v3/view/transform.hpp>
 | |
| #include <range/v3/view/zip.hpp>
 | |
| #include <range/v3/range/conversion.hpp>
 | |
| 
 | |
| using namespace std;
 | |
| using namespace solidity;
 | |
| using namespace solidity::yul;
 | |
| 
 | |
| namespace
 | |
| {
 | |
| vector<Statement> generateMemoryStore(
 | |
| 	Dialect const& _dialect,
 | |
| 	shared_ptr<DebugData const> const& _debugData,
 | |
| 	YulString _mpos,
 | |
| 	Expression _value
 | |
| )
 | |
| {
 | |
| 	BuiltinFunction const* memoryStoreFunction = _dialect.memoryStoreFunction(_dialect.defaultType);
 | |
| 	yulAssert(memoryStoreFunction, "");
 | |
| 	vector<Statement> result;
 | |
| 	result.emplace_back(ExpressionStatement{_debugData, FunctionCall{
 | |
| 		_debugData,
 | |
| 		Identifier{_debugData, memoryStoreFunction->name},
 | |
| 		{
 | |
| 			Literal{_debugData, LiteralKind::Number, _mpos, {}},
 | |
| 			move(_value)
 | |
| 		}
 | |
| 	}});
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| FunctionCall generateMemoryLoad(Dialect const& _dialect, std::shared_ptr<DebugData const> const& _debugData, YulString _mpos)
 | |
| {
 | |
| 	BuiltinFunction const* memoryLoadFunction = _dialect.memoryLoadFunction(_dialect.defaultType);
 | |
| 	yulAssert(memoryLoadFunction, "");
 | |
| 	return FunctionCall{
 | |
| 		_debugData,
 | |
| 		Identifier{_debugData, memoryLoadFunction->name}, {
 | |
| 			Literal{
 | |
| 				_debugData,
 | |
| 				LiteralKind::Number,
 | |
| 				_mpos,
 | |
| 				{}
 | |
| 			}
 | |
| 		}
 | |
| 	};
 | |
| }
 | |
| }
 | |
| 
 | |
| void StackToMemoryMover::run(
 | |
| 	OptimiserStepContext& _context,
 | |
| 	u256 _reservedMemory,
 | |
| 	map<YulString, uint64_t> const& _memorySlots,
 | |
| 	uint64_t _numRequiredSlots,
 | |
| 	Block& _block
 | |
| )
 | |
| {
 | |
| 	VariableMemoryOffsetTracker memoryOffsetTracker(_reservedMemory, _memorySlots, _numRequiredSlots);
 | |
| 	StackToMemoryMover stackToMemoryMover(
 | |
| 		_context,
 | |
| 		memoryOffsetTracker,
 | |
| 		util::applyMap(
 | |
| 			allFunctionDefinitions(_block),
 | |
| 			util::mapTuple([](YulString _name, FunctionDefinition const* _funDef) {
 | |
| 				return make_pair(_name, _funDef->returnVariables);
 | |
| 			}),
 | |
| 			map<YulString, TypedNameList>{}
 | |
| 		)
 | |
| 	);
 | |
| 	stackToMemoryMover(_block);
 | |
| 	_block.statements += move(stackToMemoryMover.m_newFunctionDefinitions);
 | |
| }
 | |
| 
 | |
| StackToMemoryMover::StackToMemoryMover(
 | |
| 	OptimiserStepContext& _context,
 | |
| 	VariableMemoryOffsetTracker const& _memoryOffsetTracker,
 | |
| 	map<YulString, TypedNameList> _functionReturnVariables
 | |
| ):
 | |
| m_context(_context),
 | |
| m_memoryOffsetTracker(_memoryOffsetTracker),
 | |
| m_nameDispenser(_context.dispenser),
 | |
| m_functionReturnVariables(move(_functionReturnVariables))
 | |
| {
 | |
| 	auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
 | |
| 	yulAssert(
 | |
| 		evmDialect && evmDialect->providesObjectAccess(),
 | |
| 		"StackToMemoryMover can only be run on objects using the EVMDialect with object access."
 | |
| 	);
 | |
| }
 | |
| 
 | |
| void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition)
 | |
| {
 | |
| 	// 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<Statement> 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<TypedNameList>;
 | |
| 		// 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<YulString, YulString> 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<vector<Expression>>
 | |
| 			}
 | |
| 		});
 | |
| 		_functionDefinition.body.statements.emplace_back(Assignment{
 | |
| 			_functionDefinition.debugData,
 | |
| 			{Identifier{_functionDefinition.debugData, _functionDefinition.returnVariables.front().name}},
 | |
| 			make_unique<Expression>(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<TypedNameList>;
 | |
| }
 | |
| 
 | |
| void StackToMemoryMover::operator()(Block& _block)
 | |
| {
 | |
| 	using OptionalStatements = optional<vector<Statement>>;
 | |
| 
 | |
| 	auto rewriteAssignmentOrVariableDeclarationLeftHandSide = [this](
 | |
| 		auto& _stmt,
 | |
| 		auto& _lhsVars
 | |
| 	) -> OptionalStatements {
 | |
| 		using StatementType = decay_t<decltype(_stmt)>;
 | |
| 
 | |
| 		auto debugData = _stmt.debugData;
 | |
| 		if (_lhsVars.size() == 1)
 | |
| 		{
 | |
| 			if (optional<YulString> 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<optional<YulString>> rhsMemorySlots;
 | |
| 		if (_stmt.value)
 | |
| 		{
 | |
| 			FunctionCall const* functionCall = get_if<FunctionCall>(_stmt.value.get());
 | |
| 			yulAssert(functionCall, "");
 | |
| 			if (m_context.dialect.builtin(functionCall->functionName.name))
 | |
| 				rhsMemorySlots = vector<optional<YulString>>(_lhsVars.size(), nullopt);
 | |
| 			else
 | |
| 				rhsMemorySlots =
 | |
| 					m_functionReturnVariables.at(functionCall->functionName.name) |
 | |
| 					ranges::views::transform(m_memoryOffsetTracker) |
 | |
| 					ranges::to<vector<optional<YulString>>>;
 | |
| 		}
 | |
| 		else
 | |
| 			rhsMemorySlots = vector<optional<YulString>>(_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<YulString> const& _slot) { return _slot.has_value(); }) &&
 | |
| 			!util::contains_if(_lhsVars, m_memoryOffsetTracker)
 | |
| 		)
 | |
| 			return {};
 | |
| 
 | |
| 		vector<Statement> memoryAssignments;
 | |
| 		vector<Statement> variableAssignments;
 | |
| 		VariableDeclaration tempDecl{debugData, {}, move(_stmt.value)};
 | |
| 
 | |
| 		yulAssert(rhsMemorySlots.size() == _lhsVars.size(), "");
 | |
| 		for (auto&& [lhsVar, rhsSlot]: ranges::views::zip(_lhsVars, rhsMemorySlots))
 | |
| 		{
 | |
| 			unique_ptr<Expression> rhs;
 | |
| 			if (rhsSlot)
 | |
| 				rhs = make_unique<Expression>(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<Expression>(Identifier{debugData, tempVarName});
 | |
| 			}
 | |
| 
 | |
| 			if (optional<YulString> offset = m_memoryOffsetTracker(lhsVar.name))
 | |
| 				memoryAssignments += generateMemoryStore(
 | |
| 					m_context.dialect,
 | |
| 					_stmt.debugData,
 | |
| 					*offset,
 | |
| 					move(*rhs)
 | |
| 				);
 | |
| 			else
 | |
| 				variableAssignments.emplace_back(StatementType{
 | |
| 					debugData,
 | |
| 					{ move(lhsVar) },
 | |
| 					move(rhs)
 | |
| 				});
 | |
| 		}
 | |
| 
 | |
| 		vector<Statement> 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) -> OptionalStatements
 | |
| 		{
 | |
| 			visit(_statement);
 | |
| 			if (auto* assignment = get_if<Assignment>(&_statement))
 | |
| 				return rewriteAssignmentOrVariableDeclarationLeftHandSide(*assignment, assignment->variableNames);
 | |
| 			else if (auto* varDecl = get_if<VariableDeclaration>(&_statement))
 | |
| 				return rewriteAssignmentOrVariableDeclarationLeftHandSide(*varDecl, varDecl->variables);
 | |
| 			return {};
 | |
| 		}
 | |
| 	);
 | |
| }
 | |
| 
 | |
| void StackToMemoryMover::visit(Expression& _expression)
 | |
| {
 | |
| 	ASTModifier::visit(_expression);
 | |
| 	if (Identifier* identifier = get_if<Identifier>(&_expression))
 | |
| 		if (optional<YulString> offset = m_memoryOffsetTracker(identifier->name))
 | |
| 			_expression = generateMemoryLoad(m_context.dialect, identifier->debugData, *offset);
 | |
| }
 | |
| 
 | |
| optional<YulString> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(YulString _variable) const
 | |
| {
 | |
| 	if (m_memorySlots.count(_variable))
 | |
| 	{
 | |
| 		uint64_t slot = m_memorySlots.at(_variable);
 | |
| 		yulAssert(slot < m_numRequiredSlots, "");
 | |
| 		return YulString{toCompactHexWithPrefix(m_reservedMemory + 32 * (m_numRequiredSlots - slot - 1))};
 | |
| 	}
 | |
| 	else
 | |
| 		return nullopt;
 | |
| }
 | |
| 
 | |
| optional<YulString> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(TypedName const& _variable) const
 | |
| {
 | |
| 	return (*this)(_variable.name);
 | |
| }
 | |
| 
 | |
| optional<YulString> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(Identifier const& _variable) const
 | |
| {
 | |
| 	return (*this)(_variable.name);
 | |
| }
 |