Skip to content

Commit e403242

Browse files
authored
Merge pull request #16265 from argotorg/optimize_ssacfg_building
Optimize SSA CFG datastructure + construction
2 parents 6156813 + d5d29bd commit e403242

30 files changed

+1139
-1106
lines changed

libsolutil/DisjointSet.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424

2525
using namespace solidity::util;
2626

27-
ContiguousDisjointSet::ContiguousDisjointSet(size_t const _numNodes):
27+
template<typename ValueType>
28+
ContiguousDisjointSet<ValueType>::ContiguousDisjointSet(size_t const _numNodes):
2829
m_parents(_numNodes),
2930
m_neighbors(_numNodes),
3031
m_sizes(_numNodes, static_cast<value_type>(1)),
@@ -35,9 +36,11 @@ ContiguousDisjointSet::ContiguousDisjointSet(size_t const _numNodes):
3536
std::iota(m_neighbors.begin(), m_neighbors.end(), 0);
3637
}
3738

38-
size_t ContiguousDisjointSet::numSets() const { return m_numSets; }
39+
template<typename ValueType>
40+
size_t ContiguousDisjointSet<ValueType>::numSets() const { return m_numSets; }
3941

40-
ContiguousDisjointSet::value_type ContiguousDisjointSet::find(value_type const _element) const
42+
template<typename ValueType>
43+
typename ContiguousDisjointSet<ValueType>::value_type ContiguousDisjointSet<ValueType>::find(value_type const _element) const
4144
{
4245
solAssert(_element < m_parents.size());
4346
// path halving
@@ -50,7 +53,8 @@ ContiguousDisjointSet::value_type ContiguousDisjointSet::find(value_type const _
5053
return rootElement;
5154
}
5255

53-
void ContiguousDisjointSet::merge(value_type const _x, value_type const _y, bool const _mergeBySize)
56+
template<typename ValueType>
57+
void ContiguousDisjointSet<ValueType>::merge(value_type const _x, value_type const _y, bool const _mergeBySize)
5458
{
5559
auto xRoot = find(_x);
5660
auto yRoot = find(_y);
@@ -69,17 +73,20 @@ void ContiguousDisjointSet::merge(value_type const _x, value_type const _y, bool
6973
--m_numSets;
7074
}
7175

72-
bool ContiguousDisjointSet::sameSubset(value_type const _x, value_type const _y) const
76+
template<typename ValueType>
77+
bool ContiguousDisjointSet<ValueType>::sameSubset(value_type const _x, value_type const _y) const
7378
{
7479
return find(_x) == find(_y);
7580
}
7681

77-
ContiguousDisjointSet::size_type ContiguousDisjointSet::sizeOfSubset(value_type const _x) const
82+
template<typename ValueType>
83+
typename ContiguousDisjointSet<ValueType>::size_type ContiguousDisjointSet<ValueType>::sizeOfSubset(value_type const _x) const
7884
{
7985
return m_sizes[find(_x)];
8086
}
8187

82-
std::set<ContiguousDisjointSet::value_type> ContiguousDisjointSet::subset(value_type const _x) const
88+
template<typename ValueType>
89+
std::set<typename ContiguousDisjointSet<ValueType>::value_type> ContiguousDisjointSet<ValueType>::subset(value_type const _x) const
8390
{
8491
solAssert(_x < m_parents.size());
8592
std::set<value_type> result{_x};
@@ -92,7 +99,8 @@ std::set<ContiguousDisjointSet::value_type> ContiguousDisjointSet::subset(value_
9299
return result;
93100
}
94101

95-
std::vector<std::set<ContiguousDisjointSet::value_type>> ContiguousDisjointSet::subsets() const
102+
template<typename ValueType>
103+
std::vector<std::set<typename ContiguousDisjointSet<ValueType>::value_type>> ContiguousDisjointSet<ValueType>::subsets() const
96104
{
97105
std::vector<std::set<value_type>> result;
98106
std::vector<std::uint8_t> visited(m_parents.size(), false);
@@ -107,3 +115,5 @@ std::vector<std::set<ContiguousDisjointSet::value_type>> ContiguousDisjointSet::
107115
}
108116
return result;
109117
}
118+
119+
template class solidity::util::ContiguousDisjointSet<std::uint32_t>;

libsolutil/DisjointSet.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ namespace solidity::util
3131
/// [1] https://en.wikipedia.org/wiki/Disjoint-set_data_structure
3232
/// [2] Tarjan, Robert E., and Jan Van Leeuwen. "Worst-case analysis of set union algorithms."
3333
/// Journal of the ACM (JACM) 31.2 (1984): 245-281.
34+
template<typename ValueType>
3435
class ContiguousDisjointSet
3536
{
3637
public:
3738
using size_type = size_t;
38-
using value_type = size_t;
39+
using value_type = ValueType;
3940

4041
/// Constructs a new disjoint set datastructure with `_numNodes` elements and each element in its own individual set
4142
explicit ContiguousDisjointSet(size_t _numNodes);

libyul/YulStack.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,7 @@ Json YulStack::cfgJson() const
405405
keepLiteralAssignments
406406
);
407407
std::unique_ptr<ssa::ControlFlowLiveness> liveness = std::make_unique<ssa::ControlFlowLiveness>(*controlFlow);
408-
ssa::SSACFGJsonExporter exporter(*controlFlow, liveness.get());
409-
return exporter.run();
408+
return ssa::json::exportControlFlow(*controlFlow, liveness.get());
410409
};
411410

412411
std::function<Json(std::vector<std::shared_ptr<ObjectNode>>)> exportCFGFromSubObjects;

libyul/backends/evm/ssa/ControlFlow.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ using namespace solidity::yul::ssa;
2424

2525
ControlFlowLiveness::ControlFlowLiveness(ControlFlow const& _controlFlow):
2626
controlFlow(_controlFlow),
27-
mainLiveness(std::make_unique<LivenessAnalysis>(*_controlFlow.mainGraph)),
28-
functionLiveness(_controlFlow.functionGraphs | ranges::views::transform([](auto const& _cfg) { return std::make_unique<LivenessAnalysis>(*_cfg); }) | ranges::to<std::vector>)
27+
cfgLiveness(_controlFlow.functionGraphs | ranges::views::transform([](auto const& _cfg) { return std::make_unique<LivenessAnalysis>(*_cfg); }) | ranges::to<std::vector>)
2928
{ }
3029

3130
std::string ControlFlowLiveness::toDot() const

libyul/backends/evm/ssa/ControlFlow.h

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include <libyul/AST.h>
2525
#include <libyul/Scope.h>
2626

27+
#include <range/v3/algorithm/find_if.hpp>
28+
2729
namespace solidity::yul::ssa
2830
{
2931

@@ -33,44 +35,52 @@ struct ControlFlowLiveness{
3335
explicit ControlFlowLiveness(ControlFlow const& _controlFlow);
3436

3537
std::reference_wrapper<ControlFlow const> controlFlow;
36-
std::unique_ptr<LivenessAnalysis> mainLiveness;
37-
std::vector<std::unique_ptr<LivenessAnalysis>> functionLiveness;
38+
std::vector<std::unique_ptr<LivenessAnalysis>> cfgLiveness;
3839

3940
std::string toDot() const;
4041
};
4142

4243
struct ControlFlow
4344
{
44-
std::unique_ptr<SSACFG> mainGraph{std::make_unique<SSACFG>()};
45-
std::vector<std::unique_ptr<SSACFG>> functionGraphs{};
46-
std::vector<std::tuple<Scope::Function const*, SSACFG const*>> functionGraphMapping{};
45+
using FunctionGraphID = std::uint32_t;
4746

48-
SSACFG const* functionGraph(Scope::Function const* _function)
47+
static FunctionGraphID constexpr mainGraphID() noexcept { return 0; }
48+
49+
SSACFG const* mainGraph() const { return functionGraph(mainGraphID()); }
50+
51+
SSACFG const* functionGraph(Scope::Function const* _function) const
4952
{
50-
auto it = std::find_if(functionGraphMapping.begin(), functionGraphMapping.end(), [_function](auto const& tup) { return _function == std::get<0>(tup); });
53+
auto it = ranges::find_if(functionGraphMapping, [_function](auto const& tup) { return _function == std::get<0>(tup); });
5154
if (it != functionGraphMapping.end())
5255
return std::get<1>(*it);
5356
return nullptr;
5457
}
5558

59+
SSACFG const* functionGraph(FunctionGraphID const _id) const
60+
{
61+
return functionGraphs.at(_id).get();
62+
}
63+
5664
std::string toDot(ControlFlowLiveness const* _liveness=nullptr) const
5765
{
5866
if (_liveness)
5967
yulAssert(&_liveness->controlFlow.get() == this);
6068
std::ostringstream output;
6169
output << "digraph SSACFG {\nnodesep=0.7;\ngraph[fontname=\"DejaVu Sans\"]\nnode[shape=box,fontname=\"DejaVu Sans\"];\n\n";
62-
output << mainGraph->toDot(false, std::nullopt, _liveness ? _liveness->mainLiveness.get() : nullptr);
6370

6471
for (size_t index=0; index < functionGraphs.size(); ++index)
6572
output << functionGraphs[index]->toDot(
6673
false,
67-
index+1,
68-
_liveness ? _liveness->functionLiveness[index].get() : nullptr
74+
index,
75+
_liveness ? _liveness->cfgLiveness[index].get() : nullptr
6976
);
7077

7178
output << "}\n";
7279
return output.str();
7380
}
81+
82+
std::vector<std::unique_ptr<SSACFG>> functionGraphs{};
83+
std::vector<std::tuple<Scope::Function const*, SSACFG const*>> functionGraphMapping{};
7484
};
7585

7686
}

libyul/backends/evm/ssa/LivenessAnalysis.cpp

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,11 @@ using namespace solidity::yul::ssa;
3131

3232
namespace
3333
{
34-
constexpr auto excludingLiteralsFilter(SSACFG const& _cfg)
34+
constexpr auto excludingLiteralsFilter()
3535
{
36-
return [&_cfg](LivenessAnalysis::LivenessData::Value const& _valueId) -> bool
36+
return [](LivenessAnalysis::LivenessData::Value const& _valueId) -> bool
3737
{
38-
return !std::holds_alternative<SSACFG::LiteralValue>(_cfg.valueInfo(_valueId));
39-
};
40-
}
41-
constexpr auto unreachableFilter(SSACFG const& _cfg)
42-
{
43-
return [&_cfg](LivenessAnalysis::LivenessData::Value const& _valueId) -> bool
44-
{
45-
return std::holds_alternative<SSACFG::UnreachableValue>(_cfg.valueInfo(_valueId));
38+
return !_valueId.isLiteral();
4639
};
4740
}
4841
}
@@ -149,18 +142,18 @@ LivenessAnalysis::LivenessData LivenessAnalysis::blockExitValues(SSACFG::BlockId
149142
[](SSACFG::BasicBlock::MainExit const&) {},
150143
[&](SSACFG::BasicBlock::FunctionReturn const& _functionReturn)
151144
{
152-
for (auto const& valueId: _functionReturn.returnValues | ranges::views::filter(excludingLiteralsFilter(m_cfg)))
145+
for (auto const& valueId: _functionReturn.returnValues | ranges::views::filter(excludingLiteralsFilter()))
153146
result.insert(valueId);
154147
},
155148
[&](SSACFG::BasicBlock::JumpTable const& _jt)
156149
{
157-
if (excludingLiteralsFilter(m_cfg)(_jt.value))
150+
if (excludingLiteralsFilter()(_jt.value))
158151
result.insert(_jt.value);
159152
},
160153
[](SSACFG::BasicBlock::Jump const&) {},
161154
[&](SSACFG::BasicBlock::ConditionalJump const& _conditionalJump)
162155
{
163-
if (excludingLiteralsFilter(m_cfg)(_conditionalJump.condition))
156+
if (excludingLiteralsFilter()(_conditionalJump.condition))
164157
result.insert(_conditionalJump.condition);
165158
},
166159
[](SSACFG::BasicBlock::Terminated const&) {}};
@@ -218,12 +211,11 @@ void LivenessAnalysis::runDagDfs()
218211
{
219212
for (auto const& phi: m_cfg.block(_successor).phis)
220213
{
221-
auto const& info = m_cfg.valueInfo(phi);
222-
yulAssert(std::holds_alternative<SSACFG::PhiValue>(info), "value info of phi wasn't PhiValue");
223-
auto const argIndex = m_cfg.phiArgumentIndex(blockId, _successor);
224-
yulAssert(argIndex < std::get<SSACFG::PhiValue>(info).arguments.size());
225-
auto const arg = std::get<SSACFG::PhiValue>(info).arguments.at(argIndex);
226-
if (!std::holds_alternative<SSACFG::LiteralValue>(m_cfg.valueInfo(arg)))
214+
auto const& info = m_cfg.phiInfo(phi);
215+
auto const& argIndex = m_cfg.phiArgumentIndex(blockId, _successor);
216+
yulAssert(argIndex < info.arguments.size());
217+
auto const& arg = info.arguments.at(argIndex);
218+
if (!arg.isLiteral())
227219
live.insert(arg);
228220
}
229221
});
@@ -242,11 +234,11 @@ void LivenessAnalysis::runDagDfs()
242234
});
243235

244236
if (std::holds_alternative<SSACFG::BasicBlock::FunctionReturn>(block.exit))
245-
for (auto const& returnValue: std::get<SSACFG::BasicBlock::FunctionReturn>(block.exit).returnValues | ranges::views::filter(excludingLiteralsFilter(m_cfg)))
237+
for (auto const& returnValue: std::get<SSACFG::BasicBlock::FunctionReturn>(block.exit).returnValues | ranges::views::filter(excludingLiteralsFilter()))
246238
live.insert(returnValue);
247239

248240
// clean out unreachables
249-
live.eraseIf([&](auto const& _entry) { return unreachableFilter(m_cfg)(_entry.first); });
241+
live.eraseIf([&](auto const& _entry) { return _entry.first.isUnreachable(); });
250242

251243
// LiveOut(B) <- live
252244
m_liveOuts[blockId.value] = live;
@@ -259,9 +251,9 @@ void LivenessAnalysis::runDagDfs()
259251
for (auto const& op: block.operations | ranges::views::reverse)
260252
{
261253
// remove variables defined at p from live
262-
live.eraseAll(op.outputs | ranges::views::filter(excludingLiteralsFilter(m_cfg)) | ranges::to<std::vector>);
254+
live.eraseAll(op.outputs | ranges::views::filter(excludingLiteralsFilter()) | ranges::to<std::vector>);
263255
// add uses at p to live
264-
live.insertAll(op.inputs | ranges::views::filter(excludingLiteralsFilter(m_cfg)) | ranges::to<std::vector>);
256+
live.insertAll(op.inputs | ranges::views::filter(excludingLiteralsFilter()) | ranges::to<std::vector>);
265257
}
266258
}
267259

@@ -272,7 +264,7 @@ void LivenessAnalysis::runDagDfs()
272264
}
273265
}
274266

275-
void LivenessAnalysis::runLoopTreeDfs(size_t const _loopHeader)
267+
void LivenessAnalysis::runLoopTreeDfs(SSACFG::BlockId::ValueType const _loopHeader)
276268
{
277269
// SSA Book, Algorithm 9.3
278270
if (m_loopNestingForest.loopNodes().contains(_loopHeader))
@@ -286,7 +278,7 @@ void LivenessAnalysis::runLoopTreeDfs(size_t const _loopHeader)
286278
// must be live out of header if live in of children
287279
m_liveOuts[_loopHeader].maxUnion(liveLoop);
288280
// for each blockId \in children(loopHeader)
289-
for (size_t blockIdValue = 0; blockIdValue < m_cfg.numBlocks(); ++blockIdValue)
281+
for (SSACFG::BlockId::ValueType blockIdValue = 0u; blockIdValue < m_cfg.numBlocks(); ++blockIdValue)
290282
if (m_loopNestingForest.loopParents()[blockIdValue] == _loopHeader)
291283
{
292284
// propagate loop liveness information down to the loop header's children
@@ -313,9 +305,9 @@ void LivenessAnalysis::fillOperationsLiveOut()
313305
for (auto const& op: operations | ranges::views::reverse)
314306
{
315307
*rit = live;
316-
for (auto const& output: op.outputs | ranges::views::filter(excludingLiteralsFilter(m_cfg)))
308+
for (auto const& output: op.outputs | ranges::views::filter(excludingLiteralsFilter()))
317309
live.erase(output);
318-
for (auto const& input: op.inputs | ranges::views::filter(excludingLiteralsFilter(m_cfg)))
310+
for (auto const& input: op.inputs | ranges::views::filter(excludingLiteralsFilter()))
319311
live.insert(input);
320312
++rit;
321313
}

libyul/backends/evm/ssa/LivenessAnalysis.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ class LivenessAnalysis
4242

4343
LivenessData() = default;
4444
template<std::input_iterator Iter, std::sentinel_for<Iter> Sentinel>
45-
LivenessData(Iter begin, Sentinel end): m_liveCounts(begin, end) {
46-
}
45+
LivenessData(Iter begin, Sentinel end): m_liveCounts(begin, end) {}
4746
explicit LivenessData(LiveCounts&& _liveCounts): m_liveCounts(std::move(_liveCounts)) {}
4847

4948
bool contains(Value const& _valueId) const;
@@ -113,7 +112,7 @@ class LivenessAnalysis
113112

114113
private:
115114
void runDagDfs();
116-
void runLoopTreeDfs(std::size_t _loopHeader);
115+
void runLoopTreeDfs(SSACFG::BlockId::ValueType _loopHeader);
117116
void fillOperationsLiveOut();
118117
LivenessData blockExitValues(SSACFG::BlockId const& _blockId) const;
119118

0 commit comments

Comments
 (0)