From 4e1fbcf83c948d0b7d45020f88d9781b3ee93f44 Mon Sep 17 00:00:00 2001 From: blishko Date: Fri, 22 May 2026 15:20:31 +0200 Subject: [PATCH] SSA-CFG: Fix detection of equivalent instructions in Outliner In the helper we use to check if arguments of two instructions are equivalent, we would say that two instructions are equivalent if they have the same index. This is, however, not correct, as the two instructions can come from two different CFGs. Thus, same indices does not mean they refer to the same instruction. We fixed the check instead to - Check if we have proven equivalence before; or - The two instructions refer to the same literal --- libyul/backends/evm/ssa/transform/Outliner.cpp | 13 +++++++------ .../externalContracts/FixedFeeRegistrar.sol | 4 ++-- .../semanticTests/externalContracts/base64.sol | 4 ++-- .../externalContracts/deposit_contract.sol | 2 +- .../externalContracts/prbmath_signed.sol | 4 ++-- .../semanticTests/externalContracts/strings.sol | 4 ++-- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/libyul/backends/evm/ssa/transform/Outliner.cpp b/libyul/backends/evm/ssa/transform/Outliner.cpp index d00a998bb878..4883a7484bd6 100644 --- a/libyul/backends/evm/ssa/transform/Outliner.cpp +++ b/libyul/backends/evm/ssa/transform/Outliner.cpp @@ -147,10 +147,12 @@ bool EquivalentBlocksDetector::areEquivalent(BlockIdentifier const _bid1, BlockI std::unordered_map instMapping; auto knownToBeEquivalent = [&](auto const& idPair) { auto && [id1, id2] = idPair; - if (id1 == id2) - return true; auto const it = instMapping.find(id1.value); - return it != instMapping.end() && it->second == id2.value; + if (it != instMapping.end() && it->second == id2.value) + return true; + if (cfg1.isLiteral(id1) && cfg2.isLiteral(id2) && cfg1.literalPayload(id1) == cfg2.literalPayload(id2)) + return true; + return false; }; for (auto&& [iid1, iid2] : ranges::views::zip(block1.instructions, block2.instructions)) @@ -180,8 +182,9 @@ bool EquivalentBlocksDetector::areEquivalent(BlockIdentifier const _bid1, BlockI } case InstOpcode::Const: { - if (iid1 != iid2) + if (cfg1.literalPayload(iid1) != cfg2.literalPayload(iid2)) return false; + instMapping.insert({iid1.value, iid2.value}); break; } case InstOpcode::Identity: @@ -233,8 +236,6 @@ EquivalentBlocksDetector::EquivalenceClasses EquivalentBlocksDetector::run() && { for (auto const& [id, cfg]: m_program.functionGraphs | ranges::views::enumerate) { - if (cfg->numBlocks() == 1) - continue; auto const graphId = static_cast(id); for (BlockId const blockId: cfg->liveBlocks()) processBlock({.graphId = graphId, .blockId = blockId}); diff --git a/test/libsolidity/semanticTests/externalContracts/FixedFeeRegistrar.sol b/test/libsolidity/semanticTests/externalContracts/FixedFeeRegistrar.sol index 1f196479dac3..08c311e9a947 100644 --- a/test/libsolidity/semanticTests/externalContracts/FixedFeeRegistrar.sol +++ b/test/libsolidity/semanticTests/externalContracts/FixedFeeRegistrar.sol @@ -83,8 +83,8 @@ contract FixedFeeRegistrar is Registrar { // gas legacy code: 792400 // gas legacyOptimized: 84598 // gas legacyOptimized code: 388000 -// gas ssaCFGOptimized: 78924 -// gas ssaCFGOptimized code: 322800 +// gas ssaCFGOptimized: 79004 +// gas ssaCFGOptimized code: 323800 // reserve(string), 69 ether: 0x20, 3, "abc" -> // ~ emit Changed(string): #0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 // gas irOptimized: 45741 diff --git a/test/libsolidity/semanticTests/externalContracts/base64.sol b/test/libsolidity/semanticTests/externalContracts/base64.sol index dc924a571c43..0efcda12d8f0 100644 --- a/test/libsolidity/semanticTests/externalContracts/base64.sol +++ b/test/libsolidity/semanticTests/externalContracts/base64.sol @@ -41,8 +41,8 @@ contract test { // gas legacy code: 629800 // gas legacyOptimized: 87926 // gas legacyOptimized code: 429800 -// gas ssaCFGOptimized: 79463 -// gas ssaCFGOptimized code: 327800 +// gas ssaCFGOptimized: 79879 +// gas ssaCFGOptimized code: 331800 // encode_inline_asm(bytes): 0x20, 0 -> 0x20, 0 // encode_inline_asm(bytes): 0x20, 1, "f" -> 0x20, 4, "Zg==" // encode_inline_asm(bytes): 0x20, 2, "fo" -> 0x20, 4, "Zm8=" diff --git a/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol b/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol index f988070ae9dc..d85cf3ec06e5 100644 --- a/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol +++ b/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol @@ -185,7 +185,7 @@ contract DepositContract is IDepositContract, ERC165 { // gas legacy code: 1438800 // gas legacyOptimized: 848699 // gas legacyOptimized code: 878200 -// gas ssaCFGOptimized: 809718 +// gas ssaCFGOptimized: 809586 // gas ssaCFGOptimized code: 570200 // supportsInterface(bytes4): 0x0 -> 0 // supportsInterface(bytes4): 0xffffffff00000000000000000000000000000000000000000000000000000000 -> false # defined to be false by ERC-165 # diff --git a/test/libsolidity/semanticTests/externalContracts/prbmath_signed.sol b/test/libsolidity/semanticTests/externalContracts/prbmath_signed.sol index 02c32706f750..e744e7cc505d 100644 --- a/test/libsolidity/semanticTests/externalContracts/prbmath_signed.sol +++ b/test/libsolidity/semanticTests/externalContracts/prbmath_signed.sol @@ -57,8 +57,8 @@ contract test { // gas legacy code: 2205000 // gas legacyOptimized: 178012 // gas legacyOptimized code: 1669600 -// gas ssaCFGOptimized: 174817 -// gas ssaCFGOptimized code: 1636800 +// gas ssaCFGOptimized: 174968 +// gas ssaCFGOptimized code: 1638600 // div(int256,int256): 3141592653589793238, 88714123 -> 35412542528203691288251815328 // gas irOptimized: 22045 // gas legacy: 22736 diff --git a/test/libsolidity/semanticTests/externalContracts/strings.sol b/test/libsolidity/semanticTests/externalContracts/strings.sol index 8bc0ec8e5f5a..dfd550b5b40a 100644 --- a/test/libsolidity/semanticTests/externalContracts/strings.sol +++ b/test/libsolidity/semanticTests/externalContracts/strings.sol @@ -58,8 +58,8 @@ contract test { // gas legacy code: 932600 // gas legacyOptimized: 102639 // gas legacyOptimized code: 612400 -// gas ssaCFGOptimized: 95825 -// gas ssaCFGOptimized code: 527800 +// gas ssaCFGOptimized: 96220 +// gas ssaCFGOptimized code: 532800 // toSlice(string): 0x20, 11, "hello world" -> 11, 0xa0 // gas irOptimized: 22646 // gas legacy: 23168