Skip to content

Commit 5c0ba6c

Browse files
committed
Optimize collection of references in dataflow analyzer clearValues
Previously, when clearing values in the data flow analyzer, referencing values were gathered by iterating over all variables and checking if they are contained inside any of the gathered references, reflected in a datastructure of form map(variable -> set<variable>). Now, it is just checked, if there is any nonempty intersection between the set of variables to clean and the values of the aforementioned map. The check makes use of sets being sorted. In pathological cases like the chains.sol benchmark, this can bring down the compilation time by approx. 50%. No functional changes, overall behavior stays the same.
1 parent 6c556c3 commit 5c0ba6c

File tree

4 files changed

+47
-20
lines changed

4 files changed

+47
-20
lines changed

libsolutil/CommonData.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,29 @@ void iterateReplacingWindow(std::vector<T>& _vector, F const& _f, std::index_seq
551551

552552
}
553553

554+
/// Checks if two collections possess a non-empty intersection.
555+
/// Assumes that both inputs are sorted in ascending order.
556+
template<typename Collection1, typename Collection2>
557+
requires (
558+
std::forward_iterator<std::ranges::iterator_t<Collection1>> &&
559+
std::forward_iterator<std::ranges::iterator_t<Collection2>>
560+
)
561+
bool hasNonemptyIntersectionSorted(Collection1 const& _collection1, Collection2 const& _collection2)
562+
{
563+
auto it1 = std::ranges::begin(_collection1);
564+
auto it2 = std::ranges::begin(_collection2);
565+
while (it1 != std::ranges::end(_collection1) && it2 != std::ranges::end(_collection2))
566+
{
567+
if (*it1 == *it2)
568+
return true;
569+
if (*it1 < *it2)
570+
++it1;
571+
else
572+
++it2;
573+
}
574+
return false;
575+
}
576+
554577
/// Function that iterates over the vector @param _vector,
555578
/// calling the function @param _f on sequences of @tparam N of its
556579
/// elements. If @param _f returns a vector, these elements are replaced by

libyul/optimiser/DataFlowAnalyzer.cpp

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,10 @@ void DataFlowAnalyzer::handleAssignment(std::set<YulName> const& _variables, Exp
263263
}
264264

265265
auto const& referencedVariables = movableChecker.referencedVariables();
266+
std::vector const referencedVariablesSorted(referencedVariables.begin(), referencedVariables.end());
266267
for (auto const& name: _variables)
267268
{
268-
m_state.references[name] = referencedVariables;
269+
m_state.sortedReferences[name] = referencedVariablesSorted;
269270
if (!_isDeclaration)
270271
{
271272
// assignment to slot denoted by "name"
@@ -310,12 +311,12 @@ void DataFlowAnalyzer::popScope()
310311
for (auto const& name: m_variableScopes.back().variables)
311312
{
312313
m_state.value.erase(name);
313-
m_state.references.erase(name);
314+
m_state.sortedReferences.erase(name);
314315
}
315316
m_variableScopes.pop_back();
316317
}
317318

318-
void DataFlowAnalyzer::clearValues(std::set<YulName> _variables)
319+
void DataFlowAnalyzer::clearValues(std::set<YulName> const& _variablesToClear)
319320
{
320321
// All variables that reference variables to be cleared also have to be
321322
// cleared, but not recursively, since only the value of the original
@@ -333,30 +334,32 @@ void DataFlowAnalyzer::clearValues(std::set<YulName> _variables)
333334
// First clear storage knowledge, because we do not have to clear
334335
// storage knowledge of variables whose expression has changed,
335336
// since the value is still unchanged.
336-
auto eraseCondition = mapTuple([&_variables](auto&& key, auto&& value) {
337-
return _variables.count(key) || _variables.count(value);
337+
auto eraseCondition = mapTuple([&_variablesToClear](auto&& key, auto&& value) {
338+
return _variablesToClear.count(key) || _variablesToClear.count(value);
338339
});
339340
std::erase_if(m_state.environment.storage, eraseCondition);
340341
std::erase_if(m_state.environment.memory, eraseCondition);
341-
std::erase_if(m_state.environment.keccak, [&_variables](auto&& _item) {
342+
std::erase_if(m_state.environment.keccak, [&_variablesToClear](auto&& _item) {
342343
return
343-
_variables.count(_item.first.first) ||
344-
_variables.count(_item.first.second) ||
345-
_variables.count(_item.second);
344+
_variablesToClear.count(_item.first.first) ||
345+
_variablesToClear.count(_item.first.second) ||
346+
_variablesToClear.count(_item.second);
346347
});
347348

348349
// Also clear variables that reference variables to be cleared.
349-
std::set<YulName> referencingVariables;
350-
for (auto const& variableToClear: _variables)
351-
for (auto const& [ref, names]: m_state.references)
352-
if (names.count(variableToClear))
353-
referencingVariables.emplace(ref);
350+
std::set<YulName> referencingVariablesToClear;
351+
std::vector const sortedVariablesToClear(_variablesToClear.begin(), _variablesToClear.end());
352+
for (auto const& [referencingVariable, referencedVariables]: m_state.sortedReferences)
353+
// instead of checking each variable in `referencedVariables`, we check if there is any intersection making use of the
354+
// sortedness of the vectors, which can increase performance by up to 50% in pathological cases
355+
if (hasNonemptyIntersectionSorted(referencedVariables, sortedVariablesToClear))
356+
referencingVariablesToClear.emplace(referencingVariable);
354357

355358
// Clear the value and update the reference relation.
356-
for (auto const& name: _variables + referencingVariables)
359+
for (auto const& name: _variablesToClear + referencingVariablesToClear)
357360
{
358361
m_state.value.erase(name);
359-
m_state.references.erase(name);
362+
m_state.sortedReferences.erase(name);
360363
}
361364
}
362365

libyul/optimiser/DataFlowAnalyzer.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class DataFlowAnalyzer: public ASTModifier
104104

105105
/// @returns the current value of the given variable, if known - always movable.
106106
AssignedValue const* variableValue(YulName _variable) const { return util::valueOrNullptr(m_state.value, _variable); }
107-
std::set<YulName> const* references(YulName _variable) const { return util::valueOrNullptr(m_state.references, _variable); }
107+
std::vector<YulName> const* sortedReferences(YulName _variable) const { return util::valueOrNullptr(m_state.sortedReferences, _variable); }
108108
std::map<YulName, AssignedValue> const& allValues() const { return m_state.value; }
109109
std::optional<YulName> storageValue(YulName _key) const;
110110
std::optional<YulName> memoryValue(YulName _key) const;
@@ -122,7 +122,7 @@ class DataFlowAnalyzer: public ASTModifier
122122

123123
/// Clears information about the values assigned to the given variables,
124124
/// for example at points where control flow is merged.
125-
void clearValues(std::set<YulName> _names);
125+
void clearValues(std::set<YulName> const& _variablesToClear);
126126

127127
virtual void assignValue(YulName _variable, Expression const* _value);
128128

@@ -180,7 +180,8 @@ class DataFlowAnalyzer: public ASTModifier
180180
/// Current values of variables, always movable.
181181
std::map<YulName, AssignedValue> value;
182182
/// m_references[a].contains(b) <=> the current expression assigned to a references b
183-
std::unordered_map<YulName, std::set<YulName>> references;
183+
/// The mapped vectors _must always_ be sorted
184+
std::unordered_map<YulName, std::vector<YulName>> sortedReferences;
184185

185186
Environment environment;
186187
};

libyul/optimiser/Rematerialiser.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ void Rematerialiser::visit(Expression& _e)
7171
)
7272
{
7373
assertThrow(m_referenceCounts[name] > 0, OptimizerException, "");
74-
auto variableReferences = references(name);
74+
auto variableReferences = sortedReferences(name);
7575
if (!variableReferences || ranges::all_of(*variableReferences, [&](auto const& ref) { return inScope(ref); }))
7676
{
7777
// update reference counts

0 commit comments

Comments
 (0)