Skip to content

Commit 776ae46

Browse files
committed
Control flow side effects on non-disambiguated source.
1 parent 25c4154 commit 776ae46

9 files changed

+128
-87
lines changed

libyul/ControlFlowSideEffectsCollector.cpp

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#include <libyul/AST.h>
2222
#include <libyul/Dialect.h>
23+
#include <libyul/FunctionReferenceResolver.h>
2324

2425
#include <libsolutil/Common.h>
2526
#include <libsolutil/CommonData.h>
@@ -35,16 +36,15 @@ using namespace solidity::yul;
3536

3637
ControlFlowBuilder::ControlFlowBuilder(Block const& _ast)
3738
{
38-
for (auto const& statement: _ast.statements)
39-
if (auto const* function = get_if<FunctionDefinition>(&statement))
40-
(*this)(*function);
39+
m_currentNode = newNode();
40+
(*this)(_ast);
4141
}
4242

4343
void ControlFlowBuilder::operator()(FunctionCall const& _functionCall)
4444
{
4545
walkVector(_functionCall.arguments | ranges::views::reverse);
4646
newConnectedNode();
47-
m_currentNode->functionCall = _functionCall.functionName.name;
47+
m_currentNode->functionCall = &_functionCall;
4848
}
4949

5050
void ControlFlowBuilder::operator()(If const& _if)
@@ -78,7 +78,9 @@ void ControlFlowBuilder::operator()(Switch const& _switch)
7878
void ControlFlowBuilder::operator()(FunctionDefinition const& _function)
7979
{
8080
ScopedSaveAndRestore currentNode(m_currentNode, nullptr);
81-
yulAssert(!m_leave && !m_break && !m_continue, "Function hoister has not been used.");
81+
ScopedSaveAndRestore leave(m_leave, nullptr);
82+
ScopedSaveAndRestore _break(m_break, nullptr);
83+
ScopedSaveAndRestore _continue(m_continue, nullptr);
8284

8385
FunctionFlow flow;
8486
flow.exit = newNode();
@@ -90,7 +92,7 @@ void ControlFlowBuilder::operator()(FunctionDefinition const& _function)
9092

9193
m_currentNode->successors.emplace_back(flow.exit);
9294

93-
m_functionFlows[_function.name] = move(flow);
95+
m_functionFlows[&_function] = move(flow);
9496

9597
m_leave = nullptr;
9698
}
@@ -164,14 +166,17 @@ ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector(
164166
Block const& _ast
165167
):
166168
m_dialect(_dialect),
167-
m_cfgBuilder(_ast)
169+
m_cfgBuilder(_ast),
170+
m_functionReferences(FunctionReferenceResolver{_ast}.references())
168171
{
169-
for (auto&& [name, flow]: m_cfgBuilder.functionFlows())
172+
for (auto&& [function, flow]: m_cfgBuilder.functionFlows())
170173
{
171174
yulAssert(!flow.entry->functionCall);
172-
m_processedNodes[name] = {};
173-
m_pendingNodes[name].push_front(flow.entry);
174-
m_functionSideEffects[name] = {false, false, false};
175+
yulAssert(function);
176+
m_processedNodes[function] = {};
177+
m_pendingNodes[function].push_front(flow.entry);
178+
m_functionSideEffects[function] = {false, false, false};
179+
m_functionCalls[function] = {};
175180
}
176181

177182
// Process functions while we have progress. For now, we are only interested
@@ -180,8 +185,8 @@ ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector(
180185
while (progress)
181186
{
182187
progress = false;
183-
for (auto const& functionName: m_pendingNodes | ranges::views::keys)
184-
if (processFunction(functionName))
188+
for (FunctionDefinition const* function: m_pendingNodes | ranges::views::keys)
189+
if (processFunction(*function))
185190
progress = true;
186191
}
187192

@@ -190,57 +195,64 @@ ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector(
190195
// If we have not set `canContinue` by now, the function's exit
191196
// is not reachable.
192197

193-
for (auto&& [functionName, calls]: m_functionCalls)
198+
// Now it is sufficient to handle the reachable function calls (`m_functionCalls`),
199+
// we do not have to consider the control-flow graph anymore.
200+
for (auto&& [function, calls]: m_functionCalls)
194201
{
195-
ControlFlowSideEffects& sideEffects = m_functionSideEffects[functionName];
196-
auto _visit = [&, visited = std::set<YulString>{}](YulString _function, auto&& _recurse) mutable {
197-
if (sideEffects.canTerminate && sideEffects.canRevert)
202+
yulAssert(function);
203+
ControlFlowSideEffects& functionSideEffects = m_functionSideEffects[function];
204+
auto _visit = [&, visited = std::set<FunctionDefinition const*>{}](FunctionDefinition const& _function, auto&& _recurse) mutable {
205+
// Worst side-effects already, stop searching.
206+
if (functionSideEffects.canTerminate && functionSideEffects.canRevert)
198207
return;
199-
if (!visited.insert(_function).second)
208+
if (!visited.insert(&_function).second)
200209
return;
201210

202-
ControlFlowSideEffects const* calledSideEffects = nullptr;
203-
if (BuiltinFunction const* f = _dialect.builtin(_function))
204-
calledSideEffects = &f->controlFlowSideEffects;
205-
else
206-
calledSideEffects = &m_functionSideEffects.at(_function);
207-
208-
if (calledSideEffects->canTerminate)
209-
sideEffects.canTerminate = true;
210-
if (calledSideEffects->canRevert)
211-
sideEffects.canRevert = true;
212-
213-
set<YulString> emptySet;
214-
for (YulString callee: util::valueOrDefault(m_functionCalls, _function, emptySet))
215-
_recurse(callee, _recurse);
211+
for (FunctionCall const* call: m_functionCalls.at(&_function))
212+
{
213+
ControlFlowSideEffects const& calledSideEffects = sideEffects(*call);
214+
if (calledSideEffects.canTerminate)
215+
functionSideEffects.canTerminate = true;
216+
if (calledSideEffects.canRevert)
217+
functionSideEffects.canRevert = true;
218+
219+
if (m_functionReferences.count(call))
220+
_recurse(*m_functionReferences.at(call), _recurse);
221+
}
216222
};
217-
for (auto const& call: calls)
218-
_visit(call, _visit);
223+
_visit(*function, _visit);
219224
}
225+
}
220226

227+
map<YulString, ControlFlowSideEffects> ControlFlowSideEffectsCollector::functionSideEffectsNamed() const
228+
{
229+
map<YulString, ControlFlowSideEffects> result;
230+
for (auto&& [function, sideEffects]: m_functionSideEffects)
231+
yulAssert(result.insert({function->name, sideEffects}).second);
232+
return result;
221233
}
222234

223-
bool ControlFlowSideEffectsCollector::processFunction(YulString _name)
235+
bool ControlFlowSideEffectsCollector::processFunction(FunctionDefinition const& _function)
224236
{
225237
bool progress = false;
226-
while (ControlFlowNode const* node = nextProcessableNode(_name))
238+
while (ControlFlowNode const* node = nextProcessableNode(_function))
227239
{
228-
if (node == m_cfgBuilder.functionFlows().at(_name).exit)
240+
if (node == m_cfgBuilder.functionFlows().at(&_function).exit)
229241
{
230-
m_functionSideEffects[_name].canContinue = true;
242+
m_functionSideEffects[&_function].canContinue = true;
231243
return true;
232244
}
233245
for (ControlFlowNode const* s: node->successors)
234-
recordReachabilityAndQueue(_name, s);
246+
recordReachabilityAndQueue(_function, s);
235247

236248
progress = true;
237249
}
238250
return progress;
239251
}
240252

241-
ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(YulString _functionName)
253+
ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(FunctionDefinition const& _function)
242254
{
243-
std::list<ControlFlowNode const*>& nodes = m_pendingNodes[_functionName];
255+
std::list<ControlFlowNode const*>& nodes = m_pendingNodes[&_function];
244256
auto it = ranges::find_if(nodes, [this](ControlFlowNode const* _node) {
245257
return !_node->functionCall || sideEffects(*_node->functionCall).canContinue;
246258
});
@@ -252,22 +264,22 @@ ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(YulS
252264
return node;
253265
}
254266

255-
ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(YulString _functionName) const
267+
ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(FunctionCall const& _call) const
256268
{
257-
if (auto const* builtin = m_dialect.builtin(_functionName))
269+
if (auto const* builtin = m_dialect.builtin(_call.functionName.name))
258270
return builtin->controlFlowSideEffects;
259271
else
260-
return m_functionSideEffects.at(_functionName);
272+
return m_functionSideEffects.at(m_functionReferences.at(&_call));
261273
}
262274

263275
void ControlFlowSideEffectsCollector::recordReachabilityAndQueue(
264-
YulString _functionName,
276+
FunctionDefinition const& _function,
265277
ControlFlowNode const* _node
266278
)
267279
{
268280
if (_node->functionCall)
269-
m_functionCalls[_functionName].insert(*_node->functionCall);
270-
if (m_processedNodes[_functionName].insert(_node).second)
271-
m_pendingNodes.at(_functionName).push_front(_node);
281+
m_functionCalls[&_function].insert(_node->functionCall);
282+
if (m_processedNodes[&_function].insert(_node).second)
283+
m_pendingNodes.at(&_function).push_front(_node);
272284
}
273285

libyul/ControlFlowSideEffectsCollector.h

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ struct Dialect;
3434
struct ControlFlowNode
3535
{
3636
std::vector<ControlFlowNode const*> successors;
37-
/// Name of the called function if the node calls a function.
38-
std::optional<YulString> functionCall;
37+
/// Function call AST node, if present.
38+
FunctionCall const* functionCall = nullptr;
3939
};
4040

4141
/**
@@ -56,7 +56,7 @@ class ControlFlowBuilder: private ASTWalker
5656
/// Computes the control-flows of all function defined in the block.
5757
/// Assumes the functions are hoisted to the topmost block.
5858
explicit ControlFlowBuilder(Block const& _ast);
59-
std::map<YulString, FunctionFlow> const& functionFlows() const { return m_functionFlows; }
59+
std::map<FunctionDefinition const*, FunctionFlow> const& functionFlows() const { return m_functionFlows; }
6060

6161
private:
6262
using ASTWalker::operator();
@@ -79,12 +79,14 @@ class ControlFlowBuilder: private ASTWalker
7979
ControlFlowNode const* m_break = nullptr;
8080
ControlFlowNode const* m_continue = nullptr;
8181

82-
std::map<YulString, FunctionFlow> m_functionFlows;
82+
std::map<FunctionDefinition const*, FunctionFlow> m_functionFlows;
8383
};
8484

8585

8686
/**
87-
* Requires: Disambiguator, Function Hoister.
87+
* Computes control-flow side-effects for user-defined functions.
88+
* Source does not have to be disambiguated, unless you want the side-effects
89+
* based on function names.
8890
*/
8991
class ControlFlowSideEffectsCollector
9092
{
@@ -94,36 +96,43 @@ class ControlFlowSideEffectsCollector
9496
Block const& _ast
9597
);
9698

97-
std::map<YulString, ControlFlowSideEffects> const& functionSideEffects() const
99+
std::map<FunctionDefinition const*, ControlFlowSideEffects> const& functionSideEffects() const
98100
{
99101
return m_functionSideEffects;
100102
}
103+
/// Returns the side effects by function name, requires unique function names.
104+
std::map<YulString, ControlFlowSideEffects> functionSideEffectsNamed() const;
101105
private:
102106

103107
/// @returns false if nothing could be processed.
104-
bool processFunction(YulString _name);
108+
bool processFunction(FunctionDefinition const& _function);
105109

106110
/// @returns the next pending node of the function that is not
107111
/// a function call to a function that might not continue.
108112
/// De-queues the node or returns nullptr if no such node is found.
109-
ControlFlowNode const* nextProcessableNode(YulString _functionName);
113+
ControlFlowNode const* nextProcessableNode(FunctionDefinition const& _function);
110114

111115
/// @returns the side-effects of either a builtin call or a user defined function
112116
/// call (as far as already computed).
113-
ControlFlowSideEffects const& sideEffects(YulString _functionName) const;
117+
ControlFlowSideEffects const& sideEffects(FunctionCall const& _call) const;
114118

115119
/// Queues the given node to be processed (if not already visited)
116120
/// and if it is a function call, records that `_functionName` calls
117121
/// `*_node->functionCall`.
118-
void recordReachabilityAndQueue(YulString _functionName, ControlFlowNode const* _node);
122+
void recordReachabilityAndQueue(FunctionDefinition const& _function, ControlFlowNode const* _node);
119123

120124
Dialect const& m_dialect;
121125
ControlFlowBuilder m_cfgBuilder;
122-
std::map<YulString, ControlFlowSideEffects> m_functionSideEffects;
123-
std::map<YulString, std::list<ControlFlowNode const*>> m_pendingNodes;
124-
std::map<YulString, std::set<ControlFlowNode const*>> m_processedNodes;
125-
/// `x` is in `m_functionCalls[y]` if a direct call to `x` is reachable inside `y`
126-
std::map<YulString, std::set<YulString>> m_functionCalls;
126+
/// Function references, but only for calls to user-defined functions.
127+
std::map<FunctionCall const*, FunctionDefinition const*> m_functionReferences;
128+
/// Side effects of user-defined functions, is being constructod.
129+
std::map<FunctionDefinition const*, ControlFlowSideEffects> m_functionSideEffects;
130+
/// Control flow nodes still to process, per function.
131+
std::map<FunctionDefinition const*, std::list<ControlFlowNode const*>> m_pendingNodes;
132+
/// Control flow nodes already processed, per function.
133+
std::map<FunctionDefinition const*, std::set<ControlFlowNode const*>> m_processedNodes;
134+
/// Set of reachable function calls nodes in each function (including calls to builtins).
135+
std::map<FunctionDefinition const*, std::set<FunctionCall const*>> m_functionCalls;
127136
};
128137

129138

libyul/optimiser/ConditionalSimplifier.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ using namespace solidity::util;
2929

3030
void ConditionalSimplifier::run(OptimiserStepContext& _context, Block& _ast)
3131
{
32-
ControlFlowSideEffectsCollector sideEffects(_context.dialect, _ast);
33-
ConditionalSimplifier{_context.dialect, sideEffects.functionSideEffects()}(_ast);
32+
ConditionalSimplifier{
33+
_context.dialect,
34+
ControlFlowSideEffectsCollector{_context.dialect, _ast}.functionSideEffectsNamed()
35+
}(_ast);
3436
}
3537

3638
void ConditionalSimplifier::operator()(Switch& _switch)

libyul/optimiser/ConditionalSimplifier.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ class ConditionalSimplifier: public ASTModifier
6262
private:
6363
explicit ConditionalSimplifier(
6464
Dialect const& _dialect,
65-
std::map<YulString, ControlFlowSideEffects> const& _sideEffects
65+
std::map<YulString, ControlFlowSideEffects> _sideEffects
6666
):
67-
m_dialect(_dialect), m_functionSideEffects(_sideEffects)
67+
m_dialect(_dialect), m_functionSideEffects(move(_sideEffects))
6868
{}
6969
Dialect const& m_dialect;
70-
std::map<YulString, ControlFlowSideEffects> const& m_functionSideEffects;
70+
std::map<YulString, ControlFlowSideEffects> m_functionSideEffects;
7171
};
7272

7373
}

libyul/optimiser/ConditionalUnsimplifier.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ using namespace solidity::util;
3030

3131
void ConditionalUnsimplifier::run(OptimiserStepContext& _context, Block& _ast)
3232
{
33-
ControlFlowSideEffectsCollector sideEffects(_context.dialect, _ast);
34-
ConditionalUnsimplifier{_context.dialect, sideEffects.functionSideEffects()}(_ast);
33+
ConditionalUnsimplifier{
34+
_context.dialect,
35+
ControlFlowSideEffectsCollector{_context.dialect, _ast}.functionSideEffectsNamed()
36+
}(_ast);
3537
}
3638

3739
void ConditionalUnsimplifier::operator()(Switch& _switch)

libyul/optimiser/DeadCodeEliminator.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ void DeadCodeEliminator::run(OptimiserStepContext& _context, Block& _ast)
4040
ControlFlowSideEffectsCollector sideEffects(_context.dialect, _ast);
4141
DeadCodeEliminator{
4242
_context.dialect,
43-
sideEffects.functionSideEffects()
43+
sideEffects.functionSideEffectsNamed()
4444
}(_ast);
4545
}
4646

libyul/optimiser/DeadCodeEliminator.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include <libyul/optimiser/ASTWalker.h>
2525
#include <libyul/YulString.h>
26+
#include <libyul/ControlFlowSideEffects.h>
2627

2728
#include <map>
2829
#include <set>
@@ -31,7 +32,6 @@ namespace solidity::yul
3132
{
3233
struct Dialect;
3334
struct OptimiserStepContext;
34-
struct ControlFlowSideEffects;
3535

3636
/**
3737
* Optimisation stage that removes unreachable code
@@ -62,11 +62,11 @@ class DeadCodeEliminator: public ASTModifier
6262
private:
6363
DeadCodeEliminator(
6464
Dialect const& _dialect,
65-
std::map<YulString, ControlFlowSideEffects> const& _sideEffects
66-
): m_dialect(_dialect), m_functionSideEffects(_sideEffects) {}
65+
std::map<YulString, ControlFlowSideEffects> _sideEffects
66+
): m_dialect(_dialect), m_functionSideEffects(move(_sideEffects)) {}
6767

6868
Dialect const& m_dialect;
69-
std::map<YulString, ControlFlowSideEffects> const& m_functionSideEffects;
69+
std::map<YulString, ControlFlowSideEffects> m_functionSideEffects;
7070
};
7171

7272
}

0 commit comments

Comments
 (0)