aboutsummaryrefslogtreecommitdiffstats
path: root/libyul
diff options
context:
space:
mode:
Diffstat (limited to 'libyul')
-rw-r--r--libyul/optimiser/CommonSubexpressionEliminator.cpp5
-rw-r--r--libyul/optimiser/DataFlowAnalyzer.cpp35
-rw-r--r--libyul/optimiser/DataFlowAnalyzer.h8
-rw-r--r--libyul/optimiser/Disambiguator.h3
-rw-r--r--libyul/optimiser/FullInliner.cpp31
-rw-r--r--libyul/optimiser/FullInliner.h6
-rw-r--r--libyul/optimiser/NameDispenser.cpp31
-rw-r--r--libyul/optimiser/NameDispenser.h26
-rw-r--r--libyul/optimiser/Rematerialiser.cpp9
-rw-r--r--libyul/optimiser/SSATransform.cpp130
-rw-r--r--libyul/optimiser/SSATransform.h98
11 files changed, 329 insertions, 53 deletions
diff --git a/libyul/optimiser/CommonSubexpressionEliminator.cpp b/libyul/optimiser/CommonSubexpressionEliminator.cpp
index 23d15cad..51737097 100644
--- a/libyul/optimiser/CommonSubexpressionEliminator.cpp
+++ b/libyul/optimiser/CommonSubexpressionEliminator.cpp
@@ -50,8 +50,8 @@ void CommonSubexpressionEliminator::visit(Expression& _e)
if (m_value.at(name)->type() == typeid(Identifier))
{
string const& value = boost::get<Identifier>(*m_value.at(name)).name;
- if (inScope(value))
- _e = Identifier{locationOf(_e), value};
+ assertThrow(inScope(value), OptimizerException, "");
+ _e = Identifier{locationOf(_e), value};
}
}
}
@@ -61,6 +61,7 @@ void CommonSubexpressionEliminator::visit(Expression& _e)
for (auto const& var: m_value)
{
assertThrow(var.second, OptimizerException, "");
+ assertThrow(inScope(var.first), OptimizerException, "");
if (SyntacticalEqualityChecker::equal(_e, *var.second))
{
_e = Identifier{locationOf(_e), var.first};
diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp
index ca1e5153..7ac42c30 100644
--- a/libyul/optimiser/DataFlowAnalyzer.cpp
+++ b/libyul/optimiser/DataFlowAnalyzer.cpp
@@ -84,19 +84,19 @@ void DataFlowAnalyzer::operator()(Switch& _switch)
void DataFlowAnalyzer::operator()(FunctionDefinition& _fun)
{
- m_variableScopes.emplace_back(true);
+ pushScope(true);
for (auto const& parameter: _fun.parameters)
m_variableScopes.back().variables.insert(parameter.name);
for (auto const& var: _fun.returnVariables)
m_variableScopes.back().variables.insert(var.name);
ASTModifier::operator()(_fun);
- m_variableScopes.pop_back();
+ popScope();
}
void DataFlowAnalyzer::operator()(ForLoop& _for)
{
// Special scope handling of the pre block.
- m_variableScopes.emplace_back(false);
+ pushScope(false);
for (auto& statement: _for.pre.statements)
visit(statement);
@@ -110,16 +110,15 @@ void DataFlowAnalyzer::operator()(ForLoop& _for)
(*this)(_for.post);
clearValues(assignments.names());
-
- m_variableScopes.pop_back();
+ popScope();
}
void DataFlowAnalyzer::operator()(Block& _block)
{
size_t numScopes = m_variableScopes.size();
- m_variableScopes.emplace_back(false);
+ pushScope(false);
ASTModifier::operator()(_block);
- m_variableScopes.pop_back();
+ popScope();
assertThrow(numScopes == m_variableScopes.size(), OptimizerException, "");
}
@@ -148,7 +147,18 @@ void DataFlowAnalyzer::handleAssignment(set<string> const& _variables, Expressio
}
}
-void DataFlowAnalyzer::clearValues(set<string> const& _variables)
+void DataFlowAnalyzer::pushScope(bool _functionScope)
+{
+ m_variableScopes.emplace_back(_functionScope);
+}
+
+void DataFlowAnalyzer::popScope()
+{
+ clearValues(std::move(m_variableScopes.back().variables));
+ m_variableScopes.pop_back();
+}
+
+void DataFlowAnalyzer::clearValues(set<string> _variables)
{
// All variables that reference variables to be cleared also have to be
// cleared, but not recursively, since only the value of the original
@@ -163,16 +173,15 @@ void DataFlowAnalyzer::clearValues(set<string> const& _variables)
// This cannot be easily tested since the substitutions will be done
// one by one on the fly, and the last line will just be add(1, 1)
- set<string> variables = _variables;
// Clear variables that reference variables to be cleared.
- for (auto const& name: variables)
+ for (auto const& name: _variables)
for (auto const& ref: m_referencedBy[name])
- variables.insert(ref);
+ _variables.insert(ref);
// Clear the value and update the reference relation.
- for (auto const& name: variables)
+ for (auto const& name: _variables)
m_value.erase(name);
- for (auto const& name: variables)
+ for (auto const& name: _variables)
{
for (auto const& ref: m_references[name])
m_referencedBy[ref].erase(name);
diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h
index f998eadf..95671467 100644
--- a/libyul/optimiser/DataFlowAnalyzer.h
+++ b/libyul/optimiser/DataFlowAnalyzer.h
@@ -56,9 +56,15 @@ protected:
/// Registers the assignment.
void handleAssignment(std::set<std::string> const& _names, Expression* _value);
+ /// Creates a new inner scope.
+ void pushScope(bool _functionScope);
+
+ /// Removes the innermost scope and clears all variables in it.
+ void popScope();
+
/// Clears information about the values assigned to the given variables,
/// for example at points where control flow is merged.
- void clearValues(std::set<std::string> const& _names);
+ void clearValues(std::set<std::string> _names);
/// Returns true iff the variable is in scope.
bool inScope(std::string const& _variableName) const;
diff --git a/libyul/optimiser/Disambiguator.h b/libyul/optimiser/Disambiguator.h
index e16ebfbf..74a491ab 100644
--- a/libyul/optimiser/Disambiguator.h
+++ b/libyul/optimiser/Disambiguator.h
@@ -47,9 +47,8 @@ public:
solidity::assembly::AsmAnalysisInfo const& _analysisInfo,
std::set<std::string> const& _externallyUsedIdentifiers = {}
):
- m_info(_analysisInfo), m_externallyUsedIdentifiers(_externallyUsedIdentifiers)
+ m_info(_analysisInfo), m_externallyUsedIdentifiers(_externallyUsedIdentifiers), m_nameDispenser(m_externallyUsedIdentifiers)
{
- m_nameDispenser.m_usedNames = m_externallyUsedIdentifiers;
}
protected:
diff --git a/libyul/optimiser/FullInliner.cpp b/libyul/optimiser/FullInliner.cpp
index aa069506..280d625a 100644
--- a/libyul/optimiser/FullInliner.cpp
+++ b/libyul/optimiser/FullInliner.cpp
@@ -40,13 +40,9 @@ using namespace dev;
using namespace dev::yul;
using namespace dev::solidity;
-FullInliner::FullInliner(Block& _ast):
- m_ast(_ast)
+FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser):
+ m_ast(_ast), m_nameDispenser(_dispenser)
{
- assertThrow(m_ast.statements.size() >= 1, OptimizerException, "");
- assertThrow(m_ast.statements.front().type() == typeid(Block), OptimizerException, "");
- m_nameDispenser.m_usedNames = NameCollector(m_ast).names();
-
// Determine constants
SSAValueTracker tracker;
tracker(m_ast);
@@ -55,10 +51,11 @@ FullInliner::FullInliner(Block& _ast):
m_constants.insert(ssaValue.first);
map<string, size_t> references = ReferencesCounter::countReferences(m_ast);
- for (size_t i = 1; i < m_ast.statements.size(); ++i)
+ for (auto& statement: m_ast.statements)
{
- assertThrow(m_ast.statements.at(i).type() == typeid(FunctionDefinition), OptimizerException, "");
- FunctionDefinition& fun = boost::get<FunctionDefinition>(m_ast.statements.at(i));
+ if (statement.type() != typeid(FunctionDefinition))
+ continue;
+ FunctionDefinition& fun = boost::get<FunctionDefinition>(statement);
m_functions[fun.name] = &fun;
// Always inline functions that are only called once.
if (references[fun.name] == 1)
@@ -69,9 +66,10 @@ FullInliner::FullInliner(Block& _ast):
void FullInliner::run()
{
- assertThrow(m_ast.statements[0].type() == typeid(Block), OptimizerException, "");
+ for (auto& statement: m_ast.statements)
+ if (statement.type() == typeid(Block))
+ handleBlock("", boost::get<Block>(statement));
- handleBlock("", boost::get<Block>(m_ast.statements[0]));
// TODO it might be good to determine a visiting order:
// first handle functions that are called from many places.
for (auto const& fun: m_functions)
@@ -157,7 +155,7 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC
// helper function to create a new variable that is supposed to model
// an existing variable.
auto newVariable = [&](TypedName const& _existingVariable, Expression* _value) {
- string newName = m_nameDispenser.newName(function.name + "_" + _existingVariable.name);
+ string newName = m_nameDispenser.newName(_existingVariable.name, function.name);
variableReplacements[_existingVariable.name] = newName;
VariableDeclaration varDecl{_funCall.location, {{_funCall.location, newName, _existingVariable.type}}, {}};
if (_value)
@@ -170,7 +168,7 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC
for (auto const& var: function.returnVariables)
newVariable(var, nullptr);
- Statement newBody = BodyCopier(m_nameDispenser, function.name + "_", variableReplacements)(function.body);
+ Statement newBody = BodyCopier(m_nameDispenser, function.name, variableReplacements)(function.body);
newStatements += std::move(boost::get<Block>(newBody).statements);
boost::apply_visitor(GenericFallbackVisitor<Assignment, VariableDeclaration>{
@@ -203,15 +201,10 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC
return newStatements;
}
-string InlineModifier::newName(string const& _prefix)
-{
- return m_nameDispenser.newName(_prefix);
-}
-
Statement BodyCopier::operator()(VariableDeclaration const& _varDecl)
{
for (auto const& var: _varDecl.variables)
- m_variableReplacements[var.name] = m_nameDispenser.newName(m_varNamePrefix + var.name);
+ m_variableReplacements[var.name] = m_nameDispenser.newName(var.name, m_varNamePrefix);
return ASTCopier::operator()(_varDecl);
}
diff --git a/libyul/optimiser/FullInliner.h b/libyul/optimiser/FullInliner.h
index 513ffc93..5ecfb57a 100644
--- a/libyul/optimiser/FullInliner.h
+++ b/libyul/optimiser/FullInliner.h
@@ -71,7 +71,7 @@ class NameCollector;
class FullInliner: public ASTModifier
{
public:
- explicit FullInliner(Block& _ast);
+ explicit FullInliner(Block& _ast, NameDispenser& _dispenser);
void run();
@@ -94,7 +94,7 @@ private:
/// Variables that are constants (used for inlining heuristic)
std::set<std::string> m_constants;
std::map<std::string, size_t> m_functionSizes;
- NameDispenser m_nameDispenser;
+ NameDispenser& m_nameDispenser;
};
/**
@@ -116,8 +116,6 @@ private:
boost::optional<std::vector<Statement>> tryInlineStatement(Statement& _statement);
std::vector<Statement> performInline(Statement& _statement, FunctionCall& _funCall);
- std::string newName(std::string const& _prefix);
-
std::string m_currentFunction;
FullInliner& m_driver;
NameDispenser& m_nameDispenser;
diff --git a/libyul/optimiser/NameDispenser.cpp b/libyul/optimiser/NameDispenser.cpp
index f7385471..d3f10bc2 100644
--- a/libyul/optimiser/NameDispenser.cpp
+++ b/libyul/optimiser/NameDispenser.cpp
@@ -20,18 +20,43 @@
#include <libyul/optimiser/NameDispenser.h>
+#include <libyul/optimiser/NameCollector.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+
using namespace std;
using namespace dev;
using namespace dev::yul;
-string NameDispenser::newName(string const& _prefix)
+NameDispenser::NameDispenser(Block const& _ast):
+ NameDispenser(NameCollector(_ast).names())
+{
+}
+
+NameDispenser::NameDispenser(set<string> _usedNames):
+ m_usedNames(std::move(_usedNames))
+{
+}
+
+string NameDispenser::newName(string const& _nameHint, string const& _context)
+{
+ // Shortening rules: Use a suffix of _prefix and a prefix of _context.
+ string prefix = _nameHint;
+
+ if (!_context.empty())
+ prefix = _context.substr(0, 10) + "_" + prefix;
+
+ return newNameInternal(prefix);
+}
+
+string NameDispenser::newNameInternal(string const& _nameHint)
{
- string name = _prefix;
size_t suffix = 0;
+ string name = _nameHint;
while (name.empty() || m_usedNames.count(name))
{
suffix++;
- name = _prefix + "_" + to_string(suffix);
+ name = _nameHint + "_" + to_string(suffix);
}
m_usedNames.insert(name);
return name;
diff --git a/libyul/optimiser/NameDispenser.h b/libyul/optimiser/NameDispenser.h
index 64ec318f..5fbf5f8e 100644
--- a/libyul/optimiser/NameDispenser.h
+++ b/libyul/optimiser/NameDispenser.h
@@ -19,6 +19,8 @@
*/
#pragma once
+#include <libyul/ASTDataForward.h>
+
#include <set>
#include <string>
@@ -27,9 +29,29 @@ namespace dev
namespace yul
{
-struct NameDispenser
+/**
+ * Optimizer component that can be used to generate new names that
+ * do not conflict with existing names.
+ *
+ * Tries to keep names short and appends decimals to disambiguate.
+ */
+class NameDispenser
{
- std::string newName(std::string const& _prefix);
+public:
+ /// Initialize the name dispenser with all the names used in the given AST.
+ explicit NameDispenser(Block const& _ast);
+ /// Initialize the name dispenser with the given used names.
+ explicit NameDispenser(std::set<std::string> _usedNames);
+
+ /// @returns a currently unused name that should be similar to _nameHint
+ /// and prefixed by _context if present.
+ /// If the resulting name would be too long, trims the context at the end
+ /// and the name hint at the start.
+ std::string newName(std::string const& _nameHint, std::string const& _context = {});
+
+private:
+ std::string newNameInternal(std::string const& _nameHint);
+
std::set<std::string> m_usedNames;
};
diff --git a/libyul/optimiser/Rematerialiser.cpp b/libyul/optimiser/Rematerialiser.cpp
index dd6653ea..a99db0b6 100644
--- a/libyul/optimiser/Rematerialiser.cpp
+++ b/libyul/optimiser/Rematerialiser.cpp
@@ -38,16 +38,11 @@ void Rematerialiser::visit(Expression& _e)
if (m_value.count(identifier.name))
{
string name = identifier.name;
- bool expressionValid = true;
for (auto const& ref: m_references[name])
- if (!inScope(ref))
- {
- expressionValid = false;
- break;
- }
+ assertThrow(inScope(ref), OptimizerException, "");
assertThrow(m_value.at(name), OptimizerException, "");
auto const& value = *m_value.at(name);
- if (expressionValid && CodeSize::codeSize(value) <= 7)
+ if (CodeSize::codeSize(value) <= 7)
_e = (ASTCopier{}).translate(value);
}
}
diff --git a/libyul/optimiser/SSATransform.cpp b/libyul/optimiser/SSATransform.cpp
new file mode 100644
index 00000000..9db7bd03
--- /dev/null
+++ b/libyul/optimiser/SSATransform.cpp
@@ -0,0 +1,130 @@
+/*
+ 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 turns subsequent assignments to variable declarations
+ * and assignments.
+ */
+
+#include <libyul/optimiser/SSATransform.h>
+
+#include <libyul/optimiser/NameCollector.h>
+#include <libyul/optimiser/NameDispenser.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+
+#include <libdevcore/CommonData.h>
+
+
+using namespace std;
+using namespace dev;
+using namespace dev::yul;
+using namespace dev::solidity;
+
+void SSATransform::operator()(Identifier& _identifier)
+{
+ if (m_currentVariableValues.count(_identifier.name))
+ _identifier.name = m_currentVariableValues[_identifier.name];
+}
+
+void SSATransform::operator()(ForLoop& _for)
+{
+ // This will clear the current value in case of a reassignment inside the
+ // init part, although the new variable would still be in scope inside the whole loop.
+ // This small inefficiency is fine if we move the pre part of all for loops out
+ // of the for loop.
+ (*this)(_for.pre);
+
+ Assignments assignments;
+ assignments(_for.body);
+ assignments(_for.post);
+ for (auto const& var: assignments.names())
+ m_currentVariableValues.erase(var);
+
+ visit(*_for.condition);
+ (*this)(_for.body);
+ (*this)(_for.post);
+}
+
+
+void SSATransform::operator()(Block& _block)
+{
+ set<string> variablesToClearAtEnd;
+
+ // Creates a new variable (and returns its declaration) with value _value
+ // and replaces _value by a reference to that new variable.
+ auto replaceByNew = [&](SourceLocation _loc, string _varName, string _type, shared_ptr<Expression>& _value) -> VariableDeclaration
+ {
+ string newName = m_nameDispenser.newName(_varName);
+ m_currentVariableValues[_varName] = newName;
+ variablesToClearAtEnd.insert(_varName);
+ shared_ptr<Expression> v = make_shared<Expression>(Identifier{_loc, newName});
+ _value.swap(v);
+ return VariableDeclaration{_loc, {TypedName{_loc, std::move(newName), std::move(_type)}}, std::move(v)};
+ };
+
+ iterateReplacing(
+ _block.statements,
+ [&](Statement& _s) -> boost::optional<vector<Statement>>
+ {
+ if (_s.type() == typeid(VariableDeclaration))
+ {
+ VariableDeclaration& varDecl = boost::get<VariableDeclaration>(_s);
+ if (varDecl.value)
+ visit(*varDecl.value);
+ if (varDecl.variables.size() != 1 || !m_variablesToReplace.count(varDecl.variables.front().name))
+ return {};
+ // Replace "let a := v" by "let a_1 := v let a := v"
+ VariableDeclaration newVarDecl = replaceByNew(
+ varDecl.location,
+ varDecl.variables.front().name,
+ varDecl.variables.front().type,
+ varDecl.value
+ );
+ return vector<Statement>{std::move(newVarDecl), std::move(varDecl)};
+ }
+ else if (_s.type() == typeid(Assignment))
+ {
+ Assignment& assignment = boost::get<Assignment>(_s);
+ visit(*assignment.value);
+ if (assignment.variableNames.size() != 1)
+ return {};
+ assertThrow(m_variablesToReplace.count(assignment.variableNames.front().name), OptimizerException, "");
+ // Replace "a := v" by "let a_1 := v a := v"
+ VariableDeclaration newVarDecl = replaceByNew(
+ assignment.location,
+ assignment.variableNames.front().name,
+ {}, // TODO determine type
+ assignment.value
+ );
+ return vector<Statement>{std::move(newVarDecl), std::move(assignment)};
+ }
+ else
+ visit(_s);
+ return {};
+ }
+ );
+ for (auto const& var: variablesToClearAtEnd)
+ m_currentVariableValues.erase(var);
+}
+
+void SSATransform::run(Block& _ast, NameDispenser& _nameDispenser)
+{
+ Assignments assignments;
+ assignments(_ast);
+ SSATransform{_nameDispenser, assignments.names()}(_ast);
+}
+
diff --git a/libyul/optimiser/SSATransform.h b/libyul/optimiser/SSATransform.h
new file mode 100644
index 00000000..2adc657d
--- /dev/null
+++ b/libyul/optimiser/SSATransform.h
@@ -0,0 +1,98 @@
+/*
+ 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 turns subsequent assignments to variable declarations
+ * and assignments.
+ */
+#pragma once
+
+#include <libyul/ASTDataForward.h>
+
+#include <libyul/optimiser/ASTWalker.h>
+
+#include <vector>
+
+namespace dev
+{
+namespace yul
+{
+
+class NameDispenser;
+
+/**
+ * Optimizer stage that tries to replace repeated assignments to
+ * existing variables by declarations of new variables as much as
+ * possible.
+ * The reassignments are still there, but all references to the
+ * reassigned variables are replaced by the newly declared variables.
+ *
+ * Example:
+ * {
+ * let a := 1
+ * mstore(a, 2)
+ * a := 3
+ * }
+ * is transformed to
+ * {
+ * let a_1 := 1
+ * let a := a_1
+ * mstore(a_1, 2)
+ * let a_3 := 3
+ * a := a_3
+ * }
+ *
+ * Exact semantics:
+ *
+ * For any variable a that is assigned to somewhere in the code (assignment with
+ * declaration does not count) perform the following transforms:
+ * - replace "let a := v" by "let a_1 := v let a := a_1"
+ * - replace "a := v" by "let a_1 := v a := a_1"
+ * Furthermore, always note the current variable/value assigned to a and replace each
+ * reference to a by this variable.
+ * The current value mapping is cleared for a variable a at the end of each block
+ * in which it was assigned and just after the for loop init block if it is assigned
+ * inside the for loop.
+ *
+ * After this stage, redundantAssignmentRemover is recommended to remove the unnecessary
+ * intermediate assignments.
+ *
+ * This stage provides best results if CSE is run right before it, because
+ * then it does not generate excessive amounts of variables.
+ *
+ * TODO Which transforms are required to keep this idempotent?
+ */
+class SSATransform: public ASTModifier
+{
+public:
+ void operator()(Identifier&) override;
+ void operator()(ForLoop&) override;
+ void operator()(Block& _block) override;
+
+ static void run(Block& _ast, NameDispenser& _nameDispenser);
+
+private:
+ explicit SSATransform(NameDispenser& _nameDispenser, std::set<std::string> const& _variablesToReplace):
+ m_nameDispenser(_nameDispenser), m_variablesToReplace(_variablesToReplace)
+ { }
+
+ NameDispenser& m_nameDispenser;
+ std::set<std::string> const& m_variablesToReplace;
+ std::map<std::string, std::string> m_currentVariableValues;
+};
+
+}
+}