Skip to content

Commit 3622b30

Browse files
committed
Refactor RedundantAssignEliminator.
1 parent d7a802e commit 3622b30

File tree

6 files changed

+360
-266
lines changed

6 files changed

+360
-266
lines changed

libsolutil/CommonData.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,31 @@ decltype(auto) mapTuple(Callable&& _callable)
295295
return detail::MapTuple<Callable>{std::forward<Callable>(_callable)};
296296
}
297297

298+
/// Merges map @a _b into map @a _a. If the same key exists in both maps,
299+
/// calls @a _conflictSolver to combine the two values.
300+
template <class K, class V, class F>
301+
void joinMap(std::map<K, V>& _a, std::map<K, V>&& _b, F _conflictSolver)
302+
{
303+
auto ita = _a.begin();
304+
auto aend = _a.end();
305+
auto itb = _b.begin();
306+
auto bend = _b.end();
307+
308+
for (; itb != bend; ++ita)
309+
{
310+
if (ita == aend)
311+
ita = _a.insert(ita, std::move(*itb++));
312+
else if (ita->first < itb->first)
313+
continue;
314+
else if (itb->first < ita->first)
315+
ita = _a.insert(ita, std::move(*itb++));
316+
else
317+
{
318+
_conflictSolver(ita->second, std::move(itb->second));
319+
++itb;
320+
}
321+
}
322+
}
298323

299324
// String conversion functions, mainly to/from hex/nibble/byte representations.
300325

libyul/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ add_library(yul
167167
optimiser/ReasoningBasedSimplifier.h
168168
optimiser/RedundantAssignEliminator.cpp
169169
optimiser/RedundantAssignEliminator.h
170+
optimiser/RedundantStoreBase.cpp
171+
optimiser/RedundantStoreBase.h
170172
optimiser/Rematerialiser.cpp
171173
optimiser/Rematerialiser.h
172174
optimiser/SMTSolver.cpp

libyul/optimiser/RedundantAssignEliminator.cpp

Lines changed: 43 additions & 202 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void RedundantAssignEliminator::run(OptimiserStepContext& _context, Block& _ast)
3838
RedundantAssignEliminator rae{_context.dialect};
3939
rae(_ast);
4040

41-
AssignmentRemover remover{rae.m_pendingRemovals};
41+
StatementRemover remover{rae.m_pendingRemovals};
4242
remover(_ast);
4343
}
4444

@@ -49,7 +49,7 @@ void RedundantAssignEliminator::operator()(Identifier const& _identifier)
4949

5050
void RedundantAssignEliminator::operator()(VariableDeclaration const& _variableDeclaration)
5151
{
52-
ASTWalker::operator()(_variableDeclaration);
52+
RedundantStoreBase::operator()(_variableDeclaration);
5353

5454
for (auto const& var: _variableDeclaration.variables)
5555
m_declaredVariables.emplace(var.name);
@@ -60,151 +60,17 @@ void RedundantAssignEliminator::operator()(Assignment const& _assignment)
6060
visit(*_assignment.value);
6161
for (auto const& var: _assignment.variableNames)
6262
changeUndecidedTo(var.name, State::Unused);
63-
64-
if (_assignment.variableNames.size() == 1)
65-
// Default-construct it in "Undecided" state if it does not yet exist.
66-
m_assignments[_assignment.variableNames.front().name][&_assignment];
67-
}
68-
69-
void RedundantAssignEliminator::operator()(If const& _if)
70-
{
71-
visit(*_if.condition);
72-
73-
TrackedAssignments skipBranch{m_assignments};
74-
(*this)(_if.body);
75-
76-
merge(m_assignments, move(skipBranch));
77-
}
78-
79-
void RedundantAssignEliminator::operator()(Switch const& _switch)
80-
{
81-
visit(*_switch.expression);
82-
83-
TrackedAssignments const preState{m_assignments};
84-
85-
bool hasDefault = false;
86-
vector<TrackedAssignments> branches;
87-
for (auto const& c: _switch.cases)
88-
{
89-
if (!c.value)
90-
hasDefault = true;
91-
(*this)(c.body);
92-
branches.emplace_back(move(m_assignments));
93-
m_assignments = preState;
94-
}
95-
96-
if (hasDefault)
97-
{
98-
m_assignments = move(branches.back());
99-
branches.pop_back();
100-
}
101-
for (auto& branch: branches)
102-
merge(m_assignments, move(branch));
10363
}
10464

10565
void RedundantAssignEliminator::operator()(FunctionDefinition const& _functionDefinition)
10666
{
107-
std::set<YulString> outerDeclaredVariables;
108-
std::set<YulString> outerReturnVariables;
109-
TrackedAssignments outerAssignments;
110-
ForLoopInfo forLoopInfo;
111-
swap(m_declaredVariables, outerDeclaredVariables);
112-
swap(m_returnVariables, outerReturnVariables);
113-
swap(m_assignments, outerAssignments);
114-
swap(m_forLoopInfo, forLoopInfo);
67+
ScopedSaveAndRestore outerDeclaredVariables(m_declaredVariables, {});
68+
ScopedSaveAndRestore outerReturnVariables(m_returnVariables, {});
11569

11670
for (auto const& retParam: _functionDefinition.returnVariables)
11771
m_returnVariables.insert(retParam.name);
11872

119-
(*this)(_functionDefinition.body);
120-
121-
for (auto const& param: _functionDefinition.parameters)
122-
finalize(param.name, State::Unused);
123-
for (auto const& retParam: _functionDefinition.returnVariables)
124-
finalize(retParam.name, State::Used);
125-
126-
swap(m_declaredVariables, outerDeclaredVariables);
127-
swap(m_returnVariables, outerReturnVariables);
128-
swap(m_assignments, outerAssignments);
129-
swap(m_forLoopInfo, forLoopInfo);
130-
}
131-
132-
void RedundantAssignEliminator::operator()(ForLoop const& _forLoop)
133-
{
134-
ForLoopInfo outerForLoopInfo;
135-
swap(outerForLoopInfo, m_forLoopInfo);
136-
++m_forLoopNestingDepth;
137-
138-
// If the pre block was not empty,
139-
// we would have to deal with more complicated scoping rules.
140-
assertThrow(_forLoop.pre.statements.empty(), OptimizerException, "");
141-
142-
// We just run the loop twice to account for the back edge.
143-
// There need not be more runs because we only have three different states.
144-
145-
visit(*_forLoop.condition);
146-
147-
TrackedAssignments zeroRuns{m_assignments};
148-
149-
(*this)(_forLoop.body);
150-
merge(m_assignments, move(m_forLoopInfo.pendingContinueStmts));
151-
m_forLoopInfo.pendingContinueStmts = {};
152-
(*this)(_forLoop.post);
153-
154-
visit(*_forLoop.condition);
155-
156-
if (m_forLoopNestingDepth < 6)
157-
{
158-
// Do the second run only for small nesting depths to avoid horrible runtime.
159-
TrackedAssignments oneRun{m_assignments};
160-
161-
(*this)(_forLoop.body);
162-
163-
merge(m_assignments, move(m_forLoopInfo.pendingContinueStmts));
164-
m_forLoopInfo.pendingContinueStmts.clear();
165-
(*this)(_forLoop.post);
166-
167-
visit(*_forLoop.condition);
168-
// Order of merging does not matter because "max" is commutative and associative.
169-
merge(m_assignments, move(oneRun));
170-
}
171-
else
172-
{
173-
// Shortcut to avoid horrible runtime:
174-
// Change all assignments that were newly introduced in the for loop to "used".
175-
// We do not have to do that with the "break" or "continue" paths, because
176-
// they will be joined later anyway.
177-
// TODO parallel traversal might be more efficient here.
178-
for (auto& var: m_assignments)
179-
for (auto& assignment: var.second)
180-
{
181-
auto zeroIt = zeroRuns.find(var.first);
182-
if (zeroIt != zeroRuns.end() && zeroIt->second.count(assignment.first))
183-
continue;
184-
assignment.second = State::Value::Used;
185-
}
186-
}
187-
188-
// Order of merging does not matter because "max" is commutative and associative.
189-
merge(m_assignments, move(zeroRuns));
190-
merge(m_assignments, move(m_forLoopInfo.pendingBreakStmts));
191-
m_forLoopInfo.pendingBreakStmts.clear();
192-
193-
// Restore potential outer for-loop states.
194-
swap(m_forLoopInfo, outerForLoopInfo);
195-
--m_forLoopNestingDepth;
196-
}
197-
198-
void RedundantAssignEliminator::operator()(Break const&)
199-
{
200-
m_forLoopInfo.pendingBreakStmts.emplace_back(move(m_assignments));
201-
m_assignments.clear();
202-
}
203-
204-
void RedundantAssignEliminator::operator()(Continue const&)
205-
{
206-
m_forLoopInfo.pendingContinueStmts.emplace_back(move(m_assignments));
207-
m_assignments.clear();
73+
RedundantStoreBase::operator()(_functionDefinition);
20874
}
20975

21076
void RedundantAssignEliminator::operator()(Leave const&)
@@ -215,101 +81,76 @@ void RedundantAssignEliminator::operator()(Leave const&)
21581

21682
void RedundantAssignEliminator::operator()(Block const& _block)
21783
{
218-
set<YulString> outerDeclaredVariables;
219-
swap(m_declaredVariables, outerDeclaredVariables);
84+
ScopedSaveAndRestore outerDeclaredVariables(m_declaredVariables, {});
22085

221-
ASTWalker::operator()(_block);
86+
RedundantStoreBase::operator()(_block);
22287

22388
for (auto const& var: m_declaredVariables)
22489
finalize(var, State::Unused);
225-
226-
swap(m_declaredVariables, outerDeclaredVariables);
22790
}
22891

229-
230-
template <class K, class V, class F>
231-
void joinMap(std::map<K, V>& _a, std::map<K, V>&& _b, F _conflictSolver)
92+
void RedundantAssignEliminator::visit(Statement const& _statement)
23293
{
233-
// TODO Perhaps it is better to just create a sorted list
234-
// and then use insert(begin, end)
94+
RedundantStoreBase::visit(_statement);
23595

236-
auto ita = _a.begin();
237-
auto aend = _a.end();
238-
auto itb = _b.begin();
239-
auto bend = _b.end();
240-
241-
for (; itb != bend; ++ita)
242-
{
243-
if (ita == aend)
244-
ita = _a.insert(ita, std::move(*itb++));
245-
else if (ita->first < itb->first)
246-
continue;
247-
else if (itb->first < ita->first)
248-
ita = _a.insert(ita, std::move(*itb++));
249-
else
250-
{
251-
_conflictSolver(ita->second, std::move(itb->second));
252-
++itb;
253-
}
254-
}
96+
if (auto const* assignment = get_if<Assignment>(&_statement))
97+
if (assignment->variableNames.size() == 1)
98+
// Default-construct it in "Undecided" state if it does not yet exist.
99+
m_stores[assignment->variableNames.front().name][&_statement];
255100
}
256101

257-
void RedundantAssignEliminator::merge(TrackedAssignments& _target, TrackedAssignments&& _other)
102+
void RedundantAssignEliminator::shortcutNestedLoop(TrackedStores const& _zeroRuns)
258103
{
259-
joinMap(_target, move(_other), [](
260-
map<Assignment const*, State>& _assignmentHere,
261-
map<Assignment const*, State>&& _assignmentThere
262-
)
263-
{
264-
return joinMap(_assignmentHere, move(_assignmentThere), State::join);
265-
});
104+
// Shortcut to avoid horrible runtime:
105+
// Change all assignments that were newly introduced in the for loop to "used".
106+
// We do not have to do that with the "break" or "continue" paths, because
107+
// they will be joined later anyway.
108+
// TODO parallel traversal might be more efficient here.
109+
for (auto& [variable, stores]: m_stores)
110+
for (auto& assignment: stores)
111+
{
112+
auto zeroIt = _zeroRuns.find(variable);
113+
if (zeroIt != _zeroRuns.end() && zeroIt->second.count(assignment.first))
114+
continue;
115+
assignment.second = State::Value::Used;
116+
}
266117
}
267118

268-
void RedundantAssignEliminator::merge(TrackedAssignments& _target, vector<TrackedAssignments>&& _source)
119+
void RedundantAssignEliminator::finalizeFunctionDefinition(FunctionDefinition const& _functionDefinition)
269120
{
270-
for (TrackedAssignments& ts: _source)
271-
merge(_target, move(ts));
272-
_source.clear();
121+
for (auto const& param: _functionDefinition.parameters)
122+
finalize(param.name, State::Unused);
123+
for (auto const& retParam: _functionDefinition.returnVariables)
124+
finalize(retParam.name, State::Used);
273125
}
274126

275127
void RedundantAssignEliminator::changeUndecidedTo(YulString _variable, RedundantAssignEliminator::State _newState)
276128
{
277-
for (auto& assignment: m_assignments[_variable])
129+
for (auto& assignment: m_stores[_variable])
278130
if (assignment.second == State::Undecided)
279131
assignment.second = _newState;
280132
}
281133

282134
void RedundantAssignEliminator::finalize(YulString _variable, RedundantAssignEliminator::State _finalState)
283135
{
284-
std::map<Assignment const*, State> assignments;
285-
joinMap(assignments, std::move(m_assignments[_variable]), State::join);
286-
m_assignments.erase(_variable);
136+
std::map<Statement const*, State> stores = std::move(m_stores[_variable]);
137+
m_stores.erase(_variable);
287138

288139
for (auto& breakAssignments: m_forLoopInfo.pendingBreakStmts)
289140
{
290-
joinMap(assignments, std::move(breakAssignments[_variable]), State::join);
141+
util::joinMap(stores, std::move(breakAssignments[_variable]), State::join);
291142
breakAssignments.erase(_variable);
292143
}
293144
for (auto& continueAssignments: m_forLoopInfo.pendingContinueStmts)
294145
{
295-
joinMap(assignments, std::move(continueAssignments[_variable]), State::join);
146+
util::joinMap(stores, std::move(continueAssignments[_variable]), State::join);
296147
continueAssignments.erase(_variable);
297148
}
298149

299-
for (auto const& assignment: assignments)
300-
{
301-
State const state = assignment.second == State::Undecided ? _finalState : assignment.second;
302-
303-
if (state == State::Unused && SideEffectsCollector{*m_dialect, *assignment.first->value}.movable())
304-
m_pendingRemovals.insert(assignment.first);
305-
}
306-
}
307-
308-
void AssignmentRemover::operator()(Block& _block)
309-
{
310-
ranges::actions::remove_if(_block.statements, [&](Statement const& _statement) -> bool {
311-
return holds_alternative<Assignment>(_statement) && m_toRemove.count(&std::get<Assignment>(_statement));
312-
});
313-
314-
ASTModifier::operator()(_block);
150+
for (auto&& [statement, state]: stores)
151+
if (
152+
(state == State::Unused || (state == State::Undecided && _finalState == State::Unused)) &&
153+
SideEffectsCollector{m_dialect, *std::get<Assignment>(*statement).value}.movable()
154+
)
155+
m_pendingRemovals.insert(statement);
315156
}

0 commit comments

Comments
 (0)