Coverage Report

Created: 2026-08-14 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/backends/evm/ssa/Stack.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
#pragma once
20
21
#include <libyul/backends/evm/ssa/ShuffleTrace.h>
22
#include <libyul/backends/evm/ssa/StackSlot.h>
23
24
#include <range/v3/algorithm/find.hpp>
25
#include <range/v3/view/reverse.hpp>
26
27
#include <cstddef>
28
29
namespace solidity::yul::ssa
30
{
31
32
/// A view over a `StackData` mimicking EVM stack semantics. When constructed with a trace, every stack
33
/// manipulation is recorded as a `ShuffleOp`; an untraced view mutates the data silently.
34
class Stack
35
{
36
public:
37
  using Slot = StackSlot;
38
  using Data = StackData;
39
  using Depth = StackDepth;
40
  using Offset = StackOffset;
41
42
  explicit Stack(Data& _data, ShuffleTrace* _trace = nullptr):
43
0
    m_data(&_data),
44
0
    m_trace(_trace)
45
0
  {}
46
47
  Slot const& top() const
48
0
  {
49
0
    yulAssert(!m_data->empty());
50
0
    return m_data->back();
51
0
  }
52
53
0
  void swap(Depth const& _depth) { swap(depthToOffset(_depth)); }
54
  void swap(Offset const& _offset)
55
0
  {
56
0
    yulAssert(isValidSwapTarget(_offset), "Stack too deep");
57
0
    std::swap((*m_data)[_offset.value], m_data->back());
58
0
    if (m_trace)
59
0
      m_trace->push_back(ShuffleOp::swap(offsetToDepth(_offset)));
60
0
  }
61
62
  void pop()
63
0
  {
64
0
    yulAssert(!m_data->empty());
65
0
    m_data->pop_back();
66
0
    if (m_trace)
67
0
      m_trace->push_back(ShuffleOp::pop());
68
0
  }
69
70
  void push(Slot const& _slot)
71
0
  {
72
0
    yulAssert(!_slot.isFunctionReturnLabel(), "Cannot push function return label");
73
0
    m_data->emplace_back(_slot);
74
0
    if (m_trace)
75
0
      m_trace->push_back(
76
        // a pushed non-literal value can only be a spill reload
77
0
        _slot.isValue() && !_slot.isLiteralValue() ? ShuffleOp::load(_slot) : ShuffleOp::push(_slot)
78
0
      );
79
0
  }
80
81
0
  void dup(Depth const& _depth) { dup(depthToOffset(_depth)); }
82
  void dup(Offset const& _offset)
83
0
  {
84
0
    auto const depth = offsetToDepth(_offset);
85
0
    yulAssert(dupReachable(depth), "Stack too deep");
86
0
    auto const slot = (*m_data)[_offset.value];
87
0
    yulAssert(!slot.isFunctionReturnLabel(), "Cannot dup function return label");
88
0
    m_data->push_back(slot);
89
0
    if (m_trace)
90
0
      m_trace->push_back(ShuffleOp::dup(Depth{depth.value + 1}));
91
0
  }
92
93
0
  bool dupReachable(Offset const& _offset) const noexcept { return dupReachable(offsetToDepth(_offset)); }
94
0
  bool dupReachable(Depth const& _depth) const noexcept { return _depth < size() && _depth.value + 1 <= reachableStackDepth; }
95
0
  bool isValidSwapTarget(Offset const& _offset) const noexcept { return isValidSwapTarget(offsetToDepth(_offset)); }
96
0
  bool isValidSwapTarget(Depth const& _depth) const noexcept { return _depth < size() && 1 <= _depth.value && _depth.value <= reachableStackDepth; }
97
0
  bool isBeyondSwapRange(Offset const& _offset) const noexcept { return isBeyondSwapRange(offsetToDepth(_offset)); }
98
0
  bool isBeyondSwapRange(Depth const& _depth) const noexcept { return _depth > reachableStackDepth; }
99
100
0
  void declareJunk(Offset const& _offset) { (*m_data)[_offset.value] = Slot::makeJunk(); }
101
0
  void declareJunk(Depth const& _depth) { declareJunk(depthToOffset(_depth)); }
102
103
0
  Slot const& slot(Depth const& _depth) const { return (*m_data)[depthToOffset(_depth).value]; }
104
0
  Slot const& slot(Offset const& _offset) const { return slot(offsetToDepth(_offset)); }
105
0
  bool empty() const noexcept { return size() == 0; }
106
0
  size_t size() const noexcept { return m_data->size(); }
107
108
  std::optional<Depth> findSlotDepth(Slot const& _value) const
109
0
  {
110
0
    auto rview = *this | ranges::views::reverse;
111
0
    auto it = ranges::find(rview, _value);
112
113
0
    if (it == ranges::end(rview))
114
0
      return std::nullopt;
115
116
0
    return Depth{static_cast<size_t>(std::distance(ranges::begin(rview), it))};
117
0
  }
118
119
0
  Slot const& operator[](Offset const& _index) const noexcept { return (*m_data)[_index.value]; }
120
0
  Data::const_iterator begin() const { return m_data->begin(); }
121
0
  Data::const_iterator end() const { return m_data->end(); }
122
123
  Data const& data() const
124
0
  {
125
0
    return *m_data;
126
0
  }
127
128
  /// index scheme conversion offset -> depth
129
  Depth offsetToDepth(Offset const& _offset) const
130
0
  {
131
0
    yulAssert(_offset < size(), "Offset out of range");
132
0
    return Depth{size() - _offset.value - 1};
133
0
  }
134
  /// index scheme conversion depth -> offset
135
  Offset depthToOffset(Depth const& _depth) const
136
0
  {
137
    yulAssert(_depth < size(), "Depth out of range");
138
0
    return Offset{size() - _depth.value - 1};
139
0
  }
140
141
private:
142
  Data* m_data;
143
  ShuffleTrace* m_trace;
144
};
145
146
}