From 5956f1cb4da5b883a579deed74f46581354f5247 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 17 Mar 2022 16:54:42 +0100 Subject: [PATCH] Better track assignments. --- libyul/optimiser/DataFlowAnalyzer.cpp | 77 ++++++++++++++++++++------- libyul/optimiser/DataFlowAnalyzer.h | 10 ++++ 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index 5e9b33342..a938fddf7 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -89,9 +89,13 @@ void DataFlowAnalyzer::operator()(ExpressionStatement& _statement) void DataFlowAnalyzer::operator()(Assignment& _assignment) { + m_assignmentCounter++; set names; for (auto const& var: _assignment.variableNames) + { + m_state.latestAssignment[var] = m_assignmentCounter; names.emplace(var.name); + } assertThrow(_assignment.value, OptimizerException, ""); clearKnowledgeIfInvalidated(*_assignment.value); visit(*_assignment.value); @@ -123,10 +127,7 @@ void DataFlowAnalyzer::operator()(If& _if) ASTModifier::operator()(_if); if (hasFlowOutControlFlow(_if.body)) - { joinKnowledge(preState); - clearValues(assignedVariableNames(_if.body)); - } else m_state = move(preState); } @@ -142,7 +143,6 @@ void DataFlowAnalyzer::operator()(Switch& _switch) if (!hasDefaultCase(_switch)) postState = m_state; - std::set assignedVariables; for (auto& _case: _switch.cases) { m_state = preState; @@ -152,7 +152,6 @@ void DataFlowAnalyzer::operator()(Switch& _switch) { if (postState) joinKnowledge(*postState); - assignedVariables += assignedVariableNames(_case.body); postState = move(m_state); } @@ -162,7 +161,6 @@ void DataFlowAnalyzer::operator()(Switch& _switch) else // No outflowing case. m_state = {}; - clearValues(assignedVariables); } void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) @@ -189,35 +187,53 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) popScope(); } +void DataFlowAnalyzer::operator()(Break&) +{ + if (m_breakState) + joinKnowledge(*m_breakState); + m_breakState = move(m_state); + // we now have an empty state, which will clear everything. + // if this is in a conditional, it will be ignored. +} + +void DataFlowAnalyzer::operator()(Continue&) +{ + if (m_continueState) + joinKnowledge(*m_continueState); + m_continueState = move(m_state); + // we now have an empty state, which will clear everything. + // if this is in a conditional, it will be ignored. +} + void DataFlowAnalyzer::operator()(ForLoop& _for) { // If the pre block was not empty, // we would have to deal with more complicated scoping rules. assertThrow(_for.pre.statements.empty(), OptimizerException, ""); + ScopedSaveAndRestore storedBreakState(m_breakState, {}); + ScopedSaveAndRestore storedContinueState(m_continueState, {}); + ++m_loopDepth; - AssignmentsSinceContinue assignmentsSinceCont; - assignmentsSinceCont(_for.body); - - set assignedVariables = - assignedVariableNames(_for.body) + assignedVariableNames(_for.post); - clearValues(assignedVariables); - - // break/continue are tricky for storage and thus we almost always clear here. + // We are joining control flow with the future, so better clear more than too little. + clearValues(assignedVariableNames(_for.body) + assignedVariableNames(_for.post)); clearKnowledgeIfInvalidated(*_for.condition); clearKnowledgeIfInvalidated(_for.post); clearKnowledgeIfInvalidated(_for.body); visit(*_for.condition); + + State preState = m_state; + (*this)(_for.body); - clearValues(assignmentsSinceCont.names()); - clearKnowledgeIfInvalidated(_for.body); + if (m_continueState) + joinKnowledge(*m_continueState); (*this)(_for.post); - clearValues(assignedVariables); - clearKnowledgeIfInvalidated(*_for.condition); - clearKnowledgeIfInvalidated(_for.post); - clearKnowledgeIfInvalidated(_for.body); + if (m_breakState) + joinKnowledge(*m_breakState); + + joinKnowledge(preState); --m_loopDepth; } @@ -434,6 +450,27 @@ void DataFlowAnalyzer::joinKnowledge(State const& _olderState) { joinKnowledgeHelper(m_state.storage, _olderState.storage); joinKnowledgeHelper(m_state.memory, _olderState.memory); + + // TODO verify that this does not have to be symmetric, + // i.e. that variables that are only in _olderState + // do not have to be cleared here. + // We should not rely on _olderState really being an ancestor. + set variablesToClear; + for (auto it = m_state.latestAssignment.begin(); it != m_state.latestAssignment.end();) + { + auto&& [var, counter] = *it; + if ( + _olderState.latestAssignment.count(var) && + _olderState.latestAssignment.at(var) == counter + ) + ++it; + else + { + variablesToClear.emplace(var); + it = m_state.latestAssignment.erase(it); + } + } + clearValues(variablesToClear); } void DataFlowAnalyzer::joinKnowledgeHelper( diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 0a0d4d8b5..560158722 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -94,6 +94,8 @@ public: void operator()(If& _if) override; void operator()(Switch& _switch) override; void operator()(FunctionDefinition&) override; + void operator()(Break&) override; + void operator()(Continue&) override; void operator()(ForLoop&) override; void operator()(Block& _block) override; @@ -168,6 +170,8 @@ protected: private: struct State { + /// Latest assignment counter per variable. + std::map latestAssignment; /// Current values of variables, always movable. std::map value; /// m_references[a].contains(b) <=> the current expression assigned to a references b @@ -191,6 +195,12 @@ private: State m_state; protected: + std::optional m_continueState; + std::optional m_breakState; + // This is incremented for each assignment to track which variables + // have been reassigned between two joining control flows. + size_t m_assignmentCounter = 0; + KnowledgeBase m_knowledgeBase; YulString m_storeFunctionName[static_cast(StoreLoadLocation::Last) + 1];