Skip to content

Commit 19a78fb

Browse files
committed
ssacfg: add termination path analysis
1 parent 2787cd3 commit 19a78fb

File tree

4 files changed

+160
-13
lines changed

4 files changed

+160
-13
lines changed

libyul/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ add_library(yul
7676
backends/evm/ssa/BridgeFinder.h
7777
backends/evm/ssa/ControlFlow.cpp
7878
backends/evm/ssa/ControlFlow.h
79+
backends/evm/ssa/JunkAdmittingBlocksFinder.cpp
80+
backends/evm/ssa/JunkAdmittingBlocksFinder.h
7981
backends/evm/ssa/LivenessAnalysis.cpp
8082
backends/evm/ssa/LivenessAnalysis.h
8183
backends/evm/ssa/SSACFGLoopNestingForest.cpp
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)