mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Detect loops in call graph generator.
This commit is contained in:
parent
287b9be7af
commit
bbeb093abf
@ -20,6 +20,7 @@ Bugfixes:
|
||||
* SMTChecker: Fix internal error when using ``abi.decode``.
|
||||
* SMTChecker: Fix internal error when using arrays or mappings of functions.
|
||||
* SMTChecker: Fix internal error in array of structs type.
|
||||
* Yul: Consider infinite loops and recursion to be not removable.
|
||||
|
||||
|
||||
|
||||
|
@ -29,7 +29,7 @@ using namespace std;
|
||||
using namespace dev;
|
||||
using namespace yul;
|
||||
|
||||
map<YulString, set<YulString>> CallGraphGenerator::callGraph(Block const& _ast)
|
||||
CallGraph CallGraphGenerator::callGraph(Block const& _ast)
|
||||
{
|
||||
CallGraphGenerator gen;
|
||||
gen(_ast);
|
||||
@ -40,28 +40,33 @@ void CallGraphGenerator::operator()(FunctionalInstruction const& _functionalInst
|
||||
{
|
||||
string name = dev::eth::instructionInfo(_functionalInstruction.instruction).name;
|
||||
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
|
||||
m_callGraph[m_currentFunction].insert(YulString{name});
|
||||
m_callGraph.functionCalls[m_currentFunction].insert(YulString{name});
|
||||
ASTWalker::operator()(_functionalInstruction);
|
||||
}
|
||||
|
||||
void CallGraphGenerator::operator()(FunctionCall const& _functionCall)
|
||||
{
|
||||
m_callGraph[m_currentFunction].insert(_functionCall.functionName.name);
|
||||
m_callGraph.functionCalls[m_currentFunction].insert(_functionCall.functionName.name);
|
||||
ASTWalker::operator()(_functionCall);
|
||||
}
|
||||
|
||||
void CallGraphGenerator::operator()(ForLoop const&)
|
||||
{
|
||||
m_callGraph.functionsWithLoops.insert(m_currentFunction);
|
||||
}
|
||||
|
||||
void CallGraphGenerator::operator()(FunctionDefinition const& _functionDefinition)
|
||||
{
|
||||
YulString previousFunction = m_currentFunction;
|
||||
m_currentFunction = _functionDefinition.name;
|
||||
yulAssert(m_callGraph.count(m_currentFunction) == 0, "");
|
||||
m_callGraph[m_currentFunction] = {};
|
||||
yulAssert(m_callGraph.functionCalls.count(m_currentFunction) == 0, "");
|
||||
m_callGraph.functionCalls[m_currentFunction] = {};
|
||||
ASTWalker::operator()(_functionDefinition);
|
||||
m_currentFunction = previousFunction;
|
||||
}
|
||||
|
||||
CallGraphGenerator::CallGraphGenerator()
|
||||
{
|
||||
m_callGraph[YulString{}] = {};
|
||||
m_callGraph.functionCalls[YulString{}] = {};
|
||||
}
|
||||
|
||||
|
@ -31,25 +31,34 @@
|
||||
namespace yul
|
||||
{
|
||||
|
||||
struct CallGraph
|
||||
{
|
||||
std::map<YulString, std::set<YulString>> functionCalls;
|
||||
std::set<YulString> functionsWithLoops;
|
||||
};
|
||||
|
||||
/**
|
||||
* Specific AST walker that generates the call graph.
|
||||
*
|
||||
* It also generates information about which functions contain for loops.
|
||||
*
|
||||
* The outermost (non-function) context is denoted by the empty string.
|
||||
*/
|
||||
class CallGraphGenerator: public ASTWalker
|
||||
{
|
||||
public:
|
||||
static std::map<YulString, std::set<YulString>> callGraph(Block const& _ast);
|
||||
static CallGraph callGraph(Block const& _ast);
|
||||
|
||||
using ASTWalker::operator();
|
||||
void operator()(FunctionalInstruction const& _functionalInstruction) override;
|
||||
void operator()(FunctionCall const& _functionCall) override;
|
||||
void operator()(ForLoop const& _forLoop) override;
|
||||
void operator()(FunctionDefinition const& _functionDefinition) override;
|
||||
|
||||
private:
|
||||
CallGraphGenerator();
|
||||
|
||||
std::map<YulString, std::set<YulString>> m_callGraph;
|
||||
CallGraph m_callGraph;
|
||||
/// The name of the function we are currently visiting during traversal.
|
||||
YulString m_currentFunction;
|
||||
};
|
||||
|
@ -108,11 +108,44 @@ void MSizeFinder::operator()(FunctionCall const& _functionCall)
|
||||
|
||||
map<YulString, SideEffects> SideEffectsPropagator::sideEffects(
|
||||
Dialect const& _dialect,
|
||||
map<YulString, std::set<YulString>> const& _directCallGraph
|
||||
CallGraph const& _directCallGraph
|
||||
)
|
||||
{
|
||||
// Any loop currently makes a function non-movable, because
|
||||
// it could be a non-terminating loop.
|
||||
// The same is true for any function part of a call cycle.
|
||||
// In the future, we should refine that, because the property
|
||||
// is actually a bit different from "not movable".
|
||||
|
||||
map<YulString, SideEffects> ret;
|
||||
for (auto const& call: _directCallGraph)
|
||||
for (auto const& function: _directCallGraph.functionsWithLoops)
|
||||
{
|
||||
ret[function].movable = false;
|
||||
ret[function].sideEffectFree = false;
|
||||
ret[function].sideEffectFreeIfNoMSize = false;
|
||||
}
|
||||
|
||||
// Detect recursive functions.
|
||||
for (auto const& call: _directCallGraph.functionCalls)
|
||||
{
|
||||
// TODO we could shortcut the search as soon as we find a
|
||||
// function that has as bad side-effects as we can
|
||||
// ever achieve via recursion.
|
||||
auto search = [&](YulString const& _functionName, CycleDetector<YulString>& _cycleDetector, size_t) {
|
||||
for (auto const& callee: _directCallGraph.functionCalls.at(_functionName))
|
||||
if (!_dialect.builtin(callee))
|
||||
if (_cycleDetector.run(callee))
|
||||
return;
|
||||
};
|
||||
if (CycleDetector<YulString>(search).run(call.first))
|
||||
{
|
||||
ret[call.first].movable = false;
|
||||
ret[call.first].sideEffectFree = false;
|
||||
ret[call.first].sideEffectFreeIfNoMSize = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& call: _directCallGraph.functionCalls)
|
||||
{
|
||||
YulString funName = call.first;
|
||||
SideEffects sideEffects;
|
||||
@ -123,11 +156,15 @@ map<YulString, SideEffects> SideEffectsPropagator::sideEffects(
|
||||
if (BuiltinFunction const* f = _dialect.builtin(_function))
|
||||
sideEffects += f->sideEffects;
|
||||
else
|
||||
for (YulString callee: _directCallGraph.at(_function))
|
||||
{
|
||||
if (ret.count(_function))
|
||||
sideEffects += ret[_function];
|
||||
for (YulString callee: _directCallGraph.functionCalls.at(_function))
|
||||
_addChild(callee);
|
||||
}
|
||||
}
|
||||
);
|
||||
ret[funName] = sideEffects;
|
||||
ret[funName] += sideEffects;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
#include <libyul/SideEffects.h>
|
||||
#include <libyul/optimiser/CallGraphGenerator.h>
|
||||
#include <libyul/AsmData.h>
|
||||
|
||||
#include <set>
|
||||
@ -86,7 +87,7 @@ class SideEffectsPropagator
|
||||
public:
|
||||
static std::map<YulString, SideEffects> sideEffects(
|
||||
Dialect const& _dialect,
|
||||
std::map<YulString, std::set<YulString>> const& _directCallGraph
|
||||
CallGraph const& _directCallGraph
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -5,6 +5,6 @@
|
||||
}
|
||||
// ----
|
||||
// : movable, sideEffectFree, sideEffectFreeIfNoMSize
|
||||
// a: movable, sideEffectFree, sideEffectFreeIfNoMSize
|
||||
// b: movable, sideEffectFree, sideEffectFreeIfNoMSize
|
||||
// c: movable, sideEffectFree, sideEffectFreeIfNoMSize
|
||||
// a:
|
||||
// b:
|
||||
// c:
|
||||
|
@ -4,5 +4,5 @@
|
||||
}
|
||||
// ----
|
||||
// : movable, sideEffectFree, sideEffectFreeIfNoMSize
|
||||
// a: movable, sideEffectFree, sideEffectFreeIfNoMSize
|
||||
// b: movable, sideEffectFree, sideEffectFreeIfNoMSize
|
||||
// a:
|
||||
// b:
|
||||
|
@ -3,4 +3,4 @@
|
||||
}
|
||||
// ----
|
||||
// : movable, sideEffectFree, sideEffectFreeIfNoMSize
|
||||
// a: movable, sideEffectFree, sideEffectFreeIfNoMSize
|
||||
// a:
|
||||
|
9
test/libyul/functionSideEffects/with_loop.yul
Normal file
9
test/libyul/functionSideEffects/with_loop.yul
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
function f() -> x { x := g() }
|
||||
function g() -> x { for {} 1 {} {} }
|
||||
pop(f())
|
||||
}
|
||||
// ----
|
||||
// :
|
||||
// f:
|
||||
// g:
|
@ -477,16 +477,15 @@
|
||||
// pos := add(pos, 0x60)
|
||||
// }
|
||||
// let _3 := mload(64)
|
||||
// let _4 := mload(0x20)
|
||||
// if slt(sub(_3, _4), 128) { revert(_1, _1) }
|
||||
// let offset := calldataload(add(_4, 64))
|
||||
// let _5 := 0xffffffffffffffff
|
||||
// if gt(offset, _5) { revert(_1, _1) }
|
||||
// let value2 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(_4, offset), _3)
|
||||
// let offset_1 := calldataload(add(_4, 0x60))
|
||||
// if gt(offset_1, _5) { revert(_1, _1) }
|
||||
// let value3 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(_4, offset_1), _3)
|
||||
// sstore(calldataload(_4), calldataload(add(_4, 0x20)))
|
||||
// if slt(sub(_3, length), 128) { revert(_1, _1) }
|
||||
// let offset := calldataload(add(length, 64))
|
||||
// let _4 := 0xffffffffffffffff
|
||||
// if gt(offset, _4) { revert(_1, _1) }
|
||||
// let value2 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(length, offset), _3)
|
||||
// let offset_1 := calldataload(add(length, 0x60))
|
||||
// if gt(offset_1, _4) { revert(_1, _1) }
|
||||
// let value3 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(length, offset_1), _3)
|
||||
// sstore(calldataload(length), calldataload(add(length, 0x20)))
|
||||
// sstore(value2, value3)
|
||||
// sstore(_1, pos)
|
||||
// }
|
||||
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
for {} msize() {
|
||||
function foo_s_0() -> x_1 { for {} caller() {} {} }
|
||||
// x_3 used to be a movable loop invariant because `foo_s_0()` used to be movable
|
||||
let x_3 := foo_s_0()
|
||||
mstore(192, x_3)
|
||||
}
|
||||
{}
|
||||
}
|
||||
// ====
|
||||
// step: fullSuite
|
||||
// ----
|
||||
// {
|
||||
// {
|
||||
// for { }
|
||||
// 1
|
||||
// {
|
||||
// for { } iszero(iszero(caller())) { }
|
||||
// { }
|
||||
// mstore(192, 0)
|
||||
// }
|
||||
// { if iszero(msize()) { break } }
|
||||
// }
|
||||
// }
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
function f() -> x { x := g() }
|
||||
function g() -> x { for {} 1 {} {} }
|
||||
|
||||
let b := 1
|
||||
for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } {
|
||||
let t := f()
|
||||
let q := g()
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// step: loopInvariantCodeMotion
|
||||
// ----
|
||||
// {
|
||||
// function f() -> x
|
||||
// { x := g() }
|
||||
// function g() -> x_1
|
||||
// {
|
||||
// for { } 1 { }
|
||||
// { }
|
||||
// }
|
||||
// let b := 1
|
||||
// let a := 1
|
||||
// for { } iszero(eq(a, 10)) { a := add(a, 1) }
|
||||
// {
|
||||
// let t := f()
|
||||
// let q := g()
|
||||
// }
|
||||
// }
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
function f() -> x { x := g() }
|
||||
function g() -> x { x := g() }
|
||||
|
||||
let b := 1
|
||||
for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } {
|
||||
let t := f()
|
||||
let q := g()
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// step: loopInvariantCodeMotion
|
||||
// ----
|
||||
// {
|
||||
// function f() -> x
|
||||
// { x := g() }
|
||||
// function g() -> x_1
|
||||
// { x_1 := g() }
|
||||
// let b := 1
|
||||
// let a := 1
|
||||
// for { } iszero(eq(a, 10)) { a := add(a, 1) }
|
||||
// {
|
||||
// let t := f()
|
||||
// let q := g()
|
||||
// }
|
||||
// }
|
Loading…
Reference in New Issue
Block a user