diff --git a/libyul/optimiser/SSATransform.cpp b/libyul/optimiser/SSATransform.cpp index 68726c486..18aa42c24 100644 --- a/libyul/optimiser/SSATransform.cpp +++ b/libyul/optimiser/SSATransform.cpp @@ -32,45 +32,32 @@ using namespace dev; using namespace langutil; using namespace yul; -void SSATransform::operator()(Identifier& _identifier) +namespace { - if (m_currentVariableValues.count(_identifier.name)) - _identifier.name = m_currentVariableValues[_identifier.name]; -} -void SSATransform::operator()(ForLoop& _for) +/** + * First step of SSA transform: Introduces new SSA variables for each assignment or + * declaration of a variable to be replaced. + */ +class IntroduceSSA: public ASTModifier { - // 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); +public: + explicit IntroduceSSA(NameDispenser& _nameDispenser, set const& _variablesToReplace): + m_nameDispenser(_nameDispenser), m_variablesToReplace(_variablesToReplace) + { } - Assignments assignments; - assignments(_for.body); - assignments(_for.post); - for (auto const& var: assignments.names()) - m_currentVariableValues.erase(var); + void operator()(Block& _block) override; - visit(*_for.condition); - (*this)(_for.body); - (*this)(_for.post); -} +private: + NameDispenser& m_nameDispenser; + /// This is a set of all variables that are assigned to anywhere in the code. + /// Variables that are only declared but never re-assigned are not touched. + set const& m_variablesToReplace; +}; -void SSATransform::operator()(Block& _block) +void IntroduceSSA::operator()(Block& _block) { - set variablesToClearAtEnd; - - // Creates a new variable and stores it in the current variable value map. - auto newVariable = [&](YulString _varName) -> YulString - { - YulString newName = m_nameDispenser.newName(_varName); - m_currentVariableValues[_varName] = newName; - variablesToClearAtEnd.emplace(_varName); - return newName; - }; - iterateReplacing( _block.statements, [&](Statement& _s) -> boost::optional> @@ -96,8 +83,8 @@ void SSATransform::operator()(Block& _block) TypedNameList newVariables; for (auto const& var: varDecl.variables) { - YulString newName = newVariable(var.name); YulString oldName = var.name; + YulString newName = m_nameDispenser.newName(oldName); newVariables.emplace_back(TypedName{loc, newName, {}}); statements.emplace_back(VariableDeclaration{ loc, @@ -123,8 +110,8 @@ void SSATransform::operator()(Block& _block) TypedNameList newVariables; for (auto const& var: assignment.variableNames) { - YulString newName = newVariable(var.name); YulString oldName = var.name; + YulString newName = m_nameDispenser.newName(oldName); newVariables.emplace_back(TypedName{loc, newName, {}}); statements.emplace_back(Assignment{ loc, @@ -140,14 +127,111 @@ void SSATransform::operator()(Block& _block) return {}; } ); - for (auto const& var: variablesToClearAtEnd) +} + +/** + * Second step of SSA transform: Replace the references to variables-to-be-replaced + * by their current values. + */ +class PropagateValues: public ASTModifier +{ +public: + explicit PropagateValues(set const& _variablesToReplace): + m_variablesToReplace(_variablesToReplace) + { } + + void operator()(Identifier& _identifier) override; + void operator()(VariableDeclaration& _varDecl) override; + void operator()(Assignment& _assignment) override; + void operator()(ForLoop& _for) override; + void operator()(Block& _block) override; + +private: + /// This is a set of all variables that are assigned to anywhere in the code. + /// Variables that are only declared but never re-assigned are not touched. + set const& m_variablesToReplace; + map m_currentVariableValues; + set m_clearAtEndOfBlock; +}; + +void PropagateValues::operator()(Identifier& _identifier) +{ + if (m_currentVariableValues.count(_identifier.name)) + _identifier.name = m_currentVariableValues[_identifier.name]; +} + +void PropagateValues::operator()(VariableDeclaration& _varDecl) +{ + ASTModifier::operator()(_varDecl); + + if (_varDecl.variables.size() != 1) + return; + YulString name = _varDecl.variables.front().name; + if (!m_variablesToReplace.count(name)) + return; + + yulAssert(_varDecl.value->type() == typeid(Identifier), ""); + m_currentVariableValues[name] = boost::get(*_varDecl.value).name; + m_clearAtEndOfBlock.insert(name); +} + + +void PropagateValues::operator()(Assignment& _assignment) +{ + visit(*_assignment.value); + + if (_assignment.variableNames.size() != 1) + return; + YulString name = _assignment.variableNames.front().name; + if (!m_variablesToReplace.count(name)) + return; + + yulAssert(_assignment.value->type() == typeid(Identifier), ""); + m_currentVariableValues[name] = boost::get(*_assignment.value).name; + m_clearAtEndOfBlock.insert(name); +} + +void PropagateValues::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 PropagateValues::operator()(Block& _block) +{ + set clearAtParentBlock = std::move(m_clearAtEndOfBlock); + m_clearAtEndOfBlock.clear(); + + ASTModifier::operator()(_block); + + for (auto const& var: m_clearAtEndOfBlock) + m_currentVariableValues.erase(var); + + m_clearAtEndOfBlock = std::move(clearAtParentBlock); +} + } void SSATransform::run(Block& _ast, NameDispenser& _nameDispenser) { Assignments assignments; assignments(_ast); - SSATransform{_nameDispenser, assignments.names()}(_ast); + IntroduceSSA{_nameDispenser, assignments.names()}(_ast); + PropagateValues{assignments.names()}(_ast); } + diff --git a/libyul/optimiser/SSATransform.h b/libyul/optimiser/SSATransform.h index 1a367afc7..66f5e3112 100644 --- a/libyul/optimiser/SSATransform.h +++ b/libyul/optimiser/SSATransform.h @@ -75,22 +75,7 @@ class NameDispenser; 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 const& _variablesToReplace): - m_nameDispenser(_nameDispenser), m_variablesToReplace(_variablesToReplace) - { } - - NameDispenser& m_nameDispenser; - /// This is a set of all variables that are assigned to anywhere in the code. - /// Variables that are only declared but never re-assigned are not touched. - std::set const& m_variablesToReplace; - std::map m_currentVariableValues; }; } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul b/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul index df26cae3e..c9523237e 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul @@ -23,8 +23,8 @@ // let b := mload(1) // for { } lt(mload(a), mload(b)) { a := mload(b) } // { -// let b_3 := mload(a) -// let a_6 := mload(b_3) -// b := mload(a_6) +// let b_4 := mload(a) +// let a_7 := mload(b_4) +// b := mload(a_7) // } // } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul b/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul index d5e1a5404..cac4295d9 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul @@ -36,12 +36,12 @@ // } // a // { -// let a_7 := add(a, 6) -// a := a_7 +// let a_6 := add(a, 6) +// a := a_6 // } // { -// let a_6 := add(a, 12) -// a := a_6 +// let a_7 := add(a, 12) +// a := a_7 // } // let a_8 := add(a, 8) // a := a_8