Skip to content

Commit 7f882ef

Browse files
committed
Bytecode difference caused by SSA transform
Changelog Review comments Unique vector Repro test
1 parent 30d7878 commit 7f882ef

File tree

7 files changed

+177
-36
lines changed

7 files changed

+177
-36
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Compiler Features:
99
Bugfixes:
1010
* SMTChecker: Fix internal error on mapping access caused by too strong requirements on sort compatibility of the index and mapping domain.
1111
* SMTChecker: Fix internal error when using bitwise operators with an array element as argument.
12+
* Yul Optimizer: Fix the order of assignments generated by ``SSATransform`` being dependent on AST IDs, sometimes resulting in different (but equivalent) bytecode when unrelated files were added to the compilation pipeline.
1213

1314

1415
### 0.8.25 (2024-03-14)

libsolutil/CommonData.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,49 @@ void joinMap(std::map<K, V>& _a, std::map<K, V>&& _b, F _conflictSolver)
343343
}
344344
}
345345

346+
template<typename T>
347+
class UniqueVector
348+
{
349+
public:
350+
std::vector<T> const& contents() const { return m_contents; }
351+
size_t size() const { return m_contents.size(); }
352+
bool empty() const { return m_contents.empty(); }
353+
auto begin() const { return m_contents.begin(); }
354+
auto end() const { return m_contents.end(); }
355+
void clear() { m_contents.clear(); }
356+
bool contains(T const& _value) const
357+
{
358+
return std::find(m_contents.begin(), m_contents.end(), _value) != m_contents.end();
359+
}
360+
361+
void pushBack(T _value)
362+
{
363+
if (!contains(_value))
364+
m_contents.emplace_back(std::move(_value));
365+
}
366+
367+
void pushBack(UniqueVector<T> const& _values)
368+
{
369+
for (auto&& value: _values)
370+
pushBack(value);
371+
}
372+
373+
void removeAll(std::vector<T> const& _values)
374+
{
375+
for (auto const& value: _values)
376+
m_contents.erase(std::find(m_contents.begin(), m_contents.end(), value));
377+
}
378+
379+
private:
380+
std::vector<T> m_contents;
381+
};
382+
383+
template<typename T>
384+
void swap(UniqueVector<T>& _lhs, UniqueVector<T>& _rhs)
385+
{
386+
std::swap(_lhs.contents(), _rhs.contents());
387+
}
388+
346389
namespace detail
347390
{
348391

libyul/optimiser/SSATransform.cpp

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -166,23 +166,23 @@ class IntroduceControlFlowSSA: public ASTModifier
166166
std::set<YulString> const& m_variablesToReplace;
167167
/// Variables (that are to be replaced) currently in scope.
168168
std::set<YulString> m_variablesInScope;
169-
/// Set of variables that do not have a specific value.
170-
std::set<YulString> m_variablesToReassign;
169+
/// Variables that do not have a specific value.
170+
util::UniqueVector<YulString> m_variablesToReassign;
171171
TypeInfo const& m_typeInfo;
172172
};
173173

174174
void IntroduceControlFlowSSA::operator()(FunctionDefinition& _function)
175175
{
176176
std::set<YulString> varsInScope;
177177
std::swap(varsInScope, m_variablesInScope);
178-
std::set<YulString> toReassign;
178+
util::UniqueVector<YulString> toReassign;
179179
std::swap(toReassign, m_variablesToReassign);
180180

181181
for (auto const& param: _function.parameters)
182182
if (m_variablesToReplace.count(param.name))
183183
{
184184
m_variablesInScope.insert(param.name);
185-
m_variablesToReassign.insert(param.name);
185+
m_variablesToReassign.pushBack(param.name);
186186
}
187187

188188
ASTModifier::operator()(_function);
@@ -196,8 +196,8 @@ void IntroduceControlFlowSSA::operator()(ForLoop& _for)
196196
yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
197197

198198
for (auto const& var: assignedVariableNames(_for.body) + assignedVariableNames(_for.post))
199-
if (m_variablesInScope.count(var))
200-
m_variablesToReassign.insert(var);
199+
if (util::contains(m_variablesInScope,var))
200+
m_variablesToReassign.pushBack(var);
201201

202202
(*this)(_for.body);
203203
(*this)(_for.post);
@@ -207,26 +207,26 @@ void IntroduceControlFlowSSA::operator()(Switch& _switch)
207207
{
208208
yulAssert(m_variablesToReassign.empty(), "");
209209

210-
std::set<YulString> toReassign;
210+
util::UniqueVector<YulString> toReassign;
211211
for (auto& c: _switch.cases)
212212
{
213213
(*this)(c.body);
214-
toReassign += m_variablesToReassign;
214+
toReassign.pushBack(m_variablesToReassign);
215215
}
216216

217-
m_variablesToReassign += toReassign;
217+
m_variablesToReassign.pushBack(toReassign);
218218
}
219219

220220
void IntroduceControlFlowSSA::operator()(Block& _block)
221221
{
222-
std::set<YulString> variablesDeclaredHere;
223-
std::set<YulString> assignedVariables;
222+
util::UniqueVector<YulString> variablesDeclaredHere;
223+
util::UniqueVector<YulString> assignedVariables;
224224

225225
util::iterateReplacing(
226226
_block.statements,
227227
[&](Statement& _s) -> std::optional<std::vector<Statement>>
228228
{
229-
std::vector<Statement> toPrepend;
229+
std::vector<Statement> toPrepend;
230230
for (YulString toReassign: m_variablesToReassign)
231231
{
232232
YulString newName = m_nameDispenser.newName(toReassign);
@@ -235,7 +235,7 @@ void IntroduceControlFlowSSA::operator()(Block& _block)
235235
{TypedName{debugDataOf(_s), newName, m_typeInfo.typeOfVariable(toReassign)}},
236236
std::make_unique<Expression>(Identifier{debugDataOf(_s), toReassign})
237237
});
238-
assignedVariables.insert(toReassign);
238+
assignedVariables.pushBack(toReassign);
239239
}
240240
m_variablesToReassign.clear();
241241

@@ -245,7 +245,7 @@ void IntroduceControlFlowSSA::operator()(Block& _block)
245245
for (auto const& var: varDecl.variables)
246246
if (m_variablesToReplace.count(var.name))
247247
{
248-
variablesDeclaredHere.insert(var.name);
248+
variablesDeclaredHere.pushBack(var.name);
249249
m_variablesInScope.insert(var.name);
250250
}
251251
}
@@ -254,7 +254,7 @@ void IntroduceControlFlowSSA::operator()(Block& _block)
254254
Assignment& assignment = std::get<Assignment>(_s);
255255
for (auto const& var: assignment.variableNames)
256256
if (m_variablesToReplace.count(var.name))
257-
assignedVariables.insert(var.name);
257+
assignedVariables.pushBack(var.name);
258258
}
259259
else
260260
visit(_s);
@@ -268,9 +268,10 @@ void IntroduceControlFlowSSA::operator()(Block& _block)
268268
}
269269
}
270270
);
271-
m_variablesToReassign += assignedVariables;
272-
m_variablesInScope -= variablesDeclaredHere;
273-
m_variablesToReassign -= variablesDeclaredHere;
271+
272+
m_variablesToReassign.pushBack(assignedVariables);
273+
m_variablesInScope -= variablesDeclaredHere.contents();
274+
m_variablesToReassign.removeAll(variablesDeclaredHere.contents());
274275
}
275276

276277
/**

scripts/common/cmdline_helpers.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,23 @@
1111
from bytecodecompare.prepare_report import parse_cli_output
1212

1313

14-
DEFAULT_PREAMBLE = dedent("""
14+
DEFAULT_PREAMBLE = dedent(
15+
"""
1516
// SPDX-License-Identifier: UNLICENSED
1617
pragma solidity >=0.0;
17-
""")
18+
"""
19+
)
1820

1921

2022
def inside_temporary_dir(prefix):
2123
"""
2224
Creates a temporary directory, enters the directory and executes the function inside it.
2325
Restores the previous working directory after executing the function.
2426
"""
27+
2528
def tmp_dir_decorator(fn):
2629
previous_dir = os.getcwd()
30+
2731
def f(*args, **kwargs):
2832
try:
2933
tmp_dir = mkdtemp(prefix=prefix)
@@ -33,35 +37,49 @@ def f(*args, **kwargs):
3337
return result
3438
finally:
3539
os.chdir(previous_dir)
40+
3641
return f
42+
3743
return tmp_dir_decorator
3844

3945

40-
def solc_bin_report(solc_binary: str, input_files: List[Path], via_ir: bool) -> FileReport:
46+
def solc_bin_report(
47+
solc_binary: str,
48+
input_files: List[Path],
49+
via_ir: bool,
50+
optimize: bool = False,
51+
yul_optimizations: Optional[str] = None,
52+
) -> FileReport:
4153
"""
4254
Runs the solidity compiler binary
4355
"""
4456

4557
output = subprocess.check_output(
46-
[solc_binary, '--bin'] +
47-
input_files +
48-
(['--via-ir'] if via_ir else []),
49-
encoding='utf8',
58+
[solc_binary, "--bin"]
59+
+ input_files
60+
+ (["--via-ir"] if via_ir else [])
61+
+ (["--optimize"] if optimize else [])
62+
+ (["--yul-optimizations", yul_optimizations] if yul_optimizations else []),
63+
encoding="utf8",
5064
)
51-
return parse_cli_output('', output, 0)
65+
return parse_cli_output("", output, 0)
5266

5367

54-
def save_bytecode(bytecode_path: Path, reports: FileReport, contract: Optional[str] = None):
55-
with open(bytecode_path, 'w', encoding='utf8') as f:
68+
def save_bytecode(
69+
bytecode_path: Path, reports: FileReport, contract: Optional[str] = None
70+
):
71+
with open(bytecode_path, "w", encoding="utf8") as f:
5672
for report in reports.contract_reports:
5773
if contract is None or report.contract_name == contract:
58-
bytecode = report.bytecode if report.bytecode is not None else '<NO BYTECODE>'
59-
f.write(f'{report.contract_name}: {bytecode}\n')
74+
bytecode = (
75+
report.bytecode if report.bytecode is not None else "<NO BYTECODE>"
76+
)
77+
f.write(f"{report.contract_name}: {bytecode}\n")
6078

6179

6280
def add_preamble(source_path: Path, preamble: str = DEFAULT_PREAMBLE):
63-
for source in source_path.glob('**/*.sol'):
64-
with open(source, 'r+', encoding='utf8') as f:
81+
for source in source_path.glob("**/*.sol"):
82+
with open(source, "r+", encoding="utf8") as f:
6583
content = f.read()
6684
f.seek(0, 0)
6785
f.write(preamble + content)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
==== Source: A.sol ====
2+
contract DummyContract1 {}
3+
contract DummyContract2 {}
4+
contract DummyContract3 {}
5+
6+
==== Source: B.sol ====
7+
contract B {
8+
function f(uint8 a_0, uint8 a_1, uint8 a_2) public pure {
9+
a_1 = 1;
10+
a_2 = 2;
11+
a_0;
12+
}
13+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import sys
5+
from pathlib import Path
6+
from textwrap import dedent
7+
8+
# pylint: disable=wrong-import-position
9+
PROJECT_ROOT = Path(__file__).parents[3]
10+
sys.path.insert(0, str(PROJECT_ROOT / "scripts"))
11+
12+
from common.cmdline_helpers import add_preamble
13+
from common.cmdline_helpers import inside_temporary_dir
14+
from common.cmdline_helpers import save_bytecode
15+
from common.cmdline_helpers import solc_bin_report
16+
from common.git_helpers import git_diff
17+
from splitSources import split_sources
18+
19+
20+
@inside_temporary_dir(Path(__file__).parent.name)
21+
def test_bytecode_equivalence():
22+
source_file_path = Path(__file__).parent / "inputs.sol"
23+
split_sources(source_file_path, suppress_output=True)
24+
add_preamble(Path.cwd())
25+
26+
solc_binary = os.environ.get("SOLC")
27+
if solc_binary is None:
28+
raise RuntimeError(
29+
dedent(
30+
"""\
31+
`solc` compiler not found.
32+
Please ensure you set the SOLC environment variable
33+
with the correct path to the compiler's binary.
34+
"""
35+
)
36+
)
37+
38+
# Repro for https://github.com/ethereum/solidity/issues/14829
39+
save_bytecode(
40+
Path("B.bin"),
41+
solc_bin_report(
42+
solc_binary,
43+
[Path("B.sol")],
44+
via_ir=True,
45+
optimize=True,
46+
yul_optimizations="a:",
47+
),
48+
contract="B",
49+
)
50+
save_bytecode(
51+
Path("AB.bin"),
52+
solc_bin_report(
53+
solc_binary,
54+
[Path("A.sol"), Path("B.sol")],
55+
via_ir=True,
56+
optimize=True,
57+
yul_optimizations="a:",
58+
),
59+
contract="B",
60+
)
61+
return git_diff(Path("B.bin"), Path("AB.bin"))
62+
63+
64+
if __name__ == "__main__":
65+
sys.exit(test_bytecode_equivalence())

test/libyul/yulOptimizerTests/ssaTransform/function.yul

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
// {
1313
// function f(a, b) -> c, d
1414
// {
15-
// let b_5 := b
16-
// let a_6 := a
17-
// let b_1 := add(b_5, a_6)
15+
// let a_5 := a
16+
// let b_6 := b
17+
// let b_1 := add(b_6, a_5)
1818
// b := b_1
1919
// let c_2 := add(c, b_1)
2020
// c := c_2
2121
// let d_3 := add(d, c_2)
2222
// d := d_3
23-
// let a_4 := add(a_6, d_3)
23+
// let a_4 := add(a_5, d_3)
2424
// a := a_4
2525
// }
2626
// }

0 commit comments

Comments
 (0)