Skip to content

Commit 7e375b1

Browse files
committed
Control flow side-effects for user-defined functions.
1 parent ff052a3 commit 7e375b1

20 files changed

+624
-32
lines changed

libsolidity/analysis/ControlFlowBuilder.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -533,14 +533,14 @@ void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
533533
yul::ASTWalker::operator()(_functionCall);
534534

535535
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
536-
if (builtinFunction->controlFlowSideEffects.terminates)
537-
{
538-
if (builtinFunction->controlFlowSideEffects.reverts)
539-
connect(m_currentNode, m_revertNode);
540-
else
541-
connect(m_currentNode, m_transactionReturnNode);
536+
{
537+
if (builtinFunction->controlFlowSideEffects.canTerminate)
538+
connect(m_currentNode, m_transactionReturnNode);
539+
if (builtinFunction->controlFlowSideEffects.canRevert)
540+
connect(m_currentNode, m_revertNode);
541+
if (!builtinFunction->controlFlowSideEffects.canContinue)
542542
m_currentNode = newLabel();
543-
}
543+
}
544544
}
545545

546546
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)

libyul/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ add_library(yul
3434
AssemblyStack.cpp
3535
CompilabilityChecker.cpp
3636
CompilabilityChecker.h
37+
ControlFlowSideEffects.h
38+
ControlFlowSideEffectsCollector.cpp
39+
ControlFlowSideEffectsCollector.h
3740
Dialect.cpp
3841
Dialect.h
3942
Exceptions.h

libyul/ControlFlowSideEffects.h

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,25 @@
1818

1919
#pragma once
2020

21-
#include <set>
22-
2321
namespace solidity::yul
2422
{
2523

2624
/**
27-
* Side effects of code related to control flow.
25+
* Side effects of a user-defined or builtin function.
2826
*/
2927
struct ControlFlowSideEffects
3028
{
31-
/// If true, this code terminates the control flow.
32-
/// State may or may not be reverted as indicated by the ``reverts`` flag.
33-
bool terminates = false;
34-
/// If true, this code reverts all state changes in the transaction.
35-
/// Whenever this is true, ``terminates`` has to be true as well.
36-
bool reverts = false;
29+
/// If true, the function contains at least one reachable branch that terminates successfully.
30+
bool canTerminate = false;
31+
/// If true, the function contains at least one reachable branch that reverts.
32+
bool canRevert = false;
33+
/// If true, the function has a regular outgoing control-flow.
34+
bool canContinue = true;
35+
36+
bool terminatesOrReverts() const
37+
{
38+
return (canTerminate || canRevert) && !canContinue;
39+
}
3740
};
3841

3942
}
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
19+
#include <libyul/ControlFlowSideEffectsCollector.h>
20+
21+
#include <libyul/optimiser/FunctionDefinitionCollector.h>
22+
23+
#include <libyul/AST.h>
24+
#include <libyul/Dialect.h>
25+
26+
#include <libsolutil/Common.h>
27+
#include <libsolutil/Algorithms.h>
28+
29+
#include <range/v3/view/reverse.hpp>
30+
#include <range/v3/algorithm/find_if.hpp>
31+
32+
using namespace std;
33+
using namespace solidity::yul;
34+
35+
36+
ControlFlowBuilder::ControlFlowBuilder(Block const& _ast)
37+
{
38+
for (auto const& statement: _ast.statements)
39+
if (auto const* function = get_if<FunctionDefinition>(&statement))
40+
(*this)(*function);
41+
}
42+
43+
void ControlFlowBuilder::operator()(FunctionCall const& _functionCall)
44+
{
45+
walkVector(_functionCall.arguments | ranges::views::reverse);
46+
newConnectedNode();
47+
m_currentNode->functionCall = _functionCall.functionName.name;
48+
}
49+
50+
void ControlFlowBuilder::operator()(If const& _if)
51+
{
52+
visit(*_if.condition);
53+
ControlFlowNode* node = m_currentNode;
54+
(*this)(_if.body);
55+
newConnectedNode();
56+
node->successors.emplace_back(m_currentNode);
57+
}
58+
59+
void ControlFlowBuilder::operator()(Switch const& _switch)
60+
{
61+
visit(*_switch.expression);
62+
ControlFlowNode* initialNode = m_currentNode;
63+
ControlFlowNode* finalNode = newNode();
64+
65+
if (_switch.cases.back().value)
66+
initialNode->successors.emplace_back(finalNode);
67+
68+
for (Case const& case_: _switch.cases)
69+
{
70+
m_currentNode = initialNode;
71+
(*this)(case_.body);
72+
newConnectedNode();
73+
m_currentNode->successors.emplace_back(finalNode);
74+
}
75+
m_currentNode = finalNode;
76+
}
77+
78+
void ControlFlowBuilder::operator()(FunctionDefinition const& _function)
79+
{
80+
ScopedSaveAndRestore currentNode(m_currentNode, nullptr);
81+
ScopedSaveAndRestore leaveNode(m_leave, nullptr);
82+
ScopedSaveAndRestore breakNode(m_break, nullptr);
83+
ScopedSaveAndRestore continueNode(m_continue, nullptr);
84+
85+
FunctionFlow flow;
86+
flow.exit = newNode();
87+
m_currentNode = newNode();
88+
flow.entry = m_currentNode;
89+
newConnectedNode();
90+
m_leave = flow.exit;
91+
92+
(*this)(_function.body);
93+
94+
m_currentNode->successors.emplace_back(flow.exit);
95+
96+
m_functionFlows[_function.name] = move(flow);
97+
}
98+
99+
void ControlFlowBuilder::operator()(ForLoop const& _for)
100+
{
101+
ScopedSaveAndRestore scopedBreakNode(m_break, nullptr);
102+
ScopedSaveAndRestore scopedContinueNode(m_continue, nullptr);
103+
104+
(*this)(_for.pre);
105+
106+
ControlFlowNode* breakNode = newNode();
107+
m_break = breakNode;
108+
ControlFlowNode* continueNode = newNode();
109+
m_continue = continueNode;
110+
111+
newConnectedNode();
112+
ControlFlowNode* loopNode = m_currentNode;
113+
visit(*_for.condition);
114+
m_currentNode->successors.emplace_back(m_break);
115+
newConnectedNode();
116+
117+
(*this)(_for.body);
118+
119+
m_currentNode->successors.emplace_back(m_continue);
120+
m_currentNode = continueNode;
121+
122+
(*this)(_for.post);
123+
m_currentNode->successors.emplace_back(loopNode);
124+
125+
m_currentNode = breakNode;
126+
}
127+
128+
void ControlFlowBuilder::operator()(Break const&)
129+
{
130+
m_currentNode->successors.emplace_back(m_break);
131+
m_currentNode = newNode();
132+
}
133+
134+
void ControlFlowBuilder::operator()(Continue const&)
135+
{
136+
m_currentNode->successors.emplace_back(m_continue);
137+
m_currentNode = newNode();
138+
}
139+
140+
void ControlFlowBuilder::operator()(Leave const&)
141+
{
142+
m_currentNode->successors.emplace_back(m_leave);
143+
m_currentNode = newNode();
144+
}
145+
146+
void ControlFlowBuilder::newConnectedNode()
147+
{
148+
ControlFlowNode* node = newNode();
149+
m_currentNode->successors.emplace_back(node);
150+
m_currentNode = node;
151+
}
152+
153+
ControlFlowNode* ControlFlowBuilder::newNode()
154+
{
155+
m_nodes.emplace_back(make_shared<ControlFlowNode>());
156+
return m_nodes.back().get();
157+
}
158+
159+
160+
ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector(
161+
Dialect const& _dialect,
162+
Block const& _ast
163+
):
164+
m_dialect(_dialect),
165+
m_cfgBuilder(_ast)
166+
{
167+
for (auto&& [name, flow]: m_cfgBuilder.functionFlows())
168+
{
169+
recordReachabilityAndQueue(name, flow.entry);
170+
m_functionSideEffects[name] = {false, false, false};
171+
}
172+
173+
// Process functions while we have progress. For now, we are only interested
174+
// in `canContinue`.
175+
bool progress = true;
176+
while (progress)
177+
{
178+
progress = false;
179+
for (auto const& fun: m_pendingNodes)
180+
if (processFunction(fun.first))
181+
progress = true;
182+
}
183+
184+
// No progress anymore: All remaining nodes are calls
185+
// to functions that always recurse.
186+
// If we have not set `canContinue` by now, the function's exit
187+
// is not reachable.
188+
189+
for (auto&& [functionName, calls]: m_functionCalls)
190+
{
191+
ControlFlowSideEffects& sideEffects = m_functionSideEffects[functionName];
192+
auto _visit = [&, visited = std::set<YulString>{}](YulString _function, auto&& _recurse) mutable {
193+
if (sideEffects.canTerminate && sideEffects.canRevert)
194+
return;
195+
if (!visited.insert(_function).second)
196+
return;
197+
198+
ControlFlowSideEffects const* calledSideEffects = nullptr;
199+
if (BuiltinFunction const* f = _dialect.builtin(_function))
200+
calledSideEffects = &f->controlFlowSideEffects;
201+
else
202+
calledSideEffects = &m_functionSideEffects.at(_function);
203+
204+
if (calledSideEffects->canTerminate)
205+
sideEffects.canTerminate = true;
206+
if (calledSideEffects->canRevert)
207+
sideEffects.canRevert = true;
208+
209+
if (m_functionCalls.count(_function))
210+
for (YulString callee: m_functionCalls.at(_function))
211+
_recurse(callee, _recurse);
212+
};
213+
for (auto const& call: calls)
214+
_visit(call, _visit);
215+
}
216+
217+
}
218+
219+
bool ControlFlowSideEffectsCollector::processFunction(YulString _name)
220+
{
221+
bool progress = false;
222+
while (ControlFlowNode const* node = nextProcessableNode(_name))
223+
{
224+
if (node == m_cfgBuilder.functionFlows().at(_name).exit)
225+
{
226+
m_functionSideEffects[_name].canContinue = true;
227+
return true;
228+
}
229+
for (ControlFlowNode const* s: node->successors)
230+
recordReachabilityAndQueue(_name, s);
231+
232+
progress = true;
233+
}
234+
return progress;
235+
}
236+
237+
ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(YulString _functionName)
238+
{
239+
std::list<ControlFlowNode const*>& nodes = m_pendingNodes[_functionName];
240+
auto it = ranges::find_if(nodes, [this](ControlFlowNode const* _node) {
241+
return !_node->functionCall || sideEffects(*_node->functionCall).canContinue;
242+
});
243+
if (it == nodes.end())
244+
return nullptr;
245+
246+
ControlFlowNode const* node = *it;
247+
m_pendingNodes[_functionName].erase(it);
248+
return node;
249+
}
250+
251+
ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(YulString _functionName) const
252+
{
253+
if (auto const* builtin = m_dialect.builtin(_functionName))
254+
return builtin->controlFlowSideEffects;
255+
else
256+
return m_functionSideEffects.at(_functionName);
257+
}
258+
259+
void ControlFlowSideEffectsCollector::recordReachabilityAndQueue(
260+
YulString _functionName,
261+
ControlFlowNode const* _node
262+
)
263+
{
264+
if (_node->functionCall)
265+
m_functionCalls[_functionName].insert(*_node->functionCall);
266+
if (m_processedNodes[_functionName].insert(_node).second)
267+
m_pendingNodes[_functionName].push_front(_node);
268+
}
269+

0 commit comments

Comments
 (0)