Skip to content

Commit db60d12

Browse files
sifmelcarachriseth
authored andcommitted
[YulOpt] Implement loop-invariant code motion
1 parent 7c06398 commit db60d12

22 files changed

+408
-15
lines changed

Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Language Features:
77

88
Compiler Features:
99
* Yul: When compiling via Yul, string literals from the Solidity code are kept as string literals if every character is safely printable.
10+
* Yul Optimizer: Perform loop-invariant code motion.
1011

1112

1213
Bugfixes:

libyul/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ add_library(yul
110110
optimiser/KnowledgeBase.h
111111
optimiser/LoadResolver.cpp
112112
optimiser/LoadResolver.h
113+
optimiser/LoopInvariantCodeMotion.cpp
114+
optimiser/LoopInvariantCodeMotion.h
113115
optimiser/MainFunction.cpp
114116
optimiser/MainFunction.h
115117
optimiser/Metrics.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
18+
#include <libyul/optimiser/LoopInvariantCodeMotion.h>
19+
20+
#include <libyul/optimiser/CallGraphGenerator.h>
21+
#include <libyul/optimiser/NameCollector.h>
22+
#include <libyul/optimiser/Semantics.h>
23+
#include <libyul/optimiser/SSAValueTracker.h>
24+
#include <libyul/AsmData.h>
25+
#include <libdevcore/CommonData.h>
26+
27+
#include <utility>
28+
29+
using namespace std;
30+
using namespace dev;
31+
using namespace yul;
32+
33+
void LoopInvariantCodeMotion::run(OptimiserStepContext& _context, Block& _ast)
34+
{
35+
map<YulString, SideEffects> functionSideEffects =
36+
SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast));
37+
38+
set<YulString> ssaVars = SSAValueTracker::ssaVariables(_ast);
39+
LoopInvariantCodeMotion{_context.dialect, ssaVars, functionSideEffects}(_ast);
40+
}
41+
42+
void LoopInvariantCodeMotion::operator()(Block& _block)
43+
{
44+
iterateReplacing(
45+
_block.statements,
46+
[&](Statement& _s) -> optional<vector<Statement>>
47+
{
48+
visit(_s);
49+
if (holds_alternative<ForLoop>(_s))
50+
return rewriteLoop(get<ForLoop>(_s));
51+
else
52+
return {};
53+
}
54+
);
55+
}
56+
57+
bool LoopInvariantCodeMotion::canBePromoted(
58+
VariableDeclaration const& _varDecl,
59+
set<YulString> const& _varsDefinedInCurrentScope
60+
) const
61+
{
62+
// A declaration can be promoted iff
63+
// 1. Its LHS is a SSA variable
64+
// 2. Its RHS only references SSA variables declared outside of the current scope
65+
// 3. Its RHS is movable
66+
67+
for (auto const& var: _varDecl.variables)
68+
if (!m_ssaVariables.count(var.name))
69+
return false;
70+
if (_varDecl.value)
71+
{
72+
for (auto const& ref: ReferencesCounter::countReferences(*_varDecl.value, ReferencesCounter::OnlyVariables))
73+
if (_varsDefinedInCurrentScope.count(ref.first) || !m_ssaVariables.count(ref.first))
74+
return false;
75+
if (!SideEffectsCollector{m_dialect, *_varDecl.value, &m_functionSideEffects}.movable())
76+
return false;
77+
}
78+
return true;
79+
}
80+
81+
optional<vector<Statement>> LoopInvariantCodeMotion::rewriteLoop(ForLoop& _for)
82+
{
83+
assertThrow(_for.pre.statements.empty(), OptimizerException, "");
84+
vector<Statement> replacement;
85+
for (Block* block: {&_for.post, &_for.body})
86+
{
87+
set<YulString> varsDefinedInScope;
88+
iterateReplacing(
89+
block->statements,
90+
[&](Statement& _s) -> optional<vector<Statement>>
91+
{
92+
if (holds_alternative<VariableDeclaration>(_s))
93+
{
94+
VariableDeclaration const& varDecl = std::get<VariableDeclaration>(_s);
95+
if (canBePromoted(varDecl, varsDefinedInScope))
96+
{
97+
replacement.emplace_back(std::move(_s));
98+
// Do not add the variables declared here to varsDefinedInScope because we are moving them.
99+
return vector<Statement>{};
100+
}
101+
for (auto const& var: varDecl.variables)
102+
varsDefinedInScope.insert(var.name);
103+
}
104+
return {};
105+
}
106+
);
107+
}
108+
if (replacement.empty())
109+
return {};
110+
else
111+
{
112+
replacement.emplace_back(std::move(_for));
113+
return { std::move(replacement) };
114+
}
115+
}
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
#pragma once
18+
19+
#include <libyul/optimiser/ASTWalker.h>
20+
#include <libyul/optimiser/Semantics.h>
21+
#include <libyul/optimiser/OptimiserStep.h>
22+
23+
namespace yul
24+
{
25+
26+
/**
27+
* Loop-invariant code motion.
28+
*
29+
* This optimization moves movable SSA variable declarations outside the loop.
30+
*
31+
* Only statements at the top level in a loop's body or post block are considered, i.e variable
32+
* declarations inside conditional branches will not be moved out of the loop.
33+
*
34+
* Requirements:
35+
* - The Disambiguator, ForLoopInitRewriter and FunctionHoister must be run upfront.
36+
* - Expression splitter and SSA transform should be run upfront to obtain better result.
37+
*/
38+
39+
class LoopInvariantCodeMotion: public ASTModifier
40+
{
41+
public:
42+
static constexpr char const* name{"LoopInvariantCodeMotion"};
43+
static void run(OptimiserStepContext& _context, Block& _ast);
44+
45+
void operator()(Block& _block) override;
46+
47+
private:
48+
explicit LoopInvariantCodeMotion(
49+
Dialect const& _dialect,
50+
std::set<YulString> const& _ssaVariables,
51+
std::map<YulString, SideEffects> const& _functionSideEffects
52+
):
53+
m_dialect(_dialect),
54+
m_ssaVariables(_ssaVariables),
55+
m_functionSideEffects(_functionSideEffects)
56+
{ }
57+
58+
/// @returns true if the given variable declaration can be moved to in front of the loop.
59+
bool canBePromoted(VariableDeclaration const& _varDecl, std::set<YulString> const& _varsDefinedInCurrentScope) const;
60+
std::optional<std::vector<Statement>> rewriteLoop(ForLoop& _for);
61+
62+
Dialect const& m_dialect;
63+
std::set<YulString> const& m_ssaVariables;
64+
std::map<YulString, SideEffects> const& m_functionSideEffects;
65+
};
66+
67+
}

libyul/optimiser/NameCollector.cpp

+8-7
Original file line numberDiff line numberDiff line change
@@ -49,27 +49,28 @@ void ReferencesCounter::operator()(Identifier const& _identifier)
4949

5050
void ReferencesCounter::operator()(FunctionCall const& _funCall)
5151
{
52-
++m_references[_funCall.functionName.name];
52+
if (m_countWhat == VariablesAndFunctions)
53+
++m_references[_funCall.functionName.name];
5354
ASTWalker::operator()(_funCall);
5455
}
5556

56-
map<YulString, size_t> ReferencesCounter::countReferences(Block const& _block)
57+
map<YulString, size_t> ReferencesCounter::countReferences(Block const& _block, CountWhat _countWhat)
5758
{
58-
ReferencesCounter counter;
59+
ReferencesCounter counter(_countWhat);
5960
counter(_block);
6061
return counter.references();
6162
}
6263

63-
map<YulString, size_t> ReferencesCounter::countReferences(FunctionDefinition const& _function)
64+
map<YulString, size_t> ReferencesCounter::countReferences(FunctionDefinition const& _function, CountWhat _countWhat)
6465
{
65-
ReferencesCounter counter;
66+
ReferencesCounter counter(_countWhat);
6667
counter(_function);
6768
return counter.references();
6869
}
6970

70-
map<YulString, size_t> ReferencesCounter::countReferences(Expression const& _expression)
71+
map<YulString, size_t> ReferencesCounter::countReferences(Expression const& _expression, CountWhat _countWhat)
7172
{
72-
ReferencesCounter counter;
73+
ReferencesCounter counter(_countWhat);
7374
counter.visit(_expression);
7475
return counter.references();
7576
}

libyul/optimiser/NameCollector.h

+10-3
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,23 @@ class NameCollector: public ASTWalker
5454
class ReferencesCounter: public ASTWalker
5555
{
5656
public:
57+
enum CountWhat { VariablesAndFunctions, OnlyVariables };
58+
59+
explicit ReferencesCounter(CountWhat _countWhat = VariablesAndFunctions):
60+
m_countWhat(_countWhat)
61+
{}
62+
5763
using ASTWalker::operator ();
5864
virtual void operator()(Identifier const& _identifier);
5965
virtual void operator()(FunctionCall const& _funCall);
6066

61-
static std::map<YulString, size_t> countReferences(Block const& _block);
62-
static std::map<YulString, size_t> countReferences(FunctionDefinition const& _function);
63-
static std::map<YulString, size_t> countReferences(Expression const& _expression);
67+
static std::map<YulString, size_t> countReferences(Block const& _block, CountWhat _countWhat = VariablesAndFunctions);
68+
static std::map<YulString, size_t> countReferences(FunctionDefinition const& _function, CountWhat _countWhat = VariablesAndFunctions);
69+
static std::map<YulString, size_t> countReferences(Expression const& _expression, CountWhat _countWhat = VariablesAndFunctions);
6470

6571
std::map<YulString, size_t> const& references() const { return m_references; }
6672
private:
73+
CountWhat m_countWhat = CountWhat::VariablesAndFunctions;
6774
std::map<YulString, size_t> m_references;
6875
};
6976

libyul/optimiser/SSAValueTracker.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ void SSAValueTracker::operator()(VariableDeclaration const& _varDecl)
4949
setValue(_varDecl.variables.front().name, _varDecl.value.get());
5050
}
5151

52+
set<YulString> SSAValueTracker::ssaVariables(Block const& _ast)
53+
{
54+
SSAValueTracker t;
55+
t(_ast);
56+
set<YulString> ssaVars;
57+
for (auto const& value: t.values())
58+
ssaVars.insert(value.first);
59+
return ssaVars;
60+
}
61+
5262
void SSAValueTracker::setValue(YulString _name, Expression const* _value)
5363
{
5464
assertThrow(

libyul/optimiser/SSAValueTracker.h

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class SSAValueTracker: public ASTWalker
4949
std::map<YulString, Expression const*> const& values() const { return m_values; }
5050
Expression const* value(YulString _name) const { return m_values.at(_name); }
5151

52+
static std::set<YulString> ssaVariables(Block const& _ast);
53+
5254
private:
5355
void setValue(YulString _name, Expression const* _value);
5456

libyul/optimiser/Suite.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#include <libyul/optimiser/RedundantAssignEliminator.h>
5353
#include <libyul/optimiser/VarNameCleaner.h>
5454
#include <libyul/optimiser/LoadResolver.h>
55+
#include <libyul/optimiser/LoopInvariantCodeMotion.h>
5556
#include <libyul/optimiser/Metrics.h>
5657
#include <libyul/backends/evm/ConstantOptimiser.h>
5758
#include <libyul/AsmAnalysis.h>
@@ -129,7 +130,8 @@ void OptimiserSuite::run(
129130
RedundantAssignEliminator::name,
130131
ExpressionSimplifier::name,
131132
CommonSubexpressionEliminator::name,
132-
LoadResolver::name
133+
LoadResolver::name,
134+
LoopInvariantCodeMotion::name
133135
}, ast);
134136
}
135137

@@ -345,6 +347,7 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
345347
FunctionHoister,
346348
LiteralRematerialiser,
347349
LoadResolver,
350+
LoopInvariantCodeMotion,
348351
RedundantAssignEliminator,
349352
Rematerialiser,
350353
SSAReverser,

scripts/codespell_whitelist.txt

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ fo
1010
compilability
1111
errorstring
1212
hist
13+
otion

test/libyul/YulOptimizerTest.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include <libyul/optimiser/ForLoopConditionOutOfBody.h>
4242
#include <libyul/optimiser/ForLoopInitRewriter.h>
4343
#include <libyul/optimiser/LoadResolver.h>
44+
#include <libyul/optimiser/LoopInvariantCodeMotion.h>
4445
#include <libyul/optimiser/MainFunction.h>
4546
#include <libyul/optimiser/NameDisplacer.h>
4647
#include <libyul/optimiser/Rematerialiser.h>
@@ -279,6 +280,12 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line
279280
ExpressionJoiner::run(*m_context, *m_ast);
280281
ExpressionJoiner::run(*m_context, *m_ast);
281282
}
283+
else if (m_optimizerStep == "loopInvariantCodeMotion")
284+
{
285+
disambiguate();
286+
ForLoopInitRewriter::run(*m_context, *m_ast);
287+
LoopInvariantCodeMotion::run(*m_context, *m_ast);
288+
}
282289
else if (m_optimizerStep == "controlFlowSimplifier")
283290
{
284291
disambiguate();

test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul

+1-1
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@
483483
// let _5 := 0xffffffffffffffff
484484
// if gt(offset, _5) { revert(_1, _1) }
485485
// let value2 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(_4, offset), _3)
486-
// let offset_1 := calldataload(add(_4, 96))
486+
// let offset_1 := calldataload(add(_4, 0x60))
487487
// if gt(offset_1, _5) { revert(_1, _1) }
488488
// let value3 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(_4, offset_1), _3)
489489
// sstore(calldataload(_4), calldataload(add(_4, 0x20)))

test/libyul/yulOptimizerTests/fullSuite/aztec.yul

+1-1
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@
311311
// }
312312
// b := add(b, _5)
313313
// }
314-
// if lt(m, n) { validatePairing(0x64) }
314+
// if lt(m, n) { validatePairing(100) }
315315
// if iszero(eq(mod(keccak256(0x2a0, add(b, not(671))), _2), challenge))
316316
// {
317317
// mstore(0, 404)

test/libyul/yulOptimizerTests/fullSuite/clear_after_if_continue.yul

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// {
1313
// {
1414
// let y := mload(0x20)
15-
// for { } and(y, 8) { if y { revert(0, 0) } }
15+
// for { } iszero(iszero(and(y, 8))) { if y { revert(0, 0) } }
1616
// {
1717
// if y { continue }
1818
// sstore(1, 0)

0 commit comments

Comments
 (0)