diff --git a/Changelog.md b/Changelog.md index b295b7bf445c..6f96c29aa74b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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) diff --git a/docs/bugs.json b/docs/bugs.json index 5f8d1a162732..20ee30d57ace 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -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" + }, { "uid": "SOL-2026-1", "name": "TransientStorageClearingHelperCollision", diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index c5576d4b298e..a9e0f0659027 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -1869,6 +1869,7 @@ }, "0.8.13": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", @@ -1884,6 +1885,7 @@ }, "0.8.14": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", @@ -1897,6 +1899,7 @@ }, "0.8.15": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", @@ -1908,6 +1911,7 @@ }, "0.8.16": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", @@ -1918,6 +1922,7 @@ }, "0.8.17": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", @@ -1927,6 +1932,7 @@ }, "0.8.18": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", @@ -1936,6 +1942,7 @@ }, "0.8.19": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", @@ -1960,6 +1967,7 @@ }, "0.8.20": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", @@ -1969,6 +1977,7 @@ }, "0.8.21": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication" ], @@ -1976,6 +1985,7 @@ }, "0.8.22": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication" ], @@ -1983,36 +1993,42 @@ }, "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" ], @@ -2020,6 +2036,7 @@ }, "0.8.29": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "TransientStorageClearingHelperCollision", "LostStorageArrayWriteOnSlotOverflow" ], @@ -2041,6 +2058,7 @@ }, "0.8.30": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "TransientStorageClearingHelperCollision", "LostStorageArrayWriteOnSlotOverflow" ], @@ -2048,6 +2066,7 @@ }, "0.8.31": { "bugs": [ + "UnusedStoreEliminatorWithoutSSATransformation", "TransientStorageClearingHelperCollision", "LostStorageArrayWriteOnSlotOverflow" ], @@ -2055,18 +2074,22 @@ }, "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": { diff --git a/libyul/optimiser/SSAValueTracker.cpp b/libyul/optimiser/SSAValueTracker.cpp index d39ce4b8d263..d640994598f9 100644 --- a/libyul/optimiser/SSAValueTracker.cpp +++ b/libyul/optimiser/SSAValueTracker.cpp @@ -22,6 +22,8 @@ #include +#include + #include using namespace solidity; @@ -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); @@ -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 +{ + 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(_expression)) + { + result = true; + for (auto const& argument: functionCall->arguments) + if (!isSSAWithDependencies(&argument)) + { + result = false; + break; + } + } + else if (auto const* identifier = std::get_if(_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(*_expression), "Impossible expression type"); + result = true; + } + + m_isSSACache[_expression] = result; + return result; +} + std::set SSAValueTracker::ssaVariables(Block const& _ast) { SSAValueTracker t; diff --git a/libyul/optimiser/SSAValueTracker.h b/libyul/optimiser/SSAValueTracker.h index 2448f99a84c4..c3347b632e34 100644 --- a/libyul/optimiser/SSAValueTracker.h +++ b/libyul/optimiser/SSAValueTracker.h @@ -25,7 +25,7 @@ #include #include // Needed for m_zero below. -#include +#include #include namespace solidity::yul @@ -47,18 +47,24 @@ class SSAValueTracker: public ASTWalker void operator()(VariableDeclaration const& _varDecl) override; void operator()(Assignment const& _assignment) override; - std::map const& values() const { return m_values; } + std::unordered_map const& values() const { return m_values; } Expression const* value(YulName _name) const { return m_values.at(_name); } static std::set 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 m_values; + std::unordered_map m_values; + /// Cache for isSSAWithDependencies to avoid redundant traversals + mutable std::unordered_map m_isSSACache; }; } diff --git a/libyul/optimiser/UnusedStoreEliminator.cpp b/libyul/optimiser/UnusedStoreEliminator.cpp index 6f2f1ee938f8..700724764b9c 100644 --- a/libyul/optimiser/UnusedStoreEliminator.cpp +++ b/libyul/optimiser/UnusedStoreEliminator.cpp @@ -55,7 +55,8 @@ void UnusedStoreEliminator::run(OptimiserStepContext& _context, Block& _ast) ssaValues(_ast); std::map values; for (auto const& [name, expression]: ssaValues.values()) - values[name] = AssignedValue{expression, {}}; + if (ssaValues.isSSAWithDependencies(expression)) + values[name] = AssignedValue{expression, {}}; bool const ignoreMemory = MSizeFinder::containsMSize(_context.dialect, _ast); UnusedStoreEliminator rse{ diff --git a/test/cmdlineTests/yul_optimizer_unused_store_eliminator_without_ssa_transform/args b/test/cmdlineTests/yul_optimizer_unused_store_eliminator_without_ssa_transform/args new file mode 100644 index 000000000000..db6d45f976dc --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_unused_store_eliminator_without_ssa_transform/args @@ -0,0 +1 @@ +--strict-assembly --optimize --yul-optimizations S: --ir-optimized diff --git a/test/cmdlineTests/yul_optimizer_unused_store_eliminator_without_ssa_transform/input.yul b/test/cmdlineTests/yul_optimizer_unused_store_eliminator_without_ssa_transform/input.yul new file mode 100644 index 000000000000..405ed8563dfa --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_unused_store_eliminator_without_ssa_transform/input.yul @@ -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) + } +} diff --git a/test/cmdlineTests/yul_optimizer_unused_store_eliminator_without_ssa_transform/output b/test/cmdlineTests/yul_optimizer_unused_store_eliminator_without_ssa_transform/output new file mode 100644 index 000000000000..35c79f3d8d41 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_unused_store_eliminator_without_ssa_transform/output @@ -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) + } + } +} diff --git a/test/libsolidity/semanticTests/array/push/array_push_struct_from_calldata.sol b/test/libsolidity/semanticTests/array/push/array_push_struct_from_calldata.sol index 254c14b061cf..b685c233dbc4 100644 --- a/test/libsolidity/semanticTests/array/push/array_push_struct_from_calldata.sol +++ b/test/libsolidity/semanticTests/array/push/array_push_struct_from_calldata.sol @@ -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 diff --git a/test/libsolidity/semanticTests/libraries/internal_types_in_library.sol b/test/libsolidity/semanticTests/libraries/internal_types_in_library.sol index d59cedfdc860..15e73fb57818 100644 --- a/test/libsolidity/semanticTests/libraries/internal_types_in_library.sol +++ b/test/libsolidity/semanticTests/libraries/internal_types_in_library.sol @@ -22,6 +22,6 @@ contract Test { // ---- // library: Lib // f() -> 4, 0x11 -// gas irOptimized: 111419 +// gas irOptimized: 109140 // gas legacy: 132930 // gas legacyOptimized: 118020 diff --git a/test/libsolidity/semanticTests/userDefinedValueType/calldata_to_storage.sol b/test/libsolidity/semanticTests/userDefinedValueType/calldata_to_storage.sol index 0815a2da7ace..4de1a2cb3981 100644 --- a/test/libsolidity/semanticTests/userDefinedValueType/calldata_to_storage.sol +++ b/test/libsolidity/semanticTests/userDefinedValueType/calldata_to_storage.sol @@ -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 diff --git a/test/libyul/YulOptimizerTestCommon.cpp b/test/libyul/YulOptimizerTestCommon.cpp index cd54318c19ef..cf321d34ef92 100644 --- a/test/libyul/YulOptimizerTestCommon.cpp +++ b/test/libyul/YulOptimizerTestCommon.cpp @@ -311,6 +311,13 @@ YulOptimizerTestCommon::YulOptimizerTestCommon(std::shared_ptr _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; + }}, {"equalStoreEliminator", [&]() { auto block = disambiguate(); updateContext(block); diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminator/sstore_location_reassigned.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminator/sstore_location_reassigned.yul new file mode 100644 index 000000000000..da42e8d38613 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminator/sstore_location_reassigned.yul @@ -0,0 +1,30 @@ +object "test" { + code { + let x := calldataload(4) + mstore(32, 0xAA) + + let a := add(x, 32) + x := add(x, 32) + let b := x + sstore(a, 32) + let ret := sload(b) + revert(ret, 32) + } +} + +// ---- +// step: unusedStoreEliminator +// +// { +// { +// let x := calldataload(4) +// let x_8 := x +// mstore(32, 0xAA) +// let a := add(x_8, 32) +// x := add(x_8, 32) +// let b := x +// sstore(a, 32) +// let ret := sload(b) +// revert(ret, 32) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/dependency_reassigned_prevents_elimination.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/dependency_reassigned_prevents_elimination.yul new file mode 100644 index 000000000000..ea5b566c9fdb --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/dependency_reassigned_prevents_elimination.yul @@ -0,0 +1,21 @@ +{ + let x := calldataload(0) + x := calldataload(32) + let a := add(x, 32) + let b := x + let outLen := 32 + mstore(a, 0xAA) + return(b, outLen) +} +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// let x := calldataload(0) +// x := calldataload(32) +// let a := add(x, 32) +// let b := x +// let outLen := 32 +// mstore(a, 0xAA) +// return(b, outLen) +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_as_mstore_location_covering.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_as_mstore_location_covering.yul new file mode 100644 index 000000000000..ef7fa925c38c --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_as_mstore_location_covering.yul @@ -0,0 +1,24 @@ +{ + function f(arg) + { + let a := add(arg, 1) + let b := arg + let outLen := 32 + mstore(a, 0xAA) + return(b, outLen) + } +} + +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// function f(arg) +// { +// let a := add(arg, 1) +// let b := arg +// let outLen := 32 +// mstore(a, 0xAA) +// return(b, outLen) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_as_mstore_location_unrelated.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_as_mstore_location_unrelated.yul new file mode 100644 index 000000000000..653bc8ca12a3 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_as_mstore_location_unrelated.yul @@ -0,0 +1,23 @@ +{ + function f(arg) + { + let a := add(arg, 32) + let b := arg + let outLen := 32 + mstore(a, 0xAA) + return(b, outLen) + } +} + +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// function f(arg) +// { +// let a := add(arg, 32) +// let b := arg +// let outLen := 32 +// return(b, outLen) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_reassigned_as_mstore_location_covering.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_reassigned_as_mstore_location_covering.yul new file mode 100644 index 000000000000..4498575791f5 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_reassigned_as_mstore_location_covering.yul @@ -0,0 +1,24 @@ +{ + function f(arg) + { + let b := arg + arg:= add(arg, 1) + let outLen := 32 + mstore(arg, 0xAA) + return(b, outLen) + } +} + +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// function f(arg) +// { +// let b := arg +// arg := add(arg, 1) +// let outLen := 32 +// mstore(arg, 0xAA) +// return(b, outLen) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_reassigned_as_mstore_location_unrelated.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_reassigned_as_mstore_location_unrelated.yul new file mode 100644 index 000000000000..b78bfa32e52c --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/function_parameter_reassigned_as_mstore_location_unrelated.yul @@ -0,0 +1,24 @@ +{ + function f(arg) + { + let b := arg + arg := add(arg, 32) + let outLen := 32 + mstore(arg, 0xAA) + return(b, outLen) + } +} + +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// function f(arg) +// { +// let b := arg +// arg := add(arg, 32) +// let outLen := 32 +// mstore(arg, 0xAA) +// return(b, outLen) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/mstore_location_reassigned.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/mstore_location_reassigned.yul new file mode 100644 index 000000000000..15b2733adca6 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/mstore_location_reassigned.yul @@ -0,0 +1,24 @@ +{ + let x := calldataload(4) + let x_5 := x + let a := add(x_5, 32) + x := add(x_5, 32) + let b := x + let outLen := 32 + mstore(a, 0xAA) + return(b, outLen) +} + +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// let x := calldataload(4) +// let x_5 := x +// let a := add(x_5, 32) +// x := add(x_5, 32) +// let b := x +// let outLen := 32 +// mstore(a, 0xAA) +// return(b, outLen) +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/multi_return_var_not_tracked.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/multi_return_var_not_tracked.yul new file mode 100644 index 000000000000..643d2a62fd3b --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/multi_return_var_not_tracked.yul @@ -0,0 +1,25 @@ +{ + function g() -> r1, r2 + { + r1 := calldataload(0) + r2 := calldataload(32) + } + let a, b := g() + let c := add(a, 32) + mstore(c, 0xAA) + return(a, b) +} +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// function g() -> r1, r2 +// { +// r1 := calldataload(0) +// r2 := calldataload(32) +// } +// let a, b := g() +// let c := add(a, 32) +// mstore(c, 0xAA) +// return(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/override_store_function_parameter_location_elimination.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/override_store_function_parameter_location_elimination.yul new file mode 100644 index 000000000000..d6ce84d7a25e --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/override_store_function_parameter_location_elimination.yul @@ -0,0 +1,32 @@ +{ + function direct(arg, value) + { + sstore(arg, value) + let v1 := add(value, 1) + sstore(arg, v1) + } + + function indirect(arg, value) + { + let loc := arg + sstore(loc, value) + let value1 := add(value, 1) + sstore(loc, value1) + } +} +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// function direct(arg, value) +// { +// let v1 := add(value, 1) +// sstore(arg, v1) +// } +// function indirect(arg_1, value_2) +// { +// let loc := arg_1 +// let value1 := add(value_2, 1) +// sstore(loc, value1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/ssa_dependency_chain_allows_elimination.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/ssa_dependency_chain_allows_elimination.yul new file mode 100644 index 000000000000..1a73e8a10359 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/ssa_dependency_chain_allows_elimination.yul @@ -0,0 +1,20 @@ +{ + let x := calldataload(0) + let y := add(x, 32) + let a := add(y, 32) + let b := x + let outLen := 32 + mstore(a, 0xAA) + return(b, outLen) +} +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// let x := calldataload(0) +// let y := add(x, 32) +// let a := add(y, 32) +// let b := x +// let outLen := 32 +// return(b, outLen) +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/sstore_dependency_as_function_reassigned_argument.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/sstore_dependency_as_function_reassigned_argument.yul new file mode 100644 index 000000000000..7b6deb302672 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/sstore_dependency_as_function_reassigned_argument.yul @@ -0,0 +1,21 @@ +{ + function f(arg) + { + let x := arg + x := calldataload(32) + sstore(x, 0xAA) + sstore(arg, 0xBB) + } +} +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// function f(arg) +// { +// let x := arg +// x := calldataload(32) +// sstore(x, 0xAA) +// sstore(arg, 0xBB) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/sstore_function_parameter_location_loaded_no_elimination.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/sstore_function_parameter_location_loaded_no_elimination.yul new file mode 100644 index 000000000000..cbcedc49f6f8 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/sstore_function_parameter_location_loaded_no_elimination.yul @@ -0,0 +1,71 @@ +{ + function direct(arg, value) + { + sstore(arg, value) + pop(sload(arg)) + let value1 := add(value, 1) + sstore(arg, value1) + } + + function indirect(arg, value) + { + let loc := arg + sstore(loc, value) + pop(sload(arg)) + let value1 := add(value, 1) + sstore(loc, value1) + } + + function mix_elimination(arg, value) + { + let loc := add(arg, 1) + sstore(arg, value) + pop(sload(loc)) + let value1 := add(value, 1) + sstore(arg, value1) + } + + function mix_no_elimination(arg, value) + { + let loc := arg + sstore(arg, value) + pop(sload(loc)) + let value1 := add(value, 1) + sstore(arg, value1) + } +} +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// function direct(arg, value) +// { +// sstore(arg, value) +// pop(sload(arg)) +// let value1 := add(value, 1) +// sstore(arg, value1) +// } +// function indirect(arg_1, value_2) +// { +// let loc := arg_1 +// sstore(loc, value_2) +// pop(sload(arg_1)) +// let value1_3 := add(value_2, 1) +// sstore(loc, value1_3) +// } +// function mix_elimination(arg_4, value_5) +// { +// let loc_6 := add(arg_4, 1) +// pop(sload(loc_6)) +// let value1_7 := add(value_5, 1) +// sstore(arg_4, value1_7) +// } +// function mix_no_elimination(arg_8, value_9) +// { +// let loc_10 := arg_8 +// sstore(arg_8, value_9) +// pop(sload(loc_10)) +// let value1_11 := add(value_9, 1) +// sstore(arg_8, value1_11) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/sstore_location_reassigned.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/sstore_location_reassigned.yul new file mode 100644 index 000000000000..e12845291bc3 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/sstore_location_reassigned.yul @@ -0,0 +1,28 @@ +object "test" { + code { + let x := calldataload(4) + mstore(32, 0xAA) + + let a := add(x, 32) + x := add(x, 32) + let b := x + // This sstore cannot be eliminated. + sstore(a, 32) + let ret := sload(b) + revert(ret, 32) + } +} + +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// let x := calldataload(4) +// mstore(32, 0xAA) +// let a := add(x, 32) +// x := add(x, 32) +// let b := x +// sstore(a, 32) +// let ret := sload(b) +// revert(ret, 32) +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/store_assigned_function_parameter_location_no_elimination.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/store_assigned_function_parameter_location_no_elimination.yul new file mode 100644 index 000000000000..26452df45b1f --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/store_assigned_function_parameter_location_no_elimination.yul @@ -0,0 +1,56 @@ +{ + function direct(arg, value) + { + sstore(arg, value) + arg := add(arg, 1) + let value1 := add(value, 1) + sstore(arg, value1) + } + + function indirect(arg, value) + { + let loc := arg + sstore(loc, value) + loc := add(loc, 1) + let value1 := add(value, 1) + sstore(loc, value1) + } + + function mix(arg, value) + { + let loc := arg + // The first sstore could theoretically be eliminated since loc == arg, + // but USE cannot prove this without a known constant value for arg; + // a subsequent optimizer pass (after, e.g., CSE resolves the alias) would eliminate it. + sstore(loc, value) + let value1 := add(value, 1) + sstore(arg, value1) + } +} +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// function direct(arg, value) +// { +// sstore(arg, value) +// arg := add(arg, 1) +// let value1 := add(value, 1) +// sstore(arg, value1) +// } +// function indirect(arg_1, value_2) +// { +// let loc := arg_1 +// sstore(loc, value_2) +// loc := add(loc, 1) +// let value1_3 := add(value_2, 1) +// sstore(loc, value1_3) +// } +// function mix(arg_4, value_5) +// { +// let loc_6 := arg_4 +// sstore(loc_6, value_5) +// let value1_7 := add(value_5, 1) +// sstore(arg_4, value1_7) +// } +// } diff --git a/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/transitive_dependency_reassigned_prevents_elimination.yul b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/transitive_dependency_reassigned_prevents_elimination.yul new file mode 100644 index 000000000000..4f11db720d68 --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedStoreEliminatorNoSsaTransform/transitive_dependency_reassigned_prevents_elimination.yul @@ -0,0 +1,23 @@ +{ + let x := calldataload(0) + let y := add(x, 32) + x := calldataload(32) + let a := y + let b := x + let outLen := 32 + mstore(a, 0xAA) + return(b, outLen) +} +// ---- +// step: unusedStoreEliminatorNoSsaTransform +// +// { +// let x := calldataload(0) +// let y := add(x, 32) +// x := calldataload(32) +// let a := y +// let b := x +// let outLen := 32 +// mstore(a, 0xAA) +// return(b, outLen) +// }