diff options
Diffstat (limited to 'libyul')
-rw-r--r-- | libyul/optimiser/RedundantAssignEliminator.cpp | 193 | ||||
-rw-r--r-- | libyul/optimiser/RedundantAssignEliminator.h | 185 |
2 files changed, 378 insertions, 0 deletions
diff --git a/libyul/optimiser/RedundantAssignEliminator.cpp b/libyul/optimiser/RedundantAssignEliminator.cpp new file mode 100644 index 00000000..478858e4 --- /dev/null +++ b/libyul/optimiser/RedundantAssignEliminator.cpp @@ -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); +} diff --git a/libyul/optimiser/RedundantAssignEliminator.h b/libyul/optimiser/RedundantAssignEliminator.h new file mode 100644 index 00000000..52092138 --- /dev/null +++ b/libyul/optimiser/RedundantAssignEliminator.h @@ -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; +}; + +} +} |