Skip to content

Optimizer: Eliminate dead mstore instructions from memoryguard-derived struct allocations - #16936

Open
OminduD wants to merge 2 commits into
argotorg:developfrom
OminduD:fix-unused-memoryguard-stores
Open

Optimizer: Eliminate dead mstore instructions from memoryguard-derived struct allocations#16936
OminduD wants to merge 2 commits into
argotorg:developfrom
OminduD:fix-unused-memoryguard-stores

Conversation

@OminduD

@OminduD OminduD commented Aug 20, 2026

Copy link
Copy Markdown

Description

Fixes #16927

Fixes a missed optimisation in the IR pipeline where accessing a single member of an abi.decode-d struct leaves several dead mstore instructions that the UnusedStoreEliminator should remove.

Reproducer:

struct OnlyUnvalidated { uint256 tokenId; uint256 b; uint256 c; uint256 d; }

contract C {
    function decodeUnvalidatedStruct(bytes calldata data) external pure returns (uint256) {
        return abi.decode(data, (OnlyUnvalidated)).tokenId;
    }
}

The IR optimizer generates a scratch region at _1 = memoryguard(0x80), writes all four struct members into it, and then — because LoadResolver already forwards the .tokenId value directly — the four mstores are dead. However, UnusedStoreEliminator was unable to remove them because KnowledgeBase::explore() did not recognise memoryguard(N) as the constant N. Without that knowledge, no non-overlap between the scratch slots (0x800xfe) and the return target (newFreePtr = 0x100) could be proved, so all four stores were conservatively kept.

Fix:

Teach KnowledgeBase::explore(Expression const&) to fold memoryguard(literal) to its argument value, exactly like a plain Literal node. memoryguard always takes a single literal operand (enforced by the builtin's literalArguments metadata), so this is always safe.

Changes:

  • libyul/optimiser/KnowledgeBase.h — add std::optional<BuiltinHandle> m_memoryGuardBuiltinHandle and initialise it in both constructors (same pattern as m_addBuiltinHandle / m_subBuiltinHandle).
  • libyul/optimiser/KnowledgeBase.cpp — add a new else if branch in explore(Expression const&) that returns VariableOffset{YulName{}, literal->value.value()} when the callee is memoryguard. Also adds the missing braces around the sub branch to keep the else if chain well-formed.
  • test/libyul/yulOptimizerTests/unusedStoreEliminator/memoryguard_dead_store.yul — new regression test directly modelling the IR pattern from the bug report; asserts that all four dead stores are eliminated.

Checklist

AI Disclosure

  • No AI tools were used

Antigravity (Claude Sonnet 4.6) was used to navigate the codebase, cross-reference the interaction between KnowledgeBase, UnusedStoreEliminator, and UnusedStoreBase, and to draft this PR description. All code changes were authored, reviewed, and tested by the contributor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dead stores into a memoryguard-derived allocation are not eliminated

1 participant