Skip to content

Commit 75abe92

Browse files
committed
Refactor stack compressor.
1 parent 5afa2ad commit 75abe92

File tree

6 files changed

+99
-130
lines changed

6 files changed

+99
-130
lines changed

libyul/optimiser/NameCollector.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,22 @@ using namespace solidity::util;
3030

3131
void NameCollector::operator()(VariableDeclaration const& _varDecl)
3232
{
33-
for (auto const& var: _varDecl.variables)
34-
m_names.emplace(var.name);
33+
if (m_collectWhat != OnlyFunctions)
34+
for (auto const& var: _varDecl.variables)
35+
m_names.emplace(var.name);
3536
}
3637

37-
void NameCollector::operator ()(FunctionDefinition const& _funDef)
38+
void NameCollector::operator()(FunctionDefinition const& _funDef)
3839
{
39-
if (m_collectWhat == VariablesAndFunctions)
40+
if (m_collectWhat != OnlyVariables)
4041
m_names.emplace(_funDef.name);
41-
for (auto const& arg: _funDef.parameters)
42-
m_names.emplace(arg.name);
43-
for (auto const& ret: _funDef.returnVariables)
44-
m_names.emplace(ret.name);
42+
if (m_collectWhat != OnlyFunctions)
43+
{
44+
for (auto const& arg: _funDef.parameters)
45+
m_names.emplace(arg.name);
46+
for (auto const& ret: _funDef.returnVariables)
47+
m_names.emplace(ret.name);
48+
}
4549
ASTWalker::operator ()(_funDef);
4650
}
4751

libyul/optimiser/NameCollector.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ namespace solidity::yul
3535
class NameCollector: public ASTWalker
3636
{
3737
public:
38-
enum CollectWhat { VariablesAndFunctions, OnlyVariables };
38+
enum CollectWhat { VariablesAndFunctions, OnlyVariables, OnlyFunctions };
3939

4040
explicit NameCollector(
4141
Block const& _block,

libyul/optimiser/Rematerialiser.cpp

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,6 @@ void Rematerialiser::run(Dialect const& _dialect, Block& _ast, set<YulString> _v
3737
Rematerialiser{_dialect, _ast, std::move(_varsToAlwaysRematerialize), _onlySelectedVariables}(_ast);
3838
}
3939

40-
void Rematerialiser::run(
41-
Dialect const& _dialect,
42-
FunctionDefinition& _function,
43-
set<YulString> _varsToAlwaysRematerialize,
44-
bool _onlySelectedVariables
45-
)
46-
{
47-
Rematerialiser{_dialect, _function, std::move(_varsToAlwaysRematerialize), _onlySelectedVariables}(_function);
48-
}
49-
5040
Rematerialiser::Rematerialiser(
5141
Dialect const& _dialect,
5242
Block& _ast,
@@ -60,19 +50,6 @@ Rematerialiser::Rematerialiser(
6050
{
6151
}
6252

63-
Rematerialiser::Rematerialiser(
64-
Dialect const& _dialect,
65-
FunctionDefinition& _function,
66-
set<YulString> _varsToAlwaysRematerialize,
67-
bool _onlySelectedVariables
68-
):
69-
DataFlowAnalyzer(_dialect),
70-
m_referenceCounts(ReferencesCounter::countReferences(_function)),
71-
m_varsToAlwaysRematerialize(std::move(_varsToAlwaysRematerialize)),
72-
m_onlySelectedVariables(_onlySelectedVariables)
73-
{
74-
}
75-
7653
void Rematerialiser::visit(Expression& _e)
7754
{
7855
if (holds_alternative<Identifier>(_e))

libyul/optimiser/Rematerialiser.h

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,6 @@ class Rematerialiser: public DataFlowAnalyzer
5353
std::set<YulString> _varsToAlwaysRematerialize = {},
5454
bool _onlySelectedVariables = false
5555
);
56-
static void run(
57-
Dialect const& _dialect,
58-
FunctionDefinition& _function,
59-
std::set<YulString> _varsToAlwaysRematerialize = {},
60-
bool _onlySelectedVariables = false
61-
);
6256

6357
protected:
6458
Rematerialiser(
@@ -67,12 +61,6 @@ class Rematerialiser: public DataFlowAnalyzer
6761
std::set<YulString> _varsToAlwaysRematerialize = {},
6862
bool _onlySelectedVariables = false
6963
);
70-
Rematerialiser(
71-
Dialect const& _dialect,
72-
FunctionDefinition& _function,
73-
std::set<YulString> _varsToAlwaysRematerialize = {},
74-
bool _onlySelectedVariables = false
75-
);
7664

7765
using DataFlowAnalyzer::operator();
7866

libyul/optimiser/StackCompressor.cpp

Lines changed: 86 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -50,31 +50,41 @@ namespace
5050
/**
5151
* Class that discovers all variables that can be fully eliminated by rematerialization,
5252
* and the corresponding approximate costs.
53+
*
54+
* Prerequisite: Disambiguator, Function Grouper
5355
*/
5456
class RematCandidateSelector: public DataFlowAnalyzer
5557
{
5658
public:
5759
explicit RematCandidateSelector(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {}
5860

59-
/// @returns a map from rematerialisation costs to a vector of variables to rematerialise
61+
/// @returns a map from function name to rematerialisation costs to a vector of variables to rematerialise
6062
/// and variables that occur in their expression.
6163
/// While the map is sorted by cost, the contained vectors are sorted by the order of occurrence.
62-
map<size_t, vector<tuple<YulString, set<YulString>>>> candidates()
64+
map<YulString, map<size_t, vector<tuple<YulString, set<YulString>>>>> candidates()
6365
{
64-
map<size_t, vector<tuple<YulString, set<YulString>>>> cand;
65-
for (auto const& candidate: m_candidates)
66+
map<YulString, map<size_t, vector<tuple<YulString, set<YulString>>>>> cand;
67+
for (auto const& [functionName, candidate]: m_candidates)
6668
{
6769
if (size_t const* cost = util::valueOrNullptr(m_expressionCodeCost, candidate))
6870
{
6971
size_t numRef = m_numReferences[candidate];
7072
set<YulString> const* ref = references(candidate);
71-
cand[*cost * numRef].emplace_back(candidate, ref ? move(*ref) : set<YulString>{});
73+
cand[functionName][*cost * numRef].emplace_back(candidate, ref ? move(*ref) : set<YulString>{});
7274
}
7375
}
7476
return cand;
7577
}
7678

7779
using DataFlowAnalyzer::operator();
80+
void operator()(FunctionDefinition& _function) override
81+
{
82+
yulAssert(m_currentFunctionName.empty());
83+
m_currentFunctionName = _function.name;
84+
DataFlowAnalyzer::operator()(_function);
85+
m_currentFunctionName = {};
86+
}
87+
7888
void operator()(VariableDeclaration& _varDecl) override
7989
{
8090
DataFlowAnalyzer::operator()(_varDecl);
@@ -84,7 +94,7 @@ class RematCandidateSelector: public DataFlowAnalyzer
8494
if (AssignedValue const* value = variableValue(varName))
8595
{
8696
yulAssert(!m_expressionCodeCost.count(varName), "");
87-
m_candidates.emplace_back(varName);
97+
m_candidates.emplace_back(m_currentFunctionName, varName);
8898
m_expressionCodeCost[varName] = CodeCost::codeCost(m_dialect, *value->value);
8999
}
90100
}
@@ -122,8 +132,10 @@ class RematCandidateSelector: public DataFlowAnalyzer
122132
m_expressionCodeCost.erase(_variable);
123133
}
124134

125-
/// All candidate variables in order of occurrence.
126-
vector<YulString> m_candidates;
135+
YulString m_currentFunctionName = {};
136+
137+
/// All candidate variables by function name, in order of occurrence.
138+
vector<pair<YulString, YulString>> m_candidates;
127139
/// Candidate variables and the code cost of their value.
128140
map<YulString, size_t> m_expressionCodeCost;
129141
/// Number of references to each candidate variable.
@@ -156,62 +168,80 @@ set<YulString> chooseVarsToEliminate(
156168
return varsToEliminate;
157169
}
158170

159-
template <typename ASTNode>
160171
void eliminateVariables(
161172
Dialect const& _dialect,
162-
ASTNode& _node,
163-
size_t _numVariables,
173+
Block& _ast,
174+
map<YulString, int> const& _numVariables,
164175
bool _allowMSizeOptimization
165176
)
166177
{
167178
RematCandidateSelector selector{_dialect};
168-
selector(_node);
169-
Rematerialiser::run(_dialect, _node, chooseVarsToEliminate(selector.candidates(), _numVariables));
170-
UnusedPruner::runUntilStabilised(_dialect, _node, _allowMSizeOptimization);
179+
selector(_ast);
180+
map<YulString, map<size_t, vector<tuple<YulString, set<YulString>>>>> candidates = selector.candidates();
181+
182+
set<YulString> varsToEliminate;
183+
for (auto const& [functionName, numVariables]: _numVariables)
184+
{
185+
yulAssert(numVariables > 0);
186+
varsToEliminate += chooseVarsToEliminate(candidates[functionName], static_cast<size_t>(numVariables));
187+
}
188+
189+
Rematerialiser::run(_dialect, _ast, move(varsToEliminate));
190+
// Do not remove functions.
191+
set<YulString> allFunctions = NameCollector{_ast, NameCollector::OnlyFunctions}.names();
192+
UnusedPruner::runUntilStabilised(_dialect, _ast, _allowMSizeOptimization, nullptr, allFunctions);
171193
}
172194

173-
void eliminateVariables(
195+
void eliminateVariablesOptimizedCodegen(
174196
Dialect const& _dialect,
175-
Block& _block,
176-
vector<StackLayoutGenerator::StackTooDeep> const& _unreachables,
197+
Block& _ast,
198+
map<YulString, vector<StackLayoutGenerator::StackTooDeep>> const& _unreachables,
177199
bool _allowMSizeOptimization
178200
)
179201
{
202+
if (std::all_of(_unreachables.begin(), _unreachables.end(), [](auto const& _item) { return _item.second.empty(); }))
203+
return;
204+
180205
RematCandidateSelector selector{_dialect};
181-
selector(_block);
182-
std::map<YulString, size_t> candidates;
183-
for (auto [cost, candidatesWithCost]: selector.candidates())
184-
for (auto candidate: candidatesWithCost)
185-
candidates[get<0>(candidate)] = cost;
206+
selector(_ast);
207+
208+
map<YulString, size_t> candidates;
209+
for (auto const& [functionName, candidatesInFunction]: selector.candidates())
210+
for (auto [cost, candidatesWithCost]: candidatesInFunction)
211+
for (auto candidate: candidatesWithCost)
212+
candidates[get<0>(candidate)] = cost;
186213

187214
set<YulString> varsToEliminate;
188215

189216
// TODO: this currently ignores the fact that variables may reference other variables we want to eliminate.
190-
for (auto const& unreachable: _unreachables)
191-
{
192-
map<size_t, vector<YulString>> suitableCandidates;
193-
size_t neededSlots = unreachable.deficit;
194-
for (auto varName: unreachable.variableChoices)
195-
{
196-
if (varsToEliminate.count(varName))
197-
--neededSlots;
198-
else if (size_t* cost = util::valueOrNullptr(candidates, varName))
199-
if (!util::contains(suitableCandidates[*cost], varName))
200-
suitableCandidates[*cost].emplace_back(varName);
201-
}
202-
for (auto candidatesByCost: suitableCandidates)
217+
for (auto const& [functionName, unreachables]: _unreachables)
218+
for (auto const& unreachable: unreachables)
203219
{
204-
for (auto candidate: candidatesByCost.second)
205-
if (neededSlots--)
206-
varsToEliminate.emplace(candidate);
207-
else
220+
map<size_t, vector<YulString>> suitableCandidates;
221+
size_t neededSlots = unreachable.deficit;
222+
for (auto varName: unreachable.variableChoices)
223+
{
224+
if (varsToEliminate.count(varName))
225+
--neededSlots;
226+
else if (size_t* cost = util::valueOrNullptr(candidates, varName))
227+
if (!util::contains(suitableCandidates[*cost], varName))
228+
suitableCandidates[*cost].emplace_back(varName);
229+
}
230+
for (auto candidatesByCost: suitableCandidates)
231+
{
232+
for (auto candidate: candidatesByCost.second)
233+
if (neededSlots--)
234+
varsToEliminate.emplace(candidate);
235+
else
236+
break;
237+
if (!neededSlots)
208238
break;
209-
if (!neededSlots)
210-
break;
239+
}
211240
}
212-
}
213-
Rematerialiser::run(_dialect, _block, std::move(varsToEliminate), true);
214-
UnusedPruner::runUntilStabilised(_dialect, _block, _allowMSizeOptimization);
241+
Rematerialiser::run(_dialect, _ast, std::move(varsToEliminate), true);
242+
// Do not remove functions.
243+
set<YulString> allFunctions = NameCollector{_ast, NameCollector::OnlyFunctions}.names();
244+
UnusedPruner::runUntilStabilised(_dialect, _ast, _allowMSizeOptimization, nullptr, allFunctions);
215245
}
216246

217247
}
@@ -239,54 +269,25 @@ bool StackCompressor::run(
239269
{
240270
yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object);
241271
unique_ptr<CFG> cfg = ControlFlowGraphBuilder::build(analysisInfo, _dialect, *_object.code);
242-
Block& mainBlock = std::get<Block>(_object.code->statements.at(0));
243-
if (
244-
auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, YulString{});
245-
!stackTooDeepErrors.empty()
246-
)
247-
eliminateVariables(_dialect, mainBlock, stackTooDeepErrors, allowMSizeOptimzation);
248-
for (size_t i = 1; i < _object.code->statements.size(); ++i)
249-
{
250-
auto& fun = std::get<FunctionDefinition>(_object.code->statements[i]);
251-
if (
252-
auto stackTooDeepErrors = StackLayoutGenerator::reportStackTooDeep(*cfg, fun.name);
253-
!stackTooDeepErrors.empty()
254-
)
255-
eliminateVariables(_dialect, fun.body, stackTooDeepErrors, allowMSizeOptimzation);
256-
}
272+
eliminateVariablesOptimizedCodegen(
273+
_dialect,
274+
*_object.code,
275+
StackLayoutGenerator::reportStackTooDeep(*cfg),
276+
allowMSizeOptimzation
277+
);
257278
}
258279
else
259280
for (size_t iterations = 0; iterations < _maxIterations; iterations++)
260281
{
261282
map<YulString, int> stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit;
262283
if (stackSurplus.empty())
263284
return true;
264-
265-
if (stackSurplus.count(YulString{}))
266-
{
267-
yulAssert(stackSurplus.at({}) > 0, "Invalid surplus value.");
268-
eliminateVariables(
269-
_dialect,
270-
std::get<Block>(_object.code->statements.at(0)),
271-
static_cast<size_t>(stackSurplus.at({})),
272-
allowMSizeOptimzation
273-
);
274-
}
275-
276-
for (size_t i = 1; i < _object.code->statements.size(); ++i)
277-
{
278-
auto& fun = std::get<FunctionDefinition>(_object.code->statements[i]);
279-
if (!stackSurplus.count(fun.name))
280-
continue;
281-
282-
yulAssert(stackSurplus.at(fun.name) > 0, "Invalid surplus value.");
283-
eliminateVariables(
284-
_dialect,
285-
fun,
286-
static_cast<size_t>(stackSurplus.at(fun.name)),
287-
allowMSizeOptimzation
288-
);
289-
}
285+
eliminateVariables(
286+
_dialect,
287+
*_object.code,
288+
stackSurplus,
289+
allowMSizeOptimzation
290+
);
290291
}
291292
return false;
292293
}

test/libyul/yulOptimizerTests/stackCompressor/inlineInFunction.yul

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
// step: stackCompressor
1212
//
1313
// {
14-
// { let x := 8 }
1514
// function f()
1615
// {
1716
// mstore(calldataload(calldataload(9)), add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(add(calldataload(calldataload(9)), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1), 1))

0 commit comments

Comments
 (0)