mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #5292 from ethereum/redundantAssignEliminator
[Yul] Redundant assign eliminator.
This commit is contained in:
		
						commit
						8d01db7c2d
					
				
							
								
								
									
										193
									
								
								libyul/optimiser/RedundantAssignEliminator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								libyul/optimiser/RedundantAssignEliminator.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,193 @@ | |||||||
|  | /*
 | ||||||
|  | 	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/>.
 | ||||||
|  | */ | ||||||
|  | /**
 | ||||||
|  |  * Optimiser component that removes assignments to variables that are not used | ||||||
|  |  * until they go out of scope or are re-assigned. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <libyul/optimiser/RedundantAssignEliminator.h> | ||||||
|  | 
 | ||||||
|  | #include <libyul/optimiser/Semantics.h> | ||||||
|  | 
 | ||||||
|  | #include <libsolidity/inlineasm/AsmData.h> | ||||||
|  | 
 | ||||||
|  | #include <libdevcore/CommonData.h> | ||||||
|  | 
 | ||||||
|  | #include <boost/range/algorithm_ext/erase.hpp> | ||||||
|  | 
 | ||||||
|  | using namespace std; | ||||||
|  | using namespace dev; | ||||||
|  | using namespace dev::yul; | ||||||
|  | using namespace dev::solidity; | ||||||
|  | 
 | ||||||
|  | void RedundantAssignEliminator::operator()(Identifier const& _identifier) | ||||||
|  | { | ||||||
|  | 	changeUndecidedTo(_identifier.name, State::Used); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RedundantAssignEliminator::operator()(VariableDeclaration const& _variableDeclaration) | ||||||
|  | { | ||||||
|  | 	ASTWalker::operator()(_variableDeclaration); | ||||||
|  | 
 | ||||||
|  | 	for (auto const& var: _variableDeclaration.variables) | ||||||
|  | 		m_declaredVariables.insert(var.name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RedundantAssignEliminator::operator()(Assignment const& _assignment) | ||||||
|  | { | ||||||
|  | 	visit(*_assignment.value); | ||||||
|  | 	for (auto const& var: _assignment.variableNames) | ||||||
|  | 		changeUndecidedTo(var.name, State::Unused); | ||||||
|  | 
 | ||||||
|  | 	if (_assignment.variableNames.size() == 1) | ||||||
|  | 		// Default-construct it in "Undecided" state if it does not yet exist.
 | ||||||
|  | 		m_assignments[_assignment.variableNames.front().name][&_assignment]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RedundantAssignEliminator::operator()(If const& _if) | ||||||
|  | { | ||||||
|  | 	visit(*_if.condition); | ||||||
|  | 
 | ||||||
|  | 	RedundantAssignEliminator branch{*this}; | ||||||
|  | 	branch(_if.body); | ||||||
|  | 
 | ||||||
|  | 	join(branch); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RedundantAssignEliminator::operator()(Switch const& _switch) | ||||||
|  | { | ||||||
|  | 	visit(*_switch.expression); | ||||||
|  | 
 | ||||||
|  | 	bool hasDefault = false; | ||||||
|  | 	vector<RedundantAssignEliminator> branches; | ||||||
|  | 	for (auto const& c: _switch.cases) | ||||||
|  | 	{ | ||||||
|  | 		if (!c.value) | ||||||
|  | 			hasDefault = true; | ||||||
|  | 		branches.emplace_back(*this); | ||||||
|  | 		branches.back()(c.body); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (hasDefault) | ||||||
|  | 	{ | ||||||
|  | 		*this = std::move(branches.back()); | ||||||
|  | 		branches.pop_back(); | ||||||
|  | 	} | ||||||
|  | 	for (auto& branch: branches) | ||||||
|  | 		join(branch); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RedundantAssignEliminator::operator()(FunctionDefinition const& _functionDefinition) | ||||||
|  | { | ||||||
|  | 	(*this)(_functionDefinition.body); | ||||||
|  | 
 | ||||||
|  | 	for (auto const& param: _functionDefinition.parameters) | ||||||
|  | 		changeUndecidedTo(param.name, State::Unused); | ||||||
|  | 	for (auto const& retParam: _functionDefinition.returnVariables) | ||||||
|  | 		changeUndecidedTo(retParam.name, State::Used); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RedundantAssignEliminator::operator()(ForLoop const& _forLoop) | ||||||
|  | { | ||||||
|  | 	// This will set all variables that are declared in this
 | ||||||
|  | 	// block to "unused" when it is destroyed.
 | ||||||
|  | 	BlockScope scope(*this); | ||||||
|  | 
 | ||||||
|  | 	// We need to visit the statements directly because of the
 | ||||||
|  | 	// scoping rules.
 | ||||||
|  | 	walkVector(_forLoop.pre.statements); | ||||||
|  | 
 | ||||||
|  | 	// We just run the loop twice to account for the
 | ||||||
|  | 	// back edge.
 | ||||||
|  | 	// There need not be more runs because we only have three different states.
 | ||||||
|  | 
 | ||||||
|  | 	visit(*_forLoop.condition); | ||||||
|  | 
 | ||||||
|  | 	RedundantAssignEliminator zeroRuns{*this}; | ||||||
|  | 
 | ||||||
|  | 	(*this)(_forLoop.body); | ||||||
|  | 	(*this)(_forLoop.post); | ||||||
|  | 
 | ||||||
|  | 	visit(*_forLoop.condition); | ||||||
|  | 
 | ||||||
|  | 	RedundantAssignEliminator oneRun{*this}; | ||||||
|  | 
 | ||||||
|  | 	(*this)(_forLoop.body); | ||||||
|  | 	(*this)(_forLoop.post); | ||||||
|  | 
 | ||||||
|  | 	visit(*_forLoop.condition); | ||||||
|  | 
 | ||||||
|  | 	// Order does not matter because "max" is commutative and associative.
 | ||||||
|  | 	join(oneRun); | ||||||
|  | 	join(zeroRuns); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RedundantAssignEliminator::operator()(Block const& _block) | ||||||
|  | { | ||||||
|  | 	// This will set all variables that are declared in this
 | ||||||
|  | 	// block to "unused" when it is destroyed.
 | ||||||
|  | 	BlockScope scope(*this); | ||||||
|  | 
 | ||||||
|  | 	ASTWalker::operator()(_block); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RedundantAssignEliminator::run(Block& _ast) | ||||||
|  | { | ||||||
|  | 	RedundantAssignEliminator rae; | ||||||
|  | 	rae(_ast); | ||||||
|  | 
 | ||||||
|  | 	std::set<Assignment const*> assignmentsToRemove; | ||||||
|  | 	for (auto const& variables: rae.m_assignments) | ||||||
|  | 		for (auto const& assignment: variables.second) | ||||||
|  | 		{ | ||||||
|  | 			assertThrow(assignment.second != State::Undecided, OptimizerException, ""); | ||||||
|  | 			if (assignment.second == State::Unused && MovableChecker{*assignment.first->value}.movable()) | ||||||
|  | 				assignmentsToRemove.insert(assignment.first); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	AssignmentRemover remover{assignmentsToRemove}; | ||||||
|  | 	remover(_ast); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RedundantAssignEliminator::join(RedundantAssignEliminator& _other) | ||||||
|  | { | ||||||
|  | 	for (auto& var: _other.m_assignments) | ||||||
|  | 		if (m_assignments.count(var.first)) | ||||||
|  | 		{ | ||||||
|  | 			map<Assignment const*, State>& assignmentsHere = m_assignments[var.first]; | ||||||
|  | 			for (auto& assignment: var.second) | ||||||
|  | 				assignmentsHere[assignment.first].join(assignment.second); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 			m_assignments[var.first] = std::move(var.second); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RedundantAssignEliminator::changeUndecidedTo(string const& _variable, RedundantAssignEliminator::State _newState) | ||||||
|  | { | ||||||
|  | 	for (auto& assignment: m_assignments[_variable]) | ||||||
|  | 		if (assignment.second == State{State::Undecided}) | ||||||
|  | 			assignment.second = _newState; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void AssignmentRemover::operator()(Block& _block) | ||||||
|  | { | ||||||
|  | 	boost::range::remove_erase_if(_block.statements, [=](Statement const& _statement) -> bool { | ||||||
|  | 		return _statement.type() == typeid(Assignment) && m_toRemove.count(&boost::get<Assignment>(_statement)); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	ASTModifier::operator()(_block); | ||||||
|  | } | ||||||
							
								
								
									
										185
									
								
								libyul/optimiser/RedundantAssignEliminator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								libyul/optimiser/RedundantAssignEliminator.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,185 @@ | |||||||
|  | /*
 | ||||||
|  | 	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/>.
 | ||||||
|  | */ | ||||||
|  | /**
 | ||||||
|  |  * Optimiser component that removes assignments to variables that are not used | ||||||
|  |  * until they go out of scope or are re-assigned. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <libyul/ASTDataForward.h> | ||||||
|  | 
 | ||||||
|  | #include <libyul/optimiser/ASTWalker.h> | ||||||
|  | 
 | ||||||
|  | #include <map> | ||||||
|  | 
 | ||||||
|  | namespace dev | ||||||
|  | { | ||||||
|  | namespace yul | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Optimiser component that removes assignments to variables that are not used | ||||||
|  |  * until they go out of scope or are re-assigned. This component | ||||||
|  |  * respects the control-flow and takes it into account for removal. | ||||||
|  |  * | ||||||
|  |  * Example: | ||||||
|  |  * | ||||||
|  |  * { | ||||||
|  |  *   let a | ||||||
|  |  *   a := 1 | ||||||
|  |  *   a := 2 | ||||||
|  |  *   b := 2 | ||||||
|  |  *   if calldataload(0) | ||||||
|  |  *   { | ||||||
|  |  *     b := mload(a) | ||||||
|  |  *   } | ||||||
|  |  *   a := b | ||||||
|  |  * } | ||||||
|  |  * | ||||||
|  |  * In the example, "a := 1" can be removed because the value from this assignment | ||||||
|  |  * is not used in any control-flow branch (it is replaced right away). | ||||||
|  |  * The assignment "a := 2" is also overwritten by "a := b" at the end, | ||||||
|  |  * but there is a control-flow path (through the condition body) which uses | ||||||
|  |  * the value from "a := 2" and thus, this assignment cannot be removed. | ||||||
|  |  * | ||||||
|  |  * Detailed rules: | ||||||
|  |  * | ||||||
|  |  * The AST is traversed twice: in an information gathering step and in the | ||||||
|  |  * actual removal step. During information gathering, we maintain a | ||||||
|  |  * mapping from assignment statements to the three states | ||||||
|  |  * "unused", "undecided" and "used". | ||||||
|  |  * When an assignment is visited, it is added to the mapping in the "undecided" state | ||||||
|  |  * (see remark about for loops below) and every other assignment to the same variable | ||||||
|  |  * that is still in the "undecided" state is changed to "unused". | ||||||
|  |  * When a variable is referenced, the state of any assignment to that variable still | ||||||
|  |  * in the "undecided" state is changed to "used". | ||||||
|  |  * At points where control flow splits, a copy | ||||||
|  |  * of the mapping is handed over to each branch. At points where control flow | ||||||
|  |  * joins, the two mappings coming from the two branches are combined in the following way: | ||||||
|  |  * Statements that are only in one mapping or have the same state are used unchanged. | ||||||
|  |  * Conflicting values are resolved in the following way: | ||||||
|  |  * "unused", "undecided" -> "undecided" | ||||||
|  |  * "unused", "used" -> "used" | ||||||
|  |  * "undecided, "used" -> "used". | ||||||
|  |  * | ||||||
|  |  * For for-loops, the condition, body and post-part are visited twice, taking | ||||||
|  |  * the joining control-flow at the condition into account. | ||||||
|  |  * In other words, we create three control flow paths: Zero runs of the loop, | ||||||
|  |  * one run and two runs and then combine them at the end. | ||||||
|  |  * Running at most twice is enough because there are only three different states. | ||||||
|  |  * | ||||||
|  |  * For switch statements that have a "default"-case, there is no control-flow | ||||||
|  |  * part that skips the switch. | ||||||
|  |  * | ||||||
|  |  * When a variable goes out of scope, all statements still in the "undecided" | ||||||
|  |  * state are changed to "unused", unless the variable is the return | ||||||
|  |  * parameter of a function - there, the state changes to "used". | ||||||
|  |  * | ||||||
|  |  * In the second traversal, all assignments that are in the "unused" state are removed. | ||||||
|  |  * | ||||||
|  |  * | ||||||
|  |  * This step is usually run right after the SSA transform to complete | ||||||
|  |  * the generation of the pseudo-SSA. | ||||||
|  |  * | ||||||
|  |  * Prerequisite: Disambiguator. | ||||||
|  |  */ | ||||||
|  | class RedundantAssignEliminator: public ASTWalker | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	RedundantAssignEliminator(RedundantAssignEliminator const&) = default; | ||||||
|  | 	RedundantAssignEliminator& operator=(RedundantAssignEliminator const&) = default; | ||||||
|  | 	RedundantAssignEliminator(RedundantAssignEliminator&&) = default; | ||||||
|  | 	RedundantAssignEliminator& operator=(RedundantAssignEliminator&&) = default; | ||||||
|  | 
 | ||||||
|  | 	void operator()(Identifier const& _identifier) override; | ||||||
|  | 	void operator()(VariableDeclaration const& _variableDeclaration) override; | ||||||
|  | 	void operator()(Assignment const& _assignment) override; | ||||||
|  | 	void operator()(If const& _if) override; | ||||||
|  | 	void operator()(Switch const& _switch) override; | ||||||
|  | 	void operator()(FunctionDefinition const&) override; | ||||||
|  | 	void operator()(ForLoop const&) override; | ||||||
|  | 	void operator()(Block const& _block) override; | ||||||
|  | 
 | ||||||
|  | 	static void run(Block& _ast); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	RedundantAssignEliminator() {} | ||||||
|  | 
 | ||||||
|  | 	class State | ||||||
|  | 	{ | ||||||
|  | 	public: | ||||||
|  | 		enum Value { Unused, Undecided, Used }; | ||||||
|  | 		State(Value _value = Undecided): m_value(_value) {} | ||||||
|  | 		bool operator==(State _other) const { return m_value == _other.m_value; } | ||||||
|  | 		bool operator!=(State _other) const { return !operator==(_other); } | ||||||
|  | 		void join(State _other) | ||||||
|  | 		{ | ||||||
|  | 			// Using "max" works here because of the order of the values in the enum.
 | ||||||
|  | 			m_value = Value(std::max(int(_other.m_value), int(m_value))); | ||||||
|  | 		} | ||||||
|  | 	private: | ||||||
|  | 		Value m_value = Undecided; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	/**
 | ||||||
|  | 	 * Takes care about storing the list of declared variables and | ||||||
|  | 	 * sets them to "unused" when it is destroyed. | ||||||
|  | 	 */ | ||||||
|  | 	class BlockScope | ||||||
|  | 	{ | ||||||
|  | 	public: | ||||||
|  | 		explicit BlockScope(RedundantAssignEliminator& _rae): m_rae(_rae) | ||||||
|  | 		{ | ||||||
|  | 			swap(m_rae.m_declaredVariables, m_outerDeclaredVariables); | ||||||
|  | 		} | ||||||
|  | 		~BlockScope() | ||||||
|  | 		{ | ||||||
|  | 			for (auto const& var: m_rae.m_declaredVariables) | ||||||
|  | 				m_rae.changeUndecidedTo(var, State::Unused); | ||||||
|  | 			swap(m_rae.m_declaredVariables, m_outerDeclaredVariables); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	private: | ||||||
|  | 		RedundantAssignEliminator& m_rae; | ||||||
|  | 		std::set<std::string> m_outerDeclaredVariables; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	/// Joins the assignment mapping with @a _other according to the rules laid out
 | ||||||
|  | 	/// above.
 | ||||||
|  | 	/// Will destroy @a _other.
 | ||||||
|  | 	void join(RedundantAssignEliminator& _other); | ||||||
|  | 	void changeUndecidedTo(std::string const& _variable, State _newState); | ||||||
|  | 
 | ||||||
|  | 	std::set<std::string> m_declaredVariables; | ||||||
|  | 	std::map<std::string, std::map<Assignment const*, State>> m_assignments; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class AssignmentRemover: public ASTModifier | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	explicit AssignmentRemover(std::set<Assignment const*> const& _toRemove): | ||||||
|  | 		m_toRemove(_toRemove) | ||||||
|  | 	{} | ||||||
|  | 	void operator()(Block& _block) override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	std::set<Assignment const*> const& m_toRemove; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | } | ||||||
| @ -36,6 +36,7 @@ | |||||||
| #include <libyul/optimiser/UnusedPruner.h> | #include <libyul/optimiser/UnusedPruner.h> | ||||||
| #include <libyul/optimiser/ExpressionJoiner.h> | #include <libyul/optimiser/ExpressionJoiner.h> | ||||||
| #include <libyul/optimiser/SSATransform.h> | #include <libyul/optimiser/SSATransform.h> | ||||||
|  | #include <libyul/optimiser/RedundantAssignEliminator.h> | ||||||
| 
 | 
 | ||||||
| #include <libsolidity/parsing/Scanner.h> | #include <libsolidity/parsing/Scanner.h> | ||||||
| #include <libsolidity/inlineasm/AsmPrinter.h> | #include <libsolidity/inlineasm/AsmPrinter.h> | ||||||
| @ -178,6 +179,18 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con | |||||||
| 		NameDispenser nameDispenser(*m_ast); | 		NameDispenser nameDispenser(*m_ast); | ||||||
| 		SSATransform::run(*m_ast, nameDispenser); | 		SSATransform::run(*m_ast, nameDispenser); | ||||||
| 	} | 	} | ||||||
|  | 	else if (m_optimizerStep == "redundantAssignEliminator") | ||||||
|  | 	{ | ||||||
|  | 		disambiguate(); | ||||||
|  | 		RedundantAssignEliminator::run(*m_ast); | ||||||
|  | 	} | ||||||
|  | 	else if (m_optimizerStep == "ssaPlusCleanup") | ||||||
|  | 	{ | ||||||
|  | 		disambiguate(); | ||||||
|  | 		NameDispenser nameDispenser(*m_ast); | ||||||
|  | 		SSATransform::run(*m_ast, nameDispenser); | ||||||
|  | 		RedundantAssignEliminator::run(*m_ast); | ||||||
|  | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		FormattedScope(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Invalid optimizer step: " << m_optimizerStep << endl; | 		FormattedScope(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Invalid optimizer step: " << m_optimizerStep << endl; | ||||||
|  | |||||||
| @ -0,0 +1,26 @@ | |||||||
|  | { | ||||||
|  |     for { | ||||||
|  |         let a := 2 | ||||||
|  |         // Should not be removed, even though you might think | ||||||
|  |         // it goes out of scope | ||||||
|  |         a := 3 | ||||||
|  |     } a { a := add(a, 1) } | ||||||
|  |     { | ||||||
|  |         a := 7 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     for { | ||||||
|  | //         let a := 2 | ||||||
|  | //         a := 3 | ||||||
|  | //     } | ||||||
|  | //     a | ||||||
|  | //     { | ||||||
|  | //         a := add(a, 1) | ||||||
|  | //     } | ||||||
|  | //     { | ||||||
|  | //         a := 7 | ||||||
|  | //     } | ||||||
|  | // } | ||||||
| @ -0,0 +1,31 @@ | |||||||
|  | { | ||||||
|  |     let x | ||||||
|  |     let y | ||||||
|  |     // Cannot be removed, because we might skip the loop | ||||||
|  |     x := 1 | ||||||
|  |     for { } calldataload(0) { } | ||||||
|  |     { | ||||||
|  |         // Cannot be removed | ||||||
|  |         x := 2 | ||||||
|  |         // Can be removed | ||||||
|  |         y := 3 | ||||||
|  |     } | ||||||
|  |     y := 8 | ||||||
|  |     mstore(x, 0) | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let x | ||||||
|  | //     let y | ||||||
|  | //     x := 1 | ||||||
|  | //     for { | ||||||
|  | //     } | ||||||
|  | //     calldataload(0) | ||||||
|  | //     { | ||||||
|  | //     } | ||||||
|  | //     { | ||||||
|  | //         x := 2 | ||||||
|  | //     } | ||||||
|  | //     mstore(x, 0) | ||||||
|  | // } | ||||||
| @ -0,0 +1,27 @@ | |||||||
|  | { | ||||||
|  |     let x | ||||||
|  |     // Cannot be removed, because we might run the loop only once | ||||||
|  |     x := 1 | ||||||
|  |     for { } calldataload(0) { } | ||||||
|  |     { | ||||||
|  |         mstore(x, 2) | ||||||
|  |         // Cannot be removed because of the line above | ||||||
|  |         x := 2 | ||||||
|  |     } | ||||||
|  |     x := 3 | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let x | ||||||
|  | //     x := 1 | ||||||
|  | //     for { | ||||||
|  | //     } | ||||||
|  | //     calldataload(0) | ||||||
|  | //     { | ||||||
|  | //     } | ||||||
|  | //     { | ||||||
|  | //         mstore(x, 2) | ||||||
|  | //         x := 2 | ||||||
|  | //     } | ||||||
|  | // } | ||||||
| @ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  |     let r | ||||||
|  |     r := 1 | ||||||
|  |     function f(x, y) -> a, b { | ||||||
|  |         // Can be removed, is param | ||||||
|  |         x := 1 | ||||||
|  |         y := 2 | ||||||
|  |         // Cannot be removed, is return param | ||||||
|  |         a := 3 | ||||||
|  |         b := 4 | ||||||
|  |     } | ||||||
|  |     r := 2 | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let r | ||||||
|  | //     function f(x, y) -> a, b | ||||||
|  | //     { | ||||||
|  | //         a := 3 | ||||||
|  | //         b := 4 | ||||||
|  | //     } | ||||||
|  | // } | ||||||
| @ -0,0 +1,24 @@ | |||||||
|  | { | ||||||
|  |     let c | ||||||
|  |     let d | ||||||
|  |     c := calldataload(0) | ||||||
|  |     d := 1 | ||||||
|  |     if c { | ||||||
|  |         d := 2 | ||||||
|  |     } | ||||||
|  |     // This enforces that none of the assignments above can be removed. | ||||||
|  |     mstore(0, d) | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let c | ||||||
|  | //     let d | ||||||
|  | //     c := calldataload(0) | ||||||
|  | //     d := 1 | ||||||
|  | //     if c | ||||||
|  | //     { | ||||||
|  | //         d := 2 | ||||||
|  | //     } | ||||||
|  | //     mstore(0, d) | ||||||
|  | // } | ||||||
| @ -0,0 +1,24 @@ | |||||||
|  | { | ||||||
|  |     let c | ||||||
|  |     let d | ||||||
|  |     c := calldataload(0) | ||||||
|  |     // This assignment will be overwritten in all branches and thus can be removed. | ||||||
|  |     d := 1 | ||||||
|  |     if c { | ||||||
|  |         d := 2 | ||||||
|  |     } | ||||||
|  |     d := 3 | ||||||
|  |     mstore(0, d) | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let c | ||||||
|  | //     let d | ||||||
|  | //     c := calldataload(0) | ||||||
|  | //     if c | ||||||
|  | //     { | ||||||
|  | //     } | ||||||
|  | //     d := 3 | ||||||
|  | //     mstore(0, d) | ||||||
|  | // } | ||||||
| @ -0,0 +1,25 @@ | |||||||
|  | { | ||||||
|  |     let c | ||||||
|  |     let d | ||||||
|  |     c := calldataload(0) | ||||||
|  |     d := 1 | ||||||
|  |     if c { | ||||||
|  |         // Uses the assignment above | ||||||
|  |         d := d | ||||||
|  |     } | ||||||
|  |     d := 3 | ||||||
|  |     mstore(0, d) | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let c | ||||||
|  | //     let d | ||||||
|  | //     c := calldataload(0) | ||||||
|  | //     d := 1 | ||||||
|  | //     if c | ||||||
|  | //     { | ||||||
|  | //     } | ||||||
|  | //     d := 3 | ||||||
|  | //     mstore(0, d) | ||||||
|  | // } | ||||||
| @ -0,0 +1,19 @@ | |||||||
|  | { | ||||||
|  |     function f() -> a, b {} | ||||||
|  |     let x, y | ||||||
|  |     x := 1 | ||||||
|  |     x := 2 | ||||||
|  |     // Will not be used, but is a multi-assign, so not removed. | ||||||
|  |     x, y := f() | ||||||
|  |     x := 3 | ||||||
|  |     y := 4 | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     function f() -> a, b | ||||||
|  | //     { | ||||||
|  | //     } | ||||||
|  | //     let x, y | ||||||
|  | //     x, y := f() | ||||||
|  | // } | ||||||
| @ -0,0 +1,15 @@ | |||||||
|  | { | ||||||
|  |     let a := 2 | ||||||
|  |     a := 7 | ||||||
|  |     let b := 8 | ||||||
|  |     b := a | ||||||
|  |     a := b | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let a := 2 | ||||||
|  | //     a := 7 | ||||||
|  | //     let b := 8 | ||||||
|  | //     b := a | ||||||
|  | // } | ||||||
| @ -0,0 +1,11 @@ | |||||||
|  | { | ||||||
|  |     let a | ||||||
|  |     a := 0 | ||||||
|  |     a := mload(0) | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let a | ||||||
|  | //     a := mload(0) | ||||||
|  | // } | ||||||
| @ -0,0 +1,16 @@ | |||||||
|  | { | ||||||
|  |     let a | ||||||
|  |     { | ||||||
|  |         let b | ||||||
|  |         b := 2 | ||||||
|  |         a := 2 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let a | ||||||
|  | //     { | ||||||
|  | //         let b | ||||||
|  | //     } | ||||||
|  | // } | ||||||
| @ -0,0 +1,10 @@ | |||||||
|  | { | ||||||
|  |     let a | ||||||
|  |     a := 1 | ||||||
|  |     a := 2 | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let a | ||||||
|  | // } | ||||||
| @ -0,0 +1,22 @@ | |||||||
|  | { | ||||||
|  |     let x | ||||||
|  |     // Will be overwritten in all branches | ||||||
|  |     x := 1 | ||||||
|  |     switch calldataload(0) | ||||||
|  |     case 0 { x := 2 } | ||||||
|  |     default { x := 3 } | ||||||
|  |     mstore(x, 0) | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let x | ||||||
|  | //     switch calldataload(0) | ||||||
|  | //     case 0 { | ||||||
|  | //         x := 2 | ||||||
|  | //     } | ||||||
|  | //     default { | ||||||
|  | //         x := 3 | ||||||
|  | //     } | ||||||
|  | //     mstore(x, 0) | ||||||
|  | // } | ||||||
| @ -0,0 +1,19 @@ | |||||||
|  | { | ||||||
|  |     let x | ||||||
|  |     // Will NOT be overwritten in all branches | ||||||
|  |     x := 1 | ||||||
|  |     switch calldataload(0) | ||||||
|  |     case 0 { x := 2 } | ||||||
|  |     mstore(x, 0) | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let x | ||||||
|  | //     x := 1 | ||||||
|  | //     switch calldataload(0) | ||||||
|  | //     case 0 { | ||||||
|  | //         x := 2 | ||||||
|  | //     } | ||||||
|  | //     mstore(x, 0) | ||||||
|  | // } | ||||||
| @ -0,0 +1,23 @@ | |||||||
|  | { | ||||||
|  |     let x | ||||||
|  |     // Will be used in some and overwritten in others | ||||||
|  |     x := 1 | ||||||
|  |     switch calldataload(0) | ||||||
|  |     case 0 { x := 2 } | ||||||
|  |     default { mstore(x, 1) } | ||||||
|  |     mstore(x, 0) | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let x | ||||||
|  | //     x := 1 | ||||||
|  | //     switch calldataload(0) | ||||||
|  | //     case 0 { | ||||||
|  | //         x := 2 | ||||||
|  | //     } | ||||||
|  | //     default { | ||||||
|  | //         mstore(x, 1) | ||||||
|  | //     } | ||||||
|  | //     mstore(x, 0) | ||||||
|  | // } | ||||||
| @ -0,0 +1,16 @@ | |||||||
|  | { | ||||||
|  |     let x | ||||||
|  |     // Not referenced anywhere. | ||||||
|  |     x := 1 | ||||||
|  |     switch calldataload(0) | ||||||
|  |     case 0 { mstore(0, 1) } | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // redundantAssignEliminator | ||||||
|  | // { | ||||||
|  | //     let x | ||||||
|  | //     switch calldataload(0) | ||||||
|  | //     case 0 { | ||||||
|  | //         mstore(0, 1) | ||||||
|  | //     } | ||||||
|  | // } | ||||||
| @ -0,0 +1,35 @@ | |||||||
|  | { | ||||||
|  |     function copy(from, to) -> length { | ||||||
|  |         length := mload(from) | ||||||
|  |         mstore(to, length) | ||||||
|  |         from := add(from, 0x20) | ||||||
|  |         to := add(to, 0x20) | ||||||
|  |         for { let x := 1 } lt(x, length) { x := add(x, 0x20) } { | ||||||
|  |             mstore(add(to, x), mload(add(from, x))) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // ssaPlusCleanup | ||||||
|  | // { | ||||||
|  | //     function copy(from, to) -> length | ||||||
|  | //     { | ||||||
|  | //         let length_1 := mload(from) | ||||||
|  | //         length := length_1 | ||||||
|  | //         mstore(to, length_1) | ||||||
|  | //         let from_1 := add(from, 0x20) | ||||||
|  | //         let to_1 := add(to, 0x20) | ||||||
|  | //         for { | ||||||
|  | //             let x_1 := 1 | ||||||
|  | //             let x := x_1 | ||||||
|  | //         } | ||||||
|  | //         lt(x, length_1) | ||||||
|  | //         { | ||||||
|  | //             let x_2 := add(x, 0x20) | ||||||
|  | //             x := x_2 | ||||||
|  | //         } | ||||||
|  | //         { | ||||||
|  | //             mstore(add(to_1, x), mload(add(from_1, x))) | ||||||
|  | //         } | ||||||
|  | //     } | ||||||
|  | // } | ||||||
| @ -0,0 +1,17 @@ | |||||||
|  | { | ||||||
|  |     let a := 1 | ||||||
|  |     a := 2 | ||||||
|  |     a := 3 | ||||||
|  |     a := 4 | ||||||
|  |     mstore(0, a) | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // ssaPlusCleanup | ||||||
|  | // { | ||||||
|  | //     let a_1 := 1 | ||||||
|  | //     let a := a_1 | ||||||
|  | //     let a_2 := 2 | ||||||
|  | //     let a_3 := 3 | ||||||
|  | //     let a_4 := 4 | ||||||
|  | //     mstore(0, a_4) | ||||||
|  | // } | ||||||
| @ -0,0 +1,17 @@ | |||||||
|  | { | ||||||
|  |     let a := 1 | ||||||
|  |     a := add(a, 2) | ||||||
|  |     a := add(a, 3) | ||||||
|  |     a := mload(add(a, 4)) | ||||||
|  |     mstore(0, a) | ||||||
|  | } | ||||||
|  | // ---- | ||||||
|  | // ssaPlusCleanup | ||||||
|  | // { | ||||||
|  | //     let a_1 := 1 | ||||||
|  | //     let a := a_1 | ||||||
|  | //     let a_2 := add(a_1, 2) | ||||||
|  | //     let a_3 := add(a_2, 3) | ||||||
|  | //     let a_4 := mload(add(a_3, 4)) | ||||||
|  | //     mstore(0, a_4) | ||||||
|  | // } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user