Skip to content

Commit 2787cd3

Browse files
committed
ssacfg: add bridge finder
1 parent 03c9d67 commit 2787cd3

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

libyul/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ 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
7879
backends/evm/ssa/LivenessAnalysis.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+
}

0 commit comments

Comments
 (0)