Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions libyul/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ add_library(yul
backends/evm/ssa/StackUtils.h
backends/evm/ssa/analysis/DepthFirstSpanningTree.cpp
backends/evm/ssa/analysis/DepthFirstSpanningTree.h
backends/evm/ssa/analysis/DominatorTree.cpp
backends/evm/ssa/analysis/DominatorTree.h
backends/evm/ssa/analysis/ReducibilityCheck.cpp
backends/evm/ssa/analysis/ReducibilityCheck.h
backends/evm/ssa/io/DotExporterBase.cpp
backends/evm/ssa/io/DotExporterBase.h
backends/evm/ssa/io/Keywords.cpp
Expand Down
3 changes: 3 additions & 0 deletions libyul/backends/evm/ssa/LivenessAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

#include <libyul/backends/evm/ssa/LivenessAnalysis.h>

#include <libyul/backends/evm/ssa/analysis/ReducibilityCheck.h>

#include <libsolutil/Visitor.h>

#include <boost/container/flat_map.hpp>
Expand Down Expand Up @@ -58,6 +60,7 @@ LivenessAnalysis::LivenessAnalysis(SSACFG const& _cfg):
m_liveIns(_cfg.numBlocks()),
m_liveOuts(_cfg.numBlocks())
{
yulAssert(analysis::isReducibleCFG(m_dfsTree), "Liveness analysis only implemented for reducible CFGs");
runDagDfs();
for (auto const loopRootNode: m_loopNestingForest.loopRootNodes())
runLoopTreeDfs(loopRootNode);
Expand Down
6 changes: 5 additions & 1 deletion libyul/backends/evm/ssa/analysis/DepthFirstSpanningTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ using namespace solidity::yul::ssa::analysis;
DepthFirstSpanningTree::DepthFirstSpanningTree(SSACFG const& _cfg):
m_cfg(_cfg),
m_explored(m_cfg.numBlocks(), false), m_blockWisePreOrder(m_cfg.numBlocks(), 0),
m_blockWiseMaxSubtreePreOrder(m_cfg.numBlocks(), 0)
m_blockWiseMaxSubtreePreOrder(m_cfg.numBlocks(), 0),
m_parent(m_cfg.numBlocks())
{
m_preOrder.reserve(m_cfg.numBlocks());
m_postOrder.reserve(m_cfg.numBlocks());
Expand All @@ -45,7 +46,10 @@ void DepthFirstSpanningTree::dfs(SSACFG::BlockId const _block)

m_cfg.block(_block).forEachExit([&](SSACFG::BlockId const& _exitBlock) {
if (!m_explored[_exitBlock.value])
{
m_parent[_exitBlock.value] = _block;
dfs(_exitBlock);
}
});

// the subtree has been visited completely and occupies the pre-order indices up to here
Expand Down
36 changes: 31 additions & 5 deletions libyul/backends/evm/ssa/analysis/DepthFirstSpanningTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

#include <libyul/backends/evm/ssa/SSACFG.h>

#include <libyul/Exceptions.h>

#include <set>
#include <vector>

Expand All @@ -45,21 +47,45 @@ class DepthFirstSpanningTree
/// (a retreating edge). In a reducible CFG, these are exactly the back edges, i.e., the target dominates the source.
bool backEdge(SSACFG::BlockId _source, SSACFG::BlockId _target) const;

SSACFG::BlockId::ValueType preOrderIndexOf(SSACFG::BlockId _block) const { return m_blockWisePreOrder[_block.value]; }

SSACFG::BlockId::ValueType maxSubtreePreOrderIndexOf(SSACFG::BlockId _block) const { return m_blockWiseMaxSubtreePreOrder[_block.value]; }
SSACFG::BlockId::ValueType preOrderIndexOf(SSACFG::BlockId _block) const
{
yulAssert(reachable(_block));
return m_blockWisePreOrder[_block.value];
}

SSACFG::BlockId::ValueType maxSubtreePreOrderIndexOf(SSACFG::BlockId _block) const
{
yulAssert(reachable(_block));
return m_blockWiseMaxSubtreePreOrder[_block.value];
}

/// Parent of a reachable non-entry block in the DFS tree.
SSACFG::BlockId parentOf(SSACFG::BlockId _block) const
{
yulAssert(reachable(_block) && _block != m_cfg.entry, "Parent only defined for reachable non-entry blocks.");
return m_parent[_block.value];
}

/// Whether the block is reachable from the entry, i.e., was visited by the DFS.
bool reachable(SSACFG::BlockId _block) const
{
yulAssert(_block.value < m_explored.size());
return m_explored[_block.value];
}

/// Checks if `_ancestor` is an ancestor of `_block`, ie there's a path from `_ancestor` to `_block` in the dfs tree
bool ancestor(SSACFG::BlockId _ancestor, SSACFG::BlockId _block) const;

private:
void dfs(SSACFG::BlockId _block);
/// Checks if block1 is an ancestor of block2, ie there's a path from block1 to block2 in the dfs tree
bool ancestor(SSACFG::BlockId _ancestor, SSACFG::BlockId _block) const;

SSACFG const& m_cfg;
std::vector<char> m_explored{};
std::vector<SSACFG::BlockId> m_postOrder{};
std::vector<SSACFG::BlockId> m_preOrder{};
std::vector<SSACFG::BlockId::ValueType> m_blockWisePreOrder{};
std::vector<SSACFG::BlockId::ValueType> m_blockWiseMaxSubtreePreOrder{};
std::vector<SSACFG::BlockId> m_parent{};
std::set<SSACFG::BlockId> m_backEdgeTargets{};
};
}
155 changes: 155 additions & 0 deletions libyul/backends/evm/ssa/analysis/DominatorTree.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#include <libyul/backends/evm/ssa/analysis/DominatorTree.h>

#include <libyul/backends/evm/ssa/analysis/DepthFirstSpanningTree.h>

#include <libyul/Exceptions.h>

#include <range/v3/view/iota.hpp>
#include <range/v3/view/reverse.hpp>
#include <range/v3/to_container.hpp>

#include <algorithm>
#include <limits>

using namespace solidity::yul::ssa;
using namespace solidity::yul::ssa::analysis;

DominatorTree::DominatorTree(DepthFirstSpanningTree const& _dfsTree)
{
SSACFG const& cfg = _dfsTree.cfg();
auto const& preOrder = _dfsTree.preOrder();
std::size_t const numConnectedBlocks = preOrder.size();
std::size_t const numBlocks = cfg.numBlocks();

m_idom.assign(numBlocks, SSACFG::BlockId{});
m_domTreePreOrder.assign(numBlocks, 0);
m_domTreeMaxSubtreePreOrder.assign(numBlocks, 0);

// SEMI-NCA algorithm [GTW06, Section 2.3]
// link and eval follow the simple version (path compression only) of [LT79]
// All arrays are indexed by DFS index (0 = entry)
std::vector<std::size_t> parent(numConnectedBlocks, 0);
for (std::size_t i = 1; i < numConnectedBlocks; ++i)
parent[i] = _dfsTree.preOrderIndexOf(_dfsTree.parentOf(preOrder[i]));

std::vector semi = ranges::views::iota(0u, numConnectedBlocks) | ranges::to<std::vector<std::size_t>>();
// label[v] = minimal semidominator value on the compressed forest path ending in a linked vertex v.
// Unlike [LT79], where label holds a vertex attaining that minimum, it holds the value itself: SEMI-NCA
// only needs the minimal value [GTW06, Section 2.3]
std::vector label = semi;
static std::size_t constexpr NO_FOREST_ANCESTOR = std::numeric_limits<std::size_t>::max();
std::vector forestAncestor(numConnectedBlocks, NO_FOREST_ANCESTOR);

// The `eval` operation of the link-eval structure [GTW06, Section 2.2], [LT79] with path
// compression only: returns the semidominator value of `_v` if it is a forest root, and otherwise the minimal
// semidominator value on the forest path from `_v` up to, but excluding, its root.
// The recursive `compress` of [LT79] is unrolled: collect the path, then compress it top-down
auto eval = [&, compressionPath = std::vector<std::size_t>{}](std::size_t const _v) mutable -> std::size_t {
if (forestAncestor[_v] == NO_FOREST_ANCESTOR)
return semi[_v];
compressionPath.clear();
for (std::size_t u = _v; forestAncestor[forestAncestor[u]] != NO_FOREST_ANCESTOR; u = forestAncestor[u])
compressionPath.push_back(u);
for (std::size_t const u: compressionPath | ranges::views::reverse)
{
label[u] = std::min(label[u], label[forestAncestor[u]]);
forestAncestor[u] = forestAncestor[forestAncestor[u]];
}
return label[_v];
};

// GTW06, Section 2.3 (a): compute semidominators in reverse DFS preorder as sdom(w) = min{s_w(v) | v in pred(w)}
// [GTW06, Section 2.2]. When w is processed, exactly the vertices > w are linked, so the forest root of a
// predecessor v is NCA(D, {v, w}) and eval(v) = s_w(v)
for (std::size_t w = numConnectedBlocks - 1; w >= 1; --w)
{
for (auto const& predBlock: cfg.block(preOrder[w]).entries)
if (_dfsTree.reachable(predBlock))
semi[w] = std::min(semi[w], eval(_dfsTree.preOrderIndexOf(predBlock)));
Comment thread
blishko marked this conversation as resolved.
// link(w, sdom(w)) [GTW06, Section 2.2]
{
forestAncestor[w] = parent[w];
label[w] = semi[w];
}
}

// GTW06, Section 2.3 (b): compute immediate dominators
//
// For each node in preorder, walk from its DFS-tree parent up toward the root (via already-computed idoms) until
// reaching a node whose preorder index is at most that of its semidominator, i.e., the semidominator itself or
// a DFS tree ancestor of it
std::vector<std::size_t> idom(numConnectedBlocks, 0);
for (std::size_t w = 1; w < numConnectedBlocks; ++w)
{
std::size_t runner = parent[w];
while (runner > semi[w])
runner = idom[runner];
idom[w] = runner;
}

// Number the dominator tree in preorder, so that `dominates` becomes an interval check. The idom of a vertex
// precedes it in DFS preorder, i.e., subtree sizes accumulate in reverse DFS preorder, and in DFS preorder each
// vertex claims the next free part of its idom's interval
std::vector<std::size_t> subtreeSize(numConnectedBlocks, 1);
for (std::size_t w = numConnectedBlocks - 1; w >= 1; --w)
subtreeSize[idom[w]] += subtreeSize[w];
std::vector<std::size_t> domTreePreOrder(numConnectedBlocks, 0);
std::vector<std::size_t> nextFreePreOrder(numConnectedBlocks, 0);
nextFreePreOrder[0] = 1;
for (std::size_t w = 1; w < numConnectedBlocks; ++w)
{
domTreePreOrder[w] = nextFreePreOrder[idom[w]];
nextFreePreOrder[idom[w]] += subtreeSize[w];
nextFreePreOrder[w] = domTreePreOrder[w] + 1;
}

// DFS indices -> block id values
for (std::size_t i = 0; i < numConnectedBlocks; ++i)
{
auto const block = preOrder[i].value;
m_idom[block] = preOrder[idom[i]];
m_domTreePreOrder[block] = static_cast<SSACFG::BlockId::ValueType>(domTreePreOrder[i]);
m_domTreeMaxSubtreePreOrder[block] = static_cast<SSACFG::BlockId::ValueType>(domTreePreOrder[i] + subtreeSize[i] - 1);
}
}

bool DominatorTree::dominates(SSACFG::BlockId _dominator, SSACFG::BlockId _dominated) const
{
yulAssert(
reachable(_dominator) && reachable(_dominated),
"Dominance is only defined for reachable blocks."
);
return
m_domTreePreOrder[_dominator.value] <= m_domTreePreOrder[_dominated.value] &&
m_domTreePreOrder[_dominated.value] <= m_domTreeMaxSubtreePreOrder[_dominator.value];
}

SSACFG::BlockId DominatorTree::immediateDominator(SSACFG::BlockId _block) const
{
yulAssert(reachable(_block), "Immediate dominator is only defined for reachable blocks.");
return m_idom[_block.value];
}

bool DominatorTree::reachable(SSACFG::BlockId _block) const
{
yulAssert(_block.value < m_idom.size());
return m_idom[_block.value].hasValue();
}
57 changes: 57 additions & 0 deletions libyul/backends/evm/ssa/analysis/DominatorTree.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#pragma once

#include <libyul/backends/evm/ssa/SSACFG.h>

#include <vector>

namespace solidity::yul::ssa::analysis
{

class DepthFirstSpanningTree;

/// Computes the dominator tree of an SSACFG using the SEMI-NCA algorithm in its SNCA variant.
///
/// See in particular Section 2.3 of
/// [GTW06] L. Georgiadis, R. E. Tarjan, R. F. Werneck. Finding Dominators in Practice. Journal of Graph Algorithms and Applications, 10(1):69-94, 2006.
/// https://doi.org/10.7155/jgaa.00119
/// and
/// [LT79] T. Lengauer, R. E. Tarjan. A Fast Algorithm for Finding Dominators in a Flowgraph. ACM Transactions on Programming Languages and Systems, 1(1):121-141, 1979.
class DominatorTree
{
public:
explicit DominatorTree(DepthFirstSpanningTree const& _dfsTree);

/// Returns true if `_dominator` dominates `_dominated`. Both must be reachable.
bool dominates(SSACFG::BlockId _dominator, SSACFG::BlockId _dominated) const;

SSACFG::BlockId immediateDominator(SSACFG::BlockId _block) const;

private:
bool reachable(SSACFG::BlockId _block) const;

/// indexed by block ID value, stores the immediate dominator; no value for unreachable blocks
std::vector<SSACFG::BlockId> m_idom;
/// indexed by block ID value, preorder numbering of the dominator tree and the largest number within each subtree
std::vector<SSACFG::BlockId::ValueType> m_domTreePreOrder;
std::vector<SSACFG::BlockId::ValueType> m_domTreeMaxSubtreePreOrder;
};

}
44 changes: 44 additions & 0 deletions libyul/backends/evm/ssa/analysis/ReducibilityCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#include <libyul/backends/evm/ssa/analysis/ReducibilityCheck.h>

#include <libyul/backends/evm/ssa/analysis/DepthFirstSpanningTree.h>
#include <libyul/backends/evm/ssa/analysis/DominatorTree.h>

using namespace solidity::yul::ssa;
using namespace solidity::yul::ssa::analysis;

bool analysis::isReducibleCFG(DepthFirstSpanningTree const& _dfsTree)
{
DominatorTree const dominatorTree(_dfsTree);
for (auto const src: _dfsTree.preOrder())
{
bool reducible = true;
_dfsTree.cfg().block(src).forEachExit([&](SSACFG::BlockId const& _target) {
if (
_dfsTree.ancestor(_target, src) &&
!dominatorTree.dominates(_target, src)
)
reducible = false;
});
if (!reducible)
return false;
}
return true;
}
29 changes: 29 additions & 0 deletions libyul/backends/evm/ssa/analysis/ReducibilityCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#pragma once

namespace solidity::yul::ssa::analysis
{

class DepthFirstSpanningTree;

/// Checks whether the CFG is reducible, i.e., every DFS back edge u -> v has v dominating u
bool isReducibleCFG(DepthFirstSpanningTree const& _dfsTree);

}
Loading
Loading