Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Copy link
Copy Markdown
Collaborator

@cameel cameel Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remember that this will need a buglist entry.

Without it the PR is not really finished so it should still be a draft.

Copy link
Copy Markdown
Contributor Author

@rodiazet rodiazet Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug list updated. How about the blogpost link? I changed it to draft for now.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can leave it unfilled until we know when we're releasing this. URLs are predictable, based on date and title (which should match the bug ID) so we could create one already, but if we do it now, we can easily forget to update the date and we'll end up with a broken link. So for now I'd put in something that clearly looks like a placeholder.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used X/Y/Z placeholder for now as it was done in prev cases.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Compiler Features:
Bugfixes:
* Yul: Fix incorrect serialization of Yul object names containing double quotes and escape sequences, producing output that could not be parsed as valid Yul.
* Yul EVM Code Transform: Improve stack shuffler performance by fixing a BFS deduplication issue.
* Yul Optimizer: Fix a bug in `UnusedStoreEliminator`, which could lead to incorrect removal of `mstore` or `sstore` in certain cases.


### 0.8.34 (2026-02-18)
Expand Down
10 changes: 10 additions & 0 deletions docs/bugs.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
[
{
"uid": "SOL-2026-2",
"name": "UnusedStoreEliminatorWithoutSSATransformation",
"summary": "When the UnusedStoreEliminators optimizer step is run on non-SSA AST form, it may result in incorrect removal of storage or memory writes.",
"description": "Solidity allows defining custom user-defined optimizer sequences. In version 0.8.12, the unused store eliminator was introduced to remove redundant writes to storage or memory. The bug was in this optimizer step's implementation. It used SSAValueTracker to identify variables that are never assigned. Based on this set of unassigned variables, a knowledge base is created to track current variable values. This knowledge is necessary for deciding whether memory or storage writes can be safely removed. Unfortunately, SSAValueTracker correctly removed assigned variables from the SSA variable set, but failed to remove variables that depend on those assigned variables. This oversight made it possible to construct inline assembly code that was incorrectly optimized by removing necessary writes. As a result, contracts compiled with different optimization sequences could exhibit different runtime behavior. The bug was introduced in version 0.8.12 alongside the unused store eliminator implementation. It was fixed in version 0.8.35 by implementing additional filtering of the variable set returned by SSAValueTracker and extending this set by functions parameters values.",
"link": "https://blog.soliditylang.org/2026/X/Y/Z/",
"introduced": "0.8.13",
"fixed": "0.8.35",
"severity": "low"
Comment on lines +7 to +10
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note that one of the blockers for this is having a release date and severity agreed on by the team.

I don't think this one can get into the next release if we're going to have one soon. We need to deal with #16508 first.

},
{
"uid": "SOL-2026-1",
"name": "TransientStorageClearingHelperCollision",
Expand Down
25 changes: 24 additions & 1 deletion docs/bugs_by_version.json
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,7 @@
},
"0.8.13": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow",
"VerbatimInvalidDeduplication",
"FullInlinerNonExpressionSplitArgumentEvaluationOrder",
Expand All @@ -1884,6 +1885,7 @@
},
"0.8.14": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow",
"VerbatimInvalidDeduplication",
"FullInlinerNonExpressionSplitArgumentEvaluationOrder",
Expand All @@ -1897,6 +1899,7 @@
},
"0.8.15": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow",
"VerbatimInvalidDeduplication",
"FullInlinerNonExpressionSplitArgumentEvaluationOrder",
Expand All @@ -1908,6 +1911,7 @@
},
"0.8.16": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow",
"VerbatimInvalidDeduplication",
"FullInlinerNonExpressionSplitArgumentEvaluationOrder",
Expand All @@ -1918,6 +1922,7 @@
},
"0.8.17": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow",
"VerbatimInvalidDeduplication",
"FullInlinerNonExpressionSplitArgumentEvaluationOrder",
Expand All @@ -1927,6 +1932,7 @@
},
"0.8.18": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow",
"VerbatimInvalidDeduplication",
"FullInlinerNonExpressionSplitArgumentEvaluationOrder",
Expand All @@ -1936,6 +1942,7 @@
},
"0.8.19": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow",
"VerbatimInvalidDeduplication",
"FullInlinerNonExpressionSplitArgumentEvaluationOrder",
Expand All @@ -1960,6 +1967,7 @@
},
"0.8.20": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow",
"VerbatimInvalidDeduplication",
"FullInlinerNonExpressionSplitArgumentEvaluationOrder",
Expand All @@ -1969,57 +1977,66 @@
},
"0.8.21": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow",
"VerbatimInvalidDeduplication"
],
"released": "2023-07-19"
},
"0.8.22": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow",
"VerbatimInvalidDeduplication"
],
"released": "2023-10-25"
},
"0.8.23": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow"
],
"released": "2023-11-08"
},
"0.8.24": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow"
],
"released": "2024-01-25"
},
"0.8.25": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow"
],
"released": "2024-03-14"
},
"0.8.26": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow"
],
"released": "2024-05-21"
},
"0.8.27": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"LostStorageArrayWriteOnSlotOverflow"
],
"released": "2024-09-04"
},
"0.8.28": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"TransientStorageClearingHelperCollision",
"LostStorageArrayWriteOnSlotOverflow"
],
"released": "2024-10-09"
},
"0.8.29": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"TransientStorageClearingHelperCollision",
"LostStorageArrayWriteOnSlotOverflow"
],
Expand All @@ -2041,32 +2058,38 @@
},
"0.8.30": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"TransientStorageClearingHelperCollision",
"LostStorageArrayWriteOnSlotOverflow"
],
"released": "2025-05-07"
},
"0.8.31": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"TransientStorageClearingHelperCollision",
"LostStorageArrayWriteOnSlotOverflow"
],
"released": "2025-12-03"
},
"0.8.32": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"TransientStorageClearingHelperCollision"
],
"released": "2025-12-18"
},
"0.8.33": {
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation",
"TransientStorageClearingHelperCollision"
],
"released": "2025-12-18"
},
"0.8.34": {
"bugs": [],
"bugs": [
"UnusedStoreEliminatorWithoutSSATransformation"
],
"released": "2026-02-18"
},
"0.8.4": {
Expand Down
46 changes: 46 additions & 0 deletions libyul/optimiser/SSAValueTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

#include <libyul/optimiser/SSAValueTracker.h>

#include <liblangutil/Exceptions.h>

#include <libyul/AST.h>

using namespace solidity;
Expand All @@ -35,6 +37,11 @@ void SSAValueTracker::operator()(Assignment const& _assignment)

void SSAValueTracker::operator()(FunctionDefinition const& _funDef)
{
solAssert(!m_values.contains(_funDef.name), "SSAValueTracker requires Disambiguator to run first");

for (auto const& param: _funDef.parameters)
m_values[param.name] = nullptr;

for (auto const& var: _funDef.returnVariables)
setValue(var.name, nullptr);
ASTWalker::operator()(_funDef);
Expand All @@ -49,6 +56,45 @@ void SSAValueTracker::operator()(VariableDeclaration const& _varDecl)
setValue(_varDecl.variables.front().name, _varDecl.value.get());
}

bool SSAValueTracker::isSSAWithDependencies(Expression const* _expression) const
Comment thread
clonker marked this conversation as resolved.
{
if (_expression == nullptr)
return true;

// Check cache first
auto cacheIt = m_isSSACache.find(_expression);
if (cacheIt != m_isSSACache.end())
return cacheIt->second;

bool result;
if (auto const* functionCall = std::get_if<FunctionCall>(_expression))
{
result = true;
for (auto const& argument: functionCall->arguments)
if (!isSSAWithDependencies(&argument))
{
result = false;
break;
}
}
else if (auto const* identifier = std::get_if<Identifier>(_expression))
{
auto const it = m_values.find(identifier->name);
if (it == m_values.end())
result = false;
else
result = isSSAWithDependencies(it->second);
}
else
{
solAssert(std::holds_alternative<Literal>(*_expression), "Impossible expression type");
result = true;
}

m_isSSACache[_expression] = result;
return result;
}

std::set<YulName> SSAValueTracker::ssaVariables(Block const& _ast)
{
SSAValueTracker t;
Expand Down
12 changes: 9 additions & 3 deletions libyul/optimiser/SSAValueTracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
#include <libyul/optimiser/ASTWalker.h>
#include <libyul/AST.h> // Needed for m_zero below.

#include <map>
#include <unordered_map>
#include <set>

namespace solidity::yul
Expand All @@ -47,18 +47,24 @@ class SSAValueTracker: public ASTWalker
void operator()(VariableDeclaration const& _varDecl) override;
void operator()(Assignment const& _assignment) override;

std::map<YulName, Expression const*> const& values() const { return m_values; }
std::unordered_map<YulName, Expression const*> const& values() const { return m_values; }
Expression const* value(YulName _name) const { return m_values.at(_name); }

static std::set<YulName> ssaVariables(Block const& _ast);

/// Determines whether the given expression and all of its identifier
/// dependencies are in Static Single Assignment (SSA) form.
bool isSSAWithDependencies(Expression const* _expression) const;

private:
void setValue(YulName _name, Expression const* _value);

/// Special expression whose address will be used in m_values.
/// YulName does not need to be reset because SSAValueTracker is short-lived.
Expression const m_zero{Literal{{}, LiteralKind::Number, LiteralValue(u256{0})}};
std::map<YulName, Expression const*> m_values;
std::unordered_map<YulName, Expression const*> m_values;
/// Cache for isSSAWithDependencies to avoid redundant traversals
mutable std::unordered_map<Expression const*, bool> m_isSSACache;
};

}
3 changes: 2 additions & 1 deletion libyul/optimiser/UnusedStoreEliminator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ void UnusedStoreEliminator::run(OptimiserStepContext& _context, Block& _ast)
ssaValues(_ast);
std::map<YulName, AssignedValue> values;
for (auto const& [name, expression]: ssaValues.values())
values[name] = AssignedValue{expression, {}};
if (ssaValues.isSSAWithDependencies(expression))
Comment thread
nikola-matic marked this conversation as resolved.
values[name] = AssignedValue{expression, {}};

bool const ignoreMemory = MSizeFinder::containsMSize(_context.dialect, _ast);
UnusedStoreEliminator rse{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--strict-assembly --optimize --yul-optimizations S: --ir-optimized
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
object "UnusedStoreEliminatorNoSSA" {
code {
let x := calldataload(4)
let a := add(x, 32)
x := add(x, 32)
let b := x
let outLen := 32
mstore(a, 0xAA)
return(b, outLen)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

======= input.yul (EVM) =======

Pretty printed source:
object "UnusedStoreEliminatorNoSSA" {
code {
{
let x := calldataload(4)
let a := add(x, 32)
x := add(x, 32)
let b := x
let outLen := 32
mstore(a, 0xAA)
return(b, outLen)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ contract c {
// test((uint16,uint16,uint16[3],uint16[])): 0x20, 2, 3, 0, 0, 4, 0xC0, 4, 0, 0, 5, 0, 0 -> 2, 3, 4, 5
// gas irOptimized: 137153
// gas legacy: 142414
// gas legacyOptimized: 137975
// gas legacyOptimized: 137854
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ contract Test {
// ----
// library: Lib
// f() -> 4, 0x11
// gas irOptimized: 111419
// gas irOptimized: 109140
// gas legacy: 132930
// gas legacyOptimized: 118020
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ contract C {
// f((uint8,uint16,bytes2,uint8)): 1, 0xff, "ab", 15 ->
// gas irOptimized: 44237
// gas legacy: 47154
// gas legacyOptimized: 44982
// gas legacyOptimized: 44734
// s() -> 1, 0xff, 0x6162000000000000000000000000000000000000000000000000000000000000, 15
// g(uint16[]): 0x20, 3, 1, 2, 3 -> 0x20, 3, 1, 2, 3
// gas irOptimized: 68578
Expand Down
7 changes: 7 additions & 0 deletions test/libyul/YulOptimizerTestCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,13 @@ YulOptimizerTestCommon::YulOptimizerTestCommon(std::shared_ptr<Object const> _ob
ExpressionJoiner::run(*m_context, block);
return block;
}},
{"unusedStoreEliminatorNoSsaTransform", [&]() {
auto block = disambiguate();
updateContext(block);
ForLoopInitRewriter::run(*m_context, block);
UnusedStoreEliminator::run(*m_context, block);
return block;
}},
Comment thread
clonker marked this conversation as resolved.
{"equalStoreEliminator", [&]() {
auto block = disambiguate();
updateContext(block);
Expand Down
Loading