Coverage Report

Created: 2025-09-08 08:10

/src/solidity/libyul/optimiser/UnusedStoreEliminator.h
Line
Count
Source
1
/*
2
  This file is part of solidity.
3
4
  solidity is free software: you can redistribute it and/or modify
5
  it under the terms of the GNU General Public License as published by
6
  the Free Software Foundation, either version 3 of the License, or
7
  (at your option) any later version.
8
9
  solidity is distributed in the hope that it will be useful,
10
  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
  GNU General Public License for more details.
13
14
  You should have received a copy of the GNU General Public License
15
  along with solidity.  If not, see <http://www.gnu.org/licenses/>.
16
*/
17
// SPDX-License-Identifier: GPL-3.0
18
/**
19
 * Optimiser component that removes stores to memory and storage slots that are not used
20
 * or overwritten later on.
21
 */
22
23
#pragma once
24
25
#include <libyul/ASTForward.h>
26
#include <libyul/optimiser/ASTWalker.h>
27
#include <libyul/optimiser/OptimiserStep.h>
28
#include <libyul/optimiser/Semantics.h>
29
#include <libyul/optimiser/UnusedStoreBase.h>
30
#include <libyul/optimiser/KnowledgeBase.h>
31
32
#include <libevmasm/SemanticInformation.h>
33
34
#include <map>
35
#include <vector>
36
37
namespace solidity::yul
38
{
39
class Dialect;
40
struct AssignedValue;
41
42
/**
43
 * Optimizer component that removes sstore and memory store statements if conditions are met for their removal.
44
 * In case of an sstore, if all outgoing code paths revert (due to an explicit revert(), invalid(),
45
 * or infinite recursion) or lead to another ``sstore`` for which the optimizer can tell that it will overwrite the first store,
46
 * the statement will be removed.
47
 *
48
 * For memory store operations, things are generally simpler, at least in the outermost yul block as all such statements
49
 * will be removed if they are never read from in any code path. At function analysis level however, the approach is similar
50
 * to sstore, as we don't know whether the memory location will be read once we leave the function's scope,
51
 * so the statement will be removed only if all code code paths lead to a memory overwrite.
52
 *
53
 * Best run in SSA form.
54
 *
55
 * Prerequisite: Disambiguator, ForLoopInitRewriter.
56
 */
57
class UnusedStoreEliminator: public UnusedStoreBase<UnusedStoreEliminatorKey>
58
{
59
public:
60
  static constexpr char const* name{"UnusedStoreEliminator"};
61
  static void run(OptimiserStepContext& _context, Block& _ast);
62
63
  explicit UnusedStoreEliminator(
64
    Dialect const& _dialect,
65
    std::map<FunctionHandle, SideEffects> const& _functionSideEffects,
66
    std::map<YulName, ControlFlowSideEffects> _controlFlowSideEffects,
67
    std::map<YulName, AssignedValue> const& _ssaValues,
68
    bool _ignoreMemory
69
  );
70
71
  using UnusedStoreBase::operator();
72
  void operator()(FunctionCall const& _functionCall) override;
73
  void operator()(FunctionDefinition const&) override;
74
  void operator()(Leave const&) override;
75
76
  using UnusedStoreBase::visit;
77
  void visit(Statement const& _statement) override;
78
79
  using Location = evmasm::SemanticInformation::Location;
80
  using Effect = evmasm::SemanticInformation::Effect;
81
  using OperationLength = std::variant<YulName, u256>;
82
  struct Operation
83
  {
84
    Location location;
85
    Effect effect;
86
    /// Start of affected area. Unknown if not provided.
87
    std::optional<YulName> start;
88
    /// Length of affected area, unknown if not provided.
89
    /// Unused for storage.
90
    std::optional<OperationLength> length;
91
  };
92
93
private:
94
2.82M
  std::set<Statement const*>& activeMemoryStores() { return m_activeStores[UnusedStoreEliminatorKey::Memory]; }
95
3.51M
  std::set<Statement const*>& activeStorageStores() { return m_activeStores[UnusedStoreEliminatorKey::Storage]; }
96
  std::optional<u256> lengthValue(OperationLength const& _length) const
97
5.49M
  {
98
5.49M
    if (YulName const* length = std::get_if<YulName>(&_length))
99
829k
      return m_knowledgeBase.valueIfKnownConstant(*length);
100
4.66M
    else
101
4.66M
      return std::get<u256>(_length);
102
5.49M
  }
103
104
  void shortcutNestedLoop(ActiveStores const&) override
105
42.3k
  {
106
    // We might only need to do this for newly introduced stores in the loop.
107
42.3k
    markActiveAsUsed();
108
42.3k
  }
109
  void finalizeFunctionDefinition(FunctionDefinition const&) override
110
179k
  {
111
179k
    markActiveAsUsed();
112
179k
  }
113
114
  std::vector<Operation> operationsFromFunctionCall(FunctionCall const& _functionCall) const;
115
  void applyOperation(Operation const& _operation);
116
  bool knownUnrelated(Operation const& _op1, Operation const& _op2) const;
117
  bool knownCovered(Operation const& _covered, Operation const& _covering) const;
118
119
  void markActiveAsUsed(std::optional<Location> _onlyLocation = std::nullopt);
120
  void clearActive(std::optional<Location> _onlyLocation = std::nullopt);
121
122
  std::optional<YulName> identifierNameIfSSA(Expression const& _expression) const;
123
124
  bool const m_ignoreMemory;
125
  std::map<FunctionHandle, SideEffects> const& m_functionSideEffects;
126
  std::map<YulName, ControlFlowSideEffects> m_controlFlowSideEffects;
127
  std::map<YulName, AssignedValue> const& m_ssaValues;
128
129
  std::map<Statement const*, Operation> m_storeOperations;
130
131
  KnowledgeBase mutable m_knowledgeBase;
132
};
133
134
}