Skip to content

Commit 9ad50e3

Browse files
committed
Yul expression simplifier: Don't substitute out of scope variables
In some circumstances (in particular if not the AST is not in SSA form), it could happen that the ExpressionSimplifier would substitute out-of-scope variables.
1 parent 635fe8f commit 9ad50e3

File tree

6 files changed

+77
-1
lines changed

6 files changed

+77
-1
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Compiler Features:
77

88
Bugfixes:
99
* Assembler: Fix not using a fixed-width type for IDs being assigned to subassemblies nested more than one level away, resulting in inconsistent `--asm-json` output between target architectures.
10+
* Yul Optimizer: Fix edge case in which invalid Yul code is produced by ExpressionSimplifier due to expressions being substituted that contain out-of-scope variables.
1011

1112
### 0.8.30 (2025-05-07)
1213

libyul/optimiser/ExpressionSimplifier.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <libyul/optimiser/SimplificationRules.h>
2626
#include <libyul/optimiser/OptimiserStep.h>
2727
#include <libyul/optimiser/OptimizerUtilities.h>
28+
#include <libyul/optimiser/Semantics.h>
2829
#include <libyul/AST.h>
2930
#include <libyul/Utilities.h>
3031

@@ -45,7 +46,19 @@ void ExpressionSimplifier::visit(Expression& _expression)
4546
while (auto const* match = SimplificationRules::findFirstMatch(
4647
_expression,
4748
m_dialect,
48-
[this](YulName _var) { return variableValue(_var); }
49+
[this](YulName const& _var) -> AssignedValue const* {
50+
AssignedValue const* value = variableValue(_var);
51+
if (!value || !value->value)
52+
return nullptr;
53+
54+
// check that all variables in the value expression are in current scope
55+
MovableChecker const checker(m_dialect, *value->value);
56+
for (YulName const& referencedVar: checker.referencedVariables())
57+
if (!inScope(referencedVar))
58+
return nullptr; // don't substitute if any referenced var is out of scope
59+
60+
return value;
61+
}
4962
))
5063
{
5164
auto const* evmDialect = dynamic_cast<EVMDialect const*>(&m_dialect);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--strict-assembly --optimize --yul-optimizations T:ts --bin
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
function identity(value) -> ret {
3+
ret := value
4+
}
5+
function fun_fun_31() -> ret {
6+
let x := 0x01
7+
let expr_9 := identity(x)
8+
9+
let expr_10 := 0x01
10+
let expr_25 := 0x05
11+
switch expr_10
12+
case 0 { }
13+
default {
14+
let expr_14 := 0x06
15+
let expr_18 := identity(x)
16+
expr_25 := or(expr_14, expr_18)
17+
}
18+
// expression simplifier spuriously gets a match on this: `expr_25 == or(0x06, expr_18)` where expr_18 is
19+
// from the inner scope
20+
ret := or(expr_9, expr_25)
21+
leave
22+
}
23+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
======= input.yul (EVM) =======
3+
4+
Binary representation:
5+
00
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
function identity(value) -> ret { ret := value }
3+
4+
function f() -> ret
5+
{
6+
let id1 := identity(0x01)
7+
let x := 0x05
8+
{
9+
let const_six := 0x06
10+
let id2 := identity(0x01)
11+
x := or(0x06, id2)
12+
}
13+
// check that we don't substitute `or(const_six, id2)` for `x`
14+
ret := or(id1, x)
15+
}
16+
17+
mstore(42, f())
18+
}
19+
// ----
20+
// step: expressionSimplifier
21+
//
22+
// {
23+
// { mstore(42, f()) }
24+
// function identity(value) -> ret
25+
// { ret := value }
26+
// function f() -> ret_1
27+
// {
28+
// let id1 := identity(0x01)
29+
// let x := 0x05
30+
// { x := or(0x06, id1) }
31+
// ret_1 := or(id1, x)
32+
// }
33+
// }

0 commit comments

Comments
 (0)