diff --git a/libsolutil/CommonData.h b/libsolutil/CommonData.h index 19d66fc91..7c2370370 100644 --- a/libsolutil/CommonData.h +++ b/libsolutil/CommonData.h @@ -295,6 +295,31 @@ decltype(auto) mapTuple(Callable&& _callable) return detail::MapTuple{std::forward(_callable)}; } +/// Merges map @a _b into map @a _a. If the same key exists in both maps, +/// calls @a _conflictSolver to combine the two values. +template +void joinMap(std::map& _a, std::map&& _b, F _conflictSolver) +{ + auto ita = _a.begin(); + auto aend = _a.end(); + auto itb = _b.begin(); + auto bend = _b.end(); + + for (; itb != bend; ++ita) + { + if (ita == aend) + ita = _a.insert(ita, std::move(*itb++)); + else if (ita->first < itb->first) + continue; + else if (itb->first < ita->first) + ita = _a.insert(ita, std::move(*itb++)); + else + { + _conflictSolver(ita->second, std::move(itb->second)); + ++itb; + } + } +} // String conversion functions, mainly to/from hex/nibble/byte representations. diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 7b9d102e8..5e816d988 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -167,6 +167,8 @@ add_library(yul optimiser/ReasoningBasedSimplifier.h optimiser/RedundantAssignEliminator.cpp optimiser/RedundantAssignEliminator.h + optimiser/RedundantStoreBase.cpp + optimiser/RedundantStoreBase.h optimiser/Rematerialiser.cpp optimiser/Rematerialiser.h optimiser/SMTSolver.cpp diff --git a/libyul/optimiser/RedundantAssignEliminator.cpp b/libyul/optimiser/RedundantAssignEliminator.cpp index 209be21c7..b6ac69c8f 100644 --- a/libyul/optimiser/RedundantAssignEliminator.cpp +++ b/libyul/optimiser/RedundantAssignEliminator.cpp @@ -38,7 +38,7 @@ void RedundantAssignEliminator::run(OptimiserStepContext& _context, Block& _ast) RedundantAssignEliminator rae{_context.dialect}; rae(_ast); - AssignmentRemover remover{rae.m_pendingRemovals}; + StatementRemover remover{rae.m_pendingRemovals}; remover(_ast); } @@ -49,7 +49,7 @@ void RedundantAssignEliminator::operator()(Identifier const& _identifier) void RedundantAssignEliminator::operator()(VariableDeclaration const& _variableDeclaration) { - ASTWalker::operator()(_variableDeclaration); + RedundantStoreBase::operator()(_variableDeclaration); for (auto const& var: _variableDeclaration.variables) m_declaredVariables.emplace(var.name); @@ -60,151 +60,17 @@ void RedundantAssignEliminator::operator()(Assignment const& _assignment) visit(*_assignment.value); for (auto const& var: _assignment.variableNames) changeUndecidedTo(var.name, State::Unused); - - if (_assignment.variableNames.size() == 1) - // Default-construct it in "Undecided" state if it does not yet exist. - m_assignments[_assignment.variableNames.front().name][&_assignment]; -} - -void RedundantAssignEliminator::operator()(If const& _if) -{ - visit(*_if.condition); - - TrackedAssignments skipBranch{m_assignments}; - (*this)(_if.body); - - merge(m_assignments, move(skipBranch)); -} - -void RedundantAssignEliminator::operator()(Switch const& _switch) -{ - visit(*_switch.expression); - - TrackedAssignments const preState{m_assignments}; - - bool hasDefault = false; - vector branches; - for (auto const& c: _switch.cases) - { - if (!c.value) - hasDefault = true; - (*this)(c.body); - branches.emplace_back(move(m_assignments)); - m_assignments = preState; - } - - if (hasDefault) - { - m_assignments = move(branches.back()); - branches.pop_back(); - } - for (auto& branch: branches) - merge(m_assignments, move(branch)); } void RedundantAssignEliminator::operator()(FunctionDefinition const& _functionDefinition) { - std::set outerDeclaredVariables; - std::set outerReturnVariables; - TrackedAssignments outerAssignments; - ForLoopInfo forLoopInfo; - swap(m_declaredVariables, outerDeclaredVariables); - swap(m_returnVariables, outerReturnVariables); - swap(m_assignments, outerAssignments); - swap(m_forLoopInfo, forLoopInfo); + ScopedSaveAndRestore outerDeclaredVariables(m_declaredVariables, {}); + ScopedSaveAndRestore outerReturnVariables(m_returnVariables, {}); for (auto const& retParam: _functionDefinition.returnVariables) m_returnVariables.insert(retParam.name); - (*this)(_functionDefinition.body); - - for (auto const& param: _functionDefinition.parameters) - finalize(param.name, State::Unused); - for (auto const& retParam: _functionDefinition.returnVariables) - finalize(retParam.name, State::Used); - - swap(m_declaredVariables, outerDeclaredVariables); - swap(m_returnVariables, outerReturnVariables); - swap(m_assignments, outerAssignments); - swap(m_forLoopInfo, forLoopInfo); -} - -void RedundantAssignEliminator::operator()(ForLoop const& _forLoop) -{ - ForLoopInfo outerForLoopInfo; - swap(outerForLoopInfo, m_forLoopInfo); - ++m_forLoopNestingDepth; - - // If the pre block was not empty, - // we would have to deal with more complicated scoping rules. - assertThrow(_forLoop.pre.statements.empty(), OptimizerException, ""); - - // We just run the loop twice to account for the back edge. - // There need not be more runs because we only have three different states. - - visit(*_forLoop.condition); - - TrackedAssignments zeroRuns{m_assignments}; - - (*this)(_forLoop.body); - merge(m_assignments, move(m_forLoopInfo.pendingContinueStmts)); - m_forLoopInfo.pendingContinueStmts = {}; - (*this)(_forLoop.post); - - visit(*_forLoop.condition); - - if (m_forLoopNestingDepth < 6) - { - // Do the second run only for small nesting depths to avoid horrible runtime. - TrackedAssignments oneRun{m_assignments}; - - (*this)(_forLoop.body); - - merge(m_assignments, move(m_forLoopInfo.pendingContinueStmts)); - m_forLoopInfo.pendingContinueStmts.clear(); - (*this)(_forLoop.post); - - visit(*_forLoop.condition); - // Order of merging does not matter because "max" is commutative and associative. - merge(m_assignments, move(oneRun)); - } - else - { - // Shortcut to avoid horrible runtime: - // Change all assignments that were newly introduced in the for loop to "used". - // We do not have to do that with the "break" or "continue" paths, because - // they will be joined later anyway. - // TODO parallel traversal might be more efficient here. - for (auto& var: m_assignments) - for (auto& assignment: var.second) - { - auto zeroIt = zeroRuns.find(var.first); - if (zeroIt != zeroRuns.end() && zeroIt->second.count(assignment.first)) - continue; - assignment.second = State::Value::Used; - } - } - - // Order of merging does not matter because "max" is commutative and associative. - merge(m_assignments, move(zeroRuns)); - merge(m_assignments, move(m_forLoopInfo.pendingBreakStmts)); - m_forLoopInfo.pendingBreakStmts.clear(); - - // Restore potential outer for-loop states. - swap(m_forLoopInfo, outerForLoopInfo); - --m_forLoopNestingDepth; -} - -void RedundantAssignEliminator::operator()(Break const&) -{ - m_forLoopInfo.pendingBreakStmts.emplace_back(move(m_assignments)); - m_assignments.clear(); -} - -void RedundantAssignEliminator::operator()(Continue const&) -{ - m_forLoopInfo.pendingContinueStmts.emplace_back(move(m_assignments)); - m_assignments.clear(); + RedundantStoreBase::operator()(_functionDefinition); } void RedundantAssignEliminator::operator()(Leave const&) @@ -215,101 +81,76 @@ void RedundantAssignEliminator::operator()(Leave const&) void RedundantAssignEliminator::operator()(Block const& _block) { - set outerDeclaredVariables; - swap(m_declaredVariables, outerDeclaredVariables); + ScopedSaveAndRestore outerDeclaredVariables(m_declaredVariables, {}); - ASTWalker::operator()(_block); + RedundantStoreBase::operator()(_block); for (auto const& var: m_declaredVariables) finalize(var, State::Unused); - - swap(m_declaredVariables, outerDeclaredVariables); } - -template -void joinMap(std::map& _a, std::map&& _b, F _conflictSolver) +void RedundantAssignEliminator::visit(Statement const& _statement) { - // TODO Perhaps it is better to just create a sorted list - // and then use insert(begin, end) + RedundantStoreBase::visit(_statement); - auto ita = _a.begin(); - auto aend = _a.end(); - auto itb = _b.begin(); - auto bend = _b.end(); + if (auto const* assignment = get_if(&_statement)) + if (assignment->variableNames.size() == 1) + // Default-construct it in "Undecided" state if it does not yet exist. + m_stores[assignment->variableNames.front().name][&_statement]; +} - for (; itb != bend; ++ita) - { - if (ita == aend) - ita = _a.insert(ita, std::move(*itb++)); - else if (ita->first < itb->first) - continue; - else if (itb->first < ita->first) - ita = _a.insert(ita, std::move(*itb++)); - else +void RedundantAssignEliminator::shortcutNestedLoop(TrackedStores const& _zeroRuns) +{ + // Shortcut to avoid horrible runtime: + // Change all assignments that were newly introduced in the for loop to "used". + // We do not have to do that with the "break" or "continue" paths, because + // they will be joined later anyway. + // TODO parallel traversal might be more efficient here. + for (auto& [variable, stores]: m_stores) + for (auto& assignment: stores) { - _conflictSolver(ita->second, std::move(itb->second)); - ++itb; + auto zeroIt = _zeroRuns.find(variable); + if (zeroIt != _zeroRuns.end() && zeroIt->second.count(assignment.first)) + continue; + assignment.second = State::Value::Used; } - } } -void RedundantAssignEliminator::merge(TrackedAssignments& _target, TrackedAssignments&& _other) +void RedundantAssignEliminator::finalizeFunctionDefinition(FunctionDefinition const& _functionDefinition) { - joinMap(_target, move(_other), []( - map& _assignmentHere, - map&& _assignmentThere - ) - { - return joinMap(_assignmentHere, move(_assignmentThere), State::join); - }); -} - -void RedundantAssignEliminator::merge(TrackedAssignments& _target, vector&& _source) -{ - for (TrackedAssignments& ts: _source) - merge(_target, move(ts)); - _source.clear(); + for (auto const& param: _functionDefinition.parameters) + finalize(param.name, State::Unused); + for (auto const& retParam: _functionDefinition.returnVariables) + finalize(retParam.name, State::Used); } void RedundantAssignEliminator::changeUndecidedTo(YulString _variable, RedundantAssignEliminator::State _newState) { - for (auto& assignment: m_assignments[_variable]) + for (auto& assignment: m_stores[_variable]) if (assignment.second == State::Undecided) assignment.second = _newState; } void RedundantAssignEliminator::finalize(YulString _variable, RedundantAssignEliminator::State _finalState) { - std::map assignments; - joinMap(assignments, std::move(m_assignments[_variable]), State::join); - m_assignments.erase(_variable); + std::map stores = std::move(m_stores[_variable]); + m_stores.erase(_variable); for (auto& breakAssignments: m_forLoopInfo.pendingBreakStmts) { - joinMap(assignments, std::move(breakAssignments[_variable]), State::join); + util::joinMap(stores, std::move(breakAssignments[_variable]), State::join); breakAssignments.erase(_variable); } for (auto& continueAssignments: m_forLoopInfo.pendingContinueStmts) { - joinMap(assignments, std::move(continueAssignments[_variable]), State::join); + util::joinMap(stores, std::move(continueAssignments[_variable]), State::join); continueAssignments.erase(_variable); } - for (auto const& assignment: assignments) - { - State const state = assignment.second == State::Undecided ? _finalState : assignment.second; - - if (state == State::Unused && SideEffectsCollector{*m_dialect, *assignment.first->value}.movable()) - m_pendingRemovals.insert(assignment.first); - } -} - -void AssignmentRemover::operator()(Block& _block) -{ - ranges::actions::remove_if(_block.statements, [&](Statement const& _statement) -> bool { - return holds_alternative(_statement) && m_toRemove.count(&std::get(_statement)); - }); - - ASTModifier::operator()(_block); + for (auto&& [statement, state]: stores) + if ( + (state == State::Unused || (state == State::Undecided && _finalState == State::Unused)) && + SideEffectsCollector{m_dialect, *std::get(*statement).value}.movable() + ) + m_pendingRemovals.insert(statement); } diff --git a/libyul/optimiser/RedundantAssignEliminator.h b/libyul/optimiser/RedundantAssignEliminator.h index bccc1e8ef..65bbb53fa 100644 --- a/libyul/optimiser/RedundantAssignEliminator.h +++ b/libyul/optimiser/RedundantAssignEliminator.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -106,91 +107,37 @@ struct Dialect; * * Prerequisite: Disambiguator, ForLoopInitRewriter. */ -class RedundantAssignEliminator: public ASTWalker +class RedundantAssignEliminator: public RedundantStoreBase { public: static constexpr char const* name{"RedundantAssignEliminator"}; static void run(OptimiserStepContext&, Block& _ast); - explicit RedundantAssignEliminator(Dialect const& _dialect): m_dialect(&_dialect) {} - RedundantAssignEliminator() = delete; - RedundantAssignEliminator(RedundantAssignEliminator const&) = delete; - RedundantAssignEliminator& operator=(RedundantAssignEliminator const&) = delete; - RedundantAssignEliminator(RedundantAssignEliminator&&) = default; - RedundantAssignEliminator& operator=(RedundantAssignEliminator&&) = default; + explicit RedundantAssignEliminator(Dialect const& _dialect): RedundantStoreBase(_dialect) {} void operator()(Identifier const& _identifier) override; void operator()(VariableDeclaration const& _variableDeclaration) override; void operator()(Assignment const& _assignment) override; - void operator()(If const& _if) override; - void operator()(Switch const& _switch) override; void operator()(FunctionDefinition const&) override; - void operator()(ForLoop const&) override; - void operator()(Break const&) override; - void operator()(Continue const&) override; void operator()(Leave const&) override; void operator()(Block const& _block) override; + using RedundantStoreBase::visit; + void visit(Statement const& _statement) override; + private: - class State - { - public: - enum Value { Unused, Undecided, Used }; - State(Value _value = Undecided): m_value(_value) {} - inline bool operator==(State _other) const { return m_value == _other.m_value; } - inline bool operator!=(State _other) const { return !operator==(_other); } - static inline void join(State& _a, State const& _b) - { - // Using "max" works here because of the order of the values in the enum. - _a.m_value = Value(std::max(int(_a.m_value), int(_b.m_value))); - } - private: - Value m_value = Undecided; - }; + void shortcutNestedLoop(TrackedStores const& _beforeLoop) override; + void finalizeFunctionDefinition(FunctionDefinition const& _functionDefinition) override; - // TODO check that this does not cause nondeterminism! - // This could also be a pseudo-map from state to assignment. - using TrackedAssignments = std::map>; - - /// Joins the assignment mapping of @a _source into @a _target according to the rules laid out - /// above. - /// Will destroy @a _source. - static void merge(TrackedAssignments& _target, TrackedAssignments&& _source); - static void merge(TrackedAssignments& _target, std::vector&& _source); void changeUndecidedTo(YulString _variable, State _newState); /// Called when a variable goes out of scope. Sets the state of all still undecided /// assignments to the final state. In this case, this also applies to pending - /// break and continue TrackedAssignments. + /// break and continue TrackedStores. void finalize(YulString _variable, State _finalState); - Dialect const* m_dialect; + std::set m_declaredVariables; std::set m_returnVariables; - std::set m_pendingRemovals; - TrackedAssignments m_assignments; - - /// Working data for traversing for-loops. - struct ForLoopInfo - { - /// Tracked assignment states for each break statement. - std::vector pendingBreakStmts; - /// Tracked assignment states for each continue statement. - std::vector pendingContinueStmts; - }; - ForLoopInfo m_forLoopInfo; - size_t m_forLoopNestingDepth = 0; -}; - -class AssignmentRemover: public ASTModifier -{ -public: - explicit AssignmentRemover(std::set const& _toRemove): - m_toRemove(_toRemove) - {} - void operator()(Block& _block) override; - -private: - std::set const& m_toRemove; }; } diff --git a/libyul/optimiser/RedundantStoreBase.cpp b/libyul/optimiser/RedundantStoreBase.cpp new file mode 100644 index 000000000..4e7c21367 --- /dev/null +++ b/libyul/optimiser/RedundantStoreBase.cpp @@ -0,0 +1,166 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Base class for both RedundantAssignEliminator and RedundantStoreEliminator. + */ + +#include + +#include +#include +#include + +#include + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::yul; + +void RedundantStoreBase::operator()(If const& _if) +{ + visit(*_if.condition); + + TrackedStores skipBranch{m_stores}; + (*this)(_if.body); + + merge(m_stores, move(skipBranch)); +} + +void RedundantStoreBase::operator()(Switch const& _switch) +{ + visit(*_switch.expression); + + TrackedStores const preState{m_stores}; + + bool hasDefault = false; + vector branches; + for (auto const& c: _switch.cases) + { + if (!c.value) + hasDefault = true; + (*this)(c.body); + branches.emplace_back(move(m_stores)); + m_stores = preState; + } + + if (hasDefault) + { + m_stores = move(branches.back()); + branches.pop_back(); + } + for (auto& branch: branches) + merge(m_stores, move(branch)); +} + +void RedundantStoreBase::operator()(FunctionDefinition const& _functionDefinition) +{ + ScopedSaveAndRestore outerAssignments(m_stores, {}); + ScopedSaveAndRestore forLoopInfo(m_forLoopInfo, {}); + + (*this)(_functionDefinition.body); + + finalizeFunctionDefinition(_functionDefinition); +} + +void RedundantStoreBase::operator()(ForLoop const& _forLoop) +{ + ScopedSaveAndRestore outerForLoopInfo(m_forLoopInfo, {}); + ScopedSaveAndRestore forLoopNestingDepth(m_forLoopNestingDepth, m_forLoopNestingDepth + 1); + + // If the pre block was not empty, + // we would have to deal with more complicated scoping rules. + assertThrow(_forLoop.pre.statements.empty(), OptimizerException, ""); + + // We just run the loop twice to account for the back edge. + // There need not be more runs because we only have three different states. + + visit(*_forLoop.condition); + + TrackedStores zeroRuns{m_stores}; + + (*this)(_forLoop.body); + merge(m_stores, move(m_forLoopInfo.pendingContinueStmts)); + m_forLoopInfo.pendingContinueStmts = {}; + (*this)(_forLoop.post); + + visit(*_forLoop.condition); + + if (m_forLoopNestingDepth < 6) + { + // Do the second run only for small nesting depths to avoid horrible runtime. + TrackedStores oneRun{m_stores}; + + (*this)(_forLoop.body); + + merge(m_stores, move(m_forLoopInfo.pendingContinueStmts)); + m_forLoopInfo.pendingContinueStmts.clear(); + (*this)(_forLoop.post); + + visit(*_forLoop.condition); + // Order of merging does not matter because "max" is commutative and associative. + merge(m_stores, move(oneRun)); + } + else + // Shortcut to avoid horrible runtime. + shortcutNestedLoop(zeroRuns); + + // Order of merging does not matter because "max" is commutative and associative. + merge(m_stores, move(zeroRuns)); + merge(m_stores, move(m_forLoopInfo.pendingBreakStmts)); + m_forLoopInfo.pendingBreakStmts.clear(); +} + +void RedundantStoreBase::operator()(Break const&) +{ + m_forLoopInfo.pendingBreakStmts.emplace_back(move(m_stores)); + m_stores.clear(); +} + +void RedundantStoreBase::operator()(Continue const&) +{ + m_forLoopInfo.pendingContinueStmts.emplace_back(move(m_stores)); + m_stores.clear(); +} + +void RedundantStoreBase::merge(TrackedStores& _target, TrackedStores&& _other) +{ + util::joinMap(_target, move(_other), []( + map& _assignmentHere, + map&& _assignmentThere + ) + { + return util::joinMap(_assignmentHere, move(_assignmentThere), State::join); + }); +} + +void RedundantStoreBase::merge(TrackedStores& _target, vector&& _source) +{ + for (TrackedStores& ts: _source) + merge(_target, move(ts)); + _source.clear(); +} + +void StatementRemover::operator()(Block& _block) +{ + ranges::actions::remove_if(_block.statements, [&](Statement const& _statement) -> bool { + return m_toRemove.count(&_statement); + }); + ASTModifier::operator()(_block); +} diff --git a/libyul/optimiser/RedundantStoreBase.h b/libyul/optimiser/RedundantStoreBase.h new file mode 100644 index 000000000..ed588927c --- /dev/null +++ b/libyul/optimiser/RedundantStoreBase.h @@ -0,0 +1,113 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Base class for both RedundantAssignEliminator and RedundantStoreEliminator. + */ + +#pragma once + +#include +#include + +#include + +#include + + +namespace solidity::yul +{ +struct Dialect; + +/** + * Base class for both RedundantAssignEliminator and RedundantStoreEliminator. + * + * Prerequisite: Disambiguator, ForLoopInitRewriter. + */ +class RedundantStoreBase: public ASTWalker +{ +public: + explicit RedundantStoreBase(Dialect const& _dialect): m_dialect(_dialect) {} + + using ASTWalker::operator(); + void operator()(If const& _if) override; + void operator()(Switch const& _switch) override; + void operator()(FunctionDefinition const&) override; + void operator()(ForLoop const&) override; + void operator()(Break const&) override; + void operator()(Continue const&) override; + +protected: + class State + { + public: + enum Value { Unused, Undecided, Used }; + State(Value _value = Undecided): m_value(_value) {} + inline bool operator==(State _other) const { return m_value == _other.m_value; } + inline bool operator!=(State _other) const { return !operator==(_other); } + static inline void join(State& _a, State const& _b) + { + // Using "max" works here because of the order of the values in the enum. + _a.m_value = Value(std::max(int(_a.m_value), int(_b.m_value))); + } + private: + Value m_value = Undecided; + }; + + using TrackedStores = std::map>; + + /// This function is called for a loop that is nested too deep to avoid + /// horrible runtime and should just resolve the situation in a pragmatic + /// and correct manner. + virtual void shortcutNestedLoop(TrackedStores const& _beforeLoop) = 0; + + /// This function is called right before the scoped restore of the function definition. + virtual void finalizeFunctionDefinition(FunctionDefinition const& /*_functionDefinition*/) {} + + /// Joins the assignment mapping of @a _source into @a _target according to the rules laid out + /// above. + /// Will destroy @a _source. + static void merge(TrackedStores& _target, TrackedStores&& _source); + static void merge(TrackedStores& _target, std::vector&& _source); + + Dialect const& m_dialect; + std::set m_pendingRemovals; + TrackedStores m_stores; + + /// Working data for traversing for-loops. + struct ForLoopInfo + { + /// Tracked assignment states for each break statement. + std::vector pendingBreakStmts; + /// Tracked assignment states for each continue statement. + std::vector pendingContinueStmts; + }; + ForLoopInfo m_forLoopInfo; + size_t m_forLoopNestingDepth = 0; +}; + +class StatementRemover: public ASTModifier +{ +public: + explicit StatementRemover(std::set const& _toRemove): m_toRemove(_toRemove) {} + + void operator()(Block& _block) override; +private: + std::set const& m_toRemove; +}; + +}