diff options
author | chriseth <chris@ethereum.org> | 2018-02-06 19:08:59 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-06 19:08:59 +0800 |
commit | 6b917eb528fcbcbb11e81810c8f6bd4d554f21e1 (patch) | |
tree | e55b307bb977c4d9e444d478b45260085bfd461d | |
parent | 5437457f46cb3a06f149af154f66334350ec439b (diff) | |
parent | 0b8060648eddceedbedebff27eb8a3362f95fe2d (diff) | |
download | dexon-solidity-6b917eb528fcbcbb11e81810c8f6bd4d554f21e1.tar.gz dexon-solidity-6b917eb528fcbcbb11e81810c8f6bd4d554f21e1.tar.zst dexon-solidity-6b917eb528fcbcbb11e81810c8f6bd4d554f21e1.zip |
Merge pull request #3351 from ethereum/remove_unused
Remove unused variables and functions
-rw-r--r-- | libjulia/optimiser/ASTWalker.h | 4 | ||||
-rw-r--r-- | libjulia/optimiser/NameCollector.cpp | 25 | ||||
-rw-r--r-- | libjulia/optimiser/NameCollector.h | 20 | ||||
-rw-r--r-- | libjulia/optimiser/UnusedPruner.cpp | 116 | ||||
-rw-r--r-- | libjulia/optimiser/UnusedPruner.h | 67 | ||||
-rw-r--r-- | libjulia/optimiser/Utilities.cpp | 39 | ||||
-rw-r--r-- | libjulia/optimiser/Utilities.h | 34 | ||||
-rw-r--r-- | test/libjulia/UnusedPruner.cpp | 129 |
8 files changed, 431 insertions, 3 deletions
diff --git a/libjulia/optimiser/ASTWalker.h b/libjulia/optimiser/ASTWalker.h index dbf8194b..97891381 100644 --- a/libjulia/optimiser/ASTWalker.h +++ b/libjulia/optimiser/ASTWalker.h @@ -71,8 +71,8 @@ protected: template <class T> void walkVector(T const& _statements) { - for (auto const& st: _statements) - visit(st); + for (auto const& statement: _statements) + visit(statement); } }; diff --git a/libjulia/optimiser/NameCollector.cpp b/libjulia/optimiser/NameCollector.cpp index 7b4c4793..f94104b7 100644 --- a/libjulia/optimiser/NameCollector.cpp +++ b/libjulia/optimiser/NameCollector.cpp @@ -42,3 +42,28 @@ void NameCollector::operator ()(FunctionDefinition const& _funDef) m_names.insert(ret.name); ASTWalker::operator ()(_funDef); } + +void ReferencesCounter::operator()(Identifier const& _identifier) +{ + ++m_references[_identifier.name]; +} + +void ReferencesCounter::operator()(FunctionCall const& _funCall) +{ + ++m_references[_funCall.functionName.name]; + ASTWalker::operator()(_funCall); +} + +map<string, size_t> ReferencesCounter::countReferences(Block const& _block) +{ + ReferencesCounter counter; + counter(_block); + return counter.references(); +} + +map<string, size_t> ReferencesCounter::countReferences(Expression const& _expression) +{ + ReferencesCounter counter; + counter.visit(_expression); + return counter.references(); +} diff --git a/libjulia/optimiser/NameCollector.h b/libjulia/optimiser/NameCollector.h index b7e38f46..7fe386f7 100644 --- a/libjulia/optimiser/NameCollector.h +++ b/libjulia/optimiser/NameCollector.h @@ -15,7 +15,7 @@ along with solidity. If not, see <http://www.gnu.org/licenses/>. */ /** - * Specific AST walker that collects all defined names. + * Specific AST walkers that collect facts about identifiers and definitions. */ #pragma once @@ -48,5 +48,23 @@ private: std::map<std::string, FunctionDefinition const*> m_functions; }; +/** + * Specific AST walker that counts all references to all declarations. + */ +class ReferencesCounter: public ASTWalker +{ +public: + using ASTWalker::operator (); + virtual void operator()(Identifier const& _identifier); + virtual void operator()(FunctionCall const& _funCall); + + static std::map<std::string, size_t> countReferences(Block const& _block); + static std::map<std::string, size_t> countReferences(Expression const& _expression); + + std::map<std::string, size_t> const& references() const { return m_references; } +private: + std::map<std::string, size_t> m_references; +}; + } } diff --git a/libjulia/optimiser/UnusedPruner.cpp b/libjulia/optimiser/UnusedPruner.cpp new file mode 100644 index 00000000..50038431 --- /dev/null +++ b/libjulia/optimiser/UnusedPruner.cpp @@ -0,0 +1,116 @@ +/*( + 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/>. +*/ +/** + * Optimisation stage that removes unused variables and functions. + */ + +#include <libjulia/optimiser/UnusedPruner.h> + +#include <libjulia/optimiser/NameCollector.h> +#include <libjulia/optimiser/Semantics.h> +#include <libjulia/optimiser/Utilities.h> + +#include <libsolidity/inlineasm/AsmData.h> + +#include <boost/algorithm/cxx11/none_of.hpp> + +using namespace std; +using namespace dev; +using namespace dev::julia; + +UnusedPruner::UnusedPruner(Block& _ast) +{ + ReferencesCounter counter; + counter(_ast); + + m_references = counter.references(); +} + +void UnusedPruner::operator()(Block& _block) +{ + for (auto&& statement: _block.statements) + if (statement.type() == typeid(FunctionDefinition)) + { + FunctionDefinition& funDef = boost::get<FunctionDefinition>(statement); + if (!used(funDef.name)) + { + subtractReferences(ReferencesCounter::countReferences(funDef.body)); + statement = Block{std::move(funDef.location), {}}; + } + } + else if (statement.type() == typeid(VariableDeclaration)) + { + VariableDeclaration& varDecl = boost::get<VariableDeclaration>(statement); + // Multi-variable declarations are special. We can only remove it + // if all vairables are unused and the right-hand-side is either + // movable or it return a single value. In the latter case, we + // replace `let a := f()` by `pop(f())` (in pure IULIA, this will be + // `drop(f())`). + if (boost::algorithm::none_of( + varDecl.variables, + [=](TypedName const& _typedName) { return used(_typedName.name); } + )) + { + if (!varDecl.value) + statement = Block{std::move(varDecl.location), {}}; + else if (MovableChecker(*varDecl.value).movable()) + { + subtractReferences(ReferencesCounter::countReferences(*varDecl.value)); + statement = Block{std::move(varDecl.location), {}}; + } + else if (varDecl.variables.size() == 1) + // In pure IULIA, this should be replaced by a function call to `drop` + // instead of `pop`. + statement = ExpressionStatement{varDecl.location, FunctionalInstruction{ + varDecl.location, + solidity::Instruction::POP, + {*std::move(varDecl.value)} + }}; + } + } + + removeEmptyBlocks(_block); + + ASTModifier::operator()(_block); +} + +void UnusedPruner::runUntilStabilised(Block& _ast) +{ + while (true) + { + UnusedPruner pruner(_ast); + pruner(_ast); + if (!pruner.shouldRunAgain()) + return; + } +} + +bool UnusedPruner::used(string const& _name) const +{ + return m_references.count(_name) && m_references.at(_name) > 0; +} + +void UnusedPruner::subtractReferences(map<string, size_t> const& _subtrahend) +{ + for (auto const& ref: _subtrahend) + { + solAssert(m_references.count(ref.first), ""); + solAssert(m_references.at(ref.first) >= ref.second, ""); + m_references[ref.first] -= ref.second; + m_shouldRunAgain = true; + } +} diff --git a/libjulia/optimiser/UnusedPruner.h b/libjulia/optimiser/UnusedPruner.h new file mode 100644 index 00000000..73e8de7c --- /dev/null +++ b/libjulia/optimiser/UnusedPruner.h @@ -0,0 +1,67 @@ +/* + 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/>. +*/ +/** + * Optimisation stage that removes unused variables and functions. + */ + +#pragma once + +#include <libjulia/optimiser/ASTWalker.h> + +#include <string> +#include <map> +#include <set> + +namespace dev +{ +namespace julia +{ + +/** + * Optimisation stage that removes unused variables and functions. + * + * TODO: Also remove intermediate variable assignments from movable expressions + * which are not referenced until after the next assignment to the same variable. + * + * Note that this does not remove circular references. + * + * Prerequisite: Disambiguator + */ +class UnusedPruner: public ASTModifier +{ +public: + explicit UnusedPruner(Block& _ast); + + using ASTModifier::operator(); + virtual void operator()(Block& _block) override; + + // @returns true iff the code changed in the previous run. + bool shouldRunAgain() const { return m_shouldRunAgain; } + + // Run the pruner until the code does not change anymore. + static void runUntilStabilised(Block& _ast); + +private: + bool used(std::string const& _name) const; + void subtractReferences(std::map<std::string, size_t> const& _subtrahend); + + bool m_shouldRunAgain = false; + std::map<std::string, size_t> m_references; +}; + +} +} diff --git a/libjulia/optimiser/Utilities.cpp b/libjulia/optimiser/Utilities.cpp new file mode 100644 index 00000000..ff108b89 --- /dev/null +++ b/libjulia/optimiser/Utilities.cpp @@ -0,0 +1,39 @@ +/*( + 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/>. +*/ +/** + * Some useful snippets for the optimiser. + */ + +#include <libjulia/optimiser/Utilities.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::julia; + +void dev::julia::removeEmptyBlocks(Block& _block) +{ + auto isEmptyBlock = [](Statement const& _st) -> bool { + return _st.type() == typeid(Block) && boost::get<Block>(_st).statements.empty(); + }; + boost::range::remove_erase_if(_block.statements, isEmptyBlock); +} diff --git a/libjulia/optimiser/Utilities.h b/libjulia/optimiser/Utilities.h new file mode 100644 index 00000000..88ba3f47 --- /dev/null +++ b/libjulia/optimiser/Utilities.h @@ -0,0 +1,34 @@ +/* + 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/>. +*/ +/** + * Small useful snippets for the optimiser. + */ + +#pragma once + +#include <libjulia/ASTDataForward.h> + +namespace dev +{ +namespace julia +{ + +/// Removes statements that are just empty blocks (non-recursive). +void removeEmptyBlocks(Block& _block); + +} +} diff --git a/test/libjulia/UnusedPruner.cpp b/test/libjulia/UnusedPruner.cpp new file mode 100644 index 00000000..b86a54b3 --- /dev/null +++ b/test/libjulia/UnusedPruner.cpp @@ -0,0 +1,129 @@ +/* + 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/>. +*/ +/** + * @date 2017 + * Unit tests for the pruning of unused variables and functions. + */ + +#include <test/libjulia/Common.h> + +#include <libjulia/optimiser/UnusedPruner.h> + +#include <libsolidity/inlineasm/AsmPrinter.h> + +#include <boost/test/unit_test.hpp> + +#include <boost/range/adaptors.hpp> +#include <boost/algorithm/string/join.hpp> + +using namespace std; +using namespace dev; +using namespace dev::julia; +using namespace dev::julia::test; +using namespace dev::solidity; + + +#define CHECK(_original, _expectation)\ +do\ +{\ + assembly::AsmPrinter p;\ + Block b = disambiguate(_original, false);\ + UnusedPruner::runUntilStabilised(b);\ + string result = p(b);\ + BOOST_CHECK_EQUAL(result, format(_expectation, false));\ +}\ +while(false) + +BOOST_AUTO_TEST_SUITE(IuliaUnusedPruner) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + CHECK("{ }", "{ }"); +} + +BOOST_AUTO_TEST_CASE(trivial) +{ + CHECK( + "{ let a := 1 let b := 1 mstore(0, 1) }", + "{ mstore(0, 1) }" + ); +} + +BOOST_AUTO_TEST_CASE(multi_declarations) +{ + CHECK( + "{ let x, y }", + "{ }" + ); +} + +BOOST_AUTO_TEST_CASE(multi_assignments) +{ + CHECK( + "{ let x, y x := 1 y := 2 }", + "{ let x, y x := 1 y := 2 }" + ); +} + +BOOST_AUTO_TEST_CASE(multi_partial_assignments) +{ + CHECK( + "{ let x, y x := 1 }", + "{ let x, y x := 1 }" + ); +} + +BOOST_AUTO_TEST_CASE(functions) +{ + CHECK( + "{ function f() { let a := 1 } function g() { f() } }", + "{ }" + ); +} + +BOOST_AUTO_TEST_CASE(intermediate_assignment) +{ + CHECK( + "{ let a := 1 a := 4 let b := 1 }", + "{ let a := 1 a := 4 }" + ); +} + +BOOST_AUTO_TEST_CASE(intermediate_multi_assignment){ + CHECK( + "{ let a, b function f() -> x { } a := f() b := 1 }", + "{ let a, b function f() -> x { } a := f() b := 1 }" + ); +} + +BOOST_AUTO_TEST_CASE(multi_declare) +{ + CHECK( + "{ function f() -> x, y { } let a, b := f() }", + "{ function f() -> x, y { } let a, b := f() }" + ); +} + +BOOST_AUTO_TEST_CASE(multi_assign) +{ + CHECK( + "{ let a let b function f() -> x, y { } a, b := f() }", + "{ let a let b function f() -> x, y { } a, b := f() }" + ); +} + +BOOST_AUTO_TEST_SUITE_END() |