Skip to content

Commit 7d30fbd

Browse files
committed
Extract side effects into their own struct.
1 parent d5744b3 commit 7d30fbd

File tree

8 files changed

+136
-107
lines changed

8 files changed

+136
-107
lines changed

libyul/Dialect.h

+2-16
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#pragma once
2222

2323
#include <libyul/YulString.h>
24+
#include <libyul/SideEffects.h>
2425

2526
#include <boost/noncopyable.hpp>
2627

@@ -45,24 +46,9 @@ struct BuiltinFunction
4546
YulString name;
4647
std::vector<Type> parameters;
4748
std::vector<Type> returns;
48-
/// If true, calls to this function can be freely moved and copied (as long as their
49-
/// arguments are either variables or also movable) without altering the semantics.
50-
/// This means the function cannot depend on storage or memory, cannot have any side-effects,
51-
/// but it can depend on state that is constant across an EVM-call.
52-
bool movable = false;
53-
/// If true, a call to this function can be omitted without changing semantics.
54-
bool sideEffectFree = false;
55-
/// If true, a call to this function can be omitted without changing semantics if the
56-
/// program does not contain the msize instruction.
57-
bool sideEffectFreeIfNoMSize = false;
49+
SideEffects sideEffects;
5850
/// If true, this is the msize instruction.
5951
bool isMSize = false;
60-
/// If false, storage of the current contract before and after the function is the same
61-
/// under every circumstance. If the function does not return, this can be false.
62-
bool invalidatesStorage = true;
63-
/// If false, memory before and after the function is the same under every circumstance.
64-
/// If the function does not return, this can be false.
65-
bool invalidatesMemory = true;
6652
/// If true, can only accept literals as arguments and they cannot be moved to variables.
6753
bool literalArguments = false;
6854
};

libyul/SideEffects.h

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
#pragma once
19+
20+
#include <set>
21+
22+
namespace yul
23+
{
24+
25+
/**
26+
* Side effects of code.
27+
*
28+
* The default-constructed value applies to the "empty code".
29+
*/
30+
struct SideEffects
31+
{
32+
/// If true, expressions in this code can be freely moved and copied without altering the
33+
/// semantics.
34+
/// At statement level, it means that functions containing this code can be
35+
/// called multiple times, their calls can be rearranged and calls can also be
36+
/// deleted without changing the semantics.
37+
/// This means it cannot depend on storage or memory, cannot have any side-effects,
38+
/// but it can depend on state that is constant across an EVM-call.
39+
bool movable = true;
40+
/// If true, the code can be removed without changing the semantics.
41+
bool sideEffectFree = true;
42+
/// If true, the code can be removed without changing the semantics as long as
43+
/// the whole program does not contain the msize instruction.
44+
bool sideEffectFreeIfNoMSize = true;
45+
/// If false, storage is guaranteed to be unchanged by the code under all
46+
/// circumstances.
47+
bool invalidatesStorage = false;
48+
/// If false, memory is guaranteed to be unchanged by the code under all
49+
/// circumstances.
50+
bool invalidatesMemory = false;
51+
52+
/// @returns the worst-case side effects.
53+
static SideEffects worst()
54+
{
55+
return SideEffects{false, false, false, true, true};
56+
}
57+
58+
/// @returns the combined side effects of two pieces of code.
59+
SideEffects operator+(SideEffects const& _other)
60+
{
61+
return SideEffects{
62+
movable && _other.movable,
63+
sideEffectFree && _other.sideEffectFree,
64+
sideEffectFreeIfNoMSize && _other.sideEffectFreeIfNoMSize,
65+
invalidatesStorage || _other.invalidatesStorage,
66+
invalidatesMemory || _other.invalidatesMemory
67+
};
68+
}
69+
70+
/// Adds the side effects of another piece of code to this side effect.
71+
SideEffects& operator+=(SideEffects const& _other)
72+
{
73+
*this = *this + _other;
74+
return *this;
75+
}
76+
};
77+
78+
}

libyul/backends/evm/EVMDialect.cpp

+32-26
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
5050
f.name = YulString{_name};
5151
f.parameters.resize(info.args);
5252
f.returns.resize(info.ret);
53-
f.movable = eth::SemanticInformation::movable(_instruction);
54-
f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction);
55-
f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction);
53+
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
5654
f.isMSize = _instruction == dev::eth::Instruction::MSIZE;
57-
f.invalidatesStorage = eth::SemanticInformation::invalidatesStorage(_instruction);
58-
f.invalidatesMemory = eth::SemanticInformation::invalidatesMemory(_instruction);
5955
f.literalArguments = false;
6056
f.instruction = _instruction;
6157
f.generateCode = [_instruction](
@@ -75,11 +71,7 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
7571
string _name,
7672
size_t _params,
7773
size_t _returns,
78-
bool _movable,
79-
bool _sideEffectFree,
80-
bool _sideEffectFreeIfNoMSize,
81-
bool _invalidatesStorage,
82-
bool _invalidatesMemory,
74+
SideEffects _sideEffects,
8375
bool _literalArguments,
8476
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode
8577
)
@@ -89,13 +81,9 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
8981
f.name = name;
9082
f.parameters.resize(_params);
9183
f.returns.resize(_returns);
92-
f.movable = _movable;
84+
f.sideEffects = std::move(_sideEffects);
9385
f.literalArguments = _literalArguments;
94-
f.sideEffectFree = _sideEffectFree;
95-
f.sideEffectFreeIfNoMSize = _sideEffectFreeIfNoMSize;
9686
f.isMSize = false;
97-
f.invalidatesStorage = _invalidatesStorage;
98-
f.invalidatesMemory = _invalidatesMemory;
9987
f.instruction = {};
10088
f.generateCode = std::move(_generateCode);
10189
return {name, f};
@@ -116,7 +104,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
116104

117105
if (_objectAccess)
118106
{
119-
builtins.emplace(createFunction("datasize", 1, 1, true, true, true, false, false, true, [](
107+
builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, true, [](
120108
FunctionCall const& _call,
121109
AbstractAssembly& _assembly,
122110
BuiltinContext& _context,
@@ -137,7 +125,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
137125
_assembly.appendDataSize(_context.subIDs.at(dataName));
138126
}
139127
}));
140-
builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, false, false, true, [](
128+
builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, true, [](
141129
FunctionCall const& _call,
142130
AbstractAssembly& _assembly,
143131
BuiltinContext& _context,
@@ -158,15 +146,22 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
158146
_assembly.appendDataOffset(_context.subIDs.at(dataName));
159147
}
160148
}));
161-
builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, true, false, [](
162-
FunctionCall const&,
163-
AbstractAssembly& _assembly,
164-
BuiltinContext&,
165-
std::function<void()> _visitArguments
166-
) {
167-
_visitArguments();
168-
_assembly.appendInstruction(dev::eth::Instruction::CODECOPY);
169-
}));
149+
builtins.emplace(createFunction(
150+
"datacopy",
151+
3,
152+
0,
153+
SideEffects{false, false, false, false, true},
154+
false,
155+
[](
156+
FunctionCall const&,
157+
AbstractAssembly& _assembly,
158+
BuiltinContext&,
159+
std::function<void()> _visitArguments
160+
) {
161+
_visitArguments();
162+
_assembly.appendInstruction(dev::eth::Instruction::CODECOPY);
163+
}
164+
));
170165
}
171166
return builtins;
172167
}
@@ -225,3 +220,14 @@ EVMDialect const& EVMDialect::yulForEVM(langutil::EVMVersion _version)
225220
dialects[_version] = make_unique<EVMDialect>(AsmFlavour::Yul, false, _version);
226221
return *dialects[_version];
227222
}
223+
224+
SideEffects EVMDialect::sideEffectsOfInstruction(eth::Instruction _instruction)
225+
{
226+
return SideEffects{
227+
eth::SemanticInformation::movable(_instruction),
228+
eth::SemanticInformation::sideEffectFree(_instruction),
229+
eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction),
230+
eth::SemanticInformation::invalidatesStorage(_instruction),
231+
eth::SemanticInformation::invalidatesMemory(_instruction)
232+
};
233+
}

libyul/backends/evm/EVMDialect.h

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ struct EVMDialect: public Dialect
8080

8181
bool providesObjectAccess() const { return m_objectAccess; }
8282

83+
static SideEffects sideEffectsOfInstruction(dev::eth::Instruction _instruction);
84+
8385
protected:
8486
bool const m_objectAccess;
8587
langutil::EVMVersion const m_evmVersion;

libyul/backends/wasm/WasmDialect.cpp

+10-18
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,19 @@ WasmDialect::WasmDialect():
4949
addFunction("i64.eqz", 1, 1);
5050

5151
addFunction("i64.store", 2, 0, false);
52-
m_functions["i64.store"_yulstring].invalidatesStorage = false;
52+
m_functions["i64.store"_yulstring].sideEffects.invalidatesStorage = false;
5353

5454
addFunction("i64.load", 1, 1, false);
55-
m_functions["i64.load"_yulstring].invalidatesStorage = false;
56-
m_functions["i64.load"_yulstring].invalidatesMemory = false;
57-
m_functions["i64.load"_yulstring].sideEffectFree = true;
58-
m_functions["i64.load"_yulstring].sideEffectFreeIfNoMSize = true;
55+
m_functions["i64.load"_yulstring].sideEffects.invalidatesStorage = false;
56+
m_functions["i64.load"_yulstring].sideEffects.invalidatesMemory = false;
57+
m_functions["i64.load"_yulstring].sideEffects.sideEffectFree = true;
58+
m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
5959

6060
addFunction("drop", 1, 0);
6161

6262
addFunction("unreachable", 0, 0, false);
63-
m_functions["unreachable"_yulstring].invalidatesStorage = false;
64-
m_functions["unreachable"_yulstring].invalidatesMemory = false;
63+
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
64+
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
6565

6666
addFunction("datasize", 1, 4, true, true);
6767
addFunction("dataoffset", 1, 4, true, true);
@@ -138,14 +138,10 @@ void WasmDialect::addEthereumExternals()
138138
f.parameters.emplace_back(YulString(p));
139139
for (string const& p: ext.returns)
140140
f.returns.emplace_back(YulString(p));
141-
f.movable = false;
142141
// TODO some of them are side effect free.
143-
f.sideEffectFree = false;
144-
f.sideEffectFreeIfNoMSize = false;
142+
f.sideEffects = SideEffects::worst();
145143
f.isMSize = false;
146-
f.invalidatesStorage = (ext.name == "storageStore");
147-
// TODO some of them do not invalidate memory
148-
f.invalidatesMemory = true;
144+
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
149145
f.literalArguments = false;
150146
}
151147
}
@@ -163,11 +159,7 @@ void WasmDialect::addFunction(
163159
f.name = name;
164160
f.parameters.resize(_params);
165161
f.returns.resize(_returns);
166-
f.movable = _movable;
167-
f.sideEffectFree = _movable;
168-
f.sideEffectFreeIfNoMSize = _movable;
162+
f.sideEffects = _movable ? SideEffects{} : SideEffects::worst();
169163
f.isMSize = false;
170-
f.invalidatesStorage = !_movable;
171-
f.invalidatesMemory = !_movable;
172164
f.literalArguments = _literalArguments;
173165
}

libyul/optimiser/Semantics.cpp

+4-29
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ using namespace std;
3333
using namespace dev;
3434
using namespace yul;
3535

36+
3637
SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Expression const& _expression):
3738
SideEffectsCollector(_dialect)
3839
{
@@ -55,43 +56,17 @@ void SideEffectsCollector::operator()(FunctionalInstruction const& _instr)
5556
{
5657
ASTWalker::operator()(_instr);
5758

58-
if (!eth::SemanticInformation::movable(_instr.instruction))
59-
m_movable = false;
60-
if (!eth::SemanticInformation::sideEffectFree(_instr.instruction))
61-
m_sideEffectFree = false;
62-
if (!eth::SemanticInformation::sideEffectFreeIfNoMSize(_instr.instruction))
63-
m_sideEffectFreeIfNoMSize = false;
64-
if (eth::SemanticInformation::invalidatesStorage(_instr.instruction))
65-
m_invalidatesStorage = true;
66-
if (eth::SemanticInformation::invalidatesMemory(_instr.instruction))
67-
m_invalidatesMemory = true;
59+
m_sideEffects += EVMDialect::sideEffectsOfInstruction(_instr.instruction);
6860
}
6961

7062
void SideEffectsCollector::operator()(FunctionCall const& _functionCall)
7163
{
7264
ASTWalker::operator()(_functionCall);
7365

7466
if (BuiltinFunction const* f = m_dialect.builtin(_functionCall.functionName.name))
75-
{
76-
if (!f->movable)
77-
m_movable = false;
78-
if (!f->sideEffectFree)
79-
m_sideEffectFree = false;
80-
if (!f->sideEffectFreeIfNoMSize)
81-
m_sideEffectFreeIfNoMSize = false;
82-
if (f->invalidatesStorage)
83-
m_invalidatesStorage = true;
84-
if (f->invalidatesMemory)
85-
m_invalidatesMemory = true;
86-
}
67+
m_sideEffects += f->sideEffects;
8768
else
88-
{
89-
m_movable = false;
90-
m_sideEffectFree = false;
91-
m_sideEffectFreeIfNoMSize = false;
92-
m_invalidatesStorage = true;
93-
m_invalidatesMemory = true;
94-
}
69+
m_sideEffects += SideEffects::worst();
9570
}
9671

9772
bool MSizeFinder::containsMSize(Dialect const& _dialect, Block const& _ast)

libyul/optimiser/Semantics.h

+7-17
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#pragma once
2222

2323
#include <libyul/optimiser/ASTWalker.h>
24+
#include <libyul/SideEffects.h>
2425

2526
#include <set>
2627

@@ -44,32 +45,21 @@ class SideEffectsCollector: public ASTWalker
4445
void operator()(FunctionalInstruction const& _functionalInstruction) override;
4546
void operator()(FunctionCall const& _functionCall) override;
4647

47-
bool movable() const { return m_movable; }
48+
bool movable() const { return m_sideEffects.movable; }
4849
bool sideEffectFree(bool _allowMSizeModification = false) const
4950
{
5051
if (_allowMSizeModification)
5152
return sideEffectFreeIfNoMSize();
5253
else
53-
return m_sideEffectFree;
54+
return m_sideEffects.sideEffectFree;
5455
}
55-
bool sideEffectFreeIfNoMSize() const { return m_sideEffectFreeIfNoMSize; }
56-
bool invalidatesStorage() const { return m_invalidatesStorage; }
57-
bool invalidatesMemory() const { return m_invalidatesMemory; }
56+
bool sideEffectFreeIfNoMSize() const { return m_sideEffects.sideEffectFreeIfNoMSize; }
57+
bool invalidatesStorage() const { return m_sideEffects.invalidatesStorage; }
58+
bool invalidatesMemory() const { return m_sideEffects.invalidatesMemory; }
5859

5960
private:
6061
Dialect const& m_dialect;
61-
/// Is the current expression movable or not.
62-
bool m_movable = true;
63-
/// Is the current expression side-effect free, i.e. can be removed
64-
/// without changing the semantics.
65-
bool m_sideEffectFree = true;
66-
/// Is the current expression side-effect free up to msize, i.e. can be removed
67-
/// without changing the semantics except for the value returned by the msize instruction.
68-
bool m_sideEffectFreeIfNoMSize = true;
69-
/// If false, storage is guaranteed to be unchanged by the code under all
70-
/// circumstances.
71-
bool m_invalidatesStorage = false;
72-
bool m_invalidatesMemory = false;
62+
SideEffects m_sideEffects;
7363
};
7464

7565
/**

test/libyul/Parser.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ BOOST_AUTO_TEST_CASE(builtins_analysis)
565565
{
566566
return _name == "builtin"_yulstring ? &f : nullptr;
567567
}
568-
BuiltinFunction f{"builtin"_yulstring, vector<Type>(2), vector<Type>(3), false, false};
568+
BuiltinFunction f{"builtin"_yulstring, vector<Type>(2), vector<Type>(3), {}};
569569
};
570570

571571
SimpleDialect dialect;

0 commit comments

Comments
 (0)