Skip to content

Commit a6945de

Browse files
authored
Merge pull request #16237 from argotorg/ssacfg_junk_blocks
Analyze the SSA CFG regarding which blocks are allowed to introduce junk slots
2 parents 03c9d67 + 19a78fb commit a6945de

File tree

5 files changed

+290
-13
lines changed

5 files changed

+290
-13
lines changed

libyul/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,11 @@ add_library(yul
7373
backends/evm/StackLayoutGenerator.h
7474
backends/evm/VariableReferenceCounter.h
7575
backends/evm/VariableReferenceCounter.cpp
76+
backends/evm/ssa/BridgeFinder.h
7677
backends/evm/ssa/ControlFlow.cpp
7778
backends/evm/ssa/ControlFlow.h
79+
backends/evm/ssa/JunkAdmittingBlocksFinder.cpp
80+
backends/evm/ssa/JunkAdmittingBlocksFinder.h
7881
backends/evm/ssa/LivenessAnalysis.cpp
7982
backends/evm/ssa/LivenessAnalysis.h
8083
backends/evm/ssa/SSACFGLoopNestingForest.cpp
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
20+
#pragma once
21+
22+
#include <libyul/backends/evm/ssa/SSACFG.h>
23+
24+
#include <range/v3/view/concat.hpp>
25+
26+
#include <cstdint>
27+
#include <optional>
28+
#include <vector>
29+
30+
namespace solidity::yul::ssa
31+
{
32+
/// Detect bridges according to Algorithm 1 of https://arxiv.org/pdf/2108.07346.pdf.
33+
///
34+
/// A bridge in an undirected graph is an edge whose removal increases the number of connected components. In control
35+
/// flow analysis, bridge vertices are critical articulation points where removing the vertex would disconnect
36+
/// reachable code from unreachable code. This implementation adapts the bridge-finding algorithm to directed control
37+
/// flow graphs by treating them as undirected, then validating edge directionality to identify vertices that gate
38+
/// access to subsequent blocks.
39+
///
40+
/// We use bridges to determine blocks in which it is fine to introduce junk slots on the stack at any point in time.
41+
class BridgeFinder
42+
{
43+
public:
44+
explicit BridgeFinder(SSACFG const& _cfg):
45+
m_cfg(_cfg),
46+
m_bridgeVertex(_cfg.numBlocks()),
47+
m_visited(_cfg.numBlocks()),
48+
m_disc(_cfg.numBlocks()),
49+
m_low(_cfg.numBlocks())
50+
{
51+
size_t time = 0;
52+
dfs(time, _cfg.entry, std::nullopt);
53+
}
54+
55+
bool bridgeVertex(SSACFG::BlockId const& _blockId) const
56+
{
57+
return m_bridgeVertex[_blockId.value];
58+
}
59+
60+
private:
61+
void dfs(size_t& _time, SSACFG::BlockId const& _vertex, std::optional<SSACFG::BlockId> const& _parent)
62+
{
63+
m_visited[_vertex.value] = true;
64+
m_disc[_vertex.value] = _time;
65+
m_low[_vertex.value] = _time;
66+
++_time;
67+
68+
auto const& currentBlock = m_cfg.block(_vertex);
69+
currentBlock.forEachExit([&](SSACFG::BlockId const& _exit)
70+
{
71+
processNeighbor(_exit, _time, _vertex, currentBlock.entries, _parent);
72+
});
73+
74+
for (SSACFG::BlockId const neighbor: currentBlock.entries)
75+
processNeighbor(neighbor, _time, _vertex, currentBlock.entries, _parent);
76+
}
77+
78+
void processNeighbor(
79+
SSACFG::BlockId const& _neighbor,
80+
size_t& _time,
81+
SSACFG::BlockId const& _vertex,
82+
std::set<SSACFG::BlockId> const& _vertexEntries,
83+
std::optional<SSACFG::BlockId> const& _parent
84+
)
85+
{
86+
if (_neighbor == _parent)
87+
return;
88+
89+
if (!m_visited[_neighbor.value])
90+
{
91+
dfs(_time, _neighbor, _vertex);
92+
m_low[_vertex.value] = std::min(m_low[_vertex.value], m_low[_neighbor.value]);
93+
if (m_low[_neighbor.value] > m_disc[_vertex.value])
94+
{
95+
// vertex <-> neighbor is a bridge in the undirected graph
96+
bool const edgeNeighborToVertex = _vertexEntries.contains(_neighbor);
97+
bool const edgeVertexToNeighbor = m_cfg.block(_neighbor).entries.contains(_vertex);
98+
99+
// special case: if it's the entry itself, we mark it as bridge vertex (provided correct orientation),
100+
// so that functions which do nothing but revert have their whole tree marked as such (sans loops)
101+
if (!_parent)
102+
m_bridgeVertex[_vertex.value] = edgeVertexToNeighbor;
103+
// Since we are not really undirected, check if we don't have a cycle (u -> v and v -> u) and see,
104+
// which edge really exists here.
105+
// Then record the targeted vertex as bridge vertex.
106+
if (edgeVertexToNeighbor && !edgeNeighborToVertex)
107+
// bridge vertex -> neighbor
108+
m_bridgeVertex[_neighbor.value] = true;
109+
else if (edgeNeighborToVertex && !edgeVertexToNeighbor)
110+
// bridge neighbor -> vertex
111+
m_bridgeVertex[_vertex.value] = true;
112+
}
113+
}
114+
else
115+
m_low[_vertex.value] = std::min(m_low[_vertex.value], m_disc[_neighbor.value]);
116+
}
117+
118+
SSACFG const& m_cfg;
119+
// determines whether a vertex is a bridge vertex. optimizing for performance over space with u8.
120+
std::vector<std::uint8_t> m_bridgeVertex;
121+
// determines whether a vertex is visited. optimizing for performance over space with u8.
122+
std::vector<std::uint8_t> m_visited;
123+
// vertex discovery time
124+
std::vector<std::size_t> m_disc;
125+
// minimum discovery time of subtree - if m_low[child] > m_low[parent], we have a bridge
126+
std::vector<std::size_t> m_low;
127+
};
128+
129+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
#include <libyul/backends/evm/ssa/JunkAdmittingBlocksFinder.h>
20+
21+
#include <libyul/backends/evm/ssa/BridgeFinder.h>
22+
23+
namespace solidity::yul::ssa
24+
{
25+
26+
JunkAdmittingBlocksFinder::JunkAdmittingBlocksFinder(SSACFG const& _cfg, ForwardSSACFGTopologicalSort const& _topologicalSort):
27+
m_blockAllowsJunk(_cfg.numBlocks(), false)
28+
{
29+
// special case: only one block here, we mark it as junkable in case it's not a function return
30+
if (_topologicalSort.preOrder().size() == 1)
31+
{
32+
SSACFG::BlockId const id {_topologicalSort.preOrder().front()};
33+
m_blockAllowsJunk[id.value] = !_cfg.block(id).isFunctionReturnBlock();
34+
return;
35+
}
36+
37+
// Find all bridges, i.e., vertices, which upon removal increase the number of connected components.
38+
// Translated to SSA CFGs this means:
39+
// - control flow that enters a bridge vertex never returns to a previously visited block
40+
// - there is no parallel path to a child of the vertex, ie, adding junk is fine in terms of stack balance
41+
BridgeFinder const bridgeFinder(_cfg);
42+
43+
// of the bridge vertices, we have the exclude the ones that can lead to a function return
44+
std::vector<SSACFG::BlockId> toVisit;
45+
for (auto const blockIndex: _topologicalSort.preOrder())
46+
{
47+
SSACFG::BlockId const blockId {blockIndex};
48+
m_blockAllowsJunk[blockIndex] = bridgeFinder.bridgeVertex(blockId) || _cfg.block(blockId).isTerminationBlock();
49+
if (_cfg.block(blockId).isFunctionReturnBlock())
50+
toVisit.emplace_back(SSACFG::BlockId{blockIndex});
51+
}
52+
53+
std::vector<uint8_t> visited(_cfg.numBlocks(), false);
54+
while (!toVisit.empty())
55+
{
56+
auto const blockId = toVisit.back();
57+
auto const& block = _cfg.block(blockId);
58+
toVisit.pop_back();
59+
60+
m_blockAllowsJunk[blockId.value] = false;
61+
visited[blockId.value] = true;
62+
for (auto const& entry: block.entries)
63+
if (!visited[entry.value])
64+
toVisit.emplace_back(entry);
65+
}
66+
}
67+
68+
bool JunkAdmittingBlocksFinder::allowsAdditionOfJunk(SSACFG::BlockId const& _blockId) const
69+
{
70+
return m_blockAllowsJunk[_blockId.value];
71+
}
72+
73+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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/SSACFGTopologicalSort.h>
22+
#include <libyul/backends/evm/ssa/SSACFG.h>
23+
24+
#include <cstdint>
25+
#include <vector>
26+
27+
namespace solidity::yul::ssa
28+
{
29+
30+
/// Identifies blocks where stack balance constraints can be relaxed.
31+
/// These are blocks that inevitably terminate down the line (i.e., there is no path to a function return exit) and
32+
/// which are "bridge vertices". For a bridge, the graph decomposes into `G1` and `G2` with a singular edge `e=(v1->v2)`
33+
/// between them. Therefore, traversal into `G2` cannot escape back into `G1` and in particular there cannot be a
34+
/// parallel path into G2 that has relaxed constraints with respect to introducing junk. Consequently, there cannot be
35+
/// a situation in which a junk-bloated stack has to be unified with a slimmer stack layout stemming from another path
36+
/// into `G2`.
37+
class JunkAdmittingBlocksFinder
38+
{
39+
public:
40+
explicit JunkAdmittingBlocksFinder(SSACFG const& _cfg, ForwardSSACFGTopologicalSort const& _topologicalSort);
41+
bool allowsAdditionOfJunk(SSACFG::BlockId const& _blockId) const;
42+
private:
43+
std::vector<std::uint8_t> m_blockAllowsJunk;
44+
};
45+
46+
}

libyul/backends/evm/ssa/SSACFG.h

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,14 @@ class SSACFG
5252
struct BlockId
5353
{
5454
size_t value = std::numeric_limits<size_t>::max();
55-
bool operator<(BlockId const& _rhs) const { return value < _rhs.value; }
56-
bool operator==(BlockId const& _rhs) const { return value == _rhs.value; }
57-
bool operator!=(BlockId const& _rhs) const { return value != _rhs.value; }
55+
auto operator<=>(BlockId const&) const = default;
5856
};
5957
struct ValueId
6058
{
61-
size_t value = std::numeric_limits<size_t>::max();
62-
bool hasValue() const { return value != std::numeric_limits<size_t>::max(); }
63-
bool operator<(ValueId const& _rhs) const { return value < _rhs.value; }
64-
bool operator==(ValueId const& _rhs) const { return value == _rhs.value; }
65-
bool operator!=(ValueId const& _rhs) const { return value != _rhs.value; }
59+
using ValueType = size_t;
60+
ValueType value = std::numeric_limits<ValueType>::max();
61+
bool hasValue() const { return value != std::numeric_limits<ValueType>::max(); }
62+
auto operator<=>(ValueId const&) const = default;
6663
};
6764

6865
struct BuiltinCall
@@ -93,26 +90,26 @@ class SSACFG
9390
struct MainExit {};
9491
struct ConditionalJump
9592
{
96-
langutil::DebugData::ConstPtr debugData;
93+
langutil::DebugData::ConstPtr debugData{};
9794
ValueId condition;
9895
BlockId nonZero;
9996
BlockId zero;
10097
};
10198
struct Jump
10299
{
103-
langutil::DebugData::ConstPtr debugData;
100+
langutil::DebugData::ConstPtr debugData{};
104101
BlockId target;
105102
};
106103
struct JumpTable
107104
{
108-
langutil::DebugData::ConstPtr debugData;
105+
langutil::DebugData::ConstPtr debugData{};
109106
ValueId value;
110107
std::map<u256, BlockId> cases;
111108
BlockId defaultCase;
112109
};
113110
struct FunctionReturn
114111
{
115-
langutil::DebugData::ConstPtr debugData;
112+
langutil::DebugData::ConstPtr debugData{};
116113
std::vector<ValueId> returnValues;
117114
};
118115
struct Terminated {};
@@ -138,6 +135,26 @@ class SSACFG
138135
_callable(jumpTable->defaultCase);
139136
}
140137
}
138+
139+
bool isMainExitBlock() const
140+
{
141+
return std::holds_alternative<MainExit>(exit);
142+
}
143+
144+
bool isTerminationBlock() const
145+
{
146+
return std::holds_alternative<Terminated>(exit);
147+
}
148+
149+
bool isFunctionReturnBlock() const
150+
{
151+
return std::holds_alternative<FunctionReturn>(exit);
152+
}
153+
154+
bool isJumpBlock() const
155+
{
156+
return std::holds_alternative<Jump>(exit);
157+
}
141158
};
142159
BlockId makeBlock(langutil::DebugData::ConstPtr _debugData)
143160
{
@@ -148,6 +165,7 @@ class SSACFG
148165
BasicBlock& block(BlockId _id) { return m_blocks.at(_id.value); }
149166
BasicBlock const& block(BlockId _id) const { return m_blocks.at(_id.value); }
150167
size_t numBlocks() const { return m_blocks.size(); }
168+
151169
private:
152170
std::vector<BasicBlock> m_blocks;
153171
public:
@@ -170,13 +188,21 @@ class SSACFG
170188
{
171189
return std::holds_alternative<LiteralValue>(valueInfo(_var));
172190
}
191+
bool isPhiValue(ValueId const _var) const
192+
{
193+
return std::holds_alternative<PhiValue>(valueInfo(_var));
194+
}
173195
ValueInfo& valueInfo(ValueId const _var)
174196
{
175197
return m_valueInfos.at(_var.value);
176198
}
177199
ValueInfo const& valueInfo(ValueId const _var) const
178200
{
179-
return m_valueInfos.at(_var.value);
201+
return valueInfo(_var.value);
202+
}
203+
ValueInfo const& valueInfo(ValueId::ValueType const _var) const
204+
{
205+
return m_valueInfos.at(_var);
180206
}
181207
ValueId newPhi(BlockId const _definingBlock)
182208
{

0 commit comments

Comments
 (0)