Merge pull request #11824 from ethereum/yulControlFlowGraphRecursiveCalls

Mark recursive calls in yul control flow graph.
This commit is contained in:
Daniel Kirchner 2021-09-01 15:18:04 +02:00 committed by GitHub
commit f4effe966e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 74 additions and 14 deletions

View File

@ -131,6 +131,9 @@ struct CFG
std::shared_ptr<DebugData const> debugData;
std::reference_wrapper<Scope::Function const> function;
std::reference_wrapper<yul::FunctionCall const> functionCall;
/// True, if the call is recursive, i.e. entering the function involves a control flow path (potentially involving
/// more intermediate function calls) that leads back to this very call.
bool recursive = false;
};
struct Assignment
{

View File

@ -47,22 +47,14 @@ using namespace solidity;
using namespace solidity::yul;
using namespace std;
std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
AsmAnalysisInfo const& _analysisInfo,
Dialect const& _dialect,
Block const& _block
)
namespace
{
// Removes edges to blocks that are not reachable.
void cleanUnreachable(CFG& _cfg)
{
auto result = std::make_unique<CFG>();
result->entry = &result->makeBlock();
ControlFlowGraphBuilder builder(*result, _analysisInfo, _dialect);
builder.m_currentBlock = result->entry;
builder(_block);
// Determine which blocks are reachable from the entry.
util::BreadthFirstSearch<CFG::BasicBlock*> reachabilityCheck{{result->entry}};
for (auto const& functionInfo: result->functionInfo | ranges::views::values)
util::BreadthFirstSearch<CFG::BasicBlock*> reachabilityCheck{{_cfg.entry}};
for (auto const& functionInfo: _cfg.functionInfo | ranges::views::values)
reachabilityCheck.verticesToTraverse.emplace_back(functionInfo.entry);
reachabilityCheck.run([&](CFG::BasicBlock* _node, auto&& _addChild) {
@ -85,6 +77,71 @@ std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
cxx20::erase_if(node->entries, [&](CFG::BasicBlock* entry) -> bool {
return !reachabilityCheck.visited.count(entry);
});
}
// Sets the ``recursive`` member to ``true`` for all recursive function calls.
void markRecursiveCalls(CFG& _cfg)
{
map<CFG::BasicBlock*, vector<CFG::FunctionCall*>> callsPerBlock;
auto const& findCalls = [&](CFG::BasicBlock* _block)
{
if (auto* calls = util::valueOrNullptr(callsPerBlock, _block))
return *calls;
vector<CFG::FunctionCall*>& calls = callsPerBlock[_block];
util::BreadthFirstSearch<CFG::BasicBlock*>{{_block}}.run([&](CFG::BasicBlock* _block, auto _addChild) {
for (auto& operation: _block->operations)
if (auto* functionCall = get_if<CFG::FunctionCall>(&operation.operation))
calls.emplace_back(functionCall);
std::visit(util::GenericVisitor{
[&](CFG::BasicBlock::MainExit const&) {},
[&](CFG::BasicBlock::Jump const& _jump)
{
_addChild(_jump.target);
},
[&](CFG::BasicBlock::ConditionalJump const& _conditionalJump)
{
_addChild(_conditionalJump.zero);
_addChild(_conditionalJump.nonZero);
},
[&](CFG::BasicBlock::FunctionReturn const&) {},
[&](CFG::BasicBlock::Terminated const&) {},
}, _block->exit);
});
return calls;
};
for (auto& functionInfo: _cfg.functionInfo | ranges::views::values)
for (CFG::FunctionCall* call: findCalls(functionInfo.entry))
{
util::BreadthFirstSearch<CFG::FunctionCall*> breadthFirstSearch{{call}};
breadthFirstSearch.run([&](CFG::FunctionCall* _call, auto _addChild) {
auto& calledFunctionInfo = _cfg.functionInfo.at(&_call->function.get());
if (&calledFunctionInfo == &functionInfo)
{
call->recursive = true;
breadthFirstSearch.abort();
return;
}
for (CFG::FunctionCall* nestedCall: findCalls(_cfg.functionInfo.at(&_call->function.get()).entry))
_addChild(nestedCall);
});
}
}
}
std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
AsmAnalysisInfo const& _analysisInfo,
Dialect const& _dialect,
Block const& _block
)
{
auto result = std::make_unique<CFG>();
result->entry = &result->makeBlock();
ControlFlowGraphBuilder builder(*result, _analysisInfo, _dialect);
builder.m_currentBlock = result->entry;
builder(_block);
cleanUnreachable(*result);
markRecursiveCalls(*result);
// TODO: It might be worthwhile to run some further simplifications on the graph itself here.
// E.g. if there is a jump to a node that has the jumping node as its only entry, the nodes can be fused, etc.